|
|
|
define([
|
|
|
|
|
|
|
|
], function () {
|
|
|
|
var Wire = {};
|
|
|
|
|
|
|
|
/* MISSION: write a generic RPC framework
|
|
|
|
|
|
|
|
Requirements
|
|
|
|
|
|
|
|
* [x] some transmission methods can be interrupted
|
|
|
|
* [x] handle disconnects and reconnects
|
|
|
|
* [x] handle callbacks
|
|
|
|
* [x] configurable timeout
|
|
|
|
* [x] be able to answer only queries with a particular id
|
|
|
|
* be able to implement arbitrary listeners on the service-side
|
|
|
|
* and not call 'ready' until those listeners are ready
|
|
|
|
* identical API for:
|
|
|
|
* iframe postMessage
|
|
|
|
* server calls over netflux
|
|
|
|
* postMessage to webworker
|
|
|
|
* postMessage to sharedWorker
|
|
|
|
* on-wire protocol should actually be the same for rewriting purposes
|
|
|
|
* q
|
|
|
|
* guid (globally unique id)
|
|
|
|
* txid (message id)
|
|
|
|
* content
|
|
|
|
* be able to compose different RPCs as streams
|
|
|
|
* intercept and rewrite capacity
|
|
|
|
* multiplex multiple streams over one stream
|
|
|
|
* blind redirect
|
|
|
|
* intelligent router
|
|
|
|
* broadcast (with ACK?)
|
|
|
|
* message
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var uid = Wire.uid = function () {
|
|
|
|
return Number(Math.floor(Math.random () *
|
|
|
|
Number.MAX_SAFE_INTEGER)).toString(32);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* tracker(options)
|
|
|
|
maintains a registry of asynchronous function calls
|
|
|
|
|
|
|
|
allows you to:
|
|
|
|
hook each call to actually send to a remote service...
|
|
|
|
abort any call
|
|
|
|
trigger the pending callback with arguments
|
|
|
|
set the state of the tracker (active/inactive)
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
Wire.tracker = function (opt) {
|
|
|
|
opt = opt || {};
|
|
|
|
var hook = opt.hook || function () {};
|
|
|
|
var timeout = opt.timeout || 5000;
|
|
|
|
var pending = {};
|
|
|
|
var timeouts = {};
|
|
|
|
|
|
|
|
var call = function (method, data, cb) {
|
|
|
|
var id = uid();
|
|
|
|
|
|
|
|
// if the callback is not invoked in time, time out
|
|
|
|
timeouts[id] = setTimeout(function () {
|
|
|
|
if (typeof(pending[id]) === 'function') {
|
|
|
|
cb("TIMEOUT");
|
|
|
|
delete pending[id];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw new Error('timed out without function to call');
|
|
|
|
}, timeout);
|
|
|
|
|
|
|
|
pending[id] = function () {
|
|
|
|
// invoke the function with arguments...
|
|
|
|
cb.apply(null, Array.prototype.slice.call(arguments));
|
|
|
|
// clear its timeout
|
|
|
|
clearTimeout(timeouts[id]);
|
|
|
|
// remove the function from pending
|
|
|
|
delete pending[id];
|
|
|
|
};
|
|
|
|
|
|
|
|
hook(id, method, data);
|
|
|
|
|
|
|
|
return id;
|
|
|
|
};
|
|
|
|
|
|
|
|
var respond = function (id, err, response) {
|
|
|
|
if (typeof(pending[id]) !== 'function') {
|
|
|
|
throw new Error('invoked non-existent callback');
|
|
|
|
}
|
|
|
|
pending[id](err, response);
|
|
|
|
};
|
|
|
|
|
|
|
|
var abort = function (id) {
|
|
|
|
if (pending[id]) {
|
|
|
|
clearTimeout(timeouts[id]);
|
|
|
|
delete pending[id];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var t = {
|
|
|
|
call: call,
|
|
|
|
respond: respond,
|
|
|
|
abort: abort,
|
|
|
|
state: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
t.setState = function (active) {
|
|
|
|
t.state = Boolean(active);
|
|
|
|
};
|
|
|
|
|
|
|
|
return t;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
opt = {
|
|
|
|
timeout: 30000,
|
|
|
|
send: function () {
|
|
|
|
|
|
|
|
},
|
|
|
|
receive: function () {
|
|
|
|
|
|
|
|
},
|
|
|
|
constructor: function (cb) {
|
|
|
|
cb(void 0 , {
|
|
|
|
send: function (content, cb) {
|
|
|
|
|
|
|
|
},
|
|
|
|
receive: function () {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
|
|
|
var parseMessage = function (raw) {
|
|
|
|
try { return JSON.parse(raw); } catch (e) { return; }
|
|
|
|
};
|
|
|
|
|
|
|
|
Wire.create = function (opt, cb) {
|
|
|
|
opt.constructor(function (e, service) {
|
|
|
|
if (e) { return setTimeout(function () { cb(e); }); }
|
|
|
|
var rpc = {};
|
|
|
|
|
|
|
|
var guid = Wire.uid();
|
|
|
|
var t = Wire.tracker({
|
|
|
|
timeout: opt.timeout,
|
|
|
|
hook: function (txid, q, content) {
|
|
|
|
service.send(JSON.stringify({
|
|
|
|
guid: guid,
|
|
|
|
q: q,
|
|
|
|
txid: txid,
|
|
|
|
content: content,
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
rpc.send = function (type, data, cb) {
|
|
|
|
t.call(type, data, cb);
|
|
|
|
};
|
|
|
|
|
|
|
|
service.receive(function (raw) {
|
|
|
|
var data = parseMessage(raw);
|
|
|
|
if (typeof(data) === 'undefined') {
|
|
|
|
return console.error("UNHANDLED_MESSAGE", raw);
|
|
|
|
}
|
|
|
|
if (!data.txid) { throw new Error('NO_TXID'); }
|
|
|
|
t.respond(data.txid, data.error, data.content);
|
|
|
|
});
|
|
|
|
|
|
|
|
cb(void 0, rpc);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return Wire;
|
|
|
|
});
|