f23eff4e02fcabc0c981ceacab1e6d0a8a1ad7497239b40af782e87de0e61f44.json raw
1 {"ast":null,"code":"import _asyncToGenerator from \"/home/mleku/src/orly.dev/next/signer/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js\";\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// mls-engine.ts — Loads marmot.wasm in the signer extension background.\n// All MLS crypto happens here with direct access to vault keys.\n// No bus round-trips for signing or encryption.\nimport { signEvent, nip44Encrypt, nip44Decrypt, nip04Encrypt, nip04Decrypt } from './background-common';\nimport browser from 'webextension-polyfill';\n// --- State ---\nlet wasmReady = false;\nlet mlsTabId = null;\nlet currentPrivkey = '';\nlet mlsInitPromise = null;\n// --- IDB Group Store (same schema as the marmot SW used) ---\nconst GDB_NAME = 'marmot-groups';\nconst GDB_VER = 1;\nconst GDB_STORE = 'groups';\n// Open DB for writes — creates the store if it doesn't exist yet.\nfunction gdbOpenWrite() {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(GDB_NAME, GDB_VER);\n req.onupgradeneeded = e => {\n const db = e.target.result;\n if (!db.objectStoreNames.contains(GDB_STORE)) {\n db.createObjectStore(GDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n// Open DB for reads — returns null if the DB/store doesn't exist (no side-effects).\nfunction gdbOpenRead() {\n return new Promise(resolve => {\n const req = indexedDB.open(GDB_NAME);\n req.onupgradeneeded = () => {\n // DB doesn't exist yet — abort so we don't create an empty one.\n req.result.close();\n req.transaction?.abort();\n };\n req.onsuccess = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(GDB_STORE)) {\n db.close();\n resolve(null);\n return;\n }\n resolve(db);\n };\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n });\n}\nfunction storeResult(id, data, err) {\n const m = globalThis._marmot;\n if (m?.storeResult) m.storeResult(id, data || '', err || '');\n}\nfunction setupStoreCallbacks() {\n const g = globalThis;\n g._marmot_store_save = (id, groupIDHex, stateHex) => {\n gdbOpenWrite().then(db => {\n const tx = db.transaction(GDB_STORE, 'readwrite');\n tx.objectStore(GDB_STORE).put(stateHex, groupIDHex);\n tx.oncomplete = () => storeResult(id, '', '');\n tx.onerror = () => storeResult(id, '', tx.error?.message || 'save failed');\n }).catch(e => storeResult(id, '', e.message));\n };\n g._marmot_store_load = (id, groupIDHex) => {\n gdbOpenRead().then(db => {\n if (!db) {\n storeResult(id, '', '');\n return;\n }\n const tx = db.transaction(GDB_STORE, 'readonly');\n const req = tx.objectStore(GDB_STORE).get(groupIDHex);\n req.onsuccess = () => storeResult(id, req.result || '', '');\n req.onerror = () => storeResult(id, '', req.error?.message || 'load failed');\n }).catch(e => storeResult(id, '', e.message));\n };\n g._marmot_store_list = id => {\n gdbOpenRead().then(db => {\n if (!db) {\n storeResult(id, '', '');\n return;\n }\n const tx = db.transaction(GDB_STORE, 'readonly');\n const req = tx.objectStore(GDB_STORE).getAllKeys();\n req.onsuccess = () => storeResult(id, (req.result || []).join(','), '');\n req.onerror = () => storeResult(id, '', req.error?.message || 'list failed');\n }).catch(e => storeResult(id, '', e.message));\n };\n g._marmot_store_delete = (id, groupIDHex) => {\n gdbOpenRead().then(db => {\n if (!db) {\n storeResult(id, '', '');\n return;\n }\n const tx = db.transaction(GDB_STORE, 'readwrite');\n tx.objectStore(GDB_STORE).delete(groupIDHex);\n tx.oncomplete = () => storeResult(id, '', '');\n tx.onerror = () => storeResult(id, '', tx.error?.message || 'delete failed');\n }).catch(e => storeResult(id, '', e.message));\n };\n}\n// --- WASM Loading ---\nfunction loadWasm() {\n return _loadWasm.apply(this, arguments);\n} // --- Callback Bridge ---\nfunction _loadWasm() {\n _loadWasm = _asyncToGenerator(function* () {\n if (wasmReady) return;\n // wasm_exec.js is loaded as a background script (manifest.json),\n // so globalThis.Go is already available.\n const GoClass = globalThis.Go;\n if (!GoClass) throw new Error('Go WASM runtime not loaded (wasm_exec.js missing from background scripts)');\n const go = new GoClass();\n const wasmUrl = browser.runtime.getURL('marmot.wasm');\n const wasmResponse = yield fetch(wasmUrl);\n const wasmBytes = yield wasmResponse.arrayBuffer();\n const result = yield WebAssembly.instantiate(wasmBytes, go.importObject);\n // Start the Go program. Don't await — it blocks forever (select {}).\n // The synchronous part sets up globalThis._marmot before yielding.\n go.run(result.instance).then(() => {\n console.error('[mls-engine] Go program exited unexpectedly');\n wasmReady = false;\n }, err => {\n console.error('[mls-engine] Go program crashed:', err);\n wasmReady = false;\n });\n // Verify the Go side registered its API before we proceed.\n if (!globalThis._marmot) {\n throw new Error('Go WASM started but _marmot global not registered');\n }\n setupStoreCallbacks();\n wasmReady = true;\n });\n return _loadWasm.apply(this, arguments);\n}\nfunction setupMarmotInit(lastEventTS) {\n const g = globalThis;\n // Override _marmot_init to wire our local callbacks.\n // Returns a Promise that resolves when NewClient completes (it runs in a\n // goroutine because store.ListGroups blocks on async IDB callbacks).\n g._marmot_init = /*#__PURE__*/function () {\n var _ref = _asyncToGenerator(function* (pubkeyHex, ...relayURLs) {\n const marmot = g._marmot;\n if (!marmot) return 'error: wasm not loaded';\n const publishFn = eventJSON => {\n pushToTab({\n cmd: 'publish',\n event: eventJSON\n });\n };\n const subscribeFn = (subId, filterJSON) => {\n pushToTab({\n cmd: 'subscribe',\n subId,\n filter: filterJSON\n });\n };\n const cryptoSendFn = (op, peerHex, data, id) => {\n handleCryptoLocal(op, peerHex, data, id);\n };\n const onDMFn = (senderHex, plaintext) => {\n const ts = Math.floor(Date.now() / 1000);\n pushToTab({\n cmd: 'dm',\n peer: senderHex,\n sender: senderHex,\n content: plaintext,\n ts,\n source: 'marmot',\n eventId: ''\n });\n };\n const onStatusFn = msg => {\n pushToTab({\n cmd: 'status',\n msg\n });\n };\n // Periodically push lastEventTS to page for localStorage persistence.\n // Page localStorage survives extension reloads; browser.storage.local does not.\n setInterval(() => {\n try {\n const ts = marmot.lastEventTS?.();\n if (ts > 0) pushToTab({\n cmd: 'mls_ts',\n ts\n });\n } catch (_) {}\n }, 30000);\n return new Promise(resolve => {\n const onReadyFn = result => {\n resolve(result || 'ok');\n };\n marmot.init(pubkeyHex, publishFn, subscribeFn, cryptoSendFn, onDMFn, onStatusFn, onReadyFn, lastEventTS, ...relayURLs);\n });\n });\n return function (_x) {\n return _ref.apply(this, arguments);\n };\n }();\n}\n// --- Local Crypto (no round-trip!) ---\nfunction handleCryptoLocal(op, peerHex, data, id) {\n const marmot = globalThis._marmot;\n if (!marmot) return;\n const resolve = (result, err) => {\n marmot.cryptoResult(id, result, err);\n };\n try {\n switch (op) {\n case 'signEvent':\n {\n const eventTemplate = JSON.parse(data);\n const signed = signEvent(eventTemplate, currentPrivkey);\n resolve(JSON.stringify(signed), '');\n break;\n }\n case 'nip44.encrypt':\n case 'nip44Encrypt':\n nip44Encrypt(currentPrivkey, peerHex, data).then(r => resolve(r, ''), e => resolve('', e.message));\n break;\n case 'nip44.decrypt':\n case 'nip44Decrypt':\n nip44Decrypt(currentPrivkey, peerHex, data).then(r => resolve(r, ''), e => resolve('', e.message));\n break;\n case 'nip04.encrypt':\n case 'nip04Encrypt':\n nip04Encrypt(currentPrivkey, peerHex, data).then(r => resolve(r, ''), e => resolve('', e.message));\n break;\n case 'nip04.decrypt':\n case 'nip04Decrypt':\n nip04Decrypt(currentPrivkey, peerHex, data).then(r => resolve(r, ''), e => resolve('', e.message));\n break;\n default:\n resolve('', 'unsupported crypto op: ' + op);\n }\n } catch (err) {\n resolve('', err.message);\n }\n}\n// --- Push to originating tab ---\nfunction pushToTab(data) {\n if (mlsTabId === null) {\n console.warn('[mls-engine] pushToTab: no tab ID');\n return;\n }\n browser.tabs.sendMessage(mlsTabId, {\n ext: 'smesh-signer',\n type: 'mls-push',\n data\n }).catch(err => {\n console.warn('[mls-engine] pushToTab failed:', err);\n mlsTabId = null;\n });\n}\n// --- Public API (called from background.ts) ---\nexport function mlsInit(_x2, _x3, _x4, _x5) {\n return _mlsInit.apply(this, arguments);\n}\nfunction _mlsInit() {\n _mlsInit = _asyncToGenerator(function* (privkey, pubkey, relayURLs, tabId, lastEventTS = 0) {\n mlsTabId = tabId;\n currentPrivkey = privkey;\n mlsInitPromise = _asyncToGenerator(function* () {\n yield loadWasm();\n setupMarmotInit(lastEventTS);\n const initFn = globalThis._marmot_init;\n if (!initFn) return 'error: wasm bridge not ready';\n const result = yield initFn(pubkey, ...relayURLs);\n // Auto-bootstrap: publish key package and start subscription loop.\n if (!result || result === 'ok') {\n const m = globalThis._marmot;\n if (m) {\n m.publishKP();\n m.subscribe();\n }\n }\n return String(result || 'ok');\n })();\n return mlsInitPromise;\n });\n return _mlsInit.apply(this, arguments);\n}\nexport function mlsSetTab(tabId) {\n mlsTabId = tabId;\n}\nexport function mlsSendDM(_x6, _x7) {\n return _mlsSendDM.apply(this, arguments);\n}\nfunction _mlsSendDM() {\n _mlsSendDM = _asyncToGenerator(function* (recipient, content) {\n if (mlsInitPromise) yield mlsInitPromise;\n const m = globalThis._marmot;\n if (!m) return 'error: not initialized';\n return String(m.sendDM(recipient, content) || 'ok');\n });\n return _mlsSendDM.apply(this, arguments);\n}\nexport function mlsSubscribe() {\n return _mlsSubscribe.apply(this, arguments);\n}\nfunction _mlsSubscribe() {\n _mlsSubscribe = _asyncToGenerator(function* () {\n if (mlsInitPromise) yield mlsInitPromise;\n const m = globalThis._marmot;\n if (!m) return 'error: not initialized';\n return String(m.subscribe() || 'ok');\n });\n return _mlsSubscribe.apply(this, arguments);\n}\nexport function mlsPublishKP() {\n return _mlsPublishKP.apply(this, arguments);\n}\nfunction _mlsPublishKP() {\n _mlsPublishKP = _asyncToGenerator(function* () {\n if (mlsInitPromise) yield mlsInitPromise;\n const m = globalThis._marmot;\n if (!m) return 'error: not initialized';\n return String(m.publishKP() || 'ok');\n });\n return _mlsPublishKP.apply(this, arguments);\n}\nexport function mlsListGroups() {\n return _mlsListGroups.apply(this, arguments);\n}\nfunction _mlsListGroups() {\n _mlsListGroups = _asyncToGenerator(function* () {\n if (mlsInitPromise) yield mlsInitPromise;\n const m = globalThis._marmot;\n if (!m) return '[]';\n return String(m.listGroups() || '[]');\n });\n return _mlsListGroups.apply(this, arguments);\n}\nexport function mlsDeliverEvent(subId, eventJSON) {\n const m = globalThis._marmot;\n if (!m) return;\n m.deliverEvent(subId, eventJSON);\n}\nexport function mlsHandleEvent(eventJSON) {\n const m = globalThis._marmot;\n if (!m) return;\n m.handleEvent(eventJSON);\n}\nexport function isMlsMethod(method) {\n return method.startsWith('mls.');\n}","map":null,"metadata":{},"sourceType":"module","externalDependencies":[]}