VulpesOS/gaia/apps/music/components/bridge/client.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

1319 lines
No EOL
30 KiB
JavaScript
Executable file

(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';
/**
* 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]);
}
},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":6}],3:[function(require,module,exports){
'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;
},{}],4:[function(require,module,exports){
'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]);
}
},{"../emitter":3,"../utils":6,"./port-adaptors":5}],5:[function(require,module,exports){
'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]);
}
},{"../utils":6}],6:[function(require,module,exports){
'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;
};
},{}]},{},[1])(1)
});