diff --git a/build.js b/build.js index 23099c5..d237368 100644 --- a/build.js +++ b/build.js @@ -31,8 +31,7 @@ const exec = require('util').promisify(require('child_process').exec); const tasks = { js: [ - { source: 'proto.js', target: '/assets', name: 'proto.{hash}.js', vars: { build: 'MAMI_PROTO_JS', html: ':source' } }, - { source: 'mami.js', target: '/assets', name: 'mami.{hash}.js', vars: { build: 'MAMI_MAIN_JS', html: ':source' } }, + { source: 'mami.js', target: '/assets', name: 'mami.{hash}.js', vars: { build: 'MAMI_JS', html: ':source' } }, { source: 'init.js', target: '/assets', name: 'init.{hash}.js', es: 'es5', vars: { html: ':source' } }, ], css: [ diff --git a/src/init.js/main.js b/src/init.js/main.js index 39de908..54f3d12 100644 --- a/src/init.js/main.js +++ b/src/init.js/main.js @@ -42,7 +42,7 @@ if(isCompatible) { (function(script) { - script.src = MAMI_MAIN_JS; + script.src = MAMI_JS; script.type = 'text/javascript'; script.charset = 'utf-8'; document.body.appendChild(script); diff --git a/src/mami.js/context.js b/src/mami.js/context.js index 5faf505..f74ec76 100644 --- a/src/mami.js/context.js +++ b/src/mami.js/context.js @@ -21,7 +21,6 @@ const MamiContext = function(globalEventTarget, eventTarget) { let textTriggers; let eeprom; let conMan; - let protoWorker; return { get globalEvents() { return globalEventTarget; }, @@ -88,12 +87,5 @@ const MamiContext = function(globalEventTarget, eventTarget) { throw 'conMan must be a non-null object'; conMan = value; }, - - get protoWorker() { return protoWorker; }, - set protoWorker(value) { - if(typeof value !== 'object' || value === null) - throw 'protoWorker must be a non-null object'; - protoWorker = value; - }, }; }; diff --git a/src/mami.js/main.js b/src/mami.js/main.js index 9633087..94d6441 100644 --- a/src/mami.js/main.js +++ b/src/mami.js/main.js @@ -18,7 +18,6 @@ window.Umi = { UI: {} }; #include users.js #include utility.js #include weeb.js -#include worker.js #include audio/autoplay.js #include chatform/form.jsx #include colpick/picker.jsx @@ -905,10 +904,7 @@ const MamiInit = async args => { } }; - const protoWorker = new MamiWorker(MAMI_PROTO_JS, ctx.events.scopeTo('worker')); - ctx.protoWorker = protoWorker; - - const sockChat = new MamiSockChat(protoWorker); + const sockChat = new MamiSockChat(ctx.events.scopeTo('sockchat')); const conMan = new MamiConnectionManager(sockChat, settings, futami.get('servers'), ctx.events.scopeTo('conn')); ctx.conMan = conMan; @@ -982,40 +978,9 @@ const MamiInit = async args => { conMan.watch('attempt', conManAttempt); conMan.watch('fail', conManFail); - let workerStarting = false; - const initWorker = async () => { - if(workerStarting) - return; - workerStarting = true; - - if(FUTAMI_DEBUG) - console.info('[proto] initialising worker...'); - - try { - await protoWorker.connect(); - await sockChat.create(); - conMan.client = sockChat; - await conMan.start(); - } finally { - workerStarting = false; - } - }; - - protoWorker.watch(':timeout', ev => { - console.warn('worker timeout', ev.detail); - initWorker(); - }); - - document.addEventListener('visibilitychange', () => { - if(document.visibilityState === 'visible') { - protoWorker.ping().catch(ex => { - console.warn('worker died', ex); - initWorker(); - }); - } - }); - - await initWorker(); + await sockChat.create(); + conMan.client = sockChat; + await conMan.start(); return ctx; }; diff --git a/src/proto.js/sockchat/authed.js b/src/mami.js/proto/sockchat/authed.js similarity index 99% rename from src/proto.js/sockchat/authed.js rename to src/mami.js/proto/sockchat/authed.js index 2d53a95..9f645be 100644 --- a/src/proto.js/sockchat/authed.js +++ b/src/mami.js/proto/sockchat/authed.js @@ -1,4 +1,4 @@ -#include sockchat/utils.js +#include proto/sockchat/utils.js const SockChatS2CPong = ctx => { const lastPong = Date.now(); diff --git a/src/proto.js/sockchat/proto.js b/src/mami.js/proto/sockchat/client.js similarity index 98% rename from src/proto.js/sockchat/proto.js rename to src/mami.js/proto/sockchat/client.js index 9469733..475c2d7 100644 --- a/src/proto.js/sockchat/proto.js +++ b/src/mami.js/proto/sockchat/client.js @@ -1,9 +1,9 @@ #include timedp.js -#include sockchat/authed.js -#include sockchat/ctx.js -#include sockchat/unauthed.js +#include proto/sockchat/authed.js +#include proto/sockchat/ctx.js +#include proto/sockchat/unauthed.js -const SockChatProtocol = function(dispatch, options) { +const SockChatClient = function(dispatch, options) { if(typeof dispatch !== 'function') throw 'dispatch must be a function'; if(typeof options !== 'object' || options === null) diff --git a/src/proto.js/sockchat/ctx.js b/src/mami.js/proto/sockchat/ctx.js similarity index 96% rename from src/proto.js/sockchat/ctx.js rename to src/mami.js/proto/sockchat/ctx.js index dc4f851..b2332cc 100644 --- a/src/proto.js/sockchat/ctx.js +++ b/src/mami.js/proto/sockchat/ctx.js @@ -1,4 +1,4 @@ -#include sockchat/keepalive.js +#include proto/sockchat/keepalive.js const SockChatContext = function(dispatch, sendPing, pingDelay) { if(typeof dispatch !== 'function') diff --git a/src/proto.js/sockchat/keepalive.js b/src/mami.js/proto/sockchat/keepalive.js similarity index 100% rename from src/proto.js/sockchat/keepalive.js rename to src/mami.js/proto/sockchat/keepalive.js diff --git a/src/proto.js/sockchat/unauthed.js b/src/mami.js/proto/sockchat/unauthed.js similarity index 97% rename from src/proto.js/sockchat/unauthed.js rename to src/mami.js/proto/sockchat/unauthed.js index 684d6ae..0649f86 100644 --- a/src/proto.js/sockchat/unauthed.js +++ b/src/mami.js/proto/sockchat/unauthed.js @@ -1,4 +1,4 @@ -#include sockchat/utils.js +#include proto/sockchat/utils.js const SockChatS2CAuthSuccess = (ctx, userId, userName, userColour, userPerms, chanName, maxLength) => { ctx.userId = userId; diff --git a/src/proto.js/sockchat/utils.js b/src/mami.js/proto/sockchat/utils.js similarity index 100% rename from src/proto.js/sockchat/utils.js rename to src/mami.js/proto/sockchat/utils.js diff --git a/src/mami.js/settings/settings.js b/src/mami.js/settings/settings.js index 044a4f0..72906a3 100644 --- a/src/mami.js/settings/settings.js +++ b/src/mami.js/settings/settings.js @@ -25,7 +25,7 @@ const MamiSettings = function(storageOrPrefix, eventTarget) { }); const dispatchUpdate = (name, value, silent, local) => eventTarget.dispatch(createUpdateEvent(name, value, false, silent, local)); - const broadcast = new BroadcastChannel(`${MAMI_MAIN_JS}:settings:${storage.name}`); + const broadcast = new BroadcastChannel(`${MAMI_JS}:settings:${storage.name}`); const broadcastUpdate = (name, value, silent) => { setTimeout(() => broadcast.postMessage({ act: 'update', name: name, value: value, silent: !!silent }), 0); }; diff --git a/src/mami.js/sockchat/client.js b/src/mami.js/sockchat/client.js index d4bbcf0..e13027f 100644 --- a/src/mami.js/sockchat/client.js +++ b/src/mami.js/sockchat/client.js @@ -1,8 +1,8 @@ #include compat.js #include mszauth.js +#include proto/sockchat/client.js -const MamiSockChat = function(protoWorker) { - const events = protoWorker.eventTarget('sockchat'); +const MamiSockChat = function(eventTarget) { let restarting = false; let client; let dumpPackets = false; @@ -10,8 +10,8 @@ const MamiSockChat = function(protoWorker) { return { get client() { return client; }, - watch: events.watch, - unwatch: events.unwatch, + watch: eventTarget.watch, + unwatch: eventTarget.unwatch, create: async () => { if(client !== undefined && typeof client.close === 'function') @@ -19,8 +19,8 @@ const MamiSockChat = function(protoWorker) { client.close(); restarting = false; - client = await protoWorker.root.create('sockchat', { ping: futami.get('ping') }); - await client.setDumpPackets(dumpPackets); + client = new SockChatClient(eventTarget.dispatch, { ping: futami.get('ping') }); + client.setDumpPackets(dumpPackets); MamiCompat('Umi.Server', { get: () => client, configurable: true }); MamiCompat('Umi.Server.SendMessage', { value: text => client.sendMessage(text), configurable: true }); @@ -38,11 +38,11 @@ const MamiSockChat = function(protoWorker) { const authInfo = MamiMisuzuAuth.getInfo(); await client.sendAuth(authInfo.method, authInfo.token); }, - setDumpPackets: async state => { + setDumpPackets: state => { dumpPackets = !!state; if(client !== undefined && typeof client.setDumpPackets === 'function') - await client.setDumpPackets(dumpPackets); + client.setDumpPackets(dumpPackets); }, }; }; diff --git a/src/proto.js/timedp.js b/src/mami.js/timedp.js similarity index 100% rename from src/proto.js/timedp.js rename to src/mami.js/timedp.js diff --git a/src/mami.js/worker.js b/src/mami.js/worker.js deleted file mode 100644 index 9474904..0000000 --- a/src/mami.js/worker.js +++ /dev/null @@ -1,281 +0,0 @@ -#include uniqstr.js - -const MamiWorker = function(url, eventTarget) { - const timeOutMs = 30000; - - let worker, workerId; - let connectTimeout; - let pingId; - let hasTimedout; - - const root = {}; - const objects = new Map; - const pending = new Map; - const clearObjects = () => { - for(const [name, object] of objects) - for(const method in object) - delete object[method]; - objects.clear(); - objects.set('', root); - }; - - clearObjects(); - - const broadcastTimeoutZone = body => { - const localWorkerId = workerId; - - body(detail => { - if(localWorkerId !== workerId || hasTimedout) - return; - hasTimedout = true; - - eventTarget.dispatch(':timeout', detail); - }); - }; - - const handlers = {}; - - const handleMessage = ev => { - 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; - } - }; - - const callObjectMethod = (objName, metName, ...args) => { - return new Promise((resolve, reject) => { - if(typeof objName !== 'string') - throw 'objName must be a string'; - if(typeof metName !== 'string') - throw 'metName must be a string'; - - const id = MamiUniqueStr(8); - const info = { id: id, resolve: resolve, reject: reject }; - pending.set(id, info); - - worker.postMessage({ type: 'metcall', detail: { id: id, object: objName, method: metName, args: args } }); - - broadcastTimeoutZone(timeout => { - info.timeOut = setTimeout(() => { - const reject = info.reject; - info.resolve = info.reject = undefined; - - info.timeOut = undefined; - pending.delete(id); - - timeout({ at: 'call', obj: objName, met: metName }); - - if(typeof reject === 'function') - reject('timeout'); - }, timeOutMs); - }); - }); - }; - - const defineObjectMethod = (object, objName, method) => object[method] = (...args) => callObjectMethod(objName, method, ...args); - - handlers['objdef'] = info => { - let object = objects.get(info.object); - if(object === undefined) - objects.set(info.object, object = {}); - - if(typeof info.eventPrefix === 'string') { - const scopedTarget = eventTarget.scopeTo(info.eventPrefix); - object.watch = scopedTarget.watch; - object.unwatch = scopedTarget.unwatch; - } - - for(const method of info.methods) - defineObjectMethod(object, info.object, method); - }; - - handlers['objdel'] = info => { - // this should never happen - if(info.object === '') { - console.error('Worker attempted to delete root object!!!!!'); - return; - } - - const object = objects.get(info.object); - if(object === undefined) - return; - - objects.delete(info.object); - - const methods = Object.keys(object); - for(const method of methods) - delete object[method]; - }; - - handlers['metdef'] = info => { - const object = objects.get(info.object); - if(object === undefined) { - console.error('Worker attempted to define method on undefined object.'); - return; - } - - defineObjectMethod(object, info.object, info.method); - }; - - handlers['metdel'] = info => { - const object = objects.get(info.object); - if(object === undefined) { - console.error('Worker attempted to delete method on undefined object.'); - return; - } - - delete object[info.method]; - }; - - handlers['funcret'] = resp => { - const info = pending.get(resp.id); - if(info === undefined) - return; - - pending.delete(info.id); - - if(info.timeOut !== undefined) - clearTimeout(info.timeOut); - - const handler = resp.success ? info.resolve : info.reject; - info.resolve = info.reject = undefined; - - if(handler !== undefined) { - let result = resp.result; - if(resp.object) - result = objects.get(result); - - handler(result); - } - }; - - handlers['evtdisp'] = resp => { - eventTarget.dispatch(resp.name, resp.detail); - }; - - return { - get root() { return root; }, - - watch: eventTarget.watch, - unwatch: eventTarget.unwatch, - eventTarget: prefix => eventTarget.scopeTo(prefix), - - ping: () => { - return new Promise((resolve, reject) => { - if(worker === undefined) - throw 'no worker active'; - - let pingTimeout; - let localPingId = pingId; - - const pingHandleMessage = ev => { - if(typeof ev.data === 'string' && ev.data.startsWith('pong:') && ev.data.substring(5) === localPingId) - try { - reject = undefined; - pingId = undefined; - - if(pingTimeout !== undefined) - clearTimeout(pingTimeout); - - worker?.removeEventListener('message', pingHandleMessage); - - if(typeof resolve === 'function') - resolve(); - } finally { - resolve = undefined; - } - }; - - worker.addEventListener('message', pingHandleMessage); - - if(localPingId === undefined) { - pingId = localPingId = MamiUniqueStr(8); - - broadcastTimeoutZone(timeout => { - pingTimeout = setTimeout(() => { - try { - resolve = undefined; - - worker?.removeEventListener('message', pingHandleMessage); - - timeout({ at: 'ping' }); - if(typeof reject === 'function') - reject('ping timeout'); - } finally { - reject = undefined; - } - }, 200); - }); - - worker.postMessage(`ping:${localPingId}`); - } - }); - }, - - sabotage: () => { - worker?.terminate(); - }, - - connect: () => { - return new Promise((resolve, reject) => { - const connectFinally = () => { - if(connectTimeout !== undefined) { - clearTimeout(connectTimeout); - connectTimeout = undefined; - } - }; - - const connectHandleMessage = ev => { - worker?.removeEventListener('message', connectHandleMessage); - - if(typeof ev.data !== 'object' || ev.data === null || ev.data.type !== 'objdef' - || typeof ev.data.detail !== 'object' || ev.data.detail.object !== '') { - callReject('data'); - } else - callResolve(); - }; - - const callResolve = () => { - reject = undefined; - connectFinally(); - try { - if(typeof resolve === 'function') - resolve(root); - } finally { - resolve = undefined; - } - }; - - const callReject = (...args) => { - resolve = undefined; - connectFinally(); - try { - broadcastTimeoutZone(timeout => { - timeout({ at: 'connect' }); - }); - - if(typeof reject === 'function') - reject(...args); - } finally { - reject = undefined; - } - }; - - if(worker !== undefined) { - worker.terminate(); - workerId = worker = undefined; - } - - hasTimedout = false; - workerId = MamiUniqueStr(5); - worker = new Worker(url); - worker.addEventListener('message', handleMessage); - worker.addEventListener('message', connectHandleMessage); - - connectTimeout = setTimeout(() => callReject('timeout'), timeOutMs); - worker.postMessage({ type: 'init' }); - }); - }, - }; -}; diff --git a/src/proto.js/main.js b/src/proto.js/main.js deleted file mode 100644 index 344e684..0000000 --- a/src/proto.js/main.js +++ /dev/null @@ -1,19 +0,0 @@ -#include skel.js -#include sockchat/proto.js - -const skel = new WorkerSkeleton; - -skel.defineMethod('create', (name, options) => { - if(typeof name !== 'string') - throw 'name must be a string'; - - let proto, prefix; - - if(name === 'sockchat') - proto = new SockChatProtocol( - skel.createDispatcher(prefix = 'sockchat'), - options - ); - - return skel.defineObject(proto, prefix); -}, true); diff --git a/src/proto.js/skel.js b/src/proto.js/skel.js deleted file mode 100644 index f97f776..0000000 --- a/src/proto.js/skel.js +++ /dev/null @@ -1,214 +0,0 @@ -#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), - }; -}; diff --git a/src/proto.js/uniqstr.js b/src/proto.js/uniqstr.js deleted file mode 100644 index 72a1111..0000000 --- a/src/proto.js/uniqstr.js +++ /dev/null @@ -1,38 +0,0 @@ -const MamiRandomInt = (min, max) => { - let ret = 0; - const range = max - min; - - const bitsNeeded = Math.ceil(Math.log2(range)); - if(bitsNeeded > 53) - return -1; - - const bytesNeeded = Math.ceil(bitsNeeded / 8), - mask = Math.pow(2, bitsNeeded) - 1; - - const bytes = new Uint8Array(bytesNeeded); - crypto.getRandomValues(bytes); - - let p = (bytesNeeded - 1) * 8; - for(let i = 0; i < bytesNeeded; ++i) { - ret += bytes[i] * Math.pow(2, p); - p -= 8; - } - - ret &= mask; - - if(ret >= range) - return MamiRandomInt(min, max); - - return min + ret; -}; - -const MamiUniqueStr = (() => { - const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'; - - return length => { - let str = ''; - for(let i = 0; i < length; ++i) - str += chars[MamiRandomInt(0, chars.length)]; - return str; - }; -})();