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
441
gaia/apps/music/components/bridge/src/message/index.js
Executable file
441
gaia/apps/music/components/bridge/src/message/index.js
Executable file
|
|
@ -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]);
|
||||
}
|
||||
Loading…
Reference in a new issue