Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.1",
|
||||
"main": "bridge.js",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"README.md",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"docs",
|
||||
"examples"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
;;;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){var bridge={'service':require('../src/service'),'client':require('../src/client'),_m:require('../src/message')};if((typeof define)[0]!='u')define([],()=>bridge);else self['bridge']=bridge;},{"../src/client":2,"../src/message":4,"../src/service":6}],2:[function(require,module,exports){'use strict';var createPort=require('./message/port-adaptors');var Emitter=require('./emitter');var message=require('./message');var uuid=require('./utils').uuid;module.exports=Client;var debug={0:()=>{},1:arg=>performance.mark(`[${self.constructor.name}][Client] - ${arg}`),2:(arg1,...args)=>{var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[Client]${type} - "${arg1}"`,...args);}}[0];var env=constructor.name
|
||||
function Client(service,endpoint,timeout){if(!(this instanceof Client))return new Client(service,endpoint,timeout);if(typeof service=='object'){endpoint=service.endpoint;timeout=service.timeout;service=service.service;}
|
||||
this.id=uuid();this.service=service;this.timeout=timeout;this.endpoint=endpoint||this.endpoint;if(!this.endpoint)throw error(1);this.setPort(this.endpoint);this.pending=new Set();this.receiver=message.receiver(this.id).on('_push',this.onPush.bind(this));debug('initialized',service);}
|
||||
Client.prototype={connect(){debug('connect');if(this.connected)return this.connected;debug('connecting...',this.service);var mc=new MessageChannel();this.channel=mc.port1;this.channel.start();var data={clientId:this.id,service:this.service,originEnv:env};return this.connected=this.message('_connect').set('transfer',[mc.port2]).set('data',data).listen(mc.port1).send().then(response=>{debug('connected',response);var usingChannel=response.event.target===this.channel;if(usingChannel)this.setPort(this.channel);else{this.channel.close();delete this.channel;}
|
||||
this.receiver.listen(this.port);}).catch(err=>{var msg=err&&err.message;if(msg=='timeout'){err=error(2,this.service);console.error(err.message);}
|
||||
throw err;});},disconnect(options){if(!this.connected)return Promise.resolve();debug('disconnecting ...');var config={noRespond:options&&options.noRespond,data:this.id};this.cancelPending();return this.message('_disconnect').set(config).send().then(()=>this.onDisconnected());},method(name,...args){return this.connect().then(()=>{debug('method',name);return this.message('_method').set({recipient:this.service,data:{name:name,args:args}}).send();}).then(response=>response.value).catch(err=>{var msg=err&&err.message;if(msg=='timeout'){err=error(3,name);console.error(err.message);}
|
||||
throw err;});},plugin(fn){fn(this,{'Emitter':Emitter,'uuid':uuid});return this;},message(type){debug('create message',type);var msg=message(type).set('port',this.port).set('timeout',this.timeout).on('response',()=>this.pending.delete(msg)).on('cancel',()=>this.pending.delete(msg));this.pending.add(msg);return msg;},cancelPending(){debug('cancel pending');this.pending.forEach(msg=>{msg.cancel();});this.pending.clear();},pendingResponded(){var responded=[];this.pending.forEach(msg=>responded.push(msg.responded));return Promise.all(responded);},onPush(message){debug('on push',message.data);this._emit(message.data.type,message.data.data);},onDisconnected(){delete this.connected;this.pendingResponded().then(()=>{debug('disconnected');if(this.channel)this.channel.close();this._emit('disconnected');});},setPort(endpoint){debug('set port');this.port=createPort(endpoint);},destroy:function(){return this.disconnect().then(()=>{if(this.destroyed)return;debug('destroy');this.destroyed=true;this.receiver.destroy();this._off();this.port=this.endpoint=this.receiver=null;});},_on:Emitter.prototype.on,_off:Emitter.prototype.off,_emit:Emitter.prototype.emit};Client.prototype.on=function(name,fn){this.connect().then(()=>{debug('bind on',name);Emitter.prototype.on.call(this,name,fn);this.message('_on').set('noRespond',true).set('data',{name:name,clientId:this.id}).send(this.port);});return this;};Client.prototype.off=function(name,fn){this.connect().then(()=>{Emitter.prototype.off.call(this,name,fn);this.message('_off').set('noRespond',true).set('data',{name:name,clientId:this.id}).send(this.port);});return this;};function error(id,...args){var help='Either the target endpoint is not alive or the Service is not `.listen()`ing.';return new Error({1:'an endpoint must be defined',2:`Unable to establish a connection with "${args[0]}". ${help}`,3:`Method "${args[0]}" didn't get a response. ${help}`}[id]);}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":7}],3:[function(require,module,exports){'use strict';module.exports=Emitter;var debug=0?console.log.bind(console,'[Emitter]'):()=>{};function Emitter(host){if(host)return Object.assign(host,Emitter.prototype);}
|
||||
Emitter.prototype={on:function(type,callback){debug('on',type,callback);if(!this._callbacks)this._callbacks={};if(!this._callbacks[type])this._callbacks[type]=[];this._callbacks[type].push(callback);return this;},off:function(type,callback){debug('off',type,callback);if(this._callbacks){switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[type];break;default:var typeListeners=this._callbacks[type];if(!typeListeners)return;var i=typeListeners.indexOf(callback);if(~i)typeListeners.splice(i,1);}}
|
||||
return this;},emit:function(type,data){debug('emit',type,data);if(this._callbacks){var fns=this._callbacks[type]||[];fns=fns.concat(this._callbacks['*']||[]);for(var i=0;i<fns.length;i++)fns[i].call(this,data,type);}
|
||||
return this;}};var p=Emitter.prototype;p['off']=p.off;p['on']=p.on;},{}],4:[function(require,module,exports){'use strict';var createPort=require('./port-adaptors');var Emitter=require('../emitter');var utils=require('../utils');var defer=utils.deferred;var uuid=utils.uuid;exports=module.exports=type=>new Message(type);exports.receiver=(id,n)=>new Receiver(id,n);exports.Receiver=Receiver;exports.Message=Message;var debug={0:()=>{},1:arg=>performance.mark(`[${self.constructor.name}][Message] - ${arg}`),2:(arg1,...args)=>{var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[Message]${type} - "${arg1}"`,...args);}}[0];var TIMEOUT=1000;function Message(type){this.cancelled=false;this.listeners=[];this.deferred=defer();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);if(typeof type==='object')this.setupInbound(type);else this.setupOutbound(type);debug('initialized',type);}
|
||||
Message.prototype={setupOutbound(type){this.id=uuid();this.type=type;this.sent=false;this.recipient='*';},setupInbound(e){debug('inbound');this.hasResponded=false;this.setSourcePort(e.source||e.target);this.event=e;Object.assign(this,e.data);},setSourcePort(endpoint){debug('set source',endpoint.constructor.name);this.sourcePort=createPort(endpoint,{ready:true});return this;},set(key,value){debug('set',key,value);if(typeof key=='object')Object.assign(this,key);else this[key]=value;return this;},serialize(){return{id:this.id,type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond};},preventDefault(){debug('prevent default');this.defaultPrevented=true;},send(endpoint){debug('send',this.type);if(this.sent)throw error(1);var serialized=this.serialize();var expectsResponse=!this.noRespond;this.port=endpoint?createPort(endpoint):this.port;if(!this.port)throw error(3);if(expectsResponse){this.listen(this.port);this.setResponseTimeout();}else this.deferred.resolve();this.port.postMessage(serialized,this.getTransfer());debug('sent',serialized);return this.deferred.promise;},setResponseTimeout(){if(this.timeout===false)return;var ms=this.timeout||TIMEOUT;this._timer=setTimeout(this.onTimeout,ms);},clearResponseTimeout(){clearTimeout(this._timer);},getTransfer(){return this.transfer||this.event&&this.event.ports;},onMessage(e){var valid=!!e.data.response&&e.data.id===this.id&&!this.cancelled;if(valid)this.onResponse(e);},onTimeout(){debug('response timeout',this.type);if(!this.silentTimeout)this.deferred.reject(error(4));this.teardown();},listen(thing){debug('add response listener',thing);var port=createPort(thing);port.addListener(this.onMessage);this.listeners.push(port);return this;},unlisten(){debug('remove response listeners');this.listeners.forEach(port=>port.removeListener(this.onMessage));this.listeners=[];},cancel(){this.teardown();this.cancelled=true;this.emit('cancel');},teardown(){this.clearResponseTimeout();this.unlisten();},respond(result){debug('respond',result);if(this.hasResponded)throw error(2);if(!this.sourcePort)return;if(this.noRespond)return;var self=this;this.hasResponded=true;if(result instanceof Error)reject(result);Promise.resolve(result).then(resolve,reject).catch(reject);function resolve(value){debug('resolve',value);respond({type:'resolve',value:value});}
|
||||
function reject(err){var msg=err&&err.message||err;debug('reject',msg);respond({type:'reject',value:msg});}
|
||||
function respond(response){self.response=response;self.sourcePort.postMessage({id:self.id,response:response},self.transfer);debug('responded with:',response);}},forward(endpoint){debug('forward');return this.set('silentTimeout',true).send(endpoint).then(result=>this.respond(result.value));},onResponse(e){debug('on response',e.data);var response=e.data.response;var type=response.type;var value=type=='reject'?response.value:response;response.event=e;this.response=response;this.teardown();this.deferred[this.response.type](value);this.emit('response',response);}};Emitter(Message.prototype);function Receiver(name){this.name=name;this.ports=new Set();this.onMessage=this.onMessage.bind(this);this['listen']=this['listen'].bind(this);this['unlisten']=this['unlisten'].bind(this);debug('receiver initialized',name);}
|
||||
Receiver.prototype={listen(thing){debug('listen');var _port=createPort(thing||self,{receiver:true});if(this.ports.has(_port))return;_port.addListener(this.onMessage,this.listen);this.ports.add(_port);return this;},unlisten(){debug('unlisten');this.ports.forEach(port=>{port.removeListener(this.onMessage,this.unlisten);});},onMessage(e){if(!e.data.id)return;if(!e.data.type)return;if(!this.isRecipient(e.data.recipient))return;debug('receiver on message',e.data);var message=new Message(e);this.emit('message',message);if(message.defaultPrevented)return;try{this.emit(message.type,message);}
|
||||
catch(e){message.respond(e);throw e;}},isRecipient(recipient){return recipient==this.name||recipient=='*'||this.name=='*';},destroy:function(){this.unlisten();delete this.name;return this;}};Emitter(Receiver.prototype);function error(id,...args){return new Error({1:'.send() can only be called once',2:'response already sent for this message',3:'a port must be defined',4:'timeout'}[id]);}},{"../emitter":3,"../utils":7,"./port-adaptors":5}],5:[function(require,module,exports){'use strict';var deferred=require('../utils').deferred;const MSG='message';var debug=0?function(arg1,...args){var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[PortAdaptor]${type} - "${arg1}"`,...args);}:()=>{};module.exports=function create(target,options){if(!target)throw error(1);if(isEndpoint(target))return target;var type=target.constructor.name;var CustomAdaptor=adaptors[type];debug('creating port adaptor for',type);if(CustomAdaptor)return CustomAdaptor(target,options);return new PortAdaptor(target,options);};function PortAdaptor(target){debug('PortAdaptor');this.target=target;}
|
||||
var PortAdaptorProto=PortAdaptor.prototype={constructor:PortAdaptor,addListener(callback){on(this.target,MSG,callback);},removeListener(callback){off(this.target,MSG,callback);},postMessage(data,transfer){this.target.postMessage(data,transfer);}};var adaptors={'HTMLIFrameElement':function(iframe){debug('HTMLIFrameElement');var ready=windowReady(iframe);return{addListener(callback,listen){on(window,MSG,callback);},removeListener(callback,listen){off(window,MSG,callback);},postMessage(data,transfer){ready.then(()=>postMessageSync(iframe.contentWindow,data,transfer));}};},'BroadcastChannel':function(channel,options){debug('BroadcastChannel',channel.name);var receiver=options&&options.receiver;var ready=options&&options.ready;var sendReady=()=>{channel.postMessage('ready');debug('sent ready');};ready=ready||receiver?Promise.resolve():setupSender();if(receiver){sendReady();on(channel,MSG,e=>{if(e.data!='ready?')return;sendReady();});}
|
||||
function setupSender(){debug('setup sender');var promise=deferred();channel.postMessage('ready?');on(channel,MSG,function fn(e){if(e.data!='ready')return;off(channel,MSG,fn);debug('BroadcastChannel: ready');promise.resolve();});return promise.promise;}
|
||||
return{target:channel,addListener:PortAdaptorProto.addListener,removeListener:PortAdaptorProto.removeListener,postMessage(data,transfer){ready.then(()=>channel.postMessage(data,transfer));}};},'Window':function(win,options){debug('Window');var ready=options&&options.ready||win===parent||win===self;ready=ready?Promise.resolve():windowReady(win);return{addListener(callback,listen){on(window,MSG,callback);},removeListener(callback,listen){off(window,MSG,callback);},postMessage(data,transfer){ready.then(()=>postMessageSync(win,data,transfer));}};},'SharedWorker':function(worker){worker.port.start();return new PortAdaptor(worker.port);},'SharedWorkerGlobalScope':function(){var ports=[];return{postMessage(){},addListener(callback,listen){this.onconnect=e=>{var port=e.ports[0];ports.push(port);port.start();listen(port);};on(self,'connect',this.onconnect);},removeListener(callback,unlisten){off(self,'connect',this.onconnect);ports.forEach(port=>{port.close();unlisten(port);});}};}};var windowReady=(function(){if(typeof window=='undefined')return;var parent=window.opener||window.parent;var domReady='DOMContentLoaded';var windows=new WeakSet();if(parent!=self){on(window,domReady,function fn(){off(window,domReady,fn);postMessageSync(parent,'load');});}
|
||||
on(self,'message',e=>e.data=='load'&&windows.add(e.source));return target=>{var win=target.contentWindow||target;if(windows.has(win))return Promise.resolve();if(win==window.parent)return Promise.resolve();var def=deferred();debug('waiting for Window to be ready ...');on(window,'message',function fn(e){if(e.data=='load'&&e.source==win){debug('Window ready');off(window,'message',fn);def.resolve();}});return def.promise;};})();function isEndpoint(thing){return!!(thing&&thing.addListener);}
|
||||
function on(target,name,fn){target.addEventListener(name,fn);}
|
||||
function off(target,name,fn){target.removeEventListener(name,fn);}
|
||||
function postMessageSync(win,data,transfer){try{void win.document;}catch(_){win.postMessage(data,location.origin,transfer||[]);return;}var event={data:data,source:self};if(transfer)event.ports=transfer;win.dispatchEvent(new MessageEvent('message',event));}
|
||||
function error(id){return new Error({1:'target is undefined'}[id]);}},{"../utils":7}],6:[function(require,module,exports){'use strict';var uuid=require('./utils').uuid;var message=require('./message');var Receiver=message.Receiver;module.exports=Service;var debug={0:()=>{},1:arg=>performance.mark(`[${self.constructor.name}][Service] - ${arg}`),2:(arg1,...args)=>{var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[Service]${type} - "${arg1}"`,...args);}}[0];Service.prototype=Object.create(Receiver.prototype);function Service(name){if(!(this instanceof Service))return new Service(name);message.Receiver.call(this,name);this.clients={};this.methods={};this.on('_disconnect',this.onDisconnect.bind(this)).on('_connect',this.onConnect.bind(this)).on('_method',this.onMethod.bind(this)).on('_off',this.onOff.bind(this)).on('_on',this.onOn.bind(this));this.destroy=this.destroy.bind(this);debug('initialized',name);}
|
||||
Service.prototype.inWindow=constructor.name==='Window';Service.prototype.method=function(name,fn){this.methods[name]=fn;return this;};Service.prototype.broadcast=function(type,data,only){debug('broadcast',type,data,only);this.eachClient(client=>{if(only&&!~only.indexOf(client.id))return;debug('broadcasting to',client.id);this.push(type,data,client.id,{noRespond:true});});return this;};Service.prototype.push=function(type,data,clientId,options){var noRespond=options&&options.noRespond;var client=this.getClient(clientId);return message('_push').set({recipient:clientId,noRespond:noRespond,data:{type:type,data:data}}).send(client.port);};Service.prototype.eachClient=function(fn){for(var id in this.clients)fn(this.clients[id]);};Service.prototype.getClient=function(id){return this.clients[id];};Service.prototype.onConnect=function(message){debug('connection attempt',message.data,this.name);var data=message.data;var clientId=data.clientId;if(!clientId)return;if(data.service!==this.name)return;if(this.clients[clientId])return;this.emit('before-connect',message);if(message.defaultPrevented)return;this.upgradeChannel(message);this.addClient(clientId,message.sourcePort);message.respond();this.emit('connected',clientId);debug('connected',clientId);};Service.prototype.upgradeChannel=function(message){if(this.inWindow&&message.data.originEnv==='Window')return;var ports=message.event.ports;var channel=ports&&ports[0];if(channel){message.setSourcePort(channel);this.listen(channel);channel.start();}
|
||||
debug('channel upgraded');};Service.prototype.onDisconnect=function(message){var client=this.clients[message.data];if(!client)return;this.emit('before-disconnect',message);if(message.defaultPrevented)return;this.removeClient(client.id);message.respond();this.emit('disconnected',client.id);debug('disconnected',client.id);};Service.prototype.onMethod=function(message){debug('on method',message.data);this.emit('before-method',message);if(message.defaultPrevented)return;var method=message.data;var name=method.name;var result;var fn=this.methods[name];if(!fn)throw error(4,name);try{result=fn.apply(this,method.args);}
|
||||
catch(err){result=err;}
|
||||
message.respond(result);};Service.prototype.onOn=function(message){debug('on on',message.data);this.emit('on',message.data);};Service.prototype.onOff=function(message){debug('on off');this.emit('off',message.data);};Service.prototype.addClient=function(id,port){this.clients[id]={id:id,port:port};};Service.prototype.removeClient=function(id){delete this.clients[id];};Service.prototype.plugin=function(fn){fn(this,{'uuid':uuid});return this;};Service.prototype.disconnect=function(client){this.removeClient(client.id);message('disconnect').set({recipient:client.id,noRespond:true}).send(client.port);};Service.prototype.destroy=function(){delete this.clients;this.unlisten();this.off();};var sp=Service.prototype;sp['broadcast']=sp.broadcast;sp['destroy']=sp.destroy;sp['method']=sp.method;sp['plugin']=sp.plugin;function error(id){var args=[].slice.call(arguments,1);return new Error({4:'method "'+args[0]+'" doesn\'t exist'}[id]);}},{"./message":4,"./utils":7}],7:[function(require,module,exports){'use strict';exports.uuid=function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};exports.deferred=function(){var promise={};promise.promise=new Promise((resolve,reject)=>{promise.resolve=resolve;promise.reject=reject;});return promise;};},{}]},{},[1]);
|
||||
31
assets/webapps/music.gaiamobile.org/components/bridge/bridge.min.js
vendored
Normal file
31
assets/webapps/music.gaiamobile.org/components/bridge/bridge.min.js
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
(function e$$0(l,n,b){function m(h,f){if(!n[h]){if(!l[h]){var e="function"==typeof require&&require;if(!f&&e)return e(h,!0);if(p)return p(h,!0);e=Error("Cannot find module '"+h+"'");throw e.code="MODULE_NOT_FOUND",e;}e=n[h]={exports:{}};l[h][0].call(e.exports,function(c){var a=l[h][1][c];return m(a?a:c)},e,e.exports,e$$0,l,n,b)}return n[h].exports}for(var p="function"==typeof require&&require,k=0;k<b.length;k++)m(b[k]);return m})({1:[function(g,l,n){var b={service:g("../src/service"),client:g("../src/client"),
|
||||
_m:g("../src/message")};"u"!=(typeof define)[0]?define([],function(){return b}):self.bridge=b},{"../src/client":2,"../src/message":4,"../src/service":6}],2:[function(g,l,n){function b(a,d,c){if(!(this instanceof b))return new b(a,d,c);"object"==typeof a&&(d=a.endpoint,c=a.timeout,a=a.service);this.id=f();this.service=a;this.timeout=c;this.endpoint=d||this.endpoint;if(!this.endpoint)throw m(1);this.setPort(this.endpoint);this.pending=new Set;this.receiver=h.receiver(this.id).on("_push",this.onPush.bind(this));
|
||||
e("initialized",a)}function m(a,d){d=[].slice.call(arguments,1);return Error({1:"an endpoint must be defined",2:'Unable to establish a connection with "'+d[0]+'". Either the target endpoint is not alive or the Service is not `.listen()`ing.',3:'Method "'+d[0]+"\" didn't get a response. Either the target endpoint is not alive or the Service is not `.listen()`ing."}[a])}var p=g("./message/port-adaptors"),k=g("./emitter"),h=g("./message"),f=g("./utils").uuid;l.exports=b;var e={0:function(){},1:function(a){return performance.mark("["+
|
||||
self.constructor.name+"][Client] - "+a)},2:function(a,d){d=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Client]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],d))}}[0],c=constructor.name;b.prototype={connect:function(){var a=this;e("connect");if(this.connected)return this.connected;e("connecting...",this.service);var d=new MessageChannel;this.channel=d.port1;this.channel.start();var f={clientId:this.id,service:this.service,originEnv:c};return this.connected=
|
||||
this.message("_connect").set("transfer",[d.port2]).set("data",f).listen(d.port1).send().then(function(d){e("connected",d);d.event.target===a.channel?a.setPort(a.channel):(a.channel.close(),delete a.channel);a.receiver.listen(a.port)}).catch(function(d){"timeout"==(d&&d.message)&&(d=m(2,a.service),console.error(d.message));throw d;})},disconnect:function(a){var d=this;if(!this.connected)return Promise.resolve();e("disconnecting ...");a={noRespond:a&&a.noRespond,data:this.id};this.cancelPending();return this.message("_disconnect").set(a).send().then(function(){return d.onDisconnected()})},
|
||||
method:function(a,d){var c=this;d=[].slice.call(arguments,1);return this.connect().then(function(){e("method",a);return c.message("_method").set({recipient:c.service,data:{name:a,args:d}}).send()}).then(function(a){return a.value}).catch(function(d){"timeout"==(d&&d.message)&&(d=m(3,a),console.error(d.message));throw d;})},plugin:function(a){a(this,{Emitter:k,uuid:f});return this},message:function(a){var d=this;e("create message",a);var c=h(a).set("port",this.port).set("timeout",this.timeout).on("response",
|
||||
function(){return d.pending.delete(c)}).on("cancel",function(){return d.pending.delete(c)});this.pending.add(c);return c},cancelPending:function(){e("cancel pending");this.pending.forEach(function(a){a.cancel()});this.pending.clear()},pendingResponded:function(){var a=[];this.pending.forEach(function(d){return a.push(d.responded)});return Promise.all(a)},onPush:function(a){e("on push",a.data);this._emit(a.data.type,a.data.data)},onDisconnected:function(){var a=this;delete this.connected;this.pendingResponded().then(function(){e("disconnected");
|
||||
a.channel&&a.channel.close();a._emit("disconnected")})},setPort:function(a){e("set port");this.port=p(a)},destroy:function(){var a=this;return this.disconnect().then(function(){a.destroyed||(e("destroy"),a.destroyed=!0,a.receiver.destroy(),a._off(),a.port=a.endpoint=a.receiver=null)})},_on:k.prototype.on,_off:k.prototype.off,_emit:k.prototype.emit};b.prototype.on=function(a,d){var c=this;this.connect().then(function(){e("bind on",a);k.prototype.on.call(c,a,d);c.message("_on").set("noRespond",!0).set("data",
|
||||
{name:a,clientId:c.id}).send(c.port)});return this};b.prototype.off=function(a,d){var c=this;this.connect().then(function(){k.prototype.off.call(c,a,d);c.message("_off").set("noRespond",!0).set("data",{name:a,clientId:c.id}).send(c.port)});return this}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":7}],3:[function(g,l,n){function b(m){if(m)return Object.assign(m,b.prototype)}l.exports=b;b.prototype={on:function(b,g){this._callbacks||(this._callbacks={});this._callbacks[b]||(this._callbacks[b]=
|
||||
[]);this._callbacks[b].push(g);return this},off:function(b,g){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[b];break;default:var k=this._callbacks[b];if(!k)return;var h=k.indexOf(g);~h&&k.splice(h,1)}return this},emit:function(b,g){if(this._callbacks)for(var k=this._callbacks[b]||[],k=k.concat(this._callbacks["*"]||[]),h=0;h<k.length;h++)k[h].call(this,g,b);return this}};g=b.prototype;g.off=g.off;g.on=g.on},{}],4:[function(g,l,n){function b(a){this.cancelled=
|
||||
!1;this.listeners=[];this.deferred=f();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);c("initialized",a)}function m(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);c("receiver initialized",a)}function p(a,c){c=[].slice.call(arguments,1);return Error({1:".send() can only be called once",2:"response already sent for this message",
|
||||
3:"a port must be defined",4:"timeout"}[a])}var k=g("./port-adaptors"),h=g("../emitter");g=g("../utils");var f=g.deferred,e=g.uuid;n=l.exports=function(a){return new b(a)};n.receiver=function(a,c){return new m(a,c)};n.Receiver=m;n.Message=b;var c={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+
|
||||
a+'"'],c))}}[0];b.prototype={setupOutbound:function(a){this.id=e();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){c("inbound");this.hasResponded=!1;this.setSourcePort(a.source||a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){c("set source",a.constructor.name);this.sourcePort=k(a,{ready:!0});return this},set:function(a,d){c("set",a,d);"object"==typeof a?Object.assign(this,a):this[a]=d;return this},serialize:function(){return{id:this.id,type:this.type,
|
||||
data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){c("prevent default");this.defaultPrevented=!0},send:function(a){c("send",this.type);if(this.sent)throw p(1);var d=this.serialize(),f=!this.noRespond;this.port=a?k(a):this.port;if(!this.port)throw p(3);f?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(d,this.getTransfer());c("sent",d);return this.deferred.promise},setResponseTimeout:function(){!1!==this.timeout&&
|
||||
(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){c("response timeout",this.type);this.silentTimeout||this.deferred.reject(p(4));this.teardown()},listen:function(a){c("add response listener",a);a=k(a);a.addListener(this.onMessage);this.listeners.push(a);
|
||||
return this},unlisten:function(){var a=this;c("remove response listeners");this.listeners.forEach(function(c){return c.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function d(a){c("resolve",a);b({type:"resolve",value:a})}function f(a){a=a&&a.message||a;c("reject",a);b({type:"reject",value:a})}function b(a){e.response=a;e.sourcePort.postMessage({id:e.id,
|
||||
response:a},e.transfer);c("responded with:",a)}c("respond",a);if(this.hasResponded)throw p(2);if(this.sourcePort&&!this.noRespond){var e=this;this.hasResponded=!0;a instanceof Error&&f(a);Promise.resolve(a).then(d,f).catch(f)}},forward:function(a){var d=this;c("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return d.respond(a.value)})},onResponse:function(a){c("on response",a.data);var d=a.data.response,f="reject"==d.type?d.value:d;d.event=a;this.response=d;this.teardown();
|
||||
this.deferred[this.response.type](f);this.emit("response",d)}};h(b.prototype);m.prototype={listen:function(a){c("listen");a=k(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),this.ports.add(a),this},unlisten:function(){var a=this;c("unlisten");this.ports.forEach(function(c){c.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(c("receiver on message",a.data),a=new b(a),this.emit("message",
|
||||
a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(d){throw a.respond(d),d;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};h(m.prototype)},{"../emitter":3,"../utils":7,"./port-adaptors":5}],5:[function(g,l,n){function b(c){this.target=c}function m(c,a,d){c.addEventListener(a,d)}function p(c,a,d){a={data:a,source:self};d&&(a.ports=d);c.dispatchEvent(new MessageEvent("message",a))}var k=g("../utils").deferred;
|
||||
l.exports=function(c,a){if(!c)throw Error({1:"target is undefined"}[1]);if(c&&c.addListener)return c;var d=f[c.constructor.name];return d?d(c,a):new b(c,a)};var h=b.prototype={constructor:b,addListener:function(c){this.target.addEventListener("message",c)},removeListener:function(c){this.target.removeEventListener("message",c)},postMessage:function(c,a){this.target.postMessage(c,a)}},f={HTMLIFrameElement:function(c){var a=e(c);return{addListener:function(a,c){window.addEventListener("message",a)},
|
||||
removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(d,f){a.then(function(){return p(c.contentWindow,d,f)})}}},BroadcastChannel:function(c,a){function d(){var a=k();c.postMessage("ready?");m(c,"message",function q(d){"ready"==d.data&&(c.removeEventListener("message",q),a.resolve())});return a.promise}var f=a&&a.receiver,b=a&&a.ready,b=b||f?Promise.resolve():d();f&&(c.postMessage("ready"),m(c,"message",function(a){"ready?"==a.data&&c.postMessage("ready")}));return{target:c,
|
||||
addListener:h.addListener,removeListener:h.removeListener,postMessage:function(a,d){b.then(function(){return c.postMessage(a,d)})}}},Window:function(c,a){var d=a&&a.ready||c===parent||c===self,d=d?Promise.resolve():e(c);return{addListener:function(a,c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,f){d.then(function(){return p(c,a,f)})}}},SharedWorker:function(c){c.port.start();return new b(c.port)},SharedWorkerGlobalScope:function(){var c=
|
||||
[];return{postMessage:function(){},addListener:function(a,d){this.onconnect=function(a){a=a.ports[0];c.push(a);a.start();d(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,d){self.removeEventListener("connect",this.onconnect);c.forEach(function(a){a.close();d(a)})}}}},e=function(){if("undefined"!=typeof window){var c=window.opener||window.parent,a=new WeakSet;c!=self&&m(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",r);p(c,"load")});
|
||||
m(self,"message",function(c){return"load"==c.data&&a.add(c.source)});return function(c){var f=c.contentWindow||c;if(a.has(f)||f==window.parent)return Promise.resolve();var b=k();m(window,"message",function q(a){"load"==a.data&&a.source==f&&(window.removeEventListener("message",q),b.resolve())});return b.promise}}}()},{"../utils":7}],6:[function(g,l,n){function b(f){if(!(this instanceof b))return new b(f);k.Receiver.call(this,f);this.clients={};this.methods={};this.on("_disconnect",this.onDisconnect.bind(this)).on("_connect",
|
||||
this.onConnect.bind(this)).on("_method",this.onMethod.bind(this)).on("_off",this.onOff.bind(this)).on("_on",this.onOn.bind(this));this.destroy=this.destroy.bind(this);h("initialized",f)}function m(f){var b=[].slice.call(arguments,1);return Error({4:'method "'+b[0]+"\" doesn't exist"}[f])}var p=g("./utils").uuid,k=g("./message");g=k.Receiver;l.exports=b;var h={0:function(){},1:function(f){return performance.mark("["+self.constructor.name+"][Service] - "+f)},2:function(f,b){b=[].slice.call(arguments,
|
||||
1);console.log.apply(console,[].concat(["[Service]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+f+'"'],b))}}[0];b.prototype=Object.create(g.prototype);b.prototype.inWindow="Window"===constructor.name;b.prototype.method=function(f,b){this.methods[f]=b;return this};b.prototype.broadcast=function(f,b,c){var a=this;h("broadcast",f,b,c);this.eachClient(function(d){if(!c||~c.indexOf(d.id))h("broadcasting to",d.id),a.push(f,b,d.id,{noRespond:!0})});return this};b.prototype.push=function(b,
|
||||
e,c,a){a=a&&a.noRespond;var d=this.getClient(c);return k("_push").set({recipient:c,noRespond:a,data:{type:b,data:e}}).send(d.port)};b.prototype.eachClient=function(b){for(var e in this.clients)b(this.clients[e])};b.prototype.getClient=function(b){return this.clients[b]};b.prototype.onConnect=function(b){h("connection attempt",b.data,this.name);var e=b.data,c=e.clientId;c&&e.service===this.name&&!this.clients[c]&&(this.emit("before-connect",b),b.defaultPrevented||(this.upgradeChannel(b),this.addClient(c,
|
||||
b.sourcePort),b.respond(),this.emit("connected",c),h("connected",c)))};b.prototype.upgradeChannel=function(b){if(!this.inWindow||"Window"!==b.data.originEnv){var e=b.event.ports;if(e=e&&e[0])b.setSourcePort(e),this.listen(e),e.start();h("channel upgraded")}};b.prototype.onDisconnect=function(b){var e=this.clients[b.data];e&&(this.emit("before-disconnect",b),b.defaultPrevented||(this.removeClient(e.id),b.respond(),this.emit("disconnected",e.id),h("disconnected",e.id)))};b.prototype.onMethod=function(b){h("on method",
|
||||
b.data);this.emit("before-method",b);if(!b.defaultPrevented){var e=b.data,c=e.name,a,d=this.methods[c];if(!d)throw m(4,c);try{a=d.apply(this,e.args)}catch(g){a=g}b.respond(a)}};b.prototype.onOn=function(b){h("on on",b.data);this.emit("on",b.data)};b.prototype.onOff=function(b){h("on off");this.emit("off",b.data)};b.prototype.addClient=function(b,e){this.clients[b]={id:b,port:e}};b.prototype.removeClient=function(b){delete this.clients[b]};b.prototype.plugin=function(b){b(this,{uuid:p});return this};
|
||||
b.prototype.disconnect=function(b){this.removeClient(b.id);k("disconnect").set({recipient:b.id,noRespond:!0}).send(b.port)};b.prototype.destroy=function(){delete this.clients;this.unlisten();this.off()};l=b.prototype;l.broadcast=l.broadcast;l.destroy=l.destroy;l.method=l.method;l.plugin=l.plugin},{"./message":4,"./utils":7}],7:[function(g,l,n){n.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(b){var g=16*Math.random()|0;return("x"==b?g:g&3|8).toString(16)})};
|
||||
n.deferred=function(){var b={};b.promise=new Promise(function(g,l){b.resolve=g;b.reject=l});return b}},{}]},{},[1]);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
var bridge = {
|
||||
'service': require('../src/service'),
|
||||
'client': require('../src/client'),
|
||||
_m: require('../src/message')
|
||||
};
|
||||
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
var bridge = module.exports = self['bridge'] || {};
|
||||
bridge['client'] = require('../src/client');
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
var bridge = module.exports = self['bridge'] || {};
|
||||
bridge['service'] = require('../src/service');
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
;;;;;;;;;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.bridge=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){var bridge=module.exports=self['bridge']||{};bridge['client']=require('../src/client');if((typeof define)[0]!='u')define([],()=>bridge);else self['bridge']=bridge;},{"../src/client":2}],2:[function(require,module,exports){'use strict';var createPort=require('./message/port-adaptors');var Emitter=require('./emitter');var message=require('./message');var uuid=require('./utils').uuid;module.exports=Client;var debug={0:()=>{},1:arg=>performance.mark(`[${self.constructor.name}][Client] - ${arg}`),2:(arg1,...args)=>{var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[Client]${type} - "${arg1}"`,...args);}}[0];var env=constructor.name
|
||||
function Client(service,endpoint,timeout){if(!(this instanceof Client))return new Client(service,endpoint,timeout);if(typeof service=='object'){endpoint=service.endpoint;timeout=service.timeout;service=service.service;}
|
||||
this.id=uuid();this.service=service;this.timeout=timeout;this.endpoint=endpoint||this.endpoint;if(!this.endpoint)throw error(1);this.setPort(this.endpoint);this.pending=new Set();this.receiver=message.receiver(this.id).on('_push',this.onPush.bind(this));debug('initialized',service);}
|
||||
Client.prototype={connect(){debug('connect');if(this.connected)return this.connected;debug('connecting...',this.service);var mc=new MessageChannel();this.channel=mc.port1;this.channel.start();var data={clientId:this.id,service:this.service,originEnv:env};return this.connected=this.message('_connect').set('transfer',[mc.port2]).set('data',data).listen(mc.port1).send().then(response=>{debug('connected',response);var usingChannel=response.event.target===this.channel;if(usingChannel)this.setPort(this.channel);else{this.channel.close();delete this.channel;}
|
||||
this.receiver.listen(this.port);}).catch(err=>{var msg=err&&err.message;if(msg=='timeout'){err=error(2,this.service);console.error(err.message);}
|
||||
throw err;});},disconnect(options){if(!this.connected)return Promise.resolve();debug('disconnecting ...');var config={noRespond:options&&options.noRespond,data:this.id};this.cancelPending();return this.message('_disconnect').set(config).send().then(()=>this.onDisconnected());},method(name,...args){return this.connect().then(()=>{debug('method',name);return this.message('_method').set({recipient:this.service,data:{name:name,args:args}}).send();}).then(response=>response.value).catch(err=>{var msg=err&&err.message;if(msg=='timeout'){err=error(3,name);console.error(err.message);}
|
||||
throw err;});},plugin(fn){fn(this,{'Emitter':Emitter,'uuid':uuid});return this;},message(type){debug('create message',type);var msg=message(type).set('port',this.port).set('timeout',this.timeout).on('response',()=>this.pending.delete(msg)).on('cancel',()=>this.pending.delete(msg));this.pending.add(msg);return msg;},cancelPending(){debug('cancel pending');this.pending.forEach(msg=>{msg.cancel();});this.pending.clear();},pendingResponded(){var responded=[];this.pending.forEach(msg=>responded.push(msg.responded));return Promise.all(responded);},onPush(message){debug('on push',message.data);this._emit(message.data.type,message.data.data);},onDisconnected(){delete this.connected;this.pendingResponded().then(()=>{debug('disconnected');if(this.channel)this.channel.close();this._emit('disconnected');});},setPort(endpoint){debug('set port');this.port=createPort(endpoint);},destroy:function(){return this.disconnect().then(()=>{if(this.destroyed)return;debug('destroy');this.destroyed=true;this.receiver.destroy();this._off();this.port=this.endpoint=this.receiver=null;});},_on:Emitter.prototype.on,_off:Emitter.prototype.off,_emit:Emitter.prototype.emit};Client.prototype.on=function(name,fn){this.connect().then(()=>{debug('bind on',name);Emitter.prototype.on.call(this,name,fn);this.message('_on').set('noRespond',true).set('data',{name:name,clientId:this.id}).send(this.port);});return this;};Client.prototype.off=function(name,fn){this.connect().then(()=>{Emitter.prototype.off.call(this,name,fn);this.message('_off').set('noRespond',true).set('data',{name:name,clientId:this.id}).send(this.port);});return this;};function error(id,...args){var help='Either the target endpoint is not alive or the Service is not `.listen()`ing.';return new Error({1:'an endpoint must be defined',2:`Unable to establish a connection with "${args[0]}". ${help}`,3:`Method "${args[0]}" didn't get a response. ${help}`}[id]);}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":6}],3:[function(require,module,exports){'use strict';module.exports=Emitter;var debug=0?console.log.bind(console,'[Emitter]'):()=>{};function Emitter(host){if(host)return Object.assign(host,Emitter.prototype);}
|
||||
Emitter.prototype={on:function(type,callback){debug('on',type,callback);if(!this._callbacks)this._callbacks={};if(!this._callbacks[type])this._callbacks[type]=[];this._callbacks[type].push(callback);return this;},off:function(type,callback){debug('off',type,callback);if(this._callbacks){switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[type];break;default:var typeListeners=this._callbacks[type];if(!typeListeners)return;var i=typeListeners.indexOf(callback);if(~i)typeListeners.splice(i,1);}}
|
||||
return this;},emit:function(type,data){debug('emit',type,data);if(this._callbacks){var fns=this._callbacks[type]||[];fns=fns.concat(this._callbacks['*']||[]);for(var i=0;i<fns.length;i++)fns[i].call(this,data,type);}
|
||||
return this;}};var p=Emitter.prototype;p['off']=p.off;p['on']=p.on;},{}],4:[function(require,module,exports){'use strict';var createPort=require('./port-adaptors');var Emitter=require('../emitter');var utils=require('../utils');var defer=utils.deferred;var uuid=utils.uuid;exports=module.exports=type=>new Message(type);exports.receiver=(id,n)=>new Receiver(id,n);exports.Receiver=Receiver;exports.Message=Message;var debug={0:()=>{},1:arg=>performance.mark(`[${self.constructor.name}][Message] - ${arg}`),2:(arg1,...args)=>{var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[Message]${type} - "${arg1}"`,...args);}}[0];var TIMEOUT=1000;function Message(type){this.cancelled=false;this.listeners=[];this.deferred=defer();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);if(typeof type==='object')this.setupInbound(type);else this.setupOutbound(type);debug('initialized',type);}
|
||||
Message.prototype={setupOutbound(type){this.id=uuid();this.type=type;this.sent=false;this.recipient='*';},setupInbound(e){debug('inbound');this.hasResponded=false;this.setSourcePort(e.source||e.target);this.event=e;Object.assign(this,e.data);},setSourcePort(endpoint){debug('set source',endpoint.constructor.name);this.sourcePort=createPort(endpoint,{ready:true});return this;},set(key,value){debug('set',key,value);if(typeof key=='object')Object.assign(this,key);else this[key]=value;return this;},serialize(){return{id:this.id,type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond};},preventDefault(){debug('prevent default');this.defaultPrevented=true;},send(endpoint){debug('send',this.type);if(this.sent)throw error(1);var serialized=this.serialize();var expectsResponse=!this.noRespond;this.port=endpoint?createPort(endpoint):this.port;if(!this.port)throw error(3);if(expectsResponse){this.listen(this.port);this.setResponseTimeout();}else this.deferred.resolve();this.port.postMessage(serialized,this.getTransfer());debug('sent',serialized);return this.deferred.promise;},setResponseTimeout(){if(this.timeout===false)return;var ms=this.timeout||TIMEOUT;this._timer=setTimeout(this.onTimeout,ms);},clearResponseTimeout(){clearTimeout(this._timer);},getTransfer(){return this.transfer||this.event&&this.event.ports;},onMessage(e){var valid=!!e.data.response&&e.data.id===this.id&&!this.cancelled;if(valid)this.onResponse(e);},onTimeout(){debug('response timeout',this.type);if(!this.silentTimeout)this.deferred.reject(error(4));this.teardown();},listen(thing){debug('add response listener',thing);var port=createPort(thing);port.addListener(this.onMessage);this.listeners.push(port);return this;},unlisten(){debug('remove response listeners');this.listeners.forEach(port=>port.removeListener(this.onMessage));this.listeners=[];},cancel(){this.teardown();this.cancelled=true;this.emit('cancel');},teardown(){this.clearResponseTimeout();this.unlisten();},respond(result){debug('respond',result);if(this.hasResponded)throw error(2);if(!this.sourcePort)return;if(this.noRespond)return;var self=this;this.hasResponded=true;if(result instanceof Error)reject(result);Promise.resolve(result).then(resolve,reject).catch(reject);function resolve(value){debug('resolve',value);respond({type:'resolve',value:value});}
|
||||
function reject(err){var msg=err&&err.message||err;debug('reject',msg);respond({type:'reject',value:msg});}
|
||||
function respond(response){self.response=response;self.sourcePort.postMessage({id:self.id,response:response},self.transfer);debug('responded with:',response);}},forward(endpoint){debug('forward');return this.set('silentTimeout',true).send(endpoint).then(result=>this.respond(result.value));},onResponse(e){debug('on response',e.data);var response=e.data.response;var type=response.type;var value=type=='reject'?response.value:response;response.event=e;this.response=response;this.teardown();this.deferred[this.response.type](value);this.emit('response',response);}};Emitter(Message.prototype);function Receiver(name){this.name=name;this.ports=new Set();this.onMessage=this.onMessage.bind(this);this['listen']=this['listen'].bind(this);this['unlisten']=this['unlisten'].bind(this);debug('receiver initialized',name);}
|
||||
Receiver.prototype={listen(thing){debug('listen');var _port=createPort(thing||self,{receiver:true});if(this.ports.has(_port))return;_port.addListener(this.onMessage,this.listen);this.ports.add(_port);return this;},unlisten(){debug('unlisten');this.ports.forEach(port=>{port.removeListener(this.onMessage,this.unlisten);});},onMessage(e){if(!e.data.id)return;if(!e.data.type)return;if(!this.isRecipient(e.data.recipient))return;debug('receiver on message',e.data);var message=new Message(e);this.emit('message',message);if(message.defaultPrevented)return;try{this.emit(message.type,message);}
|
||||
catch(e){message.respond(e);throw e;}},isRecipient(recipient){return recipient==this.name||recipient=='*'||this.name=='*';},destroy:function(){this.unlisten();delete this.name;return this;}};Emitter(Receiver.prototype);function error(id,...args){return new Error({1:'.send() can only be called once',2:'response already sent for this message',3:'a port must be defined',4:'timeout'}[id]);}},{"../emitter":3,"../utils":6,"./port-adaptors":5}],5:[function(require,module,exports){'use strict';var deferred=require('../utils').deferred;const MSG='message';var debug=0?function(arg1,...args){var type=`[${self.constructor.name}][${location.pathname}]`;console.log(`[PortAdaptor]${type} - "${arg1}"`,...args);}:()=>{};module.exports=function create(target,options){if(!target)throw error(1);if(isEndpoint(target))return target;var type=target.constructor.name;var CustomAdaptor=adaptors[type];debug('creating port adaptor for',type);if(CustomAdaptor)return CustomAdaptor(target,options);return new PortAdaptor(target,options);};function PortAdaptor(target){debug('PortAdaptor');this.target=target;}
|
||||
var PortAdaptorProto=PortAdaptor.prototype={constructor:PortAdaptor,addListener(callback){on(this.target,MSG,callback);},removeListener(callback){off(this.target,MSG,callback);},postMessage(data,transfer){this.target.postMessage(data,transfer);}};var adaptors={'HTMLIFrameElement':function(iframe){debug('HTMLIFrameElement');var ready=windowReady(iframe);return{addListener(callback,listen){on(window,MSG,callback);},removeListener(callback,listen){off(window,MSG,callback);},postMessage(data,transfer){ready.then(()=>postMessageSync(iframe.contentWindow,data,transfer));}};},'BroadcastChannel':function(channel,options){debug('BroadcastChannel',channel.name);var receiver=options&&options.receiver;var ready=options&&options.ready;var sendReady=()=>{channel.postMessage('ready');debug('sent ready');};ready=ready||receiver?Promise.resolve():setupSender();if(receiver){sendReady();on(channel,MSG,e=>{if(e.data!='ready?')return;sendReady();});}
|
||||
function setupSender(){debug('setup sender');var promise=deferred();channel.postMessage('ready?');on(channel,MSG,function fn(e){if(e.data!='ready')return;off(channel,MSG,fn);debug('BroadcastChannel: ready');promise.resolve();});return promise.promise;}
|
||||
return{target:channel,addListener:PortAdaptorProto.addListener,removeListener:PortAdaptorProto.removeListener,postMessage(data,transfer){ready.then(()=>channel.postMessage(data,transfer));}};},'Window':function(win,options){debug('Window');var ready=options&&options.ready||win===parent||win===self;ready=ready?Promise.resolve():windowReady(win);return{addListener(callback,listen){on(window,MSG,callback);},removeListener(callback,listen){off(window,MSG,callback);},postMessage(data,transfer){ready.then(()=>postMessageSync(win,data,transfer));}};},'SharedWorker':function(worker){worker.port.start();return new PortAdaptor(worker.port);},'SharedWorkerGlobalScope':function(){var ports=[];return{postMessage(){},addListener(callback,listen){this.onconnect=e=>{var port=e.ports[0];ports.push(port);port.start();listen(port);};on(self,'connect',this.onconnect);},removeListener(callback,unlisten){off(self,'connect',this.onconnect);ports.forEach(port=>{port.close();unlisten(port);});}};}};var windowReady=(function(){if(typeof window=='undefined')return;var parent=window.opener||window.parent;var domReady='DOMContentLoaded';var windows=new WeakSet();if(parent!=self){on(window,domReady,function fn(){off(window,domReady,fn);postMessageSync(parent,'load');});}
|
||||
on(self,'message',e=>e.data=='load'&&windows.add(e.source));return target=>{var win=target.contentWindow||target;if(windows.has(win))return Promise.resolve();if(win==window.parent)return Promise.resolve();var def=deferred();debug('waiting for Window to be ready ...');on(window,'message',function fn(e){if(e.data=='load'&&e.source==win){debug('Window ready');off(window,'message',fn);def.resolve();}});return def.promise;};})();function isEndpoint(thing){return!!(thing&&thing.addListener);}
|
||||
function on(target,name,fn){target.addEventListener(name,fn);}
|
||||
function off(target,name,fn){target.removeEventListener(name,fn);}
|
||||
function postMessageSync(win,data,transfer){try{void win.document;}catch(_){win.postMessage(data,location.origin,transfer||[]);return;}var event={data:data,source:self};if(transfer)event.ports=transfer;win.dispatchEvent(new MessageEvent('message',event));}
|
||||
function error(id){return new Error({1:'target is undefined'}[id]);}},{"../utils":6}],6:[function(require,module,exports){'use strict';exports.uuid=function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};exports.deferred=function(){var promise={};promise.promise=new Promise((resolve,reject)=>{promise.resolve=resolve;promise.reject=reject;});return promise;};},{}]},{},[1])(1)});
|
||||
25
assets/webapps/music.gaiamobile.org/components/bridge/client.min.js
vendored
Normal file
25
assets/webapps/music.gaiamobile.org/components/bridge/client.min.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
(function(q){"object"===typeof exports&&"undefined"!==typeof module?module.exports=q():"function"===typeof define&&define.amd?define([],q):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).bridge=q()})(function(){return function e(l,h,d){function k(f,p){if(!h[f]){if(!l[f]){var g="function"==typeof require&&require;if(!p&&g)return g(f,!0);if(m)return m(f,!0);g=Error("Cannot find module '"+f+"'");throw g.code="MODULE_NOT_FOUND",g;}g=h[f]={exports:{}};
|
||||
l[f][0].call(g.exports,function(b){var a=l[f][1][b];return k(a?a:b)},g,g.exports,e,l,h,d)}return h[f].exports}for(var m="function"==typeof require&&require,f=0;f<d.length;f++)k(d[f]);return k}({1:[function(e,l,h){var d=l.exports=self.bridge||{};d.client=e("../src/client");"u"!="undefined"[0]?(void 0)([],function(){return d}):self.bridge=d},{"../src/client":2}],2:[function(e,l,h){function d(a,c,b){if(!(this instanceof d))return new d(a,c,b);"object"==typeof a&&(c=a.endpoint,b=a.timeout,a=a.service);
|
||||
this.id=p();this.service=a;this.timeout=b;this.endpoint=c||this.endpoint;if(!this.endpoint)throw k(1);this.setPort(this.endpoint);this.pending=new Set;this.receiver=n.receiver(this.id).on("_push",this.onPush.bind(this));g("initialized",a)}function k(a,c){c=[].slice.call(arguments,1);return Error({1:"an endpoint must be defined",2:'Unable to establish a connection with "'+c[0]+'". Either the target endpoint is not alive or the Service is not `.listen()`ing.',3:'Method "'+c[0]+"\" didn't get a response. Either the target endpoint is not alive or the Service is not `.listen()`ing."}[a])}
|
||||
var m=e("./message/port-adaptors"),f=e("./emitter"),n=e("./message"),p=e("./utils").uuid;l.exports=d;var g={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Client] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Client]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],c))}}[0],b=constructor.name;d.prototype={connect:function(){var a=this;g("connect");if(this.connected)return this.connected;g("connecting...",
|
||||
this.service);var c=new MessageChannel;this.channel=c.port1;this.channel.start();var r={clientId:this.id,service:this.service,originEnv:b};return this.connected=this.message("_connect").set("transfer",[c.port2]).set("data",r).listen(c.port1).send().then(function(c){g("connected",c);c.event.target===a.channel?a.setPort(a.channel):(a.channel.close(),delete a.channel);a.receiver.listen(a.port)}).catch(function(c){"timeout"==(c&&c.message)&&(c=k(2,a.service),console.error(c.message));throw c;})},disconnect:function(a){var c=
|
||||
this;if(!this.connected)return Promise.resolve();g("disconnecting ...");a={noRespond:a&&a.noRespond,data:this.id};this.cancelPending();return this.message("_disconnect").set(a).send().then(function(){return c.onDisconnected()})},method:function(a,c){var b=this;c=[].slice.call(arguments,1);return this.connect().then(function(){g("method",a);return b.message("_method").set({recipient:b.service,data:{name:a,args:c}}).send()}).then(function(a){return a.value}).catch(function(c){"timeout"==(c&&c.message)&&
|
||||
(c=k(3,a),console.error(c.message));throw c;})},plugin:function(a){a(this,{Emitter:f,uuid:p});return this},message:function(a){var c=this;g("create message",a);var b=n(a).set("port",this.port).set("timeout",this.timeout).on("response",function(){return c.pending.delete(b)}).on("cancel",function(){return c.pending.delete(b)});this.pending.add(b);return b},cancelPending:function(){g("cancel pending");this.pending.forEach(function(a){a.cancel()});this.pending.clear()},pendingResponded:function(){var a=
|
||||
[];this.pending.forEach(function(c){return a.push(c.responded)});return Promise.all(a)},onPush:function(a){g("on push",a.data);this._emit(a.data.type,a.data.data)},onDisconnected:function(){var a=this;delete this.connected;this.pendingResponded().then(function(){g("disconnected");a.channel&&a.channel.close();a._emit("disconnected")})},setPort:function(a){g("set port");this.port=m(a)},destroy:function(){var a=this;return this.disconnect().then(function(){a.destroyed||(g("destroy"),a.destroyed=!0,a.receiver.destroy(),
|
||||
a._off(),a.port=a.endpoint=a.receiver=null)})},_on:f.prototype.on,_off:f.prototype.off,_emit:f.prototype.emit};d.prototype.on=function(a,c){var b=this;this.connect().then(function(){g("bind on",a);f.prototype.on.call(b,a,c);b.message("_on").set("noRespond",!0).set("data",{name:a,clientId:b.id}).send(b.port)});return this};d.prototype.off=function(a,c){var b=this;this.connect().then(function(){f.prototype.off.call(b,a,c);b.message("_off").set("noRespond",!0).set("data",{name:a,clientId:b.id}).send(b.port)});
|
||||
return this}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":6}],3:[function(e,l,h){function d(k){if(k)return Object.assign(k,d.prototype)}l.exports=d;d.prototype={on:function(d,e){this._callbacks||(this._callbacks={});this._callbacks[d]||(this._callbacks[d]=[]);this._callbacks[d].push(e);return this},off:function(d,e){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[d];break;default:var f=this._callbacks[d];if(!f)return;
|
||||
var h=f.indexOf(e);~h&&f.splice(h,1)}return this},emit:function(d,e){if(this._callbacks)for(var f=this._callbacks[d]||[],f=f.concat(this._callbacks["*"]||[]),h=0;h<f.length;h++)f[h].call(this,e,d);return this}};e=d.prototype;e.off=e.off;e.on=e.on},{}],4:[function(e,l,h){function d(a){this.cancelled=!1;this.listeners=[];this.deferred=p();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);b("initialized",a)}
|
||||
function k(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);b("receiver initialized",a)}function m(a,c){c=[].slice.call(arguments,1);return Error({1:".send() can only be called once",2:"response already sent for this message",3:"a port must be defined",4:"timeout"}[a])}var f=e("./port-adaptors"),n=e("../emitter");e=e("../utils");var p=e.deferred,g=e.uuid;h=l.exports=function(a){return new d(a)};h.receiver=
|
||||
function(a,c){return new k(a,c)};h.Receiver=k;h.Message=d;var b={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],c))}}[0];d.prototype={setupOutbound:function(a){this.id=g();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){b("inbound");this.hasResponded=!1;this.setSourcePort(a.source||
|
||||
a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){b("set source",a.constructor.name);this.sourcePort=f(a,{ready:!0});return this},set:function(a,c){b("set",a,c);"object"==typeof a?Object.assign(this,a):this[a]=c;return this},serialize:function(){return{id:this.id,type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){b("prevent default");this.defaultPrevented=!0},send:function(a){b("send",this.type);if(this.sent)throw m(1);
|
||||
var c=this.serialize(),d=!this.noRespond;this.port=a?f(a):this.port;if(!this.port)throw m(3);d?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(c,this.getTransfer());b("sent",c);return this.deferred.promise},setResponseTimeout:function(){!1!==this.timeout&&(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&
|
||||
a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){b("response timeout",this.type);this.silentTimeout||this.deferred.reject(m(4));this.teardown()},listen:function(a){b("add response listener",a);a=f(a);a.addListener(this.onMessage);this.listeners.push(a);return this},unlisten:function(){var a=this;b("remove response listeners");this.listeners.forEach(function(c){return c.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;
|
||||
this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function c(a){b("resolve",a);f({type:"resolve",value:a})}function d(a){a=a&&a.message||a;b("reject",a);f({type:"reject",value:a})}function f(a){e.response=a;e.sourcePort.postMessage({id:e.id,response:a},e.transfer);b("responded with:",a)}b("respond",a);if(this.hasResponded)throw m(2);if(this.sourcePort&&!this.noRespond){var e=this;this.hasResponded=!0;a instanceof Error&&d(a);Promise.resolve(a).then(c,
|
||||
d).catch(d)}},forward:function(a){var c=this;b("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return c.respond(a.value)})},onResponse:function(a){b("on response",a.data);var c=a.data.response,d="reject"==c.type?c.value:c;c.event=a;this.response=c;this.teardown();this.deferred[this.response.type](d);this.emit("response",c)}};n(d.prototype);k.prototype={listen:function(a){b("listen");a=f(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),
|
||||
this.ports.add(a),this},unlisten:function(){var a=this;b("unlisten");this.ports.forEach(function(c){c.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(b("receiver on message",a.data),a=new d(a),this.emit("message",a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(c){throw a.respond(c),c;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};
|
||||
n(k.prototype)},{"../emitter":3,"../utils":6,"./port-adaptors":5}],5:[function(e,l,h){function d(b){this.target=b}function k(b,a,c){b.addEventListener(a,c)}function m(b,a,c){a={data:a,source:self};c&&(a.ports=c);b.dispatchEvent(new MessageEvent("message",a))}var f=e("../utils").deferred;l.exports=function(b,a){if(!b)throw Error({1:"target is undefined"}[1]);if(b&&b.addListener)return b;var c=p[b.constructor.name];return c?c(b,a):new d(b,a)};var n=d.prototype={constructor:d,addListener:function(b){this.target.addEventListener("message",
|
||||
b)},removeListener:function(b){this.target.removeEventListener("message",b)},postMessage:function(b,a){this.target.postMessage(b,a)}},p={HTMLIFrameElement:function(b){var a=g(b);return{addListener:function(a,b){window.addEventListener("message",a)},removeListener:function(a,b){window.removeEventListener("message",a)},postMessage:function(c,d){a.then(function(){return m(b.contentWindow,c,d)})}}},BroadcastChannel:function(b,a){function c(){var a=f();b.postMessage("ready?");k(b,"message",function t(c){"ready"==
|
||||
c.data&&(b.removeEventListener("message",t),a.resolve())});return a.promise}var d=a&&a.receiver,e=a&&a.ready,e=e||d?Promise.resolve():c();d&&(b.postMessage("ready"),k(b,"message",function(a){"ready?"==a.data&&b.postMessage("ready")}));return{target:b,addListener:n.addListener,removeListener:n.removeListener,postMessage:function(a,c){e.then(function(){return b.postMessage(a,c)})}}},Window:function(b,a){var c=a&&a.ready||b===parent||b===self,c=c?Promise.resolve():g(b);return{addListener:function(a,
|
||||
c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,d){c.then(function(){return m(b,a,d)})}}},SharedWorker:function(b){b.port.start();return new d(b.port)},SharedWorkerGlobalScope:function(){var b=[];return{postMessage:function(){},addListener:function(a,c){this.onconnect=function(a){a=a.ports[0];b.push(a);a.start();c(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,c){self.removeEventListener("connect",
|
||||
this.onconnect);b.forEach(function(a){a.close();c(a)})}}}},g=function(){if("undefined"!=typeof window){var b=window.opener||window.parent,a=new WeakSet;b!=self&&k(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",r);m(b,"load")});k(self,"message",function(b){return"load"==b.data&&a.add(b.source)});return function(b){var d=b.contentWindow||b;if(a.has(d)||d==window.parent)return Promise.resolve();var e=f();k(window,"message",function t(a){"load"==a.data&&a.source==
|
||||
d&&(window.removeEventListener("message",t),e.resolve())});return e.promise}}}()},{"../utils":6}],6:[function(e,l,h){h.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(d){var e=16*Math.random()|0;return("x"==d?e:e&3|8).toString(16)})};h.deferred=function(){var d={};d.promise=new Promise(function(e,h){d.resolve=e;d.reject=h});return d}},{}]},{},[1])(1)});
|
||||
|
|
@ -0,0 +1 @@
|
|||
<meta http-equiv="refresh" content="0; url=docs/out/index.html" />
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
module.exports = {
|
||||
service: require('./src/service'),
|
||||
client: require('./src/client'),
|
||||
_message: require('./src/message')
|
||||
};
|
||||
1332
assets/webapps/music.gaiamobile.org/components/bridge/service.js
Normal file
1332
assets/webapps/music.gaiamobile.org/components/bridge/service.js
Normal file
File diff suppressed because it is too large
Load diff
24
assets/webapps/music.gaiamobile.org/components/bridge/service.min.js
vendored
Normal file
24
assets/webapps/music.gaiamobile.org/components/bridge/service.min.js
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
(function(p){"object"===typeof exports&&"undefined"!==typeof module?module.exports=p():"function"===typeof define&&define.amd?define([],p):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).bridge=p()})(function(){return function e(h,m,b){function l(f,c){if(!m[f]){if(!h[f]){var k="function"==typeof require&&require;if(!c&&k)return k(f,!0);if(n)return n(f,!0);k=Error("Cannot find module '"+f+"'");throw k.code="MODULE_NOT_FOUND",k;}k=m[f]={exports:{}};
|
||||
h[f][0].call(k.exports,function(d){var a=h[f][1][d];return l(a?a:d)},k,k.exports,e,h,m,b)}return m[f].exports}for(var n="function"==typeof require&&require,g=0;g<b.length;g++)l(b[g]);return l}({1:[function(e,h,m){var b=h.exports=self.bridge||{};b.service=e("../src/service");"u"!="undefined"[0]?(void 0)([],function(){return b}):self.bridge=b},{"../src/service":5}],2:[function(e,h,m){function b(l){if(l)return Object.assign(l,b.prototype)}h.exports=b;b.prototype={on:function(b,e){this._callbacks||(this._callbacks=
|
||||
{});this._callbacks[b]||(this._callbacks[b]=[]);this._callbacks[b].push(e);return this},off:function(b,e){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[b];break;default:var g=this._callbacks[b];if(!g)return;var f=g.indexOf(e);~f&&g.splice(f,1)}return this},emit:function(b,e){if(this._callbacks)for(var g=this._callbacks[b]||[],g=g.concat(this._callbacks["*"]||[]),f=0;f<g.length;f++)g[f].call(this,e,b);return this}};e=b.prototype;e.off=e.off;
|
||||
e.on=e.on},{}],3:[function(e,h,m){function b(a){this.cancelled=!1;this.listeners=[];this.deferred=c();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);d("initialized",a)}function l(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);d("receiver initialized",a)}function n(a,d){d=[].slice.call(arguments,1);return Error({1:".send() can only be called once",
|
||||
2:"response already sent for this message",3:"a port must be defined",4:"timeout"}[a])}var g=e("./port-adaptors"),f=e("../emitter");e=e("../utils");var c=e.deferred,k=e.uuid;m=h.exports=function(a){return new b(a)};m.receiver=function(a,d){return new l(a,d)};m.Receiver=l;m.Message=b;var d={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,d){d=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+
|
||||
"]["+location.pathname+"]")+' - "'+a+'"'],d))}}[0];b.prototype={setupOutbound:function(a){this.id=k();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){d("inbound");this.hasResponded=!1;this.setSourcePort(a.source||a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){d("set source",a.constructor.name);this.sourcePort=g(a,{ready:!0});return this},set:function(a,c){d("set",a,c);"object"==typeof a?Object.assign(this,a):this[a]=c;return this},serialize:function(){return{id:this.id,
|
||||
type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){d("prevent default");this.defaultPrevented=!0},send:function(a){d("send",this.type);if(this.sent)throw n(1);var c=this.serialize(),b=!this.noRespond;this.port=a?g(a):this.port;if(!this.port)throw n(3);b?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(c,this.getTransfer());d("sent",c);return this.deferred.promise},setResponseTimeout:function(){!1!==
|
||||
this.timeout&&(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){d("response timeout",this.type);this.silentTimeout||this.deferred.reject(n(4));this.teardown()},listen:function(a){d("add response listener",a);a=g(a);a.addListener(this.onMessage);
|
||||
this.listeners.push(a);return this},unlisten:function(){var a=this;d("remove response listeners");this.listeners.forEach(function(d){return d.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function c(a){d("resolve",a);k({type:"resolve",value:a})}function b(a){a=a&&a.message||a;d("reject",a);k({type:"reject",value:a})}function k(a){f.response=
|
||||
a;f.sourcePort.postMessage({id:f.id,response:a},f.transfer);d("responded with:",a)}d("respond",a);if(this.hasResponded)throw n(2);if(this.sourcePort&&!this.noRespond){var f=this;this.hasResponded=!0;a instanceof Error&&b(a);Promise.resolve(a).then(c,b).catch(b)}},forward:function(a){var c=this;d("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return c.respond(a.value)})},onResponse:function(a){d("on response",a.data);var c=a.data.response,b="reject"==c.type?c.value:c;c.event=
|
||||
a;this.response=c;this.teardown();this.deferred[this.response.type](b);this.emit("response",c)}};f(b.prototype);l.prototype={listen:function(a){d("listen");a=g(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),this.ports.add(a),this},unlisten:function(){var a=this;d("unlisten");this.ports.forEach(function(d){d.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(d("receiver on message",
|
||||
a.data),a=new b(a),this.emit("message",a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(c){throw a.respond(c),c;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};f(l.prototype)},{"../emitter":2,"../utils":6,"./port-adaptors":4}],4:[function(e,h,m){function b(d){this.target=d}function l(d,a,c){d.addEventListener(a,c)}function n(d,a,c){a={data:a,source:self};c&&(a.ports=c);d.dispatchEvent(new MessageEvent("message",
|
||||
a))}var g=e("../utils").deferred;h.exports=function(d,a){if(!d)throw Error({1:"target is undefined"}[1]);if(d&&d.addListener)return d;var k=c[d.constructor.name];return k?k(d,a):new b(d,a)};var f=b.prototype={constructor:b,addListener:function(d){this.target.addEventListener("message",d)},removeListener:function(d){this.target.removeEventListener("message",d)},postMessage:function(d,a){this.target.postMessage(d,a)}},c={HTMLIFrameElement:function(d){var a=k(d);return{addListener:function(a,d){window.addEventListener("message",
|
||||
a)},removeListener:function(a,d){window.removeEventListener("message",a)},postMessage:function(c,b){a.then(function(){return n(d.contentWindow,c,b)})}}},BroadcastChannel:function(d,a){function c(){var a=g();d.postMessage("ready?");l(d,"message",function q(c){"ready"==c.data&&(d.removeEventListener("message",q),a.resolve())});return a.promise}var b=a&&a.receiver,k=a&&a.ready,k=k||b?Promise.resolve():c();b&&(d.postMessage("ready"),l(d,"message",function(a){"ready?"==a.data&&d.postMessage("ready")}));
|
||||
return{target:d,addListener:f.addListener,removeListener:f.removeListener,postMessage:function(a,c){k.then(function(){return d.postMessage(a,c)})}}},Window:function(c,a){var b=a&&a.ready||c===parent||c===self,b=b?Promise.resolve():k(c);return{addListener:function(a,c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,k){b.then(function(){return n(c,a,k)})}}},SharedWorker:function(c){c.port.start();return new b(c.port)},
|
||||
SharedWorkerGlobalScope:function(){var c=[];return{postMessage:function(){},addListener:function(a,b){this.onconnect=function(a){a=a.ports[0];c.push(a);a.start();b(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,b){self.removeEventListener("connect",this.onconnect);c.forEach(function(a){a.close();b(a)})}}}},k=function(){if("undefined"!=typeof window){var c=window.opener||window.parent,a=new WeakSet;c!=self&&l(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",
|
||||
r);n(c,"load")});l(self,"message",function(c){return"load"==c.data&&a.add(c.source)});return function(c){var b=c.contentWindow||c;if(a.has(b)||b==window.parent)return Promise.resolve();var d=g();l(window,"message",function q(a){"load"==a.data&&a.source==b&&(window.removeEventListener("message",q),d.resolve())});return d.promise}}}()},{"../utils":6}],5:[function(e,h,m){function b(c){if(!(this instanceof b))return new b(c);g.Receiver.call(this,c);this.clients={};this.methods={};this.on("_disconnect",
|
||||
this.onDisconnect.bind(this)).on("_connect",this.onConnect.bind(this)).on("_method",this.onMethod.bind(this)).on("_off",this.onOff.bind(this)).on("_on",this.onOn.bind(this));this.destroy=this.destroy.bind(this);f("initialized",c)}function l(c){var b=[].slice.call(arguments,1);return Error({4:'method "'+b[0]+"\" doesn't exist"}[c])}var n=e("./utils").uuid,g=e("./message");e=g.Receiver;h.exports=b;var f={0:function(){},1:function(c){return performance.mark("["+self.constructor.name+"][Service] - "+
|
||||
c)},2:function(c,b){b=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Service]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+c+'"'],b))}}[0];b.prototype=Object.create(e.prototype);b.prototype.inWindow="Window"===constructor.name;b.prototype.method=function(c,b){this.methods[c]=b;return this};b.prototype.broadcast=function(c,b,d){var a=this;f("broadcast",c,b,d);this.eachClient(function(e){if(!d||~d.indexOf(e.id))f("broadcasting to",e.id),a.push(c,b,e.id,{noRespond:!0})});
|
||||
return this};b.prototype.push=function(c,b,d,a){a=a&&a.noRespond;var f=this.getClient(d);return g("_push").set({recipient:d,noRespond:a,data:{type:c,data:b}}).send(f.port)};b.prototype.eachClient=function(c){for(var b in this.clients)c(this.clients[b])};b.prototype.getClient=function(c){return this.clients[c]};b.prototype.onConnect=function(c){f("connection attempt",c.data,this.name);var b=c.data,d=b.clientId;d&&b.service===this.name&&!this.clients[d]&&(this.emit("before-connect",c),c.defaultPrevented||
|
||||
(this.upgradeChannel(c),this.addClient(d,c.sourcePort),c.respond(),this.emit("connected",d),f("connected",d)))};b.prototype.upgradeChannel=function(c){if(!this.inWindow||"Window"!==c.data.originEnv){var b=c.event.ports;if(b=b&&b[0])c.setSourcePort(b),this.listen(b),b.start();f("channel upgraded")}};b.prototype.onDisconnect=function(c){var b=this.clients[c.data];b&&(this.emit("before-disconnect",c),c.defaultPrevented||(this.removeClient(b.id),c.respond(),this.emit("disconnected",b.id),f("disconnected",
|
||||
b.id)))};b.prototype.onMethod=function(c){f("on method",c.data);this.emit("before-method",c);if(!c.defaultPrevented){var b=c.data,d=b.name,a,e=this.methods[d];if(!e)throw l(4,d);try{a=e.apply(this,b.args)}catch(g){a=g}c.respond(a)}};b.prototype.onOn=function(b){f("on on",b.data);this.emit("on",b.data)};b.prototype.onOff=function(b){f("on off");this.emit("off",b.data)};b.prototype.addClient=function(b,f){this.clients[b]={id:b,port:f}};b.prototype.removeClient=function(b){delete this.clients[b]};b.prototype.plugin=
|
||||
function(b){b(this,{uuid:n});return this};b.prototype.disconnect=function(b){this.removeClient(b.id);g("disconnect").set({recipient:b.id,noRespond:!0}).send(b.port)};b.prototype.destroy=function(){delete this.clients;this.unlisten();this.off()};h=b.prototype;h.broadcast=h.broadcast;h.destroy=h.destroy;h.method=h.method;h.plugin=h.plugin},{"./message":3,"./utils":6}],6:[function(e,h,m){m.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(b){var e=16*Math.random()|
|
||||
0;return("x"==b?e:e&3|8).toString(16)})};m.deferred=function(){var b={};b.promise=new Promise(function(e,h){b.resolve=e;b.reject=h});return b}},{}]},{},[1])(1)});
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var createPort = require('./message/port-adaptors');
|
||||
var Emitter = require('./emitter');
|
||||
var message = require('./message');
|
||||
var uuid = require('./utils').uuid;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Client] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Client]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* The type environment.
|
||||
* @type {String}
|
||||
*/
|
||||
var env = constructor.name
|
||||
|
||||
/**
|
||||
* A Client is a remote interface
|
||||
* to a Service within a given endpoint.
|
||||
*
|
||||
* See {@tutorial What's an endpoint?}
|
||||
* for more information on 'endpoints'.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var endpoint = document.querySelector('iframe');
|
||||
* var client = bridge.client('my-service', endpoint);
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} service The service name to connect to
|
||||
* @param {(Iframe|Worker|MessagePort|BroadcastChannel|Window)} endpoint
|
||||
* @param {Number} [timeout] Override default response timeout
|
||||
* The context/thread this service can be found in.
|
||||
* @public
|
||||
*/
|
||||
function Client(service, endpoint, timeout) {
|
||||
if (!(this instanceof Client)) return new Client(service, endpoint, timeout);
|
||||
|
||||
// Parameters can be passed as single object
|
||||
if (typeof service == 'object') {
|
||||
endpoint = service.endpoint;
|
||||
timeout = service.timeout;
|
||||
service = service.service;
|
||||
}
|
||||
|
||||
this.id = uuid();
|
||||
this.service = service;
|
||||
this.timeout = timeout;
|
||||
|
||||
// Keep a reference to the original endpoint
|
||||
// so that it's not garbage collected (Workers)
|
||||
this.endpoint = endpoint || this.endpoint;
|
||||
if (!this.endpoint) throw error(1);
|
||||
|
||||
this.setPort(this.endpoint);
|
||||
this.pending = new Set();
|
||||
|
||||
this.receiver = message.receiver(this.id)
|
||||
.on('_push', this.onPush.bind(this));
|
||||
|
||||
debug('initialized', service);
|
||||
}
|
||||
|
||||
Client.prototype = {
|
||||
|
||||
/**
|
||||
* Connect with the Service. Called
|
||||
* automatically internally, so
|
||||
* only required if you have
|
||||
* perposely called .disconnect().
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
connect() {
|
||||
debug('connect');
|
||||
if (this.connected) return this.connected;
|
||||
debug('connecting...', this.service);
|
||||
|
||||
var mc = new MessageChannel();
|
||||
this.channel = mc.port1;
|
||||
this.channel.start();
|
||||
|
||||
var data = {
|
||||
clientId: this.id,
|
||||
service: this.service,
|
||||
originEnv: env
|
||||
};
|
||||
|
||||
return this.connected = this.message('_connect')
|
||||
.set('transfer', [mc.port2])
|
||||
.set('data', data)
|
||||
.listen(mc.port1)
|
||||
.send()
|
||||
.then(response => {
|
||||
debug('connected', response);
|
||||
|
||||
// Check if the response came back on
|
||||
// the MessageChannel. If it did then
|
||||
// update the endpoint so that all
|
||||
// subsequent messaging uses this channel.
|
||||
var usingChannel = response.event.target === this.channel;
|
||||
if (usingChannel) this.setPort(this.channel);
|
||||
else {
|
||||
this.channel.close();
|
||||
delete this.channel;
|
||||
}
|
||||
|
||||
// Begin listening so that Clients can
|
||||
// respond to service pushed messages
|
||||
this.receiver.listen(this.port);
|
||||
})
|
||||
|
||||
// In the event of message timeout we
|
||||
// upgrade the message to something more
|
||||
// informative. console.error() is used to
|
||||
// makesure the message is seen even when
|
||||
// the user hasn't registered a .catch() handler.
|
||||
.catch(err => {
|
||||
var msg = err && err.message;
|
||||
if (msg == 'timeout') {
|
||||
err = error(2, this.service);
|
||||
console.error(err.message);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect from the `Service`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
disconnect(options) {
|
||||
if (!this.connected) return Promise.resolve();
|
||||
debug('disconnecting ...');
|
||||
|
||||
var config = {
|
||||
noRespond: options && options.noRespond,
|
||||
data: this.id
|
||||
};
|
||||
|
||||
this.cancelPending();
|
||||
|
||||
return this.message('_disconnect')
|
||||
.set(config)
|
||||
.send()
|
||||
.then(() => this.onDisconnected());
|
||||
},
|
||||
|
||||
/**
|
||||
* Call a method on the connected Service.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.method('greet', 'wilson').then(result => {
|
||||
* console.log(result); //=> 'hello wilson'
|
||||
* });
|
||||
*
|
||||
* // my-service.js:
|
||||
*
|
||||
* service.method('greet', name => {
|
||||
* return 'hello ' + name;
|
||||
* });
|
||||
*
|
||||
* @param {String} name The method name
|
||||
* @param {...*} [args] Arguments to send
|
||||
* @return {Promise}
|
||||
*/
|
||||
method(name, ...args) {
|
||||
return this.connect()
|
||||
.then(() => {
|
||||
debug('method', name);
|
||||
return this.message('_method')
|
||||
.set({
|
||||
recipient: this.service,
|
||||
data: {
|
||||
name: name,
|
||||
args: args
|
||||
}
|
||||
})
|
||||
.send();
|
||||
})
|
||||
|
||||
// Only send back the response value
|
||||
.then(response => response.value)
|
||||
|
||||
// In the event of message timeout we
|
||||
// upgrade the message to something more
|
||||
// informative. console.error() is used to
|
||||
// make sure the message is seen even when
|
||||
// the user hasn't registered a .catch() handler.
|
||||
.catch(err => {
|
||||
var msg = err && err.message;
|
||||
if (msg == 'timeout') {
|
||||
err = error(3, name);
|
||||
console.error(err.message);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a plugin with this Client.
|
||||
* See {@tutorial Writing plugins}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.plugin(megaPlugin);
|
||||
*
|
||||
* @param {Function} fn The plugin
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
plugin(fn) {
|
||||
fn(this, {
|
||||
'Emitter': Emitter,
|
||||
'uuid': uuid
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* A wrapper around Message that
|
||||
* ensures pending messages are
|
||||
* noted and the Client's endpoint
|
||||
* is predefined.
|
||||
*
|
||||
* @param {String} type The message type
|
||||
* @return {Message}
|
||||
* @private
|
||||
*/
|
||||
message(type) {
|
||||
debug('create message', type);
|
||||
|
||||
var msg = message(type)
|
||||
.set('port', this.port)
|
||||
.set('timeout', this.timeout)
|
||||
.on('response', () => this.pending.delete(msg))
|
||||
.on('cancel', () => this.pending.delete(msg));
|
||||
|
||||
this.pending.add(msg);
|
||||
return msg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel any message that we have
|
||||
* not recieved a response from yet.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
cancelPending() {
|
||||
debug('cancel pending');
|
||||
this.pending.forEach(msg => { msg.cancel();});
|
||||
this.pending.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves
|
||||
* once all pending messages have
|
||||
* responded.
|
||||
*
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
pendingResponded() {
|
||||
var responded = [];
|
||||
this.pending.forEach(msg => responded.push(msg.responded));
|
||||
return Promise.all(responded);
|
||||
},
|
||||
|
||||
/**
|
||||
* Emits a event when a 'push' Message
|
||||
* is recieved from the Service.
|
||||
*
|
||||
* @private
|
||||
* @param {Message} message The pushed message
|
||||
*/
|
||||
onPush(message) {
|
||||
debug('on push', message.data);
|
||||
this._emit(message.data.type, message.data.data);
|
||||
},
|
||||
|
||||
// Needs testing!
|
||||
onDisconnected() {
|
||||
delete this.connected;
|
||||
this.pendingResponded().then(() => {
|
||||
debug('disconnected');
|
||||
if (this.channel) this.channel.close();
|
||||
this._emit('disconnected');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the port which all messages
|
||||
* will be sent over. This can differ
|
||||
* to the endpoint if we successfully
|
||||
* upgrade transport to MessageChannel.
|
||||
*
|
||||
* @param {(Iframe|Worker|MessagePort|BroadcastChannel|Window)} endpoint
|
||||
*/
|
||||
setPort(endpoint) {
|
||||
debug('set port');
|
||||
this.port = createPort(endpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the Client. Waits from all
|
||||
* pending Messages to have responded.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.destroy().then(() => ...);
|
||||
*
|
||||
* @public
|
||||
* @return {Promise}
|
||||
*/
|
||||
destroy: function() {
|
||||
return this.disconnect()
|
||||
.then(() => {
|
||||
if (this.destroyed) return;
|
||||
debug('destroy');
|
||||
this.destroyed = true;
|
||||
this.receiver.destroy();
|
||||
this._off();
|
||||
|
||||
// Wipe references
|
||||
this.port
|
||||
= this.endpoint
|
||||
= this.receiver
|
||||
= null;
|
||||
});
|
||||
},
|
||||
|
||||
_on: Emitter.prototype.on,
|
||||
_off: Emitter.prototype.off,
|
||||
_emit: Emitter.prototype.emit
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen to a Service .broadcast() or .push().
|
||||
*
|
||||
* Services get notified whenever a Client
|
||||
* starts listening to a particular event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client
|
||||
* .on('importantevent', data => ...)
|
||||
* .on('thingchanged', thing => ...);
|
||||
*
|
||||
* @param {String} name The event name
|
||||
* @param {Function} fn Callback function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Client.prototype.on = function(name, fn) {
|
||||
this.connect().then(() => {
|
||||
debug('bind on', name);
|
||||
Emitter.prototype.on.call(this, name, fn);
|
||||
this.message('_on')
|
||||
.set('noRespond', true)
|
||||
.set('data', {
|
||||
name: name,
|
||||
clientId: this.id
|
||||
})
|
||||
.send(this.port);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlisten to a Service event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client
|
||||
* .off('importantevent') // remove all
|
||||
* .off('thingchanged', onThingChanged); // remove one
|
||||
*
|
||||
* @this Client
|
||||
* @param {String} name The event name
|
||||
* @param {Function} fn Callback function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Client.prototype.off = function(name, fn) {
|
||||
this.connect().then(() => {
|
||||
Emitter.prototype.off.call(this, name, fn);
|
||||
this.message('_off')
|
||||
.set('noRespond', true)
|
||||
.set('data', {
|
||||
name: name,
|
||||
clientId: this.id
|
||||
})
|
||||
.send(this.port);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function error(id, ...args) {
|
||||
/*jshint maxlen:false*/
|
||||
var help = 'Either the target endpoint is not alive or the Service is not `.listen()`ing.';
|
||||
return new Error({
|
||||
1: 'an endpoint must be defined',
|
||||
2: `Unable to establish a connection with "${args[0]}". ${help}`,
|
||||
3: `Method "${args[0]}" didn't get a response. ${help}`
|
||||
}[id]);
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Emitter;
|
||||
|
||||
/**
|
||||
* Simple logger
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
|
||||
var debug = 0 ? console.log.bind(console, '[Emitter]') : () => {};
|
||||
|
||||
/**
|
||||
* Create new `Emitter`
|
||||
*
|
||||
* @class Emitter
|
||||
*/
|
||||
|
||||
function Emitter(host) {
|
||||
if (host) return Object.assign(host, Emitter.prototype);
|
||||
}
|
||||
|
||||
Emitter.prototype = {
|
||||
|
||||
/**
|
||||
* Add an event listener.
|
||||
*
|
||||
* It is possible to subscript to * events.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
on: function(type, callback) {
|
||||
debug('on', type, callback);
|
||||
if (!this._callbacks) this._callbacks = {};
|
||||
if (!this._callbacks[type]) this._callbacks[type] = [];
|
||||
this._callbacks[type].push(callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* emitter.off('name', fn); // remove one callback
|
||||
* emitter.off('name'); // remove all callbacks for 'name'
|
||||
* emitter.off(); // remove all callbacks
|
||||
*
|
||||
* @param {String} [type]
|
||||
* @param {Function} [callback]
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
off: function(type, callback) {
|
||||
debug('off', type, callback);
|
||||
if (this._callbacks) {
|
||||
switch (arguments.length) {
|
||||
case 0: this._callbacks = {}; break;
|
||||
case 1: delete this._callbacks[type]; break;
|
||||
default:
|
||||
var typeListeners = this._callbacks[type];
|
||||
if (!typeListeners) return;
|
||||
var i = typeListeners.indexOf(callback);
|
||||
if (~i) typeListeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit an event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* emitter.emit('name', { some: 'data' });
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {*} [data]
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
emit: function(type, data) {
|
||||
debug('emit', type, data);
|
||||
if (this._callbacks) {
|
||||
var fns = this._callbacks[type] || [];
|
||||
fns = fns.concat(this._callbacks['*'] || []);
|
||||
for (var i = 0; i < fns.length; i++) fns[i].call(this, data, type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
var p = Emitter.prototype;
|
||||
p['off'] = p.off;
|
||||
p['on'] = p.on;
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var createPort = require('./port-adaptors');
|
||||
var Emitter = require('../emitter');
|
||||
var utils = require('../utils');
|
||||
var defer = utils.deferred;
|
||||
var uuid = utils.uuid;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
exports = module.exports = type => new Message(type);
|
||||
exports.receiver = (id, n) => new Receiver(id, n);
|
||||
exports.Receiver = Receiver;
|
||||
exports.Message = Message;
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Message] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Message]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* Default response timeout.
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
var TIMEOUT = 1000;
|
||||
|
||||
/**
|
||||
* Initialize a new `Message`
|
||||
*
|
||||
* @class Message
|
||||
* @borrows Emitter#on as #on
|
||||
* @borrows Emitter#off as #off
|
||||
* @borrows Emitter#emit as #emit
|
||||
* @param {String} type Message type
|
||||
*/
|
||||
function Message(type) {
|
||||
this.cancelled = false;
|
||||
this.listeners = [];
|
||||
this.deferred = defer();
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this.onTimeout = this.onTimeout.bind(this);
|
||||
if (typeof type === 'object') this.setupInbound(type);
|
||||
else this.setupOutbound(type);
|
||||
debug('initialized', type);
|
||||
}
|
||||
|
||||
Message.prototype = {
|
||||
setupOutbound(type) {
|
||||
this.id = uuid();
|
||||
this.type = type;
|
||||
this.sent = false;
|
||||
this.recipient = '*';
|
||||
},
|
||||
|
||||
setupInbound (e) {
|
||||
debug('inbound');
|
||||
this.hasResponded = false;
|
||||
this.setSourcePort(e.source || e.target);
|
||||
|
||||
// Keep a reference to the MessageEvent
|
||||
this.event = e;
|
||||
|
||||
// Mixin the properties of the original message
|
||||
Object.assign(this, e.data);
|
||||
},
|
||||
|
||||
setSourcePort(endpoint) {
|
||||
debug('set source', endpoint.constructor.name);
|
||||
this.sourcePort = createPort(endpoint, { ready: true });
|
||||
return this;
|
||||
},
|
||||
|
||||
set(key, value) {
|
||||
debug('set', key, value);
|
||||
if (typeof key == 'object') Object.assign(this, key);
|
||||
else this[key] = value;
|
||||
return this;
|
||||
},
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
recipient: this.recipient,
|
||||
noRespond: this.noRespond
|
||||
};
|
||||
},
|
||||
|
||||
preventDefault() {
|
||||
debug('prevent default');
|
||||
this.defaultPrevented = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send the message to an endpoint.
|
||||
*
|
||||
* @param {(Iframe|Window|Worker|MessagePort)} endpoint
|
||||
* @return {Promise}
|
||||
*/
|
||||
send(endpoint) {
|
||||
debug('send', this.type);
|
||||
if (this.sent) throw error(1);
|
||||
var serialized = this.serialize();
|
||||
var expectsResponse = !this.noRespond;
|
||||
|
||||
// A port is resolved from either a predefined
|
||||
// port, or an endpoint given as first argument
|
||||
this.port = endpoint ? createPort(endpoint) : this.port;
|
||||
if (!this.port) throw error(3);
|
||||
|
||||
// If we're expecting a response listen
|
||||
// on the port else resolve promise instantly
|
||||
if (expectsResponse) {
|
||||
this.listen(this.port);
|
||||
this.setResponseTimeout();
|
||||
} else this.deferred.resolve();
|
||||
|
||||
this.port.postMessage(serialized, this.getTransfer());
|
||||
debug('sent', serialized);
|
||||
return this.deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the response timeout.
|
||||
*
|
||||
* When set to `false` no timeout
|
||||
* is installed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
setResponseTimeout() {
|
||||
if (this.timeout === false) return;
|
||||
var ms = this.timeout || TIMEOUT;
|
||||
this._timer = setTimeout(this.onTimeout, ms);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the response timeout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
clearResponseTimeout() {
|
||||
clearTimeout(this._timer);
|
||||
},
|
||||
|
||||
getTransfer() {
|
||||
return this.transfer || this.event && this.event.ports;
|
||||
},
|
||||
|
||||
onMessage(e) {
|
||||
var valid = !!e.data.response
|
||||
&& e.data.id === this.id
|
||||
&& !this.cancelled;
|
||||
|
||||
if (valid) this.onResponse(e);
|
||||
},
|
||||
|
||||
onTimeout() {
|
||||
debug('response timeout', this.type);
|
||||
if (!this.silentTimeout) this.deferred.reject(error(4));
|
||||
this.teardown();
|
||||
},
|
||||
|
||||
listen(thing) {
|
||||
debug('add response listener', thing);
|
||||
var port = createPort(thing);
|
||||
port.addListener(this.onMessage);
|
||||
this.listeners.push(port);
|
||||
return this;
|
||||
},
|
||||
|
||||
unlisten() {
|
||||
debug('remove response listeners');
|
||||
this.listeners.forEach(port => port.removeListener(this.onMessage));
|
||||
this.listeners = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel a pending Message.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var msg = message('foo')
|
||||
*
|
||||
* msg.send(new Worker('my-worker.js'))
|
||||
* .then(response => {
|
||||
* // this will never run
|
||||
* })
|
||||
*
|
||||
* msg.cancel();
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cancel() {
|
||||
this.teardown();
|
||||
this.cancelled = true;
|
||||
this.emit('cancel');
|
||||
},
|
||||
|
||||
teardown() {
|
||||
this.clearResponseTimeout();
|
||||
this.unlisten();
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond to a message.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* receiver.on('hello', message => {
|
||||
* message.respond('world');
|
||||
* });
|
||||
*
|
||||
* @public
|
||||
* @param {*} [result] Data to send back with the response
|
||||
*/
|
||||
respond(result) {
|
||||
debug('respond', result);
|
||||
|
||||
if (this.hasResponded) throw error(2);
|
||||
if (!this.sourcePort) return;
|
||||
if (this.noRespond) return;
|
||||
|
||||
var self = this;
|
||||
this.hasResponded = true;
|
||||
|
||||
// Repsond with rejection when result is an `Error`
|
||||
if (result instanceof Error) reject(result);
|
||||
|
||||
// Call the handler and make
|
||||
// sure return value is a promise.
|
||||
// If the returned value is unclonable
|
||||
// then the send() method will throw,
|
||||
// the .catch() will reject in this case.
|
||||
Promise.resolve(result)
|
||||
.then(resolve, reject)
|
||||
.catch(reject);
|
||||
|
||||
function resolve(value) {
|
||||
debug('resolve', value);
|
||||
respond({
|
||||
type: 'resolve',
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
function reject(err) {
|
||||
var msg = err && err.message || err;
|
||||
debug('reject', msg);
|
||||
respond({
|
||||
type: 'reject',
|
||||
value: msg
|
||||
});
|
||||
}
|
||||
|
||||
function respond(response) {
|
||||
self.response = response;
|
||||
self.sourcePort.postMessage({
|
||||
id: self.id,
|
||||
response: response
|
||||
}, self.transfer);
|
||||
|
||||
debug('responded with:', response);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Forward a `Message` onto another endpoint.
|
||||
*
|
||||
* The `silentTrue` option prevents the
|
||||
* message request timing out should
|
||||
* the response come back via an
|
||||
* alternative route.
|
||||
*
|
||||
* @param {(HTMLIframeElement|MessagePort|Window)} endpoint
|
||||
* @public
|
||||
*/
|
||||
forward(endpoint) {
|
||||
debug('forward');
|
||||
return this
|
||||
.set('silentTimeout', true)
|
||||
.send(endpoint)
|
||||
.then(result => this.respond(result.value));
|
||||
},
|
||||
|
||||
onResponse(e) {
|
||||
debug('on response', e.data);
|
||||
var response = e.data.response;
|
||||
var type = response.type;
|
||||
var value = type == 'reject'
|
||||
? response.value
|
||||
: response;
|
||||
|
||||
response.event = e;
|
||||
this.response = response;
|
||||
this.teardown();
|
||||
|
||||
this.deferred[this.response.type](value);
|
||||
this.emit('response', response);
|
||||
}
|
||||
};
|
||||
|
||||
// Mixin Emitter methods
|
||||
Emitter(Message.prototype);
|
||||
|
||||
/**
|
||||
* Initialize a new `Receiver`.
|
||||
*
|
||||
* @class Receiver
|
||||
* @extends Emitter
|
||||
* @param {String} name - corresponds to `Message.recipient`
|
||||
*/
|
||||
function Receiver(name) {
|
||||
this.name = name;
|
||||
this.ports = new Set();
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this['listen'] = this['listen'].bind(this);
|
||||
this['unlisten'] = this['unlisten'].bind(this);
|
||||
debug('receiver initialized', name);
|
||||
}
|
||||
|
||||
Receiver.prototype = {
|
||||
|
||||
/**
|
||||
* Begin listening for inbound messages.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // When no arguments are given
|
||||
* // messages will be listened for
|
||||
* // on the default global scope
|
||||
* .listen();
|
||||
*
|
||||
* // When an endpoint is out of reach
|
||||
* // BroadcastChannel can be used.
|
||||
* .listen(new BroadcastChannel('foo'));
|
||||
*
|
||||
* @param {(HTMLIframeElement|Worker|MessagePort|
|
||||
* BroadcastChannel|Window|Object)} [thing]
|
||||
* @public
|
||||
*/
|
||||
listen(thing) {
|
||||
debug('listen');
|
||||
var _port = createPort(thing || self, { receiver: true });
|
||||
if (this.ports.has(_port)) return;
|
||||
_port.addListener(this.onMessage, this.listen);
|
||||
this.ports.add(_port);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening for inbound messages
|
||||
* on all endpoints listened to prior.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
unlisten() {
|
||||
debug('unlisten');
|
||||
this.ports.forEach(port => {
|
||||
port.removeListener(this.onMessage, this.unlisten);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to handle inbound messages.
|
||||
* @param {MessageEvent} e
|
||||
* @private
|
||||
*/
|
||||
onMessage(e) {
|
||||
if (!e.data.id) return;
|
||||
if (!e.data.type) return;
|
||||
if (!this.isRecipient(e.data.recipient)) return;
|
||||
debug('receiver on message', e.data);
|
||||
var message = new Message(e);
|
||||
|
||||
// Before hook
|
||||
this.emit('message', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
try { this.emit(message.type, message); }
|
||||
catch (e) {
|
||||
message.respond(e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
isRecipient(recipient) {
|
||||
return recipient == this.name
|
||||
|| recipient == '*'
|
||||
|| this.name == '*';
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.unlisten();
|
||||
delete this.name;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Mixin Emitter methods
|
||||
Emitter(Receiver.prototype);
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registry.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id, ...args) {
|
||||
return new Error({
|
||||
1: '.send() can only be called once',
|
||||
2: 'response already sent for this message',
|
||||
3: 'a port must be defined',
|
||||
4: 'timeout'
|
||||
}[id]);
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var deferred = require('../utils').deferred;
|
||||
|
||||
/**
|
||||
* Message event name
|
||||
* @type {String}
|
||||
*/
|
||||
const MSG = 'message';
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[PortAdaptor]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
/**
|
||||
* Creates a bridge.js port abstraction
|
||||
* with a consistent interface.
|
||||
*
|
||||
* @param {Object} target
|
||||
* @param {Object} options
|
||||
* @return {PortAdaptor}
|
||||
*/
|
||||
module.exports = function create(target, options) {
|
||||
if (!target) throw error(1);
|
||||
if (isEndpoint(target)) return target;
|
||||
var type = target.constructor.name;
|
||||
var CustomAdaptor = adaptors[type];
|
||||
debug('creating port adaptor for', type);
|
||||
if (CustomAdaptor) return CustomAdaptor(target, options);
|
||||
return new PortAdaptor(target, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* The default adaptor.
|
||||
* @private
|
||||
*/
|
||||
function PortAdaptor(target) {
|
||||
debug('PortAdaptor');
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
var PortAdaptorProto = PortAdaptor.prototype = {
|
||||
constructor: PortAdaptor,
|
||||
addListener(callback) { on(this.target, MSG, callback); },
|
||||
removeListener(callback) { off(this.target, MSG, callback); },
|
||||
postMessage(data, transfer) { this.target.postMessage(data, transfer); }
|
||||
};
|
||||
|
||||
/**
|
||||
* A registry of specific adaptors
|
||||
* for when the default PortAdaptor
|
||||
* is not suitable.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var adaptors = {
|
||||
|
||||
/**
|
||||
* Create an HTMLIframeElement PortAdaptor.
|
||||
*
|
||||
* @param {HTMLIframeElement} iframe
|
||||
*/
|
||||
'HTMLIFrameElement': function(iframe) {
|
||||
debug('HTMLIFrameElement');
|
||||
var ready = windowReady(iframe);
|
||||
return {
|
||||
addListener(callback, listen) { on(window, MSG, callback); },
|
||||
removeListener(callback, listen) { off(window, MSG, callback); },
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => postMessageSync(iframe.contentWindow, data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a BroadcastChannel port-adaptor.
|
||||
*
|
||||
* @param {Object} channel
|
||||
* @param {[type]} options [description]
|
||||
*/
|
||||
'BroadcastChannel': function(channel, options) {
|
||||
debug('BroadcastChannel', channel.name);
|
||||
var receiver = options && options.receiver;
|
||||
var ready = options && options.ready;
|
||||
var sendReady = () => {
|
||||
channel.postMessage('ready');
|
||||
debug('sent ready');
|
||||
};
|
||||
|
||||
ready = ready || receiver
|
||||
? Promise.resolve()
|
||||
: setupSender();
|
||||
|
||||
if (receiver) {
|
||||
sendReady();
|
||||
on(channel, MSG, e => {
|
||||
if (e.data != 'ready?') return;
|
||||
sendReady();
|
||||
});
|
||||
}
|
||||
|
||||
function setupSender() {
|
||||
debug('setup sender');
|
||||
var promise = deferred();
|
||||
|
||||
channel.postMessage('ready?');
|
||||
on(channel, MSG, function fn(e) {
|
||||
if (e.data != 'ready') return;
|
||||
off(channel, MSG, fn);
|
||||
debug('BroadcastChannel: ready');
|
||||
promise.resolve();
|
||||
});
|
||||
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
return {
|
||||
target: channel,
|
||||
addListener: PortAdaptorProto.addListener,
|
||||
removeListener: PortAdaptorProto.removeListener,
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => channel.postMessage(data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'Window': function(win, options) {
|
||||
debug('Window');
|
||||
var ready = options && options.ready
|
||||
|| win === parent // parent always ready
|
||||
|| win === self; // self always ready
|
||||
|
||||
ready = ready ? Promise.resolve() : windowReady(win);
|
||||
|
||||
return {
|
||||
addListener(callback, listen) { on(window, MSG, callback); },
|
||||
removeListener(callback, listen) { off(window, MSG, callback); },
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => postMessageSync(win, data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'SharedWorker': function(worker) {
|
||||
worker.port.start();
|
||||
return new PortAdaptor(worker.port);
|
||||
},
|
||||
|
||||
'SharedWorkerGlobalScope': function() {
|
||||
var ports = [];
|
||||
|
||||
return {
|
||||
postMessage() {}, // noop
|
||||
addListener(callback, listen) {
|
||||
this.onconnect = e => {
|
||||
var port = e.ports[0];
|
||||
ports.push(port);
|
||||
port.start();
|
||||
listen(port);
|
||||
};
|
||||
|
||||
on(self, 'connect', this.onconnect);
|
||||
},
|
||||
|
||||
removeListener(callback, unlisten) {
|
||||
off(self, 'connect', this.onconnect);
|
||||
ports.forEach(port => {
|
||||
port.close();
|
||||
unlisten(port);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise that resolves
|
||||
* when a Window is ready to start
|
||||
* recieving messages.
|
||||
*
|
||||
* @param {Window} target
|
||||
* @return {Promise}
|
||||
*/
|
||||
var windowReady = (function() {
|
||||
if (typeof window == 'undefined') return;
|
||||
var parent = window.opener || window.parent;
|
||||
var domReady = 'DOMContentLoaded';
|
||||
var windows = new WeakSet();
|
||||
|
||||
// Side B: Dispatches 'load'
|
||||
// from the child window
|
||||
if (parent != self) {
|
||||
on(window, domReady, function fn() {
|
||||
off(window, domReady, fn);
|
||||
postMessageSync(parent, 'load');
|
||||
});
|
||||
}
|
||||
|
||||
// Side A: Listens for 'ready' in the parent window
|
||||
on(self, 'message', e => e.data == 'load' && windows.add(e.source));
|
||||
|
||||
return target => {
|
||||
var win = target.contentWindow || target;
|
||||
|
||||
// Ready if the target has previously announces itself ready
|
||||
if (windows.has(win)) return Promise.resolve();
|
||||
|
||||
// Ready if the target is the parent window
|
||||
if (win == window.parent) return Promise.resolve();
|
||||
|
||||
var def = deferred();
|
||||
debug('waiting for Window to be ready ...');
|
||||
on(window, 'message', function fn(e) {
|
||||
if (e.data == 'load' && e.source == win) {
|
||||
debug('Window ready');
|
||||
off(window, 'message', fn);
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
return def.promise;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Utils
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
function isEndpoint(thing) {
|
||||
return !!(thing && thing.addListener);
|
||||
}
|
||||
|
||||
// Shorthand
|
||||
function on(target, name, fn) { target.addEventListener(name, fn); }
|
||||
function off(target, name, fn) { target.removeEventListener(name, fn); }
|
||||
|
||||
/**
|
||||
* Dispatches syncronous 'message'
|
||||
* event on a Window.
|
||||
*
|
||||
* We use this because standard
|
||||
* window.postMessage() gets blocked
|
||||
* until the main-thread is free.
|
||||
*
|
||||
* @param {Window} win
|
||||
* @param {*} data
|
||||
* @private
|
||||
*/
|
||||
function postMessageSync(win, data, transfer) { try { void win.document; } catch (_) { win.postMessage(data, location.origin, transfer || []); return; }
|
||||
var event = {
|
||||
data: data,
|
||||
source: self
|
||||
};
|
||||
|
||||
if (transfer) event.ports = transfer;
|
||||
|
||||
win.dispatchEvent(new MessageEvent('message', event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
return new Error({
|
||||
1: 'target is undefined'
|
||||
}[id]);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
module.exports = function() {
|
||||
|
||||
};
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('contractsPlugin',this));
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[ClientStream]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = (client, utils) => {
|
||||
client._activeStreams = [];
|
||||
client.on('streamevent', onStreamEvent);
|
||||
|
||||
client.stream = function(name) {
|
||||
debug('stream open', name);
|
||||
var args = [].slice.call(arguments, 1);
|
||||
var stream = new ClientStream(client, utils);
|
||||
var self = this;
|
||||
|
||||
this.method('_stream', name, stream.id, this.id, args)
|
||||
.then(() => {
|
||||
debug('stream connected', name);
|
||||
stream._connected.resolve();
|
||||
})
|
||||
|
||||
.catch(err => {
|
||||
debug('error', err);
|
||||
onStreamEvent({
|
||||
type: 'abort',
|
||||
id: stream.id,
|
||||
data: err
|
||||
});
|
||||
});
|
||||
|
||||
this._activeStreams[stream.id] = stream;
|
||||
stream.emitter.on('cancel', onEnd);
|
||||
stream.emitter.on('abort', onEnd);
|
||||
stream.emitter.on('close', onEnd);
|
||||
|
||||
function onEnd() {
|
||||
debug('end');
|
||||
delete self._activeStreams[stream.id];
|
||||
stream.destroy();
|
||||
}
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called every time the service calls
|
||||
* write/abort/close on the ServiceStream
|
||||
*
|
||||
* @param {Object} broadcast
|
||||
* @param {String} broadcast.id Stream ID
|
||||
* @param {String} broadcast.type Event type ('write', 'abort' or 'close')
|
||||
* @private
|
||||
*/
|
||||
|
||||
function onStreamEvent(data) {
|
||||
debug('stream event', data.type);
|
||||
var stream = client._activeStreams[data.streamId];
|
||||
stream.emitter.emit(data.type, data.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Readable stream instance returned by
|
||||
* a `client.stream('methodName')` call.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} options.id Stream Id, used to match client/service streams
|
||||
* @param {Client} options.client Client instance
|
||||
*/
|
||||
|
||||
function ClientStream(client, utils) {
|
||||
this.id = utils.uuid();
|
||||
this.client = client;
|
||||
this.emitter = new utils.Emitter();
|
||||
this.emitter.on('close', this.onClose.bind(this));
|
||||
this.emitter.on('abort', this.onAbort.bind(this));
|
||||
this._connected = defer();
|
||||
this.connected = this._connected.promise;
|
||||
this._closed = defer();
|
||||
this.closed = this._closed.promise;
|
||||
debug('initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prototype assigned to variable
|
||||
* to improve compression.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
ClientStream.prototype = {
|
||||
|
||||
/**
|
||||
* Add a listener that will be called
|
||||
* every time the service broadcasts
|
||||
* a new chunk of data.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
listen(callback) {
|
||||
debug('listen', callback);
|
||||
this.emitter.on('write', callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes 'data' listener
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
unlisten(callback) {
|
||||
debug('unlisten', callback);
|
||||
this.emitter.off('write', callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify the service that
|
||||
* action should be canceled
|
||||
*
|
||||
* @param {*} [reason] Optional data to be sent to service.
|
||||
*/
|
||||
|
||||
cancel(reason) {
|
||||
debug('cancel', reason);
|
||||
return this.client.method('_streamcancel', this.id, reason)
|
||||
.then(result => {
|
||||
debug('cancelled', result);
|
||||
this.emitter.emit('cancel');
|
||||
return result;
|
||||
})
|
||||
|
||||
.catch(e => {
|
||||
this.emitter.emit('cancel');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by Client when
|
||||
* it receives an 'abort' event
|
||||
* from the service.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
onAbort(reason) {
|
||||
debug('abort', reason);
|
||||
this._closed.reject(reason);
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by Client when
|
||||
* it receives a 'close' event
|
||||
* from the service.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
onClose() {
|
||||
debug('close');
|
||||
this._closed.resolve();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
debug('destroy');
|
||||
this.emitter.off();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function defer() {
|
||||
var result = {};
|
||||
result.promise = new Promise((resolve, reject) => {
|
||||
result.resolve = resolve;
|
||||
result.reject = reject;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('streamClient',this));
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[ServiceStream]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
module.exports = function(service) {
|
||||
debug('attaching plugin');
|
||||
|
||||
service
|
||||
.method('_stream', onStream)
|
||||
.method('_streamcancel', onStreamCancel);
|
||||
|
||||
service._streamHandlers = {};
|
||||
service._activeStreams = [];
|
||||
|
||||
service.stream = function(name, fn) {
|
||||
debug('register stream', name);
|
||||
this._streamHandlers[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
function onStream(name, id, clientId, args) {
|
||||
debug('on stream open', name);
|
||||
var e = {
|
||||
preventDefault: () => e.defaultPrevented = true,
|
||||
arguments: arguments
|
||||
};
|
||||
|
||||
// Allow other plugins to override default behaviour
|
||||
service.emit('before-stream', e);
|
||||
if (e.defaultPrevented) return;
|
||||
|
||||
var fn = service._streamHandlers[name];
|
||||
if (!fn) throw error(1, name);
|
||||
|
||||
var stream = new ServiceStream(id, service, clientId);
|
||||
service._activeStreams[id] = stream;
|
||||
|
||||
// Always pass stream object as first
|
||||
// argument to simplify the process
|
||||
fn.apply(service, [stream].concat(args));
|
||||
}
|
||||
|
||||
function onStreamCancel(id, reason) {
|
||||
debug('stream cancel');
|
||||
var stream = service._activeStreams[id];
|
||||
delete service._activeStreams[id];
|
||||
return stream.cancel(reason);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writable Stream instance passed to the
|
||||
* `service.stream` implementation
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} options.id Stream ID used to sync client and service streams
|
||||
* @param {BroadcastChannel} options.channel Channel used to postMessage
|
||||
* @param {String} options.serviceId ID of the service
|
||||
* @param {String} options.clientId ID of client that should receive message
|
||||
*/
|
||||
|
||||
function ServiceStream(streamId, service, clientId) {
|
||||
this.id = streamId;
|
||||
this.service = service;
|
||||
this.clientId = clientId;
|
||||
this.writable = true;
|
||||
debug('initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prototype assigned to variable
|
||||
* to improve compression.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
var ServiceStreamPrototype = ServiceStream.prototype;
|
||||
|
||||
/**
|
||||
* Services that allows clients to
|
||||
* cancel the operation before it's
|
||||
* complete should override the
|
||||
* `stream.cancel` method.
|
||||
*
|
||||
* @param {*} [reason] Data sent from client about the cancellation
|
||||
* @returns {(Promise|*)}
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.cancel = function(reason) {
|
||||
var err = new TypeError('service should implement stream.cancel()');
|
||||
return Promise.reject(err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal to client that action was
|
||||
* aborted during the process, this
|
||||
* should be used as a way to
|
||||
* communicate errors.
|
||||
*
|
||||
* @param {*} [data] Reason of failure
|
||||
* @returns {Promise}
|
||||
* @public
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.abort = function(data) {
|
||||
debug('abort', data);
|
||||
this._post('abort', data);
|
||||
this.writable = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a chunk of data to the client.
|
||||
*
|
||||
* @param {*} data Chunk of data to be sent to client.
|
||||
* @returns {Promise}
|
||||
* @public
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.write = function(data) {
|
||||
debug('write', data);
|
||||
this._post('write', data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the stream, signals that
|
||||
* action was completed with success.
|
||||
*
|
||||
* According to whatwg streams spec,
|
||||
* WritableStream#close() doesn't send data.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.close = function() {
|
||||
debug('close');
|
||||
this._post('close');
|
||||
this.writable = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the current state and
|
||||
* call cancel on the target stream.
|
||||
*
|
||||
* Called by the Service when client
|
||||
* sends a 'streamcancel' message.
|
||||
*
|
||||
* @param {*} [reason] Reason for cancelation sent by the client
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype._cancel = function(reason) {
|
||||
debug('cancel', reason);
|
||||
return this.cancel(reason)
|
||||
.then(result => {
|
||||
this.writable = false;
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the current state and post message to client.
|
||||
*
|
||||
* @param {String} type 'write', 'abort' or 'close'
|
||||
* @param {String} state 'writable', 'aborted' or 'closed'
|
||||
* @param {*} [data] Data to be sent to the client
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype._post = function(type, data) {
|
||||
debug('post', type, data, this.writable);
|
||||
if (!this.writable) return Promise.reject();
|
||||
this.service.broadcast('streamevent', {
|
||||
streamId: this.id,
|
||||
type: type,
|
||||
data: data
|
||||
}, [this.clientId]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
return new Error({
|
||||
1: 'stream "' + args[0] + '" doesn\'t exist'
|
||||
}[id]);
|
||||
}
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('streamService',this));
|
||||
|
|
@ -0,0 +1,462 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var uuid = require('./utils').uuid;
|
||||
var message = require('./message');
|
||||
var Receiver = message.Receiver;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Service;
|
||||
|
||||
/**
|
||||
* Debug logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Service] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Service]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* Extends `Receiver`
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
Service.prototype = Object.create(Receiver.prototype);
|
||||
|
||||
/**
|
||||
* A `Service` is a collection of methods
|
||||
* exposed to a `Client`. Methods can be
|
||||
* sync or async (using Promises).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* bridge.service('my-service')
|
||||
* .method('ping', param => 'pong: ' + param)
|
||||
* .listen();
|
||||
*
|
||||
* @class Service
|
||||
* @extends Receiver
|
||||
* @param {String} name The service name
|
||||
* @public
|
||||
*/
|
||||
function Service(name) {
|
||||
if (!(this instanceof Service)) return new Service(name);
|
||||
message.Receiver.call(this, name); // call super
|
||||
|
||||
this.clients = {};
|
||||
this.methods = {};
|
||||
|
||||
this
|
||||
.on('_disconnect', this.onDisconnect.bind(this))
|
||||
.on('_connect', this.onConnect.bind(this))
|
||||
.on('_method', this.onMethod.bind(this))
|
||||
.on('_off', this.onOff.bind(this))
|
||||
.on('_on', this.onOn.bind(this));
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
debug('initialized', name);
|
||||
}
|
||||
|
||||
Service.prototype.inWindow = constructor.name === 'Window';
|
||||
|
||||
/**
|
||||
* Define a method to expose to Clients.
|
||||
* The return value of the result of a
|
||||
* returned Promise will be sent back
|
||||
* to the Client.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* bridge.service('my-service')
|
||||
*
|
||||
* // sync return value
|
||||
* .method('myMethod', function(param) {
|
||||
* return 'hello: ' + param;
|
||||
* })
|
||||
*
|
||||
* // or async Promise
|
||||
* .method('myOtherMethod', function() {
|
||||
* return new Promise(resolve => {
|
||||
* setTimeout(() => resolve('result'), 1000);
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* .listen();
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} fn
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
Service.prototype.method = function(name, fn) {
|
||||
this.methods[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast's an event from a `Service`
|
||||
* to connected `Client`s.
|
||||
*
|
||||
* The third argument can be used to
|
||||
* target selected clients by their
|
||||
* `client.id`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.broadcast('my-event', { some: data }); // all clients
|
||||
* service.broadcast('my-event', { some: data }, [ clientId ]); // one client
|
||||
*
|
||||
* @memberof Service
|
||||
* @param {String} type The message type/name
|
||||
* @param {*} [data] Data to send with the event
|
||||
* @param {Array} [only] A select list of clients to message
|
||||
* @return {this}
|
||||
*/
|
||||
Service.prototype.broadcast = function(type, data, only) {
|
||||
debug('broadcast', type, data, only);
|
||||
|
||||
this.eachClient(client => {
|
||||
if (only && !~only.indexOf(client.id)) return;
|
||||
debug('broadcasting to', client.id);
|
||||
this.push(type, data, client.id, { noRespond: true });
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push message to a single connected Client.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.on('my-event', data => ...)
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* service.push('my-event', { some: data}, clientId)
|
||||
* .then(() => console.log('sent'));
|
||||
*
|
||||
* @public
|
||||
* @param {String} type
|
||||
* @param {Object} data
|
||||
* @param {String} clientId The Id of the Client to push to
|
||||
* @param {Object} options Optional parameters
|
||||
* @param {Boolean} options.noResponse Tell the Client not to respond
|
||||
* (Promise resolves instantly)
|
||||
* @return {Promise}
|
||||
*/
|
||||
Service.prototype.push = function(type, data, clientId, options) {
|
||||
var noRespond = options && options.noRespond;
|
||||
var client = this.getClient(clientId);
|
||||
return message('_push')
|
||||
.set({
|
||||
recipient: clientId,
|
||||
noRespond: noRespond,
|
||||
data: {
|
||||
type: type,
|
||||
data: data
|
||||
}
|
||||
}).send(client.port);
|
||||
};
|
||||
|
||||
Service.prototype.eachClient = function(fn) {
|
||||
for (var id in this.clients) fn(this.clients[id]);
|
||||
};
|
||||
|
||||
Service.prototype.getClient = function(id) {
|
||||
return this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-connect
|
||||
* @fires Service#connected
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onConnect = function(message) {
|
||||
debug('connection attempt', message.data, this.name);
|
||||
var data = message.data;
|
||||
var clientId = data.clientId;
|
||||
|
||||
if (!clientId) return;
|
||||
if (data.service !== this.name) return;
|
||||
if (this.clients[clientId]) return;
|
||||
|
||||
// before hook
|
||||
this.emit('before-connect', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
this.upgradeChannel(message);
|
||||
this.addClient(clientId, message.sourcePort);
|
||||
message.respond();
|
||||
|
||||
this.emit('connected', clientId);
|
||||
debug('connected', clientId);
|
||||
};
|
||||
|
||||
/**
|
||||
* When a Client attempt to connect we
|
||||
* can sometimes upgrade the to a direct
|
||||
* MessageChannel 'pipe' to prevent
|
||||
* hopping threads.
|
||||
*
|
||||
* We only do this if both:
|
||||
*
|
||||
* A. `MessagePort` was supplied with the 'connect' event.
|
||||
* B. The Client and Service are not both in `Window` contexts
|
||||
* (it's faster to use sync messaging window -> window).
|
||||
*
|
||||
* @param {Message} message the 'connect' message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.upgradeChannel = function(message) {
|
||||
if (this.inWindow && message.data.originEnv === 'Window') return;
|
||||
|
||||
var ports = message.event.ports;
|
||||
var channel = ports && ports[0];
|
||||
|
||||
if (channel) {
|
||||
message.setSourcePort(channel);
|
||||
this.listen(channel);
|
||||
channel.start();
|
||||
}
|
||||
|
||||
debug('channel upgraded');
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-disconnect
|
||||
* @fires Service#disconnected
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onDisconnect = function(message) {
|
||||
var client = this.clients[message.data];
|
||||
if (!client) return;
|
||||
|
||||
// before hook
|
||||
this.emit('before-disconnect', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
this.removeClient(client.id);
|
||||
message.respond();
|
||||
|
||||
this.emit('disconnected', client.id);
|
||||
debug('disconnected', client.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-method
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onMethod = function(message) {
|
||||
debug('on method', message.data);
|
||||
this.emit('before-method', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
var method = message.data;
|
||||
var name = method.name;
|
||||
var result;
|
||||
|
||||
var fn = this.methods[name];
|
||||
if (!fn) throw error(4, name);
|
||||
try { result = fn.apply(this, method.args); }
|
||||
catch (err) { result = err; }
|
||||
message.respond(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#on
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onOn = function(message) {
|
||||
debug('on on', message.data);
|
||||
this.emit('on', message.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#off
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onOff = function(message) {
|
||||
debug('on off');
|
||||
this.emit('off', message.data);
|
||||
};
|
||||
|
||||
Service.prototype.addClient = function(id, port) {
|
||||
this.clients[id] = {
|
||||
id: id,
|
||||
port: port
|
||||
};
|
||||
};
|
||||
|
||||
Service.prototype.removeClient = function(id) {
|
||||
delete this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Use a plugin with this Service.
|
||||
* @param {Function} fn Plugin function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Service.prototype.plugin = function(fn) {
|
||||
fn(this, { 'uuid': uuid });
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect a Client from the Service.
|
||||
* @param {Object} client
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.disconnect = function(client) {
|
||||
this.removeClient(client.id);
|
||||
message('disconnect')
|
||||
.set({
|
||||
recipient: client.id,
|
||||
noRespond: true
|
||||
})
|
||||
.send(client.port);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the Service.
|
||||
* @public
|
||||
*/
|
||||
Service.prototype.destroy = function() {
|
||||
delete this.clients;
|
||||
this.unlisten();
|
||||
this.off();
|
||||
};
|
||||
|
||||
var sp = Service.prototype;
|
||||
sp['broadcast'] = sp.broadcast;
|
||||
sp['destroy'] = sp.destroy;
|
||||
sp['method'] = sp.method;
|
||||
sp['plugin'] = sp.plugin;
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
return new Error({
|
||||
4: 'method "' + args[0] + '" doesn\'t exist'
|
||||
}[id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the default 'connect' logic.
|
||||
* This event acts as a hook for plugin authors
|
||||
* to override default 'connect' behaviour.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('before-connect', message => {
|
||||
* message.preventDefault();
|
||||
* // alternative connection logic ...
|
||||
* });
|
||||
*
|
||||
* @event Service#before-connect
|
||||
* @param {Message} message - The connect message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has connected.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('connected', clientId => {
|
||||
* console.log('client (%s) has connected', clientId);
|
||||
* });
|
||||
*
|
||||
* @event Service#connected
|
||||
* @param {String} clientId - The id of the connected Client
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires before the default 'disconnect' logic.
|
||||
* This event acts as a hook for plugin authors
|
||||
* to override default 'disconnect' behaviour.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('before-disconnect', message => {
|
||||
* message.preventDefault();
|
||||
* // alternative disconnection logic ...
|
||||
* });
|
||||
*
|
||||
* @event Service#before-disconnect
|
||||
* @param {Message} message - The disconnect message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has disconnected.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('disconnected', clientId => {
|
||||
* console.log('client (%s) has disconnected', clientId);
|
||||
* });
|
||||
*
|
||||
* @event Service#disconnected
|
||||
* @param {String} clientId - The id of the disconnected Client
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has begun
|
||||
* listening to a broadcast event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('on', data => {
|
||||
* console.log('client (%s) is listening to %s', data.clientId, data.name);
|
||||
* });
|
||||
*
|
||||
* @event Service#on
|
||||
* @type {Object}
|
||||
* @property {String} name - The broadcast name
|
||||
* @property {String} clientId - The id of the Client that started listening
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has stopped
|
||||
* listening to a broadcast event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('off', data => {
|
||||
* console.log('client (%s) stopped listening to %s', data.clientId, data.name);
|
||||
* });
|
||||
*
|
||||
* @event Service#off
|
||||
* @param {Object} data
|
||||
* @param {String} data.name - The broadcast name
|
||||
* @param {String} data.clientId - The id of the Client that stopped listening
|
||||
*/
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Create a UUID string.
|
||||
*
|
||||
* http://jsperf.com/guid-generation-stackoverflow
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
exports.uuid = function() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
exports.deferred = function() {
|
||||
var promise = {};
|
||||
promise.promise = new Promise((resolve, reject) => {
|
||||
promise.resolve = resolve;
|
||||
promise.reject = reject;
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
Loading…
Reference in a new issue