/** *CacheofWebSocketinstancesperchannel * *ForreadingtherecanonlybeonechannelwitheachUUID,sowe *justhaveasimplemapof{uuid:WebSocket}.Thesocketcanbe *closedwhenthechannelisclosed. * *Forwritingtherecanbemanychannelsforeachuuid.Thosecan *shareawebsocket(withinaspecificglobal),sowehaveamap *of{uuid:[WebSocket,count]}.Countisincrementedwhena *channelisopenedwithagivenuuid,anddecrementedwhenits *closed.Whenthecountreacheszerowecanclosetheunderlying *socket.
*/ class SocketCache {
constructor() { this.readSockets = new Map(); this.writeSockets = new Map();
};
async getOrCreate(type, uuid, onmessage=null) { function createSocket() {
let protocol = self.isSecureContext ? "wss" : "ws";
let port = self.isSecureContext? "{{ports[wss][0]}}" : "{{ports[ws][0]}}";
let url = `${protocol}://{{host}}:${port}/msg_channel?uuid=${uuid}&direction=${type}`;
let socket = new WebSocket(url); if (onmessage !== null) {
socket.onmessage = onmessage;
}; returnnew Promise(resolve => socket.addEventListener("open", () => resolve(socket)));
}
let socket; if (type === "read") { if (this.readSockets.has(uuid)) { thrownew Error("Can't create multiple read sockets with same UUID");
}
socket = await createSocket(); // If the socket is closed by the server, ensure it's removed from the cache
socket.addEventListener("close", () => this.readSockets.delete(uuid)); this.readSockets.set(uuid, socket);
} elseif (type === "write") {
let count; if (onmessage !== null) { thrownew Error("Can't set message handler for write sockets");
} if (this.writeSockets.has(uuid)) {
[socket, count] = this.writeSockets.get(uuid);
} else {
socket = await createSocket();
count = 0;
}
count += 1; // If the socket is closed by the server, ensure it's removed from the cache
socket.addEventListener("close", () => this.writeSockets.delete(uuid)); this.writeSockets.set(uuid, [socket, count]);
} else { thrownew Error(`Unknown type ${type}`);
} return socket;
};
async close(type, uuid) {
let target = type === "read" ? this.readSockets : this.writeSockets; const data = target.get(uuid); if (!data) { return;
}
let count, socket; if (type == "read") {
socket = data;
count = 0;
} elseif (type === "write") {
[socket, count] = data;
count -= 1; if (count > 0) {
target.set(uuid, [socket, count]);
}
}; if (count <= 0 && socket) {
target.delete(uuid);
socket.close(1000);
await new Promise(resolve => socket.addEventListener("close", resolve));
}
};
/** *Addaneventcallbackfunction.Supportedmessagetypesare *"connect","close",and"message"(for``RecvChannel``). * *@param{string}type-Messagetype. *@param{Function}fn-Callbackfunction.Thisiscalled *withanevent-likeobject,with``type``and``data`` *properties.
*/
addEventListener(type, fn) { if (typeof type !== "string") { thrownew TypeError(`Expected string, got ${typeof type}`);
} if (typeof fn !== "function") { thrownew TypeError(`Expected function, got ${typeof fn}`);
} if (!this.eventListeners.hasOwnProperty(type)) { thrownew Error(`Unrecognised event type ${type}`);
} this.eventListeners[type].add(fn);
};
/** *Removeaneventcallbackfunction. * *@param{string}type-Eventtype. *@param{Function}fn-Callbackfunctiontoremove.
*/
removeEventListener(type, fn) { if (!typeof type === "string") { thrownew TypeError(`Expected string, got ${typeof type}`);
} if (typeof fn !== "function") { thrownew TypeError(`Expected function, got ${typeof fn}`);
}
let listeners = this.eventListeners[type]; if (listeners) {
listeners.delete(fn);
}
};
_dispatch(type, data) {
let listeners = this.eventListeners[type]; if (listeners) { // If any listener throws we end up not calling the other // listeners. This hopefully makes debugging easier, but // is different to DOM event listeners.
listeners.forEach(fn => fn({type, data}));
}
};
}
/** *Sendmessagesoverachannel
*/ class SendChannel extends Channel {
type = "write";
/** *Receivemessagesoverachannel
*/ class RecvChannel extends Channel {
type = "read";
constructor(uuid) { if (recvChannelsCreated.has(uuid)) { thrownew Error(`Already created RecvChannel with id ${uuid}`);
} super(uuid); this.eventListeners.message = new Set();
}
/** *Createanewchannelpair * *@returns{Array}-Arrayof[RecvChannel,SendChannel]forthesamechannel.
*/
self.channel = function() {
let uuid = createUuid();
let recvChannel = new RecvChannel(uuid);
let sendChannel = new SendChannel(uuid); return [recvChannel, sendChannel];
};
/** *Createanunconnectedchanneldefinedbya`uuid`in *``location.href``forlisteningfor`RemoteGlobal *<#RemoteGlobal>`_messages. * *@returns{RemoteGlobalCommandRecvChannel}-Disconnectedchannel
*/
self.global_channel = function() {
let uuid = new URLSearchParams(location.search).get("uuid"); if (!uuid) { thrownew Error("URL must have a uuid parameter to use as a RemoteGlobal");
} returnnew RemoteGlobalCommandRecvChannel(new RecvChannel(uuid));
};
/** *CloseallWebSocketsusedbychannelsinthecurrentrealm. *
*/
self.close_all_channel_sockets = async function() {
await socketCache.closeAll(); // Spinning the event loop after the close events is necessary to // ensure that the channels really are closed and don't affect // bfcache behaviour in at least some implementations.
await new Promise(resolve => setTimeout(resolve, 0));
};
/** *Disconnecttheassociated`RemoteGlobalCommandRecvChannel *<#RemoteGlobalCommandRecvChannel>`_,ifany,ontheserver *side. * *@returns{Promise}-Resolvedoncethechannelisdisconnected.
*/
disconnectReader() { // This causes any readers to disconnect until they are explicitly reconnected returnthis.sendChannel.disconnectReader();
}
/** *Closethechannelandunderlyingwebsocketconnections
*/
close() {
let closers = [this.sendChannel.close()]; if (this.recvChannel !== null) {
closers.push(this.recvChannel.close());
} if (this.respChannel !== null) {
closers.push(this.respChannel.close());
} return Promise.all(closers);
}
}
self.RemoteGlobal = RemoteGlobal;
function typeName(value) {
let type = typeof value; if (type === "undefined" ||
type === "string" ||
type === "boolean" ||
type === "number" ||
type === "bigint" ||
type === "symbol" ||
type === "function") { return type;
}
if (value === null) { return"null";
} // The handling of cross-global objects here is broken if (value instanceof RemoteObject) { return"remoteobject";
} if (value instanceof SendChannel) { return"sendchannel";
} if (value instanceof RecvChannel) { return"recvchannel";
} if (value instanceof Error) { return"error";
} if (Array.isArray(value)) { return"array";
}
let constructor = value.constructor && value.constructor.name; if (constructor === "RegExp" ||
constructor === "Date" ||
constructor === "Map" ||
constructor === "Set" ||
constructor == "WeakMap" ||
constructor == "WeakSet") { return constructor.toLowerCase();
} // The handling of cross-global objects here is broken if (typeof window == "object" && window === self) { if (value instanceof Element) { return"element";
} if (value instanceof Document) { return"document";
} if (value instanceof Node) { return"node";
} if (value instanceof Window) { return"window";
}
} if (Promise.resolve(value) === value) { return"promise";
} return"object";
}
let remoteObjectsById = new Map();
function remoteId(obj) {
let rv;
rv = createUuid();
remoteObjectsById.set(rv, obj); return rv;
}
/** *CreateaRemoteObjectcontainingahandletoreferenceobj * *@param{Any}obj-Theobjecttoreference.
*/ static from(obj) {
let type = typeName(obj);
let id = remoteId(obj); returnnew RemoteObject(type, id);
}
// Map from container object input to output value
let objectsSeen = new Map();
let lastObjectId = 0;
/* Instead of making this recursive, use a queue holding the objects to be *serialized.Eachiteminthequeuecanhavethefollowingproperties: * *item(required)-theinputitemtobeserialized * *target-Forcollections,theoutputserializedobjectto *whichtheserializationofthecurrentitemwillbeadded. * *targetName-Forserializingobjectmembers,thenameof *theproperty.Forserializingmapseither"key"or"value", *dependingonwhethertheitemrepresentsakeyoravalue *inthemap.
*/ while (queue.length > 0) { const {item, target, targetName} = queue.shift();
let type = typeName(item);
if (target === undefined) { if (outValue !== null) { thrownew Error("Tried to create multiple output values");
}
outValue = serialized;
} else { switch (target.type) { case"array": case"set":
target.value.push(serialized); break; case"object":
target.value[targetName] = serialized; break; case"map": // We always serialize key and value as adjacent items in the queue, // so when we get the key push a new output array and then the value will // be added on the next iteration. if (targetName === "key") {
target.value.push([]);
}
target.value[target.value.length - 1].push(serialized); break; default: thrownew Error(`Unknown collection target type ${target.type}`);
}
}
} return outValue;
}
/** *DeserializeanobjectfromaJSON-compatiblerepresentation. * *Fordetailsontheserializedrepresentationseeserialize(). * *@param{Object}obj-Thevaluetobedeserialized. *@returns{Any}-Thedeserializedvalue.
*/ function deserialize(obj) {
let deserialized = null;
let queue = [{item: obj, target: null}];
let objectMap = new Map();
/* Instead of making this recursive, use a queue holding the objects to be *deserialized.Eachiteminthequeuehasthefollowingproperties: * *item-Theinputitemtobedeserialised. * *target-Formembersofacollection,awrapperaroundthe *outputcollection.Thishasa``type``fieldwhichisthe *nameofthecollectiontype,anda``value``fieldwhichis *theactualoutputcollection.Forprimitives,thisisnull. * *targetName-Forobjectmembers,thepropertynameonthe *outputobject.Formaps,"key"iftheitemisakeyintheoutputmap, *or"value"ifit'savalueintheoutputmap.
*/ while (queue.length > 0) { const {item, target, targetName} = queue.shift(); const {type, value, objectId} = item;
let result;
let newTarget; if (objectId !== undefined && value === undefined) {
result = objectMap.get(objectId);
} else { switch(type) { case"undefined":
result = undefined; break; case"null":
result = null; break; case"string": case"boolean":
result = value; break; case"number": if (typeof value === "string") { switch(value) { case"NaN":
result = NaN; break; case"-0":
result = -0; break; case"+Infinity":
result = Number.POSITIVE_INFINITY; break; case"-Infinity":
result = Number.NEGATIVE_INFINITY; break; default: thrownew Error(`Unexpected number value "${value}"`);
}
} else {
result = value;
} break; case"bigint":
result = BigInt(value); break; case"function":
result = newFunction("...args", `return (${value}).apply(null, args)`); break; case"remoteobject":
let remote = new RemoteObject(value.type, value.objectId);
let local = remote.toLocal(); if (local !== null) {
result = local;
} else {
result = remote;
} break; case"sendchannel":
result = new SendChannel(value); break; case"regexp":
result = new RegExp(value.pattern, value.flags); break; case"date":
result = new Date(value); break; case"error": // The item.value.type property is the name of the error constructor. // If we have a constructor with the same name in the current realm, // construct an instance of that type, otherwise use a generic Error // type. if (item.value.type in self && typeof self[item.value.type] === "function") {
result = new self[item.value.type](item.value.message);
} else {
result = new Error(item.value.message);
}
result.name = item.value.name;
result.lineNumber = item.value.lineNumber;
result.columnNumber = item.value.columnNumber;
result.fileName = item.value.fileName;
result.stack = item.value.stack; break; case"array":
result = [];
newTarget = {type, value: result}; for (let child of value) {
queue.push({item: child, target: newTarget});
} break; case"set":
result = new Set();
newTarget = {type, value: result}; for (let child of value) {
queue.push({item: child, target: newTarget});
} break; case"object":
result = {};
newTarget = {type, value: result}; for (let [targetName, child] of Object.entries(value)) {
queue.push({item: child, target: newTarget, targetName});
} break; case"map":
result = new Map();
newTarget = {type, value: result}; for (let [key, child] of value) {
queue.push({item: key, target: newTarget, targetName: "key"});
queue.push({item: child, target: newTarget, targetName: "value"});
} break; default: thrownew TypeError(`Can't deserialize object of type ${type}`);
} if (objectId !== undefined) {
objectMap.set(objectId, result);
}
}
if (target === null) { if (deserialized !== null) { thrownew Error(`Tried to deserialized a non-root output value without a target`
` container object.`);
}
deserialized = result;
} else { switch(target.type) { case"array":
target.value.push(result); break; case"set":
target.value.add(result); break; case"object":
target.value[targetName] = result; break; case"map": // For maps the same target wrapper is shared between key and value. // After deserializing the key, set the `key` property on the target // until we come to the value. if (targetName === "key") {
target.key = result;
} else {
target.value.set(target.key, result);
} break; default: thrownew Error(`Unknown target type ${target.type}`);
}
}
} return deserialized;
}
})();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.