Home Reference Source

packages/oo7/src/bondProxy.js

// (C) Copyright 2016-2017 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//         http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The parent-side cache-server to which child-side BondCaches can connect.
// Will send messages of the form { bondCacheUpdate: { uuid: '...', value: ... }}
// value, if provided is the actual Bond value, not a stringification of it.
// Will try to send these only for UUIDs that it knows the child is interested
// in - child can register interest with a message { useBond: uuid } and
// unregister interest with { dropBond: uuid }.
//
// If you construct BondCache passing a deferParentPrefix arg, then it's up to
// you to ensure that the parent actually has a BondCacheProxy constructed. If
// it doesn't, things will go screwy.

let consoleDebug = typeof window !== 'undefined' && window.debugging ? console.debug : () => {};

// Prepare value `v` for being sent over `window.postMessage`.
function prepUpdate (uuid, bond) {
	let value = bond.isReady() ? bond._value : undefined;

	if (typeof value === 'object' && value !== null && bond._stringify) {
		return { uuid, valueString: bond._stringify(value) };
	}

	return { uuid, value };
}

class BondProxy {
	constructor (deferParentPrefix, fromUuid, surrogateWindow = null) {
		this.bonds = {};
		this.deferParentPrefix = deferParentPrefix;
		this.fromUuid = fromUuid;
		this.window = surrogateWindow || (typeof window === 'undefined' ? null : window);

		// set up listener so that we get notified by our child.
		this.window.addEventListener('message', this.onMessage.bind(this));
	}

	onMessage (e) {
		if (e.source.parent !== this.window) {
			console.warn(`BondProxy.onMessage: Unknown client at ${e.origin} attempting to message proxy with ${e.data}. Ignoring.`);
			return;
		}
		if (typeof e.data === 'object' && e.data !== null) {
			consoleDebug('BondProxy.onMessage: Received message from child: ', e.data);
			if (e.data.helloBondProxy) {
				e.source.postMessage({ bondProxyInfo: { deferParentPrefix: this.deferParentPrefix } }, '*');
			} else if (typeof e.data.useBond === 'string') {
				let uuid = e.data.useBond;
				let entry = this.bonds[uuid];
				consoleDebug('BondProxy.onMessage: useBond ', uuid, entry);
				if (entry) {
					// already here - increase refs.
					if (entry.users.indexOf(e.source) !== -1) {
						console.warn(`BondProxy.onMessage: Source using UUID ${uuid} more than once.`);
					}
					consoleDebug('BondProxy.onMessage: Another user');
					entry.users.push(e.source);
				} else {
					// create it.
					let newBond = this.fromUuid(uuid);
					if (newBond) {
						consoleDebug('BondProxy.onMessage: Creating new bond');
						entry = this.bonds[uuid] = { bond: newBond, users: [e.source] };
						entry.notifyKey = newBond.notify(() => {
							let bondCacheUpdate = prepUpdate(uuid, newBond);
							consoleDebug('BondProxy.onMessage: Bond changed. Updating child:', bondCacheUpdate);
							entry.users.forEach(u =>
								u.postMessage({ bondCacheUpdate }, '*')
							);
						});
					} else {
						console.warn(`BondProxy.onMessage: UUID ${uuid} is unknown - cannot create a Bond for it.`);
						e.source.postMessage({ bondUnknown: { uuid } }, '*');
						return;
					}
				}
				let bondCacheUpdate = prepUpdate(uuid, entry.bond);
				consoleDebug('BondProxy.onMessage: Posting update back to child', bondCacheUpdate);
				e.source.postMessage({ bondCacheUpdate }, '*');
			} else if (typeof e.data.dropBond === 'string') {
				let uuid = e.data.dropBond;
				let entry = this.bonds[uuid];
				consoleDebug('BondProxy.onMessage: dropBond ', uuid, entry);
				if (entry) {
					let i = entry.users.indexOf(e.source);
					if (i !== -1) {
						consoleDebug('BondProxy.onMessage: Removing child from updates list');
						entry.users.splice(i, 1);
					} else {
						console.warn(`BondProxy.onMessage: Source asking to drop UUID ${uuid} that they do not track. They probably weren't getting updates.`);
					}
					if (entry.users.length === 0) {
						consoleDebug('BondProxy.onMessage: No users - retiring bond');
						entry.bond.unnotify(entry.notifyKey);
						delete this.bonds[uuid];
					}
				} else {
					console.warn(`BondProxy.onMessage: Cannot drop a Bond (${uuid}) that we do not track.`);
				}
			}
		}
	}
}

module.exports = BondProxy;