Implement new service invocation API
This commit is contained in:
Родитель
2e9767e2ff
Коммит
42626f1528
|
@ -10,11 +10,7 @@
|
|||
},
|
||||
"experimental": {
|
||||
"services": [
|
||||
{
|
||||
"suite": "id",
|
||||
"method": "getProfile",
|
||||
"frame": "http://localhost:8123/tools/aboutme_svc.html"
|
||||
}
|
||||
]
|
||||
{ "profile.get": "/service" }
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,13 +1,38 @@
|
|||
<html>
|
||||
<head></head>
|
||||
<script src="/static/jschannel.js"></script>
|
||||
<script>
|
||||
var id = {
|
||||
|
||||
getProfile: function(requested_fields) {
|
||||
|
||||
function parseSelector(src) {
|
||||
|
||||
|
||||
var chan = Channel.build({window: window.parent, origin: "*", scope: "aboutme"});
|
||||
var gPendingRequest;
|
||||
var gOfferedData;
|
||||
|
||||
chan.bind("profile.get", function(t, args) {
|
||||
|
||||
gOfferedData = getProfile(args);
|
||||
renderOffer();
|
||||
|
||||
/* t.delayReturn(true);
|
||||
// when the user consents, we'll return
|
||||
gPendingRequest = (function() {
|
||||
return {
|
||||
confirm: function(result) {
|
||||
t.complete(result);
|
||||
}
|
||||
}
|
||||
})();*/
|
||||
});
|
||||
|
||||
chan.bind("confirm", function(t, args) {
|
||||
return gOfferedData;
|
||||
});
|
||||
|
||||
function confirm()
|
||||
{
|
||||
gPendingRequest.confirm(gOfferedData);
|
||||
}
|
||||
|
||||
function getProfile(requested_fields) {
|
||||
|
||||
function extractInto(srcObj, dstObj, tArray, idx) {
|
||||
var term = tArray[idx];
|
||||
|
@ -32,14 +57,37 @@ var id = {
|
|||
extractInto(full_profile, result, requested_fields[f].split("/"), 0);
|
||||
}
|
||||
}
|
||||
return ["ok", result];
|
||||
return result;
|
||||
}
|
||||
|
||||
function renderOffer()
|
||||
{
|
||||
var container = document.getElementById("message");
|
||||
container.innerHTML = "";
|
||||
container.appendChild(document.createTextNode("Imagine some nice rendering of this data..."));
|
||||
for (var k in gOfferedData) {
|
||||
var div = document.createElement("div");
|
||||
div.appendChild(document.createTextNode(k + ": " + JSON.stringify(gOfferedData[k])));
|
||||
container.appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
BODY {font:9pt Helvetica}
|
||||
BODY {
|
||||
font:9pt Helvetica;
|
||||
background-color:#f0f0f8;
|
||||
}
|
||||
.branding {
|
||||
font: small-caption;
|
||||
margin-bottom:6px;
|
||||
color:black;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
(This is the confirmation UI for about:me)
|
||||
<div class="branding">about:me by Mozilla</div>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,552 @@
|
|||
/**
|
||||
* js_channel is a very lightweight abstraction on top of
|
||||
* postMessage which defines message formats and semantics
|
||||
* to support interactions more rich than just message passing
|
||||
* js_channel supports:
|
||||
* + query/response - traditional rpc
|
||||
* + query/update/response - incremental async return of results
|
||||
* to a query
|
||||
* + notifications - fire and forget
|
||||
* + error handling
|
||||
*
|
||||
* js_channel is based heavily on json-rpc, but is focused at the
|
||||
* problem of inter-iframe RPC.
|
||||
*
|
||||
* Message types:
|
||||
* There are 5 types of messages that can flow over this channel,
|
||||
* and you may determine what type of message an object is by
|
||||
* examining its parameters:
|
||||
* 1. Requests
|
||||
* + integer id
|
||||
* + string method
|
||||
* + (optional) any params
|
||||
* 2. Callback Invocations (or just "Callbacks")
|
||||
* + integer id
|
||||
* + string callback
|
||||
* + (optional) params
|
||||
* 3. Error Responses (or just "Errors)
|
||||
* + integer id
|
||||
* + string error
|
||||
* + (optional) string message
|
||||
* 4. Responses
|
||||
* + integer id
|
||||
* + (optional) any result
|
||||
* 5. Notifications
|
||||
* + string method
|
||||
* + (optional) any params
|
||||
*/
|
||||
|
||||
;Channel = (function() {
|
||||
// current transaction id, start out at a random *odd* number between 1 and a million
|
||||
// There is one current transaction counter id per page, and it's shared between
|
||||
// channel instances. That means of all messages posted from a single javascript
|
||||
// evaluation context, we'll never have two with the same id.
|
||||
var s_curTranId = Math.floor(Math.random()*1000001);
|
||||
|
||||
// no two bound channels in the same javascript evaluation context may have the same origin & scope.
|
||||
// futher if two bound channels have the same scope, they may not have *overlapping* origins
|
||||
// (either one or both support '*'). This restriction allows a single onMessage handler to efficiently
|
||||
// route messages based on origin and scope. The s_boundChans maps origins to scopes, to message
|
||||
// handlers. Request and Notification messages are routed using this table.
|
||||
// Finally, channels are inserted into this table when built, and removed when destroyed.
|
||||
var s_boundChans = { };
|
||||
|
||||
// add a channel to s_boundChans, throwing if a dup exists
|
||||
function s_addBoundChan(origin, scope, handler) {
|
||||
// does she exist?
|
||||
var exists = false;
|
||||
if (origin === '*') {
|
||||
// we must check all other origins, sadly.
|
||||
for (var k in s_boundChans) {
|
||||
if (!s_boundChans.hasOwnProperty(k)) continue;
|
||||
if (k === '*') continue;
|
||||
if (typeof s_boundChans[k][scope] === 'object') {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we must check only '*'
|
||||
if ((s_boundChans['*'] && s_boundChans['*'][scope]) ||
|
||||
(s_boundChans[origin] && s_boundChans[origin][scope]))
|
||||
{
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
if (exists) throw "A channel already exists which overlaps with origin '"+ origin +"' and has scope '"+scope+"'";
|
||||
|
||||
if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { };
|
||||
s_boundChans[origin][scope] = handler;
|
||||
}
|
||||
|
||||
function s_removeBoundChan(origin, scope) {
|
||||
delete s_boundChans[origin][scope];
|
||||
// possibly leave a empty object around. whatevs.
|
||||
}
|
||||
|
||||
function s_isArray(obj) {
|
||||
if (Array.isArray) return Array.isArray(obj);
|
||||
else {
|
||||
return (obj.constructor.toString().indexOf("Array") != -1);
|
||||
}
|
||||
}
|
||||
|
||||
// No two outstanding outbound messages may have the same id, period. Given that, a single table
|
||||
// mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and
|
||||
// Response messages. Entries are added to this table when requests are sent, and removed when
|
||||
// responses are received.
|
||||
var s_transIds = { };
|
||||
|
||||
// class singleton onMessage handler
|
||||
// this function is registered once and all incoming messages route through here. This
|
||||
// arrangement allows certain efficiencies, message data is only parsed once and dispatch
|
||||
// is more efficient, especially for large numbers of simultaneous channels.
|
||||
var s_onMessage = function(e) {
|
||||
var m = JSON.parse(e.data);
|
||||
if (typeof m !== 'object') return;
|
||||
|
||||
var o = e.origin;
|
||||
var s = null;
|
||||
var i = null;
|
||||
var meth = null;
|
||||
|
||||
if (typeof m.method === 'string') {
|
||||
var ar = m.method.split('::');
|
||||
if (ar.length == 2) {
|
||||
s = ar[0];
|
||||
meth = ar[1];
|
||||
} else {
|
||||
meth = m.method;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof m.id !== 'undefined') i = m.id;
|
||||
|
||||
// o is message origin
|
||||
// m is parsed message
|
||||
// s is message scope
|
||||
// i is message id (or null)
|
||||
// meth is unscoped method name
|
||||
// ^^ based on these factors we can route the message
|
||||
|
||||
// if it has a method it's either a notification or a request,
|
||||
// route using s_boundChans
|
||||
if (typeof meth === 'string') {
|
||||
if (s_boundChans[o] && s_boundChans[o][s]) {
|
||||
s_boundChans[o][s](o, meth, m);
|
||||
} else if (s_boundChans['*'] && s_boundChans['*'][s]) {
|
||||
s_boundChans['*'][s](o, meth, m);
|
||||
}
|
||||
}
|
||||
// otherwise it must have an id (or be poorly formed
|
||||
else if (typeof i != 'undefined') {
|
||||
if (s_transIds[i]) s_transIds[i](o, meth, m);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup postMessage event listeners
|
||||
if (window.addEventListener) window.addEventListener('message', s_onMessage, false);
|
||||
else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage);
|
||||
|
||||
/* a messaging channel is constructed from a window and an origin.
|
||||
* the channel will assert that all messages received over the
|
||||
* channel match the origin
|
||||
*
|
||||
* Arguments to Channel.build(cfg):
|
||||
*
|
||||
* cfg.window - the remote window with which we'll communication
|
||||
* cfg.origin - the expected origin of the remote window, may be '*'
|
||||
* which matches any origin
|
||||
* cfg.scope - the 'scope' of messages. a scope string that is
|
||||
* prepended to message names. local and remote endpoints
|
||||
* of a single channel must agree upon scope. Scope may
|
||||
* not contain double colons ('::').
|
||||
* cfg.debugOutput - A boolean value. If true and window.console.log is
|
||||
* a function, then debug strings will be emitted to that
|
||||
* function.
|
||||
* cfg.debugOutput - A boolean value. If true and window.console.log is
|
||||
* a function, then debug strings will be emitted to that
|
||||
* function.
|
||||
* cfg.postMessageObserver - A function that will be passed two arguments,
|
||||
* an origin and a message. It will be passed these immediately
|
||||
* before messages are posted.
|
||||
* cfg.gotMessageObserver - A function that will be passed two arguments,
|
||||
* an origin and a message. It will be passed these arguments
|
||||
* immediately after they pass scope and origin checks, but before
|
||||
* they are processed.
|
||||
* cfg.onReady - A function that will be invoked when a channel becomes "ready",
|
||||
* this occurs once both sides of the channel have been
|
||||
* instantiated and an application level handshake is exchanged.
|
||||
* the onReady function will be passed a single argument which is
|
||||
* the channel object that was returned from build().
|
||||
*/
|
||||
return {
|
||||
build: function(cfg) {
|
||||
var debug = function(m) {
|
||||
if (cfg.debugOutput && window.console && window.console.log) {
|
||||
// try to stringify, if it doesn't work we'll let javascript's built in toString do its magic
|
||||
try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { }
|
||||
console.log("["+chanId+"] " + m);
|
||||
}
|
||||
}
|
||||
|
||||
/* browser capabilities check */
|
||||
if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage");
|
||||
if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) {
|
||||
throw("jschannel cannot run this browser, no JSON parsing/serialization");
|
||||
}
|
||||
|
||||
/* basic argument validation */
|
||||
if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument");
|
||||
|
||||
if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument");
|
||||
|
||||
/* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same
|
||||
* window... Not sure if we care to support that */
|
||||
if (window === cfg.window) throw("target window is same as present window -- not allowed");
|
||||
|
||||
// let's require that the client specify an origin. if we just assume '*' we'll be
|
||||
// propagating unsafe practices. that would be lame.
|
||||
var validOrigin = false;
|
||||
if (typeof cfg.origin === 'string') {
|
||||
var oMatch;
|
||||
if (cfg.origin === "*") validOrigin = true;
|
||||
// allow valid domains under http and https. Also, trim paths off otherwise valid origins.
|
||||
else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9\.])+(?::\d+)?/))) {
|
||||
cfg.origin = oMatch[0];
|
||||
validOrigin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validOrigin) throw ("Channel.build() called with an invalid origin");
|
||||
|
||||
if (typeof cfg.scope !== 'undefined') {
|
||||
if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string';
|
||||
if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'"
|
||||
}
|
||||
|
||||
/* private variables */
|
||||
// generate a random and psuedo unique id for this channel
|
||||
var chanId = (function () {
|
||||
var text = "";
|
||||
var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length));
|
||||
return text;
|
||||
})();
|
||||
|
||||
// registrations: mapping method names to call objects
|
||||
var regTbl = { };
|
||||
// current oustanding sent requests
|
||||
var outTbl = { };
|
||||
// current oustanding received requests
|
||||
var inTbl = { };
|
||||
// are we ready yet? when false we will block outbound messages.
|
||||
var ready = false;
|
||||
var pendingQueue = [ ];
|
||||
|
||||
var createTransaction = function(id,origin,callbacks) {
|
||||
var shouldDelayReturn = false;
|
||||
var completed = false;
|
||||
|
||||
return {
|
||||
origin: origin,
|
||||
invoke: function(cbName, v) {
|
||||
// verify in table
|
||||
if (!inTbl[id]) throw "attempting to invoke a callback of a non-existant transaction: " + id;
|
||||
// verify that the callback name is valid
|
||||
var valid = false;
|
||||
for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; }
|
||||
if (!valid) throw "request supports no such callback '" + cbName + "'";
|
||||
|
||||
// send callback invocation
|
||||
postMessage({ id: id, callback: cbName, params: v});
|
||||
},
|
||||
error: function(error, message) {
|
||||
completed = true;
|
||||
// verify in table
|
||||
if (!inTbl[id]) throw "error called for non-existant message: " + id;
|
||||
|
||||
// remove transaction from table
|
||||
delete inTbl[id];
|
||||
|
||||
// send error
|
||||
postMessage({ id: id, error: error, message: message });
|
||||
},
|
||||
complete: function(v) {
|
||||
completed = true;
|
||||
// verify in table
|
||||
if (!inTbl[id]) throw "complete called for non-existant message: " + id;
|
||||
// remove transaction from table
|
||||
delete inTbl[id];
|
||||
// send complete
|
||||
postMessage({ id: id, result: v });
|
||||
},
|
||||
delayReturn: function(delay) {
|
||||
if (typeof delay === 'boolean') {
|
||||
shouldDelayReturn = (delay === true);
|
||||
}
|
||||
return shouldDelayReturn;
|
||||
},
|
||||
completed: function() {
|
||||
return completed;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var onMessage = function(origin, method, m) {
|
||||
// if an observer was specified at allocation time, invoke it
|
||||
if (typeof cfg.gotMessageObserver === 'function') {
|
||||
// pass observer a clone of the object so that our
|
||||
// manipulations are not visible (i.e. method unscoping).
|
||||
// This is not particularly efficient, but then we expect
|
||||
// that message observers are primarily for debugging anyway.
|
||||
try {
|
||||
cfg.gotMessageObserver(origin, m);
|
||||
} catch (e) {
|
||||
debug("gotMessageObserver() raised an exception: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// now, what type of message is this?
|
||||
if (m.id && method) {
|
||||
// a request! do we have a registered handler for this request?
|
||||
if (regTbl[method]) {
|
||||
var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]);
|
||||
inTbl[m.id] = { };
|
||||
try {
|
||||
// callback handling. we'll magically create functions inside the parameter list for each
|
||||
// callback
|
||||
if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) {
|
||||
for (var i = 0; i < m.callbacks.length; i++) {
|
||||
var path = m.callbacks[i];
|
||||
var obj = m.params;
|
||||
var pathItems = path.split('/');
|
||||
for (var j = 0; j < pathItems.length - 1; j++) {
|
||||
var cp = pathItems[j];
|
||||
if (typeof obj[cp] !== 'object') obj[cp] = { };
|
||||
obj = obj[cp];
|
||||
}
|
||||
obj[pathItems[pathItems.length - 1]] = (function() {
|
||||
var cbName = path;
|
||||
return function(params) {
|
||||
return trans.invoke(cbName, params);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
var resp = regTbl[method](trans, m.params);
|
||||
if (!trans.delayReturn() && !trans.completed()) trans.complete(resp);
|
||||
} catch(e) {
|
||||
// automagic handling of exceptions:
|
||||
var error = "runtime_error";
|
||||
var message = null;
|
||||
// * if its a string then it gets an error code of 'runtime_error' and string is the message
|
||||
if (typeof e === 'string') {
|
||||
message = e;
|
||||
} else if (typeof e === 'object') {
|
||||
// either an array or an object
|
||||
// * if its an array of length two, then array[0] is the code, array[1] is the error message
|
||||
if (e && s_isArray(e) && e.length == 2) {
|
||||
error = e[0];
|
||||
message = e[1];
|
||||
}
|
||||
// * if its an object then we'll look form error and message parameters
|
||||
else if (typeof e.error === 'string') {
|
||||
error = e.error;
|
||||
if (!e.message) message = "";
|
||||
else if (typeof e.message === 'string') message = e.message;
|
||||
else e = e.message; // let the stringify/toString message give us a reasonable verbose error string
|
||||
}
|
||||
}
|
||||
|
||||
// message is *still* null, let's try harder
|
||||
if (message === null) {
|
||||
try {
|
||||
message = JSON.stringify(e);
|
||||
} catch (e2) {
|
||||
message = e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
trans.error(error,message);
|
||||
}
|
||||
}
|
||||
} else if (m.id && m.callback) {
|
||||
if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback])
|
||||
{
|
||||
debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")");
|
||||
} else {
|
||||
// XXX: what if client code raises an exception here?
|
||||
outTbl[m.id].callbacks[m.callback](m.params);
|
||||
}
|
||||
} else if (m.id) {
|
||||
if (!outTbl[m.id]) {
|
||||
debug("ignoring invalid response: " + m.id);
|
||||
} else {
|
||||
// XXX: what if client code raises an exception here?
|
||||
if (m.error) {
|
||||
(1,outTbl[m.id].error)(m.error, m.message);
|
||||
} else {
|
||||
if (m.result !== undefined) (1,outTbl[m.id].success)(m.result);
|
||||
else (1,outTbl[m.id].success)();
|
||||
}
|
||||
delete outTbl[m.id];
|
||||
delete s_transIds[m.id];
|
||||
}
|
||||
} else if (method) {
|
||||
// tis a notification.
|
||||
if (regTbl[method]) {
|
||||
// yep, there's a handler for that.
|
||||
// transaction is null for notifications.
|
||||
regTbl[method](null, m.params);
|
||||
// if the client throws, we'll just let it bubble out
|
||||
// what can we do? Also, here we'll ignore return values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now register our bound channel for msg routing
|
||||
s_addBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage);
|
||||
|
||||
// scope method names based on cfg.scope specified when the Channel was instantiated
|
||||
var scopeMethod = function(m) {
|
||||
if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::");
|
||||
return m;
|
||||
}
|
||||
|
||||
// a small wrapper around postmessage whose primary function is to handle the
|
||||
// case that clients start sending messages before the other end is "ready"
|
||||
var postMessage = function(msg, force) {
|
||||
if (!msg) throw "postMessage called with null message";
|
||||
|
||||
// delay posting if we're not ready yet.
|
||||
var verb = (ready ? "post " : "queue ");
|
||||
debug(verb + " message: " + JSON.stringify(msg));
|
||||
if (!force && !ready) {
|
||||
pendingQueue.push(msg);
|
||||
} else {
|
||||
if (typeof cfg.postMessageObserver === 'function') {
|
||||
try {
|
||||
cfg.postMessageObserver(cfg.origin, msg);
|
||||
} catch (e) {
|
||||
debug("postMessageObserver() raised an exception: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
cfg.window.postMessage(JSON.stringify(msg), cfg.origin);
|
||||
}
|
||||
}
|
||||
|
||||
var onReady = function(trans, type) {
|
||||
debug('ready msg received');
|
||||
if (ready) throw "received ready message while in ready state. help!";
|
||||
|
||||
if (type === 'ping') {
|
||||
chanId += '-R';
|
||||
} else {
|
||||
chanId += '-L';
|
||||
}
|
||||
|
||||
obj.unbind('__ready'); // now this handler isn't needed any more.
|
||||
ready = true;
|
||||
debug('ready msg accepted.');
|
||||
|
||||
if (type === 'ping') {
|
||||
obj.notify({ method: '__ready', params: 'pong' });
|
||||
}
|
||||
|
||||
// flush queue
|
||||
while (pendingQueue.length) {
|
||||
postMessage(pendingQueue.pop());
|
||||
}
|
||||
|
||||
// invoke onReady observer if provided
|
||||
if (typeof cfg.onReady === 'function') cfg.onReady(obj);
|
||||
};
|
||||
|
||||
var obj = {
|
||||
// tries to unbind a bound message handler. returns false if not possible
|
||||
unbind: function (method) {
|
||||
if (regTbl[method]) {
|
||||
if (!(delete regTbl[method])) throw ("can't delete method: " + method);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
bind: function (method, cb) {
|
||||
if (!method || typeof method !== 'string') throw "'method' argument to bind must be string";
|
||||
if (!cb || typeof cb !== 'function') throw "callback missing from bind params";
|
||||
|
||||
if (regTbl[method]) throw "method '"+method+"' is already bound!";
|
||||
regTbl[method] = cb;
|
||||
},
|
||||
call: function(m) {
|
||||
if (!m) throw 'missing arguments to call function';
|
||||
if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string";
|
||||
if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call";
|
||||
|
||||
// now it's time to support the 'callback' feature of jschannel. We'll traverse the argument
|
||||
// object and pick out all of the functions that were passed as arguments.
|
||||
var callbacks = { };
|
||||
var callbackNames = [ ];
|
||||
|
||||
var pruneFunctions = function (path, obj) {
|
||||
if (typeof obj === 'object') {
|
||||
for (var k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) continue;
|
||||
var np = path + (path.length ? '/' : '') + k;
|
||||
if (typeof obj[k] === 'function') {
|
||||
callbacks[np] = obj[k];
|
||||
callbackNames.push(np);
|
||||
delete obj[k];
|
||||
} else if (typeof obj[k] === 'object') {
|
||||
pruneFunctions(np, obj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
pruneFunctions("", m.params);
|
||||
|
||||
// build a 'request' message and send it
|
||||
var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params };
|
||||
if (callbackNames.length) msg.callbacks = callbackNames;
|
||||
|
||||
// insert into the transaction table
|
||||
outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success };
|
||||
s_transIds[s_curTranId] = onMessage;
|
||||
|
||||
// increment current id
|
||||
s_curTranId++;
|
||||
|
||||
postMessage(msg);
|
||||
},
|
||||
notify: function(m) {
|
||||
if (!m) throw 'missing arguments to notify function';
|
||||
if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string";
|
||||
|
||||
// no need to go into any transaction table
|
||||
postMessage({ method: scopeMethod(m.method), params: m.params });
|
||||
},
|
||||
destroy: function () {
|
||||
s_removeBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''));
|
||||
if (window.removeEventListener) window.removeEventListener('message', onMessage, false);
|
||||
else if(window.detachEvent) window.detachEvent('onmessage', onMessage);
|
||||
ready = false;
|
||||
regTbl = { };
|
||||
inTbl = { };
|
||||
outTbl = { };
|
||||
cfg.origin = null;
|
||||
pendingQueue = [ ];
|
||||
debug("channel destroyed");
|
||||
chanId = "";
|
||||
}
|
||||
};
|
||||
|
||||
obj.bind('__ready', onReady);
|
||||
setTimeout(function() {
|
||||
postMessage({ method: scopeMethod('__ready'), params: "ping" }, true);
|
||||
}, 0);
|
||||
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
})();
|
Загрузка…
Ссылка в новой задаче