mami/src/proto.js/skel.js
flash cf71bab92d Rewrote connection handling.
This has been in the works for over a month and might break things because it's a very radical change.
If it causes you to be unable to join chat, report it on the forum or try joining using the legacy chat on https://sockchat.flashii.net.
2024-04-17 15:42:50 +00:00

215 lines
7.2 KiB
JavaScript

#include uniqstr.js
const WorkerSkeletonObject = function(name, defineObjectMethod, deleteObjectMethod, deleteObject) {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof defineObjectMethod !== 'function')
throw 'defineObjectMethod must be a function';
if(typeof deleteObjectMethod !== 'function')
throw 'deleteObjectMethod must be a function';
if(typeof deleteObject !== 'function')
throw 'deleteObject must be a function';
return {
getObjectName: () => name,
defineMethod: (...args) => defineObjectMethod(name, ...args),
deleteMethod: (...args) => deleteObjectMethod(name, ...args),
destroy: () => deleteObject(name),
};
};
const WorkerSkeleton = function(globalScope) {
if(globalScope === undefined)
globalScope = self;
const objects = new Map;
const handlers = {};
let initialised = false;
const sendPayload = (type, detail) => {
globalScope.postMessage({ type: type, detail: detail });
};
const sendObjectDefinePayload = (objName, objBody, eventPrefix) => {
sendPayload('objdef', { object: objName, methods: Object.keys(objBody), eventPrefix: eventPrefix });
};
const defineObject = (objName, objBody, eventPrefix) => {
if(typeof objName !== 'string')
throw 'objName must be a string';
if(typeof eventPrefix !== 'string' && eventPrefix !== undefined)
throw 'eventPrefix must be string or undefined';
if(objects.has(objName))
throw 'objName is already defined';
const object = {};
for(const name in objBody) {
const item = objBody[name];
if(typeof item === 'function')
object[name] = { body: item };
}
objects.set(objName, object);
if(initialised)
sendObjectDefinePayload(objName, object, eventPrefix);
};
const deleteObject = objName => {
if(typeof objName !== 'string')
throw 'objName must be a string';
if(!objects.has(objName))
throw 'objName is not defined';
objects.delete(objName);
if(initialised)
sendPayload('objdel', { object: objName });
};
const defineObjectMethod = (objName, metName, metBody, returnsObject) => {
if(typeof objName !== 'string')
throw 'objName must be a string';
if(typeof metName !== 'string')
throw 'metName must be a string';
if(typeof metBody !== 'function')
throw 'metBody must be a function';
const objBody = objects.get(objName);
if(objBody === undefined)
throw 'objName has not been defined';
objBody[metName] = { body: metBody, returnsObject: returnsObject === true };
if(initialised)
sendPayload('metdef', { object: objName, method: metName });
};
const deleteObjectMethod = (objName, metName) => {
if(typeof objName !== 'string')
throw 'objName must be a string';
if(typeof metName !== 'string')
throw 'metName must be a string';
const objBody = objects.get(objName);
if(objBody === undefined)
throw 'objName has not been defined';
delete objBody[objName];
if(initialised)
sendPayload('metdel', { object: objName, method: metName });
};
const createDispatcher = prefix => {
if(prefix === undefined)
prefix = '';
else if(typeof prefix !== 'string')
throw 'prefix must be a string or undefined';
if(prefix !== '' && !prefix.endsWith(':'))
prefix += ':';
return (name, detail) => sendPayload('evtdisp', { name: prefix + name, detail: detail });
};
defineObject('', {});
const defineHandler = (name, handler) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof handler !== 'function')
throw 'handler must be a function';
if(name in handlers)
throw 'name is already defined';
handlers[name] = handler;
};
defineHandler('init', () => {
if(initialised)
return;
initialised = true;
sendObjectDefinePayload('', objects.get(''));
});
defineHandler('metcall', req => {
if(typeof req.id !== 'string')
throw 'call id is not a string';
const respond = (id, success, result, mightBeObject) => {
let isObject = false;
if(mightBeObject) {
const resultType = typeof result;
if(resultType === 'string' && objects.has(result))
isObject = true;
else if(resultType === 'object' && result !== null && typeof result.getObjectName === 'function') {
const objectName = result.getObjectName();
if(objects.has(objectName)) {
isObject = true;
result = objectName;
}
}
}
sendPayload('funcret', {
id: id,
success: success,
result: result,
object: isObject,
});
};
try {
if(typeof req.object !== 'string')
throw 'object name is not a string';
if(typeof req.method !== 'string')
throw 'method name is not a string';
const object = objects.get(req.object);
if(object === undefined)
throw 'object is not defined';
if(!(req.method in object))
throw 'method is not defined in object';
const args = Array.isArray(req.args) ? req.args : [];
const info = object[req.method];
let result = info.body(...args);
if(result instanceof Promise) {
result.then(result => respond(req.id, true, result, info.returnsObject)).catch(ex => respond(req.id, false, ex));
} else
respond(req.id, true, result, info.returnsObject);
} catch(ex) {
respond(req.id, false, ex);
}
});
globalScope.addEventListener('message', ev => {
if(typeof ev.data === 'string') {
if(ev.data.startsWith('ping:')) {
globalScope.postMessage(`pong:${ev.data.substring(5)}`);
return;
}
}
if(typeof ev.data === 'object' && ev.data !== null && typeof ev.data.type === 'string') {
if(ev.data.type in handlers)
handlers[ev.data.type](ev.data.detail);
return;
}
});
return {
sendPayload: sendPayload,
createDispatcher: createDispatcher,
defineObject: (object, eventPrefix) => {
if(typeof object !== 'object' || object === null)
return undefined;
const name = MamiUniqueStr(8);
defineObject(name, object, eventPrefix);
return new WorkerSkeletonObject(name, defineObjectMethod, deleteObjectMethod, deleteObject);
},
defineMethod: (...args) => defineObjectMethod('', ...args),
deleteMethod: (...args) => deleteObjectMethod('', ...args),
};
};