p2party 0.14.2 → 0.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -30
- package/docs/getting-started.md +15 -0
- package/docs/protocol-v4-security.md +40 -0
- package/docs/references.md +28 -8
- package/docs/session-api.md +10 -2
- package/lib/api/signalingServerApi.d.ts +13 -2
- package/lib/api/webrtc/applyIceConfiguration.d.ts +9 -0
- package/lib/api/webrtc/dataChannelHandler.d.ts +10 -0
- package/lib/api/webrtc/deleteRoomData.d.ts +13 -0
- package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +2 -1
- package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +2 -1
- package/lib/api/webrtc/disconnectQuery.d.ts +2 -1
- package/lib/api/webrtc/iceRepair.d.ts +3 -1
- package/lib/api/webrtc/index.d.ts +2 -1
- package/lib/api/webrtc/interfaces.d.ts +37 -0
- package/lib/api/webrtc/negotiationLock.d.ts +24 -0
- package/lib/api/webrtc/pendingOffer.d.ts +29 -0
- package/lib/api/webrtc/rebindDeadline.d.ts +61 -0
- package/lib/api/webrtc/remoteTransportChange.d.ts +142 -0
- package/lib/api/webrtc/retiredRemoteTransports.d.ts +70 -0
- package/lib/api/webrtc/roomEdgeBudget.d.ts +10 -0
- package/lib/api/webrtc/setDescriptionQuery.d.ts +8 -1
- package/lib/api/webrtc/settleOnClose.d.ts +36 -0
- package/lib/blindRendezvous/admissionInput.d.ts +11 -0
- package/lib/blindRendezvous/admissionPolicy.d.ts +217 -0
- package/lib/blindRendezvous/canonicalSchema.d.ts +139 -0
- package/lib/blindRendezvous/carrierEnvelope.d.ts +105 -0
- package/lib/blindRendezvous/coreReduce.d.ts +199 -0
- package/lib/blindRendezvous/derivations.d.ts +381 -0
- package/lib/blindRendezvous/identityHelloAdmission.d.ts +89 -0
- package/lib/blindRendezvous/identityHelloAdmissionPersistence.d.ts +133 -0
- package/lib/blindRendezvous/identityHelloHistoricalEvidence.d.ts +52 -0
- package/lib/blindRendezvous/identityHelloOpen.d.ts +167 -0
- package/lib/blindRendezvous/identityHelloTrial.d.ts +271 -0
- package/lib/blindRendezvous/localAdmissibleProject.d.ts +101 -0
- package/lib/blindRendezvous/localPresenceKeyBinding.d.ts +108 -0
- package/lib/blindRendezvous/localStableIdentityCustody.d.ts +46 -0
- package/lib/blindRendezvous/normativeKernel.d.ts +222 -0
- package/lib/blindRendezvous/operation.d.ts +137 -0
- package/lib/blindRendezvous/operationPayload.d.ts +134 -0
- package/lib/blindRendezvous/pairBootstrapSuite.d.ts +93 -0
- package/lib/blindRendezvous/pairCrypto.d.ts +122 -0
- package/lib/blindRendezvous/pairLedger.d.ts +154 -0
- package/lib/blindRendezvous/pairOpenBudget.d.ts +67 -0
- package/lib/blindRendezvous/pairOpenReservation.d.ts +157 -0
- package/lib/blindRendezvous/pairPlaintext.d.ts +240 -0
- package/lib/blindRendezvous/pairStepEnvelope.d.ts +60 -0
- package/lib/blindRendezvous/plannerState.d.ts +61 -0
- package/lib/blindRendezvous/projections.d.ts +71 -0
- package/lib/blindRendezvous/publicWindow.d.ts +330 -0
- package/lib/blindRendezvous/retainedOperationStore.d.ts +187 -0
- package/lib/blindRendezvous/roomInviteV2.d.ts +92 -0
- package/lib/blindRendezvous/roomPolicyV4.d.ts +90 -0
- package/lib/blindRendezvous/schemas.d.ts +221 -0
- package/lib/blindRendezvous/storeDescriptor.d.ts +160 -0
- package/lib/cryptography/aeadWasm.d.ts +18 -0
- package/lib/cryptography/byteInput.d.ts +12 -0
- package/lib/cryptography/carrierAead.d.ts +23 -0
- package/lib/cryptography/chacha20poly1305.d.ts +7 -0
- package/lib/cryptography/fips202.d.ts +10 -0
- package/lib/cryptography/hpke.d.ts +84 -0
- package/lib/cryptography/hpkeSuite.d.ts +39 -0
- package/lib/cryptography/hybridKem.d.ts +42 -0
- package/lib/cryptography/hybridKemSuite.d.ts +22 -0
- package/lib/cryptography/interfaces.d.ts +3 -0
- package/lib/cryptography/mlkem.d.ts +27 -1
- package/lib/cryptography/mnemonic.d.ts +65 -1
- package/lib/cryptography/ownedEd25519KeyMaterial.d.ts +27 -0
- package/lib/cryptography/webCryptoSha256.d.ts +20 -0
- package/lib/cryptography/x25519.d.ts +18 -0
- package/lib/cryptography/xchacha20poly1305.d.ts +7 -0
- package/lib/db/api.d.ts +6 -3
- package/lib/db/identityEd25519ClientBoundary.d.ts +13 -0
- package/lib/db/src/getDB.d.ts +2 -2
- package/lib/db/types.d.ts +52 -4
- package/lib/db.worker.js +1 -1
- package/lib/handlers/coverChannelRegistry.d.ts +22 -0
- package/lib/handlers/edgeTeardownFollowUp.d.ts +35 -0
- package/lib/handlers/handleChallenge.d.ts +2 -1
- package/lib/handlers/handleConnectToPeer.d.ts +1 -1
- package/lib/handlers/handleHandshake.d.ts +2 -6
- package/lib/handlers/handleSendMessage.d.ts +60 -7
- package/lib/handlers/handleWebSocketMessage.d.ts +1 -1
- package/lib/handlers/handshakeCore.d.ts +2 -7
- package/lib/handlers/handshakeFrame.d.ts +25 -0
- package/lib/handlers/ratchetGateWait.d.ts +13 -0
- package/lib/handlers/reconcile.d.ts +24 -0
- package/lib/handlers/requestRoom.d.ts +30 -0
- package/lib/handlers/roomResponse.d.ts +53 -0
- package/lib/index.d.ts +99 -12
- package/lib/index.js +1 -1
- package/lib/index.min.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/libcrypto.provenance.json +5 -5
- package/lib/libcrypto.wasm +0 -0
- package/lib/middleware/roomListenerMiddleware.d.ts +4 -1
- package/lib/reducers/commonSlice.d.ts +3 -2
- package/lib/reducers/keyPairSlice.d.ts +3 -6
- package/lib/reducers/roomSlice.d.ts +81 -5
- package/lib/reducers/signalingServerSlice.d.ts +3 -2
- package/lib/session.d.ts +6 -3
- package/lib/session.js +1 -1
- package/lib/session.mjs +1 -1
- package/lib/store.d.ts +4 -2
- package/lib/utils/constants.d.ts +12 -0
- package/lib/utils/correlationId.d.ts +4 -0
- package/lib/utils/identityRestore.d.ts +108 -0
- package/lib/utils/interfaces.d.ts +25 -6
- package/lib/utils/roomConnectAdmission.d.ts +15 -0
- package/lib/utils/roomLeaveCoordinator.d.ts +38 -0
- package/lib/utils/roomRequestCoordinator.d.ts +104 -0
- package/lib/utils/roomRosterReconciler.d.ts +83 -0
- package/lib/utils/sdpFingerprint.d.ts +6 -0
- package/lib/utils/signalingAttemptLifecycle.d.ts +33 -0
- package/lib/utils/signalingAuth.d.ts +21 -2
- package/lib/utils/signalingBounds.d.ts +6 -0
- package/lib/utils/signalingFrame.d.ts +21 -0
- package/lib/utils/signalingHeartbeatWatchdog.d.ts +45 -0
- package/lib/utils/signalingIngressQueue.d.ts +49 -0
- package/lib/utils/signalingPeerRequestDebouncer.d.ts +13 -0
- package/lib/utils/signalingRoomLifecycle.d.ts +7 -0
- package/lib/utils/signalingRoomOperationLifecycle.d.ts +14 -0
- package/lib/utils/signalingServerBoundary.d.ts +11 -0
- package/lib/utils/terminalSettlement.d.ts +53 -0
- package/lib/utils/transportIdentity.d.ts +5 -0
- package/package.json +1 -1
package/lib/db.worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=(e,t)=>t.some(t=>e instanceof t);let t,a;const n=new WeakMap,r=new WeakMap,o=new WeakMap;let i={get(e,t,a){if(e instanceof IDBTransaction){if("done"===t)return n.get(e);if("store"===t)return a.objectStoreNames[1]?void 0:a.objectStore(a.objectStoreNames[0])}return d(e[t])},set:(e,t,a)=>(e[t]=a,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function s(e){i=e(i)}function c(e){return(a||(a=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(e)?function(...t){return e.apply(u(this),t),d(this.request)}:function(...t){return d(e.apply(u(this),t))}}function l(a){return"function"==typeof a?c(a):(a instanceof IDBTransaction&&function(e){if(n.has(e))return;const t=new Promise((t,a)=>{const n=()=>{e.removeEventListener("complete",r),e.removeEventListener("error",o),e.removeEventListener("abort",o)},r=()=>{t(),n()},o=()=>{a(e.error||new DOMException("AbortError","AbortError")),n()};e.addEventListener("complete",r),e.addEventListener("error",o),e.addEventListener("abort",o)});n.set(e,t)}(a),e(a,t||(t=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]))?new Proxy(a,i):a)}function d(e){if(e instanceof IDBRequest)return function(e){const t=new Promise((t,a)=>{const n=()=>{e.removeEventListener("success",r),e.removeEventListener("error",o)},r=()=>{t(d(e.result)),n()},o=()=>{a(e.error),n()};e.addEventListener("success",r),e.addEventListener("error",o)});return o.set(t,e),t}(e);if(r.has(e))return r.get(e);const t=l(e);return t!==e&&(r.set(e,t),o.set(t,e)),t}const u=e=>o.get(e);const h=["get","getKey","getAll","getAllKeys","count"],y=["put","add","delete","clear"],w=new Map;function g(e,t){if(!(e instanceof IDBDatabase)||t in e||"string"!=typeof t)return;if(w.get(t))return w.get(t);const a=t.replace(/FromIndex$/,""),n=t!==a,r=y.includes(a);if(!(a in(n?IDBIndex:IDBObjectStore).prototype)||!r&&!h.includes(a))return;const o=async function(e,...t){const o=this.transaction(e,r?"readwrite":"readonly");let i=o.store;return n&&(i=i.index(t.shift())),(await Promise.all([i[a](...t),r&&o.done]))[0]};return w.set(t,o),o}s(e=>({...e,get:(t,a,n)=>g(t,a)||e.get(t,a,n),has:(t,a)=>!!g(t,a)||e.has(t,a)}));const f=["continue","continuePrimaryKey","advance"],m={},p=new WeakMap,b=new WeakMap,k={get(e,t){if(!f.includes(t))return e[t];let a=m[t];return a||(a=m[t]=function(...e){p.set(this,b.get(this)[t](...e))}),a}};async function*S(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;const a=new Proxy(t,k);for(b.set(a,t),o.set(a,u(t));t;)yield a,t=await(p.get(a)||t.continue()),p.delete(a)}function I(t,a){return a===Symbol.asyncIterator&&e(t,[IDBIndex,IDBObjectStore,IDBCursor])||"iterate"===a&&e(t,[IDBIndex,IDBObjectStore])}s(e=>({...e,get:(t,a,n)=>I(t,a)?S:e.get(t,a,n),has:(t,a)=>I(t,a)||e.has(t,a)}));const x=64*Uint8Array.BYTES_PER_ELEMENT,K=65405-(9+x+8+8+256+8+8+8+(4+48*(x+1)));(new TextEncoder).encode("p2party-x25519-idsig-v1");const v=["hybrid-3dh-mlkem512-cpace21-v4","hybrid-3dh-mlkem768-cpace21-v4","hybrid-3dh-mlkem1024-cpace21-v4"],C="p2party-reassembled",R="p2party";async function A(){return function(e,t,{blocked:a,upgrade:n,blocking:r,terminated:o}={}){const i=indexedDB.open(e,t),s=d(i);return n&&i.addEventListener("upgradeneeded",e=>{n(d(i.result),e.oldVersion,e.newVersion,d(i.transaction),e)}),a&&i.addEventListener("blocked",e=>a(e.oldVersion,e.newVersion,e)),s.then(e=>{o&&e.addEventListener("close",()=>o()),r&&e.addEventListener("versionchange",e=>r(e.oldVersion,e.newVersion,e))}).catch(()=>{}),s}(R,19,{upgrade(e,t,a,n){if(e.objectStoreNames.contains("addressBook")){const e=n.objectStore("addressBook");e.indexNames.contains("username")||e.createIndex("username","username",{unique:!1}),e.indexNames.contains("peerId")||e.createIndex("peerId","peerId",{unique:!0}),e.indexNames.contains("peerPublicKey")||e.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}else{const t=e.createObjectStore("addressBook",{keyPath:["peerId"]});t.createIndex("username","username",{unique:!1}),t.createIndex("peerId","peerId",{unique:!0}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}if(e.objectStoreNames.contains("blacklist")){const e=n.objectStore("blacklist");e.indexNames.contains("username")||e.createIndex("username","username",{unique:!1}),e.indexNames.contains("peerId")||e.createIndex("peerId","peerId",{unique:!0}),e.indexNames.contains("peerPublicKey")||e.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}else{const t=e.createObjectStore("blacklist",{keyPath:["peerId"]});t.createIndex("username","username",{unique:!1}),t.createIndex("peerId","peerId",{unique:!0}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}if(e.objectStoreNames.contains("uniqueRoom")){const e=n.objectStore("uniqueRoom");e.indexNames.contains("roomUrl")||e.createIndex("roomUrl","roomUrl",{unique:!0}),e.indexNames.contains("roomId")||e.createIndex("roomId","roomId",{unique:!0})}else{const t=e.createObjectStore("uniqueRoom",{keyPath:["roomId"]});t.createIndex("roomUrl","roomUrl",{unique:!0}),t.createIndex("roomId","roomId",{unique:!0})}if(e.objectStoreNames.contains("messageData")){const e=n.objectStore("messageData");e.indexNames.contains("roomId")||e.createIndex("roomId","roomId",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1}),e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!0}),e.indexNames.contains("fromPeerId")||e.createIndex("fromPeerId","fromPeerId",{unique:!1})}else{const t=e.createObjectStore("messageData",{keyPath:["timestamp","roomId","merkleRoot"]});t.createIndex("roomId","roomId",{unique:!1}),t.createIndex("hash","hash",{unique:!1}),t.createIndex("merkleRoot","merkleRoot",{unique:!0}),t.createIndex("fromPeerId","fromPeerId",{unique:!1})}if(e.objectStoreNames.contains("chunks")){const e=n.objectStore("chunks");e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1})}else{const t=e.createObjectStore("chunks",{keyPath:["merkleRoot","chunkIndex"]});t.createIndex("merkleRoot","merkleRoot",{unique:!1}),t.createIndex("hash","hash",{unique:!1})}if(t<18&&e.objectStoreNames.contains("newChunks")&&e.deleteObjectStore("newChunks"),e.objectStoreNames.contains("newChunks")){const e=n.objectStore("newChunks");e.indexNames.contains("transferId")||e.createIndex("transferId","transferId",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1}),e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!1}),e.indexNames.contains("receiptScope")||e.createIndex("receiptScope",["merkleRoot","receiptToken"],{unique:!1})}else{const t=e.createObjectStore("newChunks",{keyPath:["transferId","chunkIndex"]});t.createIndex("transferId","transferId",{unique:!1}),t.createIndex("hash","hash",{unique:!1}),t.createIndex("merkleRoot","merkleRoot",{unique:!1}),t.createIndex("receiptScope",["merkleRoot","receiptToken"],{unique:!1})}if(e.objectStoreNames.contains("sendQueue")){const e=n.objectStore("sendQueue");e.indexNames.contains("labelPeer")||e.createIndex("labelPeer",["label","toPeerId"],{unique:!1})}else{e.createObjectStore("sendQueue",{keyPath:["position","label","toPeerId"]}).createIndex("labelPeer",["label","toPeerId"],{unique:!1})}if(!e.objectStoreNames.contains("ratchetSessions")){const t=e.createObjectStore("ratchetSessions",{keyPath:["roomId","peerPublicKey"]});t.createIndex("peerId","peerId",{unique:!1}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!1}),t.createIndex("roomId","roomId",{unique:!1})}t>0&&t<19&&(n.objectStore("ratchetSessions").clear(),n.objectStore("sendQueue").clear()),e.objectStoreNames.contains("meta")||e.createObjectStore("meta")}})}const D="ratchetWrapKey",P=12,E=16,L=16,B=Uint8Array.of(80,50,82,87),j=B.byteLength+1+L+P,M=262144,N=(new TextEncoder).encode("p2party-edge-crypto-at-rest-aad-v2");var U;!function(e){e[e.RootKey=1]="RootKey",e[e.SendingChainKey=2]="SendingChainKey",e[e.ReceivingChainKey=3]="ReceivingChainKey",e[e.DhSelfSecret=4]="DhSelfSecret",e[e.SkippedMessageKey=5]="SkippedMessageKey",e[e.EdgeCryptoState=6]="EdgeCryptoState"}(U||(U={}));const q=e=>e.slice(0),$=(e,t,a)=>{if(!(e instanceof ArrayBuffer))throw new Error(`${t} must be an ArrayBuffer`);if(void 0!==a&&e.byteLength!==a)throw new Error(`${t} must be ${String(a)} bytes`);return e},T=(e,t)=>{if("number"!=typeof e||!Number.isSafeInteger(e)||e<0)throw new Error(`${t} must be a safe unsigned integer`);return e},z=(e,t)=>{if("string"!=typeof e||0===e.length)throw new Error(`${t} must be a non-empty string`);const a=(new TextEncoder).encode(e);if(a.byteLength>1048576)throw new Error(`${t} is too long`);if((new TextDecoder).decode(a)!==e)throw new Error(`${t} is not canonical Unicode`);return e},F=e=>{if(!Number.isInteger(e)||e<0||e>4294967295)throw new Error("ratchet wrap: value exceeds uint32");const t=new Uint8Array(4);return new DataView(t.buffer).setUint32(0,e,!1),t},H=e=>{T(e,"ratchet wrap counter");const t=new Uint8Array(8);return new DataView(t.buffer).setBigUint64(0,BigInt(e),!1),t},O=e=>{const t=e.reduce((e,t)=>e+t.byteLength,0),a=new Uint8Array(t);let n=0;for(const t of e)a.set(t,n),n+=t.byteLength;return a},Q=e=>O([F(e.byteLength),e]),V=e=>Q((new TextEncoder).encode(e)),W=e=>{const t=e.rootSuite;if("string"!=typeof(a=t)||!v.includes(a))throw new Error("Unsupported ratchet root suite");var a;if(z(e.roomId,"ratchet roomId"),z(e.peerPublicKey,"ratchet peerPublicKey"),!/^[0-9a-f]{64}$/.test(e.peerPublicKey))throw new Error("ratchet peerPublicKey must be 32-byte lowercase hex");if(z(e.peerId,"ratchet peerId"),$(e.dhSelfPub,"ratchet dhSelfPub",32),null!==e.dhRemotePub&&$(e.dhRemotePub,"ratchet dhRemotePub",32),T(e.Ns,"ratchet Ns"),T(e.Nr,"ratchet Nr"),T(e.PN,"ratchet PN"),T(e.updatedAt,"ratchet updatedAt"),!Array.isArray(e.skippedMessageKeys)||e.skippedMessageKeys.length>2e3)throw new Error("ratchet skipped-message-key count is invalid");if(e.skippedMessageKeys.forEach((e,t)=>{$(e.dhPub,`ratchet skipped[${String(t)}].dhPub`,32),T(e.n,`ratchet skipped[${String(t)}].n`)}),null!==e.edgeCryptoState){const t=$(e.edgeCryptoState,"ratchet edge crypto state");if(t.byteLength<1||t.byteLength>M+j+E)throw new Error("ratchet edge crypto state length is invalid")}},X=e=>({rootSuite:e.rootSuite,roomId:e.roomId,peerPublicKey:e.peerPublicKey,peerId:e.peerId,rootKey:q($(e.rootKey,"ratchet rootKey")),sendingChainKey:null===e.sendingChainKey?null:q($(e.sendingChainKey,"ratchet sendingChainKey")),receivingChainKey:null===e.receivingChainKey?null:q($(e.receivingChainKey,"ratchet receivingChainKey")),dhSelfPub:q($(e.dhSelfPub,"ratchet dhSelfPub")),dhSelfSec:q($(e.dhSelfSec,"ratchet dhSelfSec")),dhRemotePub:null===e.dhRemotePub?null:q($(e.dhRemotePub,"ratchet dhRemotePub")),Ns:e.Ns,Nr:e.Nr,PN:e.PN,skippedMessageKeys:(()=>{if(!Array.isArray(e.skippedMessageKeys))throw new Error("ratchet skippedMessageKeys must be an array");return e.skippedMessageKeys.map((e,t)=>({dhPub:q($(e.dhPub,`ratchet skipped[${String(t)}].dhPub`)),n:e.n,messageKey:q($(e.messageKey,`ratchet skipped[${String(t)}].messageKey`))}))})(),edgeCryptoState:null===e.edgeCryptoState?null:q($(e.edgeCryptoState,"ratchet edge crypto state")),updatedAt:e.updatedAt}),G=e=>{new Uint8Array(e.rootKey).fill(0),e.sendingChainKey&&new Uint8Array(e.sendingChainKey).fill(0),e.receivingChainKey&&new Uint8Array(e.receivingChainKey).fill(0),new Uint8Array(e.dhSelfSec).fill(0);for(const t of e.skippedMessageKeys)new Uint8Array(t.messageKey).fill(0);e.edgeCryptoState&&new Uint8Array(e.edgeCryptoState).fill(0)},_=(e,t,a,n=0)=>{if(W(e),a.byteLength!==L)throw new Error("ratchet wrap: invalid record ID");const r=(null===e.sendingChainKey?0:1)|(null===e.receivingChainKey?0:2)|(null===e.dhRemotePub?0:4)|(null===e.edgeCryptoState?0:8),o=[Q(N),Uint8Array.of(2,t),Q(a),F(n),V(e.rootSuite),V(e.roomId),V(e.peerPublicKey),V(e.peerId),Q(new Uint8Array(e.dhSelfPub)),Uint8Array.of(r)];null!==e.dhRemotePub&&o.push(Q(new Uint8Array(e.dhRemotePub))),o.push(H(e.Ns),H(e.Nr),H(e.PN),H(e.updatedAt),F(e.skippedMessageKeys.length));for(const t of e.skippedMessageKeys)o.push(Q(new Uint8Array(t.dhPub)),H(t.n));return O(o)},Y=(e,t=32)=>{const a=new Uint8Array(e),n=j+1+E,r=j+M+E;if(null===t&&(a.byteLength<n||a.byteLength>r)||null!==t&&a.byteLength!==j+t+E)throw new Error("Unsupported or malformed ratchet wrap envelope");for(let e=0;e<B.byteLength;e++)if(a[e]!==B[e])throw new Error("Unsupported or malformed ratchet wrap envelope");if(2!==a[B.byteLength])throw new Error("Unsupported or malformed ratchet wrap envelope");return Uint8Array.from(a.subarray(B.byteLength+1,B.byteLength+1+L))},J=async(e,t,a,n,r=32)=>{if($(t,"ratchet secret",null===r?void 0:r),null===r&&(t.byteLength<1||t.byteLength>M))throw new Error("ratchet variable secret length is invalid");if(n.byteLength!==L)throw new Error("ratchet wrap: invalid record ID");const o=crypto.getRandomValues(new Uint8Array(P)),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:o,additionalData:a,tagLength:128},e,t),s=new Uint8Array(j+i.byteLength);return s.set(B,0),s[B.byteLength]=2,s.set(n,B.byteLength+1),s.set(o,B.byteLength+1+L),s.set(new Uint8Array(i),j),s.buffer},Z=async(e,t,a,n,r=32)=>{$(t,"ratchet wrapped secret");const o=Y(t,r);if(!te(o,n))throw new Error("Ratchet wrap record ID mismatch");const i=new Uint8Array(t),s=await crypto.subtle.decrypt({name:"AES-GCM",iv:i.subarray(B.byteLength+1+L,j),additionalData:a,tagLength:128},e,i.subarray(j));if(null!==r&&s.byteLength!==r||null===r&&(s.byteLength<1||s.byteLength>M))throw new Uint8Array(s).fill(0),new Error("Ratchet wrapped secret has an invalid plaintext length");return s},ee=async e=>{W(e);const t=Y(e.rootKey),a=[e.sendingChainKey,e.receivingChainKey,e.dhSelfSec,...e.skippedMessageKeys.map(e=>e.messageKey)];for(const e of a)if(null!==e&&!te(Y(e),t))throw new Error("Ratchet wrap record ID mismatch");if(null!==e.edgeCryptoState&&!te(Y(e.edgeCryptoState,null),t))throw new Error("Ratchet wrap record ID mismatch");const n=[_(e,U.RootKey,t),Q(new Uint8Array(e.rootKey)),Q(e.sendingChainKey?new Uint8Array(e.sendingChainKey):new Uint8Array),Q(e.receivingChainKey?new Uint8Array(e.receivingChainKey):new Uint8Array),Q(new Uint8Array(e.dhSelfSec))];for(const t of e.skippedMessageKeys)n.push(Q(new Uint8Array(t.messageKey)));n.push(Q(e.edgeCryptoState?new Uint8Array(e.edgeCryptoState):new Uint8Array));const r=O(n);try{return new Uint8Array(await crypto.subtle.digest("SHA-256",r))}finally{r.fill(0)}},te=(e,t)=>{if(e.byteLength!==t.byteLength)return!1;let a=0;for(let n=0;n<e.byteLength;n++)a|=e[n]^t[n];return 0===a};async function ae(){const e=await A();try{const t=await e.get("meta",D);if(t)return t;const a=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),n=e.transaction("meta","readwrite"),r=n.objectStore("meta"),o=await r.get(D);return o?(await n.done,o):(await r.put(a,D),await n.done,a)}finally{e.close()}}async function ne(e,t){const a=crypto.getRandomValues(new Uint8Array(P)),n=await crypto.subtle.encrypt({name:"AES-GCM",iv:a},e,t),r=new Uint8Array(P+n.byteLength);return r.set(a,0),r.set(new Uint8Array(n),P),r.buffer}async function re(e,t){const a=new Uint8Array(t);if(a.byteLength<28)throw new Error("Malformed wrapped secret");return crypto.subtle.decrypt({name:"AES-GCM",iv:a.subarray(0,P)},e,a.subarray(P))}const oe=()=>globalThis.navigator?.storage,ie=new Map,se=e=>e.createSyncAccessHandle,ce=(e,t,a,n)=>new File([e],t.length>0?t:a,{type:n});async function le(e,t,a,n){if(e.length!==2*x)return null;const r=ie.get(e);if(r)return r;const o=async function(e,t,a,n){try{const r=oe();if(!r||"function"!=typeof r.getDirectory)return console.warn("OPFS unavailable — reassembling file in memory"),null;const o=await r.getDirectory(),i=await o.getDirectoryHandle(C,{create:!0}),s=await i.getFileHandle(e,{create:!0}),c=await s.getFile();if(t>0&&c.size===t)return ce(c,a,e,n);const l=s.createSyncAccessHandle;if("function"!=typeof l)return console.warn("OPFS sync access handle unavailable — reassembling in memory"),null;const d=await l.call(s);let u;try{u=await A(),d.truncate(0);let t=0;const a=u.transaction("chunks","readonly"),n=a.objectStore("chunks").index("merkleRoot");let r=await n.openCursor(e);for(;r;){const e=new Uint8Array(r.value.data??new ArrayBuffer(0));let a=0;for(;a<e.length;){const n=d.write(e.subarray(a),{at:t+a});if(n<=0)throw new Error("OPFS short write");a+=n}t+=e.length,r=await r.continue()}await a.done,d.flush()}catch(t){try{d.close()}catch{}if(u)try{u.close()}catch{}throw await i.removeEntry(e).catch(()=>{}),t}return d.close(),u.close(),ce(await s.getFile(),a,e,n)}catch(e){return console.error(e),null}}(e,t,a,n);ie.set(e,o);try{return await o}finally{ie.delete(e)}}async function de(e){try{const t=oe();if(!t||"function"!=typeof t.getDirectory)return;const a=await t.getDirectory(),n=await a.getDirectoryHandle(C).catch(()=>null);n&&await n.removeEntry(e).catch(()=>{})}catch{}}const ue=new Map,he=new Map,ye=new Map,we=new Map;async function ge(e,t){const a=we.get(e)??Promise.resolve();let n;const r=new Promise(e=>{n=e}),o=a.then(()=>r);we.set(e,o),await a;try{return await t()}finally{n(),we.get(e)===o&&we.delete(e)}}async function fe(e,t){const a=ue.get(e);if(a)return a;const n=he.get(e);if(n)return n;if(ue.size>=8)for(const t of ue.keys())if(t!==e){await be(t);break}const r=async function(e,t){const a=oe();if(!a||"function"!=typeof a.getDirectory)return null;const n=await a.getDirectory(),r=await n.getDirectoryHandle(C,{create:!0}),o=await r.getFileHandle(e,{create:!0}),i=se(o);if("function"!=typeof i)return null;const s=await i.call(o);try{return t>0&&s.getSize()!==t&&s.truncate(t),{access:s,uniformSize:0,uniformKnown:!1,maxRealLen:0,seenCount:0}}catch(e){try{s.close()}catch{}throw e}}(e,t).catch(e=>(console.error(e),null));he.set(e,r);try{const t=await r;return t&&ue.set(e,t),t}finally{he.delete(e)}}function me(e,t,a){const n=new Uint8Array(a);let r=0;for(;r<n.length;){const a=e.write(n.subarray(r),{at:t+r});if(a<=0)throw new Error("OPFS short write");r+=a}}async function pe(e){const{schemaVersion:t,roomId:a,fromPeerId:n,channelLabel:r,timestamp:o,merkleRoot:i,hash:s,filename:c,messageType:l,chunkIndex:d,mimeType:u,leafHash:h,realLen:y,totalSize:w,data:g,storage:f}=e;if(1!==t||!Number.isSafeInteger(l)||l<1||l>64||!Number.isSafeInteger(d)||d<0||!Number.isSafeInteger(y)||y<=0||y>K||!Number.isSafeInteger(w)||w<=0||w>10737418240||y>w||d>=w||g.byteLength!==y||!Number.isSafeInteger(o))throw new Error("invalid receive chunk bounds");const m=e=>{if(e&&(e.roomId!==a||e.fromPeerId!==n||e.hash!==s||e.totalSize!==w||e.messageType!==l||e.filename!==c||e.channelLabel!==r||e.timestamp!==o))throw new Error("receive chunk metadata conflicts with message")},p=e=>{const t=e.map(e=>({chunkIndex:e.chunkIndex,realLen:e.realLen??0})).sort((e,t)=>e.chunkIndex-t.chunkIndex);let a=0;for(let e=0;e<t.length;e++){const n=t[e];if(!Number.isSafeInteger(n.chunkIndex)||n.chunkIndex<0||!Number.isSafeInteger(n.realLen)||n.realLen<=0||n.realLen>K||n.realLen>w||n.chunkIndex>=w||e>0&&t[e-1].chunkIndex===n.chunkIndex)throw new Error("invalid receive chunk layout");if(a+=n.realLen,!Number.isSafeInteger(a)||a>w)throw new Error("receive chunks exceed declared total")}const n=t.find(e=>0===e.chunkIndex);let r;if(n)r=n.realLen;else if(t.length>=2){r=0;for(const e of t)r=Math.max(r,e.realLen)}if(void 0!==r){const e=Math.ceil(w/r),n=e-1,o=w-r*n;for(const a of t){if(a.chunkIndex>=e)throw new Error("receive chunk index exceeds canonical manifest");const t=a.chunkIndex===n?o:r;if(a.realLen!==t)throw new Error("receive chunk length conflicts with manifest")}const i=a===w&&t.length===e&&t.every((e,t)=>e.chunkIndex===t);if(a===w&&!i)throw new Error("receive completion has a chunk-index gap");return{savedSize:a,complete:i}}if(a===w)throw new Error("receive completion is missing chunk zero");return{savedSize:a,complete:!1}},b=await A();let k;try{const e=b.transaction(["chunks","messageData"],"readonly");k=await e.objectStore("chunks").count([i,d]);m(await e.objectStore("messageData").index("merkleRoot").get(i)),await e.done}finally{b.close()}if(k>0){const e=await A();try{const t=e.transaction(["chunks","messageData"],"readonly"),a=t.objectStore("chunks").index("merkleRoot"),n=[];let r=await a.openCursor(i);for(;r;)n.push(r.value),r=await r.continue();m(await t.objectStore("messageData").index("merkleRoot").get(i));const o=p(n);return await t.done,{stored:!1,...o}}finally{e.close()}}const S=await A();try{const e=S.transaction(["chunks","messageData"],"readonly"),t=[];let a=await e.objectStore("chunks").index("merkleRoot").openCursor(i);for(;a;)t.push(a.value),a=await a.continue();m(await e.objectStore("messageData").index("merkleRoot").get(i)),p([...t,{chunkIndex:d,realLen:y}]),await e.done}finally{S.close()}let I=!1;if("opfs"===f)try{I=await async function(e,t,a,n,r){const o=await fe(e,n);if(!o)return!1;if(o.maxRealLen=Math.max(o.maxRealLen,a),o.seenCount+=1,o.uniformKnown||(0===t?(a<n&&(o.uniformSize=a),o.uniformKnown=!0):o.seenCount>=2&&(o.uniformSize=o.maxRealLen,o.uniformKnown=!0)),!o.uniformKnown)return!1;const i=0===t?0:t*o.uniformSize;return me(o.access,i,r),o.access.flush(),!0}(i,d,y,w,g)}catch(e){console.error(e),I=!1}const x="opfs"===f&&I?{merkleRoot:i,hash:s,chunkIndex:d,mimeType:u,realLen:y,leafHash:h}:{merkleRoot:i,hash:s,chunkIndex:d,mimeType:u,realLen:y,leafHash:h,data:g},v=await A();try{const e=v.transaction(["chunks","messageData","uniqueRoom"],"readwrite"),t=e.objectStore("chunks"),u=await t.getKey([i,d]);let h=!1;const g=[];let f=await t.index("merkleRoot").openCursor(i);for(;f;)g.push(f.value),f=await f.continue();void 0===u&&(p([...g,{chunkIndex:d,realLen:y}]),await t.add(x),g.push(x),h=!0);const b=p(g);if(h){const t=e.objectStore("messageData"),d=await t.index("merkleRoot").get(i);m(d),await t.put({roomId:a,timestamp:d?.timestamp??o,merkleRoot:i,hash:s,fromPeerId:n,filename:c,messageType:l,savedSize:b.savedSize,totalSize:w,channelLabel:r});const u=e.objectStore("uniqueRoom"),h=await u.index("roomId").get(a);h&&h.lastMessageMerkleRoot!==i&&await u.put({...h,lastMessageMerkleRoot:i,messageCount:h.messageCount+1,updatedAt:o})}return await e.done,{stored:h,...b}}finally{v.close()}}async function be(e){const t=he.get(e);if(t)try{await t}catch{}const a=ue.get(e);if(a){ue.delete(e);try{a.access.flush(),a.access.close()}catch{}}}async function ke(e,t,a,n){if(e.length!==2*x)return null;await be(e);const r=ye.get(e);if(r)return r;const o=async function(e,t,a,n){try{const r=oe();if(!r||"function"!=typeof r.getDirectory)return null;const o=await r.getDirectory(),i=await o.getDirectoryHandle(C,{create:!0}),s=await i.getFileHandle(e,{create:!0}),c=await A(),l=[];let d=0;try{const t=c.transaction("chunks","readonly"),a=t.objectStore("chunks").index("merkleRoot");let n=await a.openCursor(e);for(;n;){const e=n.value;e.realLen&&e.realLen>d&&(d=e.realLen),e.data&&e.data.byteLength>0&&l.push(e),n=await n.continue()}await t.done}catch(e){return console.error(e),c.close(),null}const u=await s.getFile();if(t>0&&u.size===t&&0===l.length)return c.close(),ce(u,a,e,n);if(l.length>0&&(0===d||l.some(e=>null==e.realLen)))return c.close(),null;const h=se(s);if("function"!=typeof h)return c.close(),null;const y=await h.call(s);try{t>0&&y.getSize()!==t&&y.truncate(t);for(let e=0;e<l.length;e++){const t=l[e];me(y,0===t.chunkIndex?0:t.chunkIndex*d,t.data)}y.flush()}finally{y.close()}if(l.length>0)try{const e=c.transaction("chunks","readwrite"),t=e.objectStore("chunks");for(let e=0;e<l.length;e++){const a=l[e];await t.put({merkleRoot:a.merkleRoot,hash:a.hash,chunkIndex:a.chunkIndex,mimeType:a.mimeType,realLen:a.realLen,leafHash:a.leafHash})}await e.done}catch(e){console.error(e)}c.close();const w=await s.getFile();return t>0&&w.size!==t?null:ce(w,a,e,n)}catch(e){return console.error(e),null}}(e,t,a,n);ye.set(e,o);try{return await o}finally{ye.delete(e)}}const Se=new class{#e=new Map;#t(e,t){const a=(e=>`${String(e.roomId.length)}:${e.roomId}${String(e.peerPublicKey.length)}:${e.peerPublicKey}`)(e),n=this.#e.get(a);if(n&&e.updatedAt<n.updatedAt)throw t.fill(0),new Error("Ratchet persistence rollback detected");if(e.updatedAt===n?.updatedAt&&!te(t,n.fingerprint))throw t.fill(0),new Error("Ratchet persistence equivocation detected");!n||e.updatedAt>n.updatedAt?(n?.fingerprint.fill(0),this.#e.set(a,{updatedAt:e.updatedAt,fingerprint:t})):t.fill(0)}async acceptAuthenticated(e){const t=await ee(e);this.#t(e,t)}async rememberTrustedWrite(e){const t=await ee(e);this.#t(e,t)}forget(e,t){const a=`${String(e.length)}:${e}${String(t.length)}:${t}`;this.#e.get(a)?.fingerprint.fill(0),this.#e.delete(a)}clear(){for(const e of this.#e.values())e.fingerprint.fill(0);this.#e.clear()}},Ie=new Map,xe=new Map,Ke=(e,t)=>`${String(e.length)}:${e}${String(t.length)}:${t}`,ve=async(e,t,a)=>{const n=Ke(e,t),r=xe.get(n)??Promise.resolve();let o;const i=new Promise(e=>{o=e}),s=r.catch(()=>{}).then(()=>i);xe.set(n,s),await r.catch(()=>{});try{return await a()}finally{o(),xe.get(n)===s&&xe.delete(n)}};async function Ce(e,t){return ve(e,t,async()=>{try{const a=await A();let n;try{n=await a.get("ratchetSessions",[e,t])}finally{a.close()}if(!n)return;const r=await ae(),o=await async function(e,t,a){const n=X(e);W(n);const r=[];try{const e=Y(n.rootKey),o=await Z(t,n.rootKey,_(n,U.RootKey,e),e);r.push(o);const i=n.sendingChainKey?await Z(t,n.sendingChainKey,_(n,U.SendingChainKey,e),e):null;i&&r.push(i);const s=n.receivingChainKey?await Z(t,n.receivingChainKey,_(n,U.ReceivingChainKey,e),e):null;s&&r.push(s);const c=await Z(t,n.dhSelfSec,_(n,U.DhSelfSecret,e),e);r.push(c);const l=[];for(let a=0;a<n.skippedMessageKeys.length;a++){const o=n.skippedMessageKeys[a],i=await Z(t,o.messageKey,_(n,U.SkippedMessageKey,e,a),e);r.push(i),l.push({dhPub:o.dhPub,n:o.n,messageKey:i})}const d=n.edgeCryptoState?await Z(t,n.edgeCryptoState,_(n,U.EdgeCryptoState,e),e,null):null;return d&&r.push(d),a&&await a.acceptAuthenticated(n),{...n,rootKey:o,sendingChainKey:i,receivingChainKey:s,dhSelfSec:c,skippedMessageKeys:l,edgeCryptoState:d}}catch(e){for(const e of r)new Uint8Array(e).fill(0);throw e}finally{G(n)}}(n,r,Se),i=Ke(e,t);return Ie.set(i,Math.max(Ie.get(i)??0,o.updatedAt)),o}catch(e){return void console.error(e)}})}async function Re(e){await ve(e.roomId,e.peerPublicKey,async()=>{const t=Ke(e.roomId,e.peerPublicKey),a=Ie.get(t)??0,n=Math.max(Date.now(),e.updatedAt),r=n>a?n:a+1;if(!Number.isSafeInteger(r))throw new Error("Ratchet persistence timestamp exhausted");const o={...e,updatedAt:r};try{const e=await ae(),a=await async function(e,t){const a=X(e);if(W(a),$(a.rootKey,"ratchet rootKey",32),a.sendingChainKey&&$(a.sendingChainKey,"ratchet sendingChainKey",32),a.receivingChainKey&&$(a.receivingChainKey,"ratchet receivingChainKey",32),$(a.dhSelfSec,"ratchet dhSelfSec",32),a.skippedMessageKeys.forEach((e,t)=>$(e.messageKey,`ratchet skipped[${String(t)}].messageKey`,32)),a.edgeCryptoState&&($(a.edgeCryptoState,"ratchet edge crypto state"),a.edgeCryptoState.byteLength<1||a.edgeCryptoState.byteLength>M))throw new Error("ratchet edge crypto state length is invalid");try{const e=crypto.getRandomValues(new Uint8Array(L)),n=await J(t,a.rootKey,_(a,U.RootKey,e),e),r=a.sendingChainKey?await J(t,a.sendingChainKey,_(a,U.SendingChainKey,e),e):null,o=a.receivingChainKey?await J(t,a.receivingChainKey,_(a,U.ReceivingChainKey,e),e):null,i=await J(t,a.dhSelfSec,_(a,U.DhSelfSecret,e),e),s=[];for(let n=0;n<a.skippedMessageKeys.length;n++){const r=a.skippedMessageKeys[n];s.push({dhPub:r.dhPub,n:r.n,messageKey:await J(t,r.messageKey,_(a,U.SkippedMessageKey,e,n),e)})}const c=a.edgeCryptoState?await J(t,a.edgeCryptoState,_(a,U.EdgeCryptoState,e),e,null):null;return{...a,rootKey:n,sendingChainKey:r,receivingChainKey:o,dhSelfSec:i,skippedMessageKeys:s,edgeCryptoState:c}}finally{G(a)}}(o,e),n=await A();try{await n.put("ratchetSessions",a)}finally{n.close()}await Se.rememberTrustedWrite(a),Ie.set(t,r)}finally{(e=>{new Uint8Array(e.rootKey).fill(0),e.sendingChainKey&&new Uint8Array(e.sendingChainKey).fill(0),e.receivingChainKey&&new Uint8Array(e.receivingChainKey).fill(0),new Uint8Array(e.dhSelfSec).fill(0);for(const t of e.skippedMessageKeys)new Uint8Array(t.messageKey).fill(0);e.edgeCryptoState&&new Uint8Array(e.edgeCryptoState).fill(0)})(o)}})}const Ae=/^[0-9a-f]{64}$/,De=(e,t)=>{if("string"!=typeof e||0===e.length)throw new Error("PIN attempt room ID must not be empty");if(void 0!==t&&!Ae.test(t))throw new Error("PIN attempt peer identity must be 32-byte lowercase hexadecimal")},Pe=e=>`pinAttempts:v2:${e.length}:${e}:`,Ee=(e,t)=>`${Pe(e)}${t}`;const Le="identityX25519";const Be="identityEd25519";async function je(){try{await async function(){const e=[...he.values()];for(let t=0;t<e.length;t++)try{await e[t]}catch{}const t=[...ue.keys()];for(let e=0;e<t.length;e++)await be(t[e])}();const e=oe();if(e&&"function"==typeof e.getDirectory){const t=await e.getDirectory();await t.removeEntry(C,{recursive:!0}).catch(()=>{})}}catch{}await function(e,{blocked:t}={}){const a=indexedDB.deleteDatabase(e);return t&&a.addEventListener("blocked",e=>t(e.oldVersion,e)),d(a).then(()=>{})}(R,{blocked(){console.error("DB deletion BLOCKED")}}),Se.clear(),Ie.clear()}onmessage=async e=>{const t=e.data,{id:a,method:n}=t;try{let e;switch(n){case"getDBAddressBookEntry":e=await async function(e,t){if(!e&&!t)return;if(e&&e.length<10&&!t)return;if(t&&64!==t.length&&!e)return;const a=await A();try{const n=a.transaction("addressBook","readonly"),r=e?n.objectStore("addressBook").index("peerId"):n.objectStore("addressBook").index("peerPublicKey"),o=e?await r.get(e):await r.get(t??"");return await n.done,a.close(),o?{username:o.username,peerId:o.peerId,peerPublicKey:o.peerPublicKey}:void 0}catch{return void a.close()}}(...t.args);break;case"getAllDBAddressBookEntries":e=await async function(){try{const e=await A(),t=await e.getAll("addressBook");return e.close(),t}catch{return[]}}(...t.args);break;case"setDBAddressBookEntry":await async function(e,t,a){try{const n=await A(),r=n.transaction(["addressBook"],"readwrite"),o=r.objectStore("addressBook"),i=o.index("peerId"),s=o.index("peerPublicKey"),c=await i.get(t),l=await s.get(a);(!c&&!l||c&&!l||!c&&l)&&await o.put({username:e,peerId:t,peerPublicKey:a,dateAdded:Date.now()}),await r.done,n.close()}catch(e){console.error(e)}}(...t.args),e=void 0;break;case"deleteDBAddressBookEntry":e=await async function(e,t,a){const n=!e||0===e.length,r=!t||t.length<10,o=64!==a?.length;if(n&&r&&o)throw new Error("Cannot delete address book with no data");let i=t??"";try{const n=await A(),s=n.transaction("addressBook","readwrite"),c=s.objectStore("addressBook");if(r)if(o){const t=c.index("username"),a=await t.getKey(e??"");if(a){const n=await t.get(e??"");i=n?.peerId??"",await c.delete(a)}}else{const e=c.index("peerPublicKey"),t=await e.getKey(a);if(t){const n=await e.get(a);i=n?.peerId??"",await c.delete(t)}}else{const e=c.index("peerId"),a=await e.getKey(t);a&&await c.delete(a)}await s.done,n.close()}catch{}return i}(...t.args);break;case"getDBPeerIsBlacklisted":e=await async function(e,t){if(!e&&!t)return!1;if(e&&e.length<10&&!t)return!1;if(t&&64!==t.length&&!e)return!1;const a=await A();try{const n=a.transaction("blacklist","readonly"),r=e?n.objectStore("blacklist").index("peerId"):n.objectStore("blacklist").index("peerPublicKey"),o=e?await r.get(e):await r.get(t??"");return await n.done,!!o}finally{a.close()}}(...t.args);break;case"getAllDBBlacklisted":e=await async function(){const e=await A();try{const t=await e.getAll("blacklist");return e.close(),t}catch{return e.close(),[]}}(...t.args);break;case"setDBPeerInBlacklist":await async function(e,t){try{const a=await A(),n=a.transaction(["blacklist"],"readwrite"),r=n.objectStore("blacklist"),o=r.index("peerId"),i=r.index("peerPublicKey"),s=await o.get(e),c=await i.get(t);(!s&&!c||s&&!c||!s&&c)&&await r.put({peerId:e,peerPublicKey:t,dateAdded:Date.now()}),await n.done,a.close()}catch(e){throw console.error(e),e}}(...t.args),e=void 0;break;case"deleteDBPeerFromBlacklist":await async function(e,t){const a=!e||e.length<10;if(a&&64!==t?.length)throw new Error("Cannot delete blacklisted with no data");try{const n=await A(),r=n.transaction("blacklist","readwrite"),o=r.objectStore("blacklist");if(a){const e=o.index("peerPublicKey"),a=await e.getKey(t??"");a&&await o.delete(a)}else{const t=o.index("peerId"),a=await t.getKey(e);a&&await o.delete(a)}await r.done,n.close()}catch(e){console.error(e)}}(...t.args),e=void 0;break;case"getAllDBUniqueRooms":e=await async function(){const e=await A();try{const t=await e.getAll("uniqueRoom");return e.close(),t}catch{return e.close(),[]}}(...t.args);break;case"setDBUniqueRoom":await async function(e,t,a){if(32!==a.byteLength)throw new Error("Room policy must be the canonical 32-byte descriptor");const n=await A();try{const r=n.transaction(["uniqueRoom"],"readwrite"),o=r.objectStore("uniqueRoom"),i=o.index("roomUrl"),s=o.index("roomId"),c=await i.get(e),l=await s.get(t),d=c??l;if(d)await o.put({...d,roomId:t,roomUrl:e,roomPolicy:a,updatedAt:Date.now()});else{const n=Date.now();await o.put({roomId:t,roomUrl:e,roomPolicy:a,messageCount:0,lastMessageMerkleRoot:"",createdAt:n,updatedAt:n})}await r.done}finally{n.close()}}(...t.args),e=void 0;break;case"getDBMessageData":e=await async function(e,t){try{const a=await A(),n=a.transaction(["messageData"],"readonly"),r=n.objectStore("messageData"),o=r.index("merkleRoot"),i=r.index("hash");if(e?.length===2*x){const r=await o.get(e);if(r)return await n.done,a.close(),r;if(t?.length===2*x){const e=await i.get(t);return await n.done,a.close(),e}return await n.done,void a.close()}if(t?.length===2*x){const e=await i.get(t);return await n.done,a.close(),e}return await n.done,void a.close()}catch(e){return void console.error(e)}}(...t.args);break;case"getDBRoomMessageData":e=await async function(e){const t=await A(),a=await t.getAllFromIndex("messageData","roomId",e);t.close();const n=a.length,r=[];for(let t=0;t<n;t++)r.push({transferId:a[t].transferId,roomId:e,merkleRoot:a[t].merkleRoot,hash:a[t].hash,fromPeerId:a[t].fromPeerId,filename:a[t].filename,messageType:a[t].messageType,savedSize:a[t].savedSize,totalSize:a[t].totalSize,channelLabel:a[t].channelLabel,timestamp:a[t].timestamp});return r}(...t.args);break;case"getDBRoomStats":e=await async function(e){const t=await A(),a=await t.getAllFromIndex("messageData","roomId",e);t.close();const n=new Set;let r=0,o=0,i=0,s=0;for(const e of a){const t=void 0!==e.transferId,a=t?e.totalSize:e.savedSize;t?(r+=a,i+=1):(o+=a,s+=1,e.fromPeerId.length>0&&n.add(e.fromPeerId))}return{messageCount:a.length,messagesSent:i,messagesReceived:s,bytesSent:r,bytesReceived:o,bytesTotal:r+o,peerIds:[...n]}}(...t.args);break;case"setDBRoomMessageData":await async function(e,t,a,n,r,o,i,s,c,l,d){try{const u=await A(),h=u.transaction(["messageData","uniqueRoom"],"readwrite"),y=h.objectStore("messageData"),w=await y.index("merkleRoot").get(t),g=w?.savedSize??0;try{await y.put({transferId:d,roomId:e,timestamp:l,merkleRoot:t,hash:a,fromPeerId:n,filename:s,messageType:i,savedSize:g+r<=o?g+r:o,totalSize:o,channelLabel:c})}catch(e){throw await h.done,u.close(),e}const f=h.objectStore("uniqueRoom"),m=await f.index("roomId").get(e);if(m&&m.lastMessageMerkleRoot!==t)try{await f.put({...m,lastMessageMerkleRoot:t,messageCount:m.messageCount+1,updatedAt:l})}catch(e){throw await h.done,u.close(),e}await h.done,u.close()}catch(e){throw console.error(e),e}}(...t.args),e=void 0;break;case"getDBChunk":e=await async function(e,t){const a=await A(),n=await a.get("chunks",[e,t]);return a.close(),n?.data}(...t.args);break;case"existsDBChunk":e=await async function(e,t){const a=await A(),n=await a.count("chunks",[e,t]);return a.close(),n>0}(...t.args);break;case"getDBNewChunk":e=await async function(e,t){const a=await A();try{return await a.get("newChunks",[e,t])}finally{a.close()}}(...t.args);break;case"getDBNewChunkByReceipt":e=await async function(e,t){if(e.length!==2*x||t.length!==2*x)return;const a=await A();try{return await a.getFromIndex("newChunks","receiptScope",[e,t])}finally{a.close()}}(...t.args);break;case"existsDBNewChunk":e=await async function(e,t){const a=await A(),n=await a.count("newChunks",[e,t]);return a.close(),n>0}(...t.args);break;case"getDBSendQueue":e=await async function(e,t,a){const n=await A();if(a){const r=await n.get("sendQueue",[a,e,t]);return n.close(),r?[r]:[]}const r=n.transaction("sendQueue","readonly").objectStore("sendQueue").index("labelPeer"),o=IDBKeyRange.only([e,t]),i=await r.getAll(o);return n.close(),i}(...t.args);break;case"getDBAllChunks":e=await async function(e,t){try{const a=await A(),n=a.transaction("chunks","readonly"),r=n.objectStore("chunks"),o=r.index("hash"),i=r.index("merkleRoot");if(e?.length===2*x){const t=await i.getAll(e);return 0===t.length?(await n.done,a.close(),[]):(await n.done,a.close(),t)}if(t?.length===2*x){const e=await o.getAll(t);return await n.done,a.close(),e.length>0?e.filter(t=>t.merkleRoot===e[0].merkleRoot):e}return await n.done,a.close(),[]}catch(e){return console.error(e),[]}}(...t.args);break;case"getDBAllChunkLeafHashes":e=await async function(e){if(e.length!==2*x)return[];try{const t=await A(),a=t.transaction("chunks","readonly"),n=a.objectStore("chunks").index("merkleRoot"),r=[];let o=await n.openCursor(e);for(;o;)r.push({chunkIndex:o.value.chunkIndex,leafHash:o.value.leafHash}),o=await o.continue();return await a.done,t.close(),r}catch(e){return console.error(e),[]}}(...t.args);break;case"assembleToOPFS":e=await le(...t.args);break;case"getDBAllChunksCount":e=await async function(e,t){try{const a=await A(),n=a.transaction(["chunks"],"readonly"),r=n.objectStore("chunks"),o=r.index("hash"),i=r.index("merkleRoot");if(t?.length===2*x){const r=await o.count(t);if(0===r){if(e?.length===2*x){const t=await i.count(e);return await n.done,a.close(),t}return await n.done,a.close(),0}return await n.done,a.close(),r}if(e?.length===2*x){const t=await i.count(e);return await n.done,a.close(),t}return await n.done,a.close(),0}catch{return 0}}(...t.args);break;case"setDBChunk":await async function(e){const t=await A();try{await t.add("chunks",e)}finally{t.close()}}(...t.args),e=void 0;break;case"storeReceiveChunk":{const a=t.args[0];e=await ge(a.merkleRoot,()=>async function(e){return pe(e)}(a));break}case"getReceiveFile":{const a=t.args;e=await ge(a[0],()=>ke(...a));break}case"closeReceiveFile":await ge(t.args[0],()=>be(...t.args)),e=void 0;break;case"deleteReceiveTransfer":await ge(t.args[0],()=>async function(e){if(e.length!==2*x||!/^[0-9a-f]+$/.test(e))throw new Error("invalid receive transfer Merkle root");await be(e);const t=await A();try{const a=t.transaction(["chunks","messageData","uniqueRoom"],"readwrite"),n=a.objectStore("chunks"),r=a.objectStore("messageData"),o=a.objectStore("uniqueRoom"),i=await n.index("merkleRoot").getAllKeys(e);for(const e of i)await n.delete(e);const s=await r.index("merkleRoot").getAllKeys(e);let c="";for(const e of s){const t=await r.get(e);!c&&t&&(c=t.roomId),await r.delete(e)}if(c){const e=await o.index("roomId").get(c);if(e){const t=r.index("roomId"),a=await t.count(c),n=await t.openCursor(IDBKeyRange.only(c),"prev");await o.put({...e,messageCount:a,updatedAt:n?.value.timestamp??Date.now(),lastMessageMerkleRoot:n?.value.merkleRoot??""})}}await a.done}finally{t.close()}await de(e)}(...t.args)),e=void 0;break;case"getDBAllNewChunks":e=await async function(e){const{transferId:t,merkleRootHex:a,hashHex:n}=e,r=t?"transferId":a?"merkleRoot":n?"hash":void 0,o=t??a??n;if(!r||!o)return[];const i=await A();try{return await i.getAllFromIndex("newChunks",r,o)}finally{i.close()}}(...t.args);break;case"getDBAllNewChunksCount":e=await async function(e){const t=await A();try{return await t.countFromIndex("newChunks","transferId",e)}finally{t.close()}}(...t.args);break;case"setDBNewChunk":await async function(e){const t=await A();try{await t.put("newChunks",e)}finally{t.close()}}(...t.args),e=void 0;break;case"setDBSendQueue":await async function(e){const t=await A();await t.put("sendQueue",e),t.close()}(...t.args),e=void 0;break;case"countDBSendQueue":e=await async function(e,t){const a=await A(),n=a.transaction("sendQueue","readonly").objectStore("sendQueue").index("labelPeer"),r=IDBKeyRange.only([e,t]),o=await n.count(r);return a.close(),o}(...t.args);break;case"deleteDBChunk":await async function(e,t){try{const a=await A(),n=a.transaction("chunks","readwrite"),r=n.objectStore("chunks");if(void 0!==t){const a=IDBKeyRange.only([e,t]);await r.delete(a)}else{const t=r.index("hash"),a=await t.getAllKeys(e),n=a.length;if(0===n){const t=r.index("merkleRoot"),a=await t.getAllKeys(e),n=a.length;for(let e=0;e<n;e++)await r.delete(a[e])}else for(let e=0;e<n;e++)await r.delete(a[e])}await n.done,a.close()}catch{}}(...t.args),e=void 0;break;case"deleteDBNewChunk":await async function(e){const{transferId:t,merkleRootHex:a,hashHex:n,chunkIndex:r}=e,o=await A();try{const e=o.transaction("newChunks","readwrite"),i=e.objectStore("newChunks");if(t&&void 0!==r)await i.delete([t,r]);else if(t){const e=await i.index("transferId").getAllKeys(t);for(const t of e)await i.delete(t)}else if(a){const e=await i.index("merkleRoot").getAllKeys(a);for(const t of e)await i.delete(t)}else if(n){const e=await i.index("hash").getAllKeys(n);for(const t of e)await i.delete(t)}await e.done}catch(e){throw console.error(e),e}finally{o.close()}}(...t.args),e=void 0;break;case"deleteDBMessageData":await async function(e){try{const t=await A(),a=t.transaction(["messageData","uniqueRoom"],"readwrite"),n=a.objectStore("messageData"),r=a.objectStore("uniqueRoom"),o=n.index("merkleRoot"),i=await o.getAllKeys(e),s=i.length;let c="";for(let e=0;e<s;e++){if(0===e||0===c.length){const t=await n.get(i[e]);c=t?.roomId??""}await n.delete(i[e])}if(c.length>0){const e=r.index("roomId"),t=await e.get(c);if(t){const e=n.index("roomId"),a=await e.getAllKeys(c),o=a.length,i=o>0?await n.get(a[o-1]):void 0;await r.put({...t,messageCount:t.messageCount>0?t.messageCount-1:0,updatedAt:i?.timestamp??Date.now(),lastMessageMerkleRoot:i?.merkleRoot??""})}}await a.done,t.close(),await ge(e,async()=>{await be(e),await de(e)})}catch{}}(...t.args),e=void 0;break;case"deleteDBUniqueRoom":await async function(e){try{const t=await A(),a=t.transaction(["uniqueRoom","ratchetSessions"],"readwrite"),n=a.objectStore("uniqueRoom"),r=n.index("roomId"),o=await r.getKey(e);void 0!==o&&await n.delete(o);const i=a.objectStore("ratchetSessions"),s=await i.index("roomId").getAllKeys(e);for(const t of s)await i.delete(t),Array.isArray(t)&&"string"==typeof t[1]&&(Se.forget(e,t[1]),Ie.delete(Ke(e,t[1])));await a.done,t.close()}catch{}}(...t.args),e=void 0;break;case"deleteDBSendQueue":await async function(e,t,a){try{const n=await A();if(void 0!==a)await n.delete("sendQueue",[a,e,t]);else{const a=IDBKeyRange.only([e,t]);await n.delete("sendQueue",a)}n.close()}catch{}}(...t.args),e=void 0;break;case"getRatchetSession":e=await Ce(...t.args);break;case"setRatchetSession":await Re(...t.args),e=void 0;break;case"deleteRatchetSession":await async function(e,t){await ve(e,t,async()=>{try{const a=await A();await a.delete("ratchetSessions",[e,t]),a.close(),Se.forget(e,t),Ie.delete(Ke(e,t))}catch(e){console.error(e)}})}(...t.args),e=void 0;break;case"getPinAttemptState":e=await async function(e,t){De(e,t);const a=await A();try{return await a.get("meta",Ee(e,t))}finally{a.close()}}(...t.args);break;case"incrementPinAttemptState":e=await async function(e,t,a,n,r,o){if(De(e,t),!Number.isSafeInteger(a)||!Number.isSafeInteger(n)||!Number.isSafeInteger(r)||!Number.isSafeInteger(o)||a<0||n<1||r<1||o<r)throw new Error("Invalid PIN backoff parameters");const i=await A();try{const s=i.transaction("meta","readwrite"),c=s.objectStore("meta"),l=Ee(e,t),d=await c.get(l),u=(d?.failures??0)+1,h={failures:u,retryAfter:a+(u<n?0:Math.min(r*2**(u-n),o))};return await c.put(h,l),await s.done,h}finally{i.close()}}(...t.args);break;case"deletePinAttemptState":await async function(e,t){De(e,t);const a=await A();try{if(void 0!==t)return void await a.delete("meta",Ee(e,t));const n=a.transaction("meta","readwrite"),r=n.objectStore("meta"),o=Pe(e),i=await r.getAllKeys();await Promise.all(i.filter(e=>"string"==typeof e&&e.startsWith(o)).map(e=>r.delete(e))),await r.delete(`pinAttempts:${e}`),await n.done}finally{a.close()}}(...t.args),e=void 0;break;case"getIdentityX25519":e=await async function(){const e=await A();let t;try{t=await e.get("meta",Le)}finally{e.close()}if(!t)return;if(32!==t.pub.byteLength||64!==t.crossSig.byteLength)throw new Error("Stored X25519 identity has invalid public fields");const a=await ae(),n=await re(a,t.wrappedSecret);if(32!==n.byteLength)throw new Error("Stored X25519 identity has an invalid secret length");return{pub:t.pub,secret:n,crossSig:t.crossSig}}();break;case"setIdentityX25519":await async function(e){if(32!==e.pub.byteLength||32!==e.secret.byteLength||64!==e.crossSig.byteLength)throw new Error("X25519 identity has invalid field lengths");const t=await ae(),a=await ne(t,e.secret),n={pub:e.pub,wrappedSecret:a,crossSig:e.crossSig},r=await A();try{await r.put("meta",n,Le)}finally{r.close()}}(...t.args),e=void 0;break;case"deleteIdentityX25519":await async function(){const e=await A();try{const t=e.transaction(["meta","ratchetSessions"],"readwrite");await t.objectStore("meta").delete(Le),await t.objectStore("ratchetSessions").clear(),await t.done}finally{e.close()}Se.clear(),Ie.clear()}(),e=void 0;break;case"getIdentityEd25519":e=await async function(){const e=await A();let t;try{t=await e.get("meta",Be)}finally{e.close()}if(!t)return;if(32!==t.pub.byteLength)throw new Error("Stored Ed25519 public key has an invalid length");const a=await ae(),n=await re(a,t.wrappedSecret);if(64!==n.byteLength)throw new Error("Stored Ed25519 secret key has an invalid length");return{pub:t.pub,secret:n}}();break;case"setIdentityEd25519":await async function(e){if(32!==e.pub.byteLength||64!==e.secret.byteLength)throw new Error("Ed25519 identity has invalid key lengths");const t=await ae(),a=await ne(t,e.secret),n={pub:e.pub,wrappedSecret:a},r=await A();try{await r.put("meta",n,Be)}finally{r.close()}}(...t.args),e=void 0;break;case"deleteIdentityEd25519":await async function(){const e=await A();try{const t=e.transaction(["meta","ratchetSessions"],"readwrite");await t.objectStore("meta").delete(Be),await t.objectStore("ratchetSessions").clear(),await t.done}finally{e.close()}Se.clear(),Ie.clear()}(),e=void 0;break;case"deleteDB":await je(),e=void 0;break;default:return void postMessage({id:a,error:"Method not found"})}postMessage({id:a,result:e})}catch(e){postMessage({id:a,error:String(e)})}};
|
|
1
|
+
const e=(e,t)=>t.some(t=>e instanceof t);let t,n;const a=new WeakMap,r=new WeakMap,o=new WeakMap;let i={get(e,t,n){if(e instanceof IDBTransaction){if("done"===t)return a.get(e);if("store"===t)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return d(e[t])},set:(e,t,n)=>(e[t]=n,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function s(e){i=e(i)}function c(e){return(n||(n=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(e)?function(...t){return e.apply(u(this),t),d(this.request)}:function(...t){return d(e.apply(u(this),t))}}function l(n){return"function"==typeof n?c(n):(n instanceof IDBTransaction&&function(e){if(a.has(e))return;const t=new Promise((t,n)=>{const a=()=>{e.removeEventListener("complete",r),e.removeEventListener("error",o),e.removeEventListener("abort",o)},r=()=>{t(),a()},o=()=>{n(e.error||new DOMException("AbortError","AbortError")),a()};e.addEventListener("complete",r),e.addEventListener("error",o),e.addEventListener("abort",o)});a.set(e,t)}(n),e(n,t||(t=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]))?new Proxy(n,i):n)}function d(e){if(e instanceof IDBRequest)return function(e){const t=new Promise((t,n)=>{const a=()=>{e.removeEventListener("success",r),e.removeEventListener("error",o)},r=()=>{t(d(e.result)),a()},o=()=>{n(e.error),a()};e.addEventListener("success",r),e.addEventListener("error",o)});return o.set(t,e),t}(e);if(r.has(e))return r.get(e);const t=l(e);return t!==e&&(r.set(e,t),o.set(t,e)),t}const u=e=>o.get(e);const y=["get","getKey","getAll","getAllKeys","count"],w=["put","add","delete","clear"],h=new Map;function f(e,t){if(!(e instanceof IDBDatabase)||t in e||"string"!=typeof t)return;if(h.get(t))return h.get(t);const n=t.replace(/FromIndex$/,""),a=t!==n,r=w.includes(n);if(!(n in(a?IDBIndex:IDBObjectStore).prototype)||!r&&!y.includes(n))return;const o=async function(e,...t){const o=this.transaction(e,r?"readwrite":"readonly");let i=o.store;return a&&(i=i.index(t.shift())),(await Promise.all([i[n](...t),r&&o.done]))[0]};return h.set(t,o),o}s(e=>({...e,get:(t,n,a)=>f(t,n)||e.get(t,n,a),has:(t,n)=>!!f(t,n)||e.has(t,n)}));const g=["continue","continuePrimaryKey","advance"],m={},p=new WeakMap,b=new WeakMap,k={get(e,t){if(!g.includes(t))return e[t];let n=m[t];return n||(n=m[t]=function(...e){p.set(this,b.get(this)[t](...e))}),n}};async function*S(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;const n=new Proxy(t,k);for(b.set(n,t),o.set(n,u(t));t;)yield n,t=await(p.get(n)||t.continue()),p.delete(n)}function I(t,n){return n===Symbol.asyncIterator&&e(t,[IDBIndex,IDBObjectStore,IDBCursor])||"iterate"===n&&e(t,[IDBIndex,IDBObjectStore])}s(e=>({...e,get:(t,n,a)=>I(t,n)?S:e.get(t,n,a),has:(t,n)=>I(t,n)||e.has(t,n)}));const v=64*Uint8Array.BYTES_PER_ELEMENT,x=65405-(9+v+8+8+256+8+8+8+(4+48*(v+1)));(new TextEncoder).encode("p2party-x25519-idsig-v1");const K=["hybrid-3dh-mlkem512-cpace21-v4","hybrid-3dh-mlkem768-cpace21-v4","hybrid-3dh-mlkem1024-cpace21-v4"],A="p2party-reassembled",R="p2party";async function C(){return function(e,t,{blocked:n,upgrade:a,blocking:r,terminated:o}={}){const i=indexedDB.open(e,t),s=d(i);return a&&i.addEventListener("upgradeneeded",e=>{a(d(i.result),e.oldVersion,e.newVersion,d(i.transaction),e)}),n&&i.addEventListener("blocked",e=>n(e.oldVersion,e.newVersion,e)),s.then(e=>{o&&e.addEventListener("close",()=>o()),r&&e.addEventListener("versionchange",e=>r(e.oldVersion,e.newVersion,e))}).catch(()=>{}),s}(R,19,{upgrade(e,t,n,a){if(e.objectStoreNames.contains("addressBook")){const e=a.objectStore("addressBook");e.indexNames.contains("username")||e.createIndex("username","username",{unique:!1}),e.indexNames.contains("peerId")||e.createIndex("peerId","peerId",{unique:!0}),e.indexNames.contains("peerPublicKey")||e.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}else{const t=e.createObjectStore("addressBook",{keyPath:["peerId"]});t.createIndex("username","username",{unique:!1}),t.createIndex("peerId","peerId",{unique:!0}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}if(e.objectStoreNames.contains("blacklist")){const e=a.objectStore("blacklist");e.indexNames.contains("username")||e.createIndex("username","username",{unique:!1}),e.indexNames.contains("peerId")||e.createIndex("peerId","peerId",{unique:!0}),e.indexNames.contains("peerPublicKey")||e.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}else{const t=e.createObjectStore("blacklist",{keyPath:["peerId"]});t.createIndex("username","username",{unique:!1}),t.createIndex("peerId","peerId",{unique:!0}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!0})}if(e.objectStoreNames.contains("uniqueRoom")){const e=a.objectStore("uniqueRoom");e.indexNames.contains("roomUrl")||e.createIndex("roomUrl","roomUrl",{unique:!0}),e.indexNames.contains("roomId")||e.createIndex("roomId","roomId",{unique:!0})}else{const t=e.createObjectStore("uniqueRoom",{keyPath:["roomId"]});t.createIndex("roomUrl","roomUrl",{unique:!0}),t.createIndex("roomId","roomId",{unique:!0})}if(e.objectStoreNames.contains("messageData")){const e=a.objectStore("messageData");e.indexNames.contains("roomId")||e.createIndex("roomId","roomId",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1}),e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!0}),e.indexNames.contains("fromPeerId")||e.createIndex("fromPeerId","fromPeerId",{unique:!1})}else{const t=e.createObjectStore("messageData",{keyPath:["timestamp","roomId","merkleRoot"]});t.createIndex("roomId","roomId",{unique:!1}),t.createIndex("hash","hash",{unique:!1}),t.createIndex("merkleRoot","merkleRoot",{unique:!0}),t.createIndex("fromPeerId","fromPeerId",{unique:!1})}if(e.objectStoreNames.contains("chunks")){const e=a.objectStore("chunks");e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1})}else{const t=e.createObjectStore("chunks",{keyPath:["merkleRoot","chunkIndex"]});t.createIndex("merkleRoot","merkleRoot",{unique:!1}),t.createIndex("hash","hash",{unique:!1})}if(t<18&&e.objectStoreNames.contains("newChunks")&&e.deleteObjectStore("newChunks"),e.objectStoreNames.contains("newChunks")){const e=a.objectStore("newChunks");e.indexNames.contains("transferId")||e.createIndex("transferId","transferId",{unique:!1}),e.indexNames.contains("hash")||e.createIndex("hash","hash",{unique:!1}),e.indexNames.contains("merkleRoot")||e.createIndex("merkleRoot","merkleRoot",{unique:!1}),e.indexNames.contains("receiptScope")||e.createIndex("receiptScope",["merkleRoot","receiptToken"],{unique:!1})}else{const t=e.createObjectStore("newChunks",{keyPath:["transferId","chunkIndex"]});t.createIndex("transferId","transferId",{unique:!1}),t.createIndex("hash","hash",{unique:!1}),t.createIndex("merkleRoot","merkleRoot",{unique:!1}),t.createIndex("receiptScope",["merkleRoot","receiptToken"],{unique:!1})}if(e.objectStoreNames.contains("sendQueue")){const e=a.objectStore("sendQueue");e.indexNames.contains("labelPeer")||e.createIndex("labelPeer",["label","toPeerId"],{unique:!1})}else{e.createObjectStore("sendQueue",{keyPath:["position","label","toPeerId"]}).createIndex("labelPeer",["label","toPeerId"],{unique:!1})}if(!e.objectStoreNames.contains("ratchetSessions")){const t=e.createObjectStore("ratchetSessions",{keyPath:["roomId","peerPublicKey"]});t.createIndex("peerId","peerId",{unique:!1}),t.createIndex("peerPublicKey","peerPublicKey",{unique:!1}),t.createIndex("roomId","roomId",{unique:!1})}t>0&&t<19&&(a.objectStore("ratchetSessions").clear(),a.objectStore("sendQueue").clear()),e.objectStoreNames.contains("meta")||e.createObjectStore("meta")}})}const P="ratchetWrapKey",D=12,E=16,L=16,B=Uint8Array.of(80,50,82,87),j=B.byteLength+1+L+D,M=262144,U=(new TextEncoder).encode("p2party-edge-crypto-at-rest-aad-v2");var N;!function(e){e[e.RootKey=1]="RootKey",e[e.SendingChainKey=2]="SendingChainKey",e[e.ReceivingChainKey=3]="ReceivingChainKey",e[e.DhSelfSecret=4]="DhSelfSecret",e[e.SkippedMessageKey=5]="SkippedMessageKey",e[e.EdgeCryptoState=6]="EdgeCryptoState"}(N||(N={}));const q=e=>e.slice(0),$=(e,t,n)=>{if(!(e instanceof ArrayBuffer))throw new Error(`${t} must be an ArrayBuffer`);if(void 0!==n&&e.byteLength!==n)throw new Error(`${t} must be ${String(n)} bytes`);return e},T=(e,t)=>{if("number"!=typeof e||!Number.isSafeInteger(e)||e<0)throw new Error(`${t} must be a safe unsigned integer`);return e},z=(e,t)=>{if("string"!=typeof e||0===e.length)throw new Error(`${t} must be a non-empty string`);const n=(new TextEncoder).encode(e);if(n.byteLength>1048576)throw new Error(`${t} is too long`);if((new TextDecoder).decode(n)!==e)throw new Error(`${t} is not canonical Unicode`);return e},F=e=>{if(!Number.isInteger(e)||e<0||e>4294967295)throw new Error("ratchet wrap: value exceeds uint32");const t=new Uint8Array(4);return new DataView(t.buffer).setUint32(0,e,!1),t},O=e=>{T(e,"ratchet wrap counter");const t=new Uint8Array(8);return new DataView(t.buffer).setBigUint64(0,BigInt(e),!1),t},H=e=>{const t=e.reduce((e,t)=>e+t.byteLength,0),n=new Uint8Array(t);let a=0;for(const t of e)n.set(t,a),a+=t.byteLength;return n},Q=e=>H([F(e.byteLength),e]),V=e=>Q((new TextEncoder).encode(e)),W=e=>{const t=e.rootSuite;if("string"!=typeof(n=t)||!K.includes(n))throw new Error("Unsupported ratchet root suite");var n;if(z(e.roomId,"ratchet roomId"),z(e.peerPublicKey,"ratchet peerPublicKey"),!/^[0-9a-f]{64}$/.test(e.peerPublicKey))throw new Error("ratchet peerPublicKey must be 32-byte lowercase hex");if(z(e.peerId,"ratchet peerId"),$(e.dhSelfPub,"ratchet dhSelfPub",32),null!==e.dhRemotePub&&$(e.dhRemotePub,"ratchet dhRemotePub",32),T(e.Ns,"ratchet Ns"),T(e.Nr,"ratchet Nr"),T(e.PN,"ratchet PN"),T(e.updatedAt,"ratchet updatedAt"),!Array.isArray(e.skippedMessageKeys)||e.skippedMessageKeys.length>2e3)throw new Error("ratchet skipped-message-key count is invalid");if(e.skippedMessageKeys.forEach((e,t)=>{$(e.dhPub,`ratchet skipped[${String(t)}].dhPub`,32),T(e.n,`ratchet skipped[${String(t)}].n`)}),null!==e.edgeCryptoState){const t=$(e.edgeCryptoState,"ratchet edge crypto state");if(t.byteLength<1||t.byteLength>M+j+E)throw new Error("ratchet edge crypto state length is invalid")}},X=e=>({rootSuite:e.rootSuite,roomId:e.roomId,peerPublicKey:e.peerPublicKey,peerId:e.peerId,rootKey:q($(e.rootKey,"ratchet rootKey")),sendingChainKey:null===e.sendingChainKey?null:q($(e.sendingChainKey,"ratchet sendingChainKey")),receivingChainKey:null===e.receivingChainKey?null:q($(e.receivingChainKey,"ratchet receivingChainKey")),dhSelfPub:q($(e.dhSelfPub,"ratchet dhSelfPub")),dhSelfSec:q($(e.dhSelfSec,"ratchet dhSelfSec")),dhRemotePub:null===e.dhRemotePub?null:q($(e.dhRemotePub,"ratchet dhRemotePub")),Ns:e.Ns,Nr:e.Nr,PN:e.PN,skippedMessageKeys:(()=>{if(!Array.isArray(e.skippedMessageKeys))throw new Error("ratchet skippedMessageKeys must be an array");return e.skippedMessageKeys.map((e,t)=>({dhPub:q($(e.dhPub,`ratchet skipped[${String(t)}].dhPub`)),n:e.n,messageKey:q($(e.messageKey,`ratchet skipped[${String(t)}].messageKey`))}))})(),edgeCryptoState:null===e.edgeCryptoState?null:q($(e.edgeCryptoState,"ratchet edge crypto state")),updatedAt:e.updatedAt}),G=e=>{new Uint8Array(e.rootKey).fill(0),e.sendingChainKey&&new Uint8Array(e.sendingChainKey).fill(0),e.receivingChainKey&&new Uint8Array(e.receivingChainKey).fill(0),new Uint8Array(e.dhSelfSec).fill(0);for(const t of e.skippedMessageKeys)new Uint8Array(t.messageKey).fill(0);e.edgeCryptoState&&new Uint8Array(e.edgeCryptoState).fill(0)},_=(e,t,n,a=0)=>{if(W(e),n.byteLength!==L)throw new Error("ratchet wrap: invalid record ID");const r=(null===e.sendingChainKey?0:1)|(null===e.receivingChainKey?0:2)|(null===e.dhRemotePub?0:4)|(null===e.edgeCryptoState?0:8),o=[Q(U),Uint8Array.of(2,t),Q(n),F(a),V(e.rootSuite),V(e.roomId),V(e.peerPublicKey),V(e.peerId),Q(new Uint8Array(e.dhSelfPub)),Uint8Array.of(r)];null!==e.dhRemotePub&&o.push(Q(new Uint8Array(e.dhRemotePub))),o.push(O(e.Ns),O(e.Nr),O(e.PN),O(e.updatedAt),F(e.skippedMessageKeys.length));for(const t of e.skippedMessageKeys)o.push(Q(new Uint8Array(t.dhPub)),O(t.n));return H(o)},Y=(e,t=32)=>{const n=new Uint8Array(e),a=j+1+E,r=j+M+E;if(null===t&&(n.byteLength<a||n.byteLength>r)||null!==t&&n.byteLength!==j+t+E)throw new Error("Unsupported or malformed ratchet wrap envelope");for(let e=0;e<B.byteLength;e++)if(n[e]!==B[e])throw new Error("Unsupported or malformed ratchet wrap envelope");if(2!==n[B.byteLength])throw new Error("Unsupported or malformed ratchet wrap envelope");return Uint8Array.from(n.subarray(B.byteLength+1,B.byteLength+1+L))},J=async(e,t,n,a,r=32)=>{if($(t,"ratchet secret",null===r?void 0:r),null===r&&(t.byteLength<1||t.byteLength>M))throw new Error("ratchet variable secret length is invalid");if(a.byteLength!==L)throw new Error("ratchet wrap: invalid record ID");const o=crypto.getRandomValues(new Uint8Array(D)),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:o,additionalData:n,tagLength:128},e,t),s=new Uint8Array(j+i.byteLength);return s.set(B,0),s[B.byteLength]=2,s.set(a,B.byteLength+1),s.set(o,B.byteLength+1+L),s.set(new Uint8Array(i),j),s.buffer},Z=async(e,t,n,a,r=32)=>{$(t,"ratchet wrapped secret");const o=Y(t,r);if(!te(o,a))throw new Error("Ratchet wrap record ID mismatch");const i=new Uint8Array(t),s=await crypto.subtle.decrypt({name:"AES-GCM",iv:i.subarray(B.byteLength+1+L,j),additionalData:n,tagLength:128},e,i.subarray(j));if(null!==r&&s.byteLength!==r||null===r&&(s.byteLength<1||s.byteLength>M))throw new Uint8Array(s).fill(0),new Error("Ratchet wrapped secret has an invalid plaintext length");return s},ee=async e=>{W(e);const t=Y(e.rootKey),n=[e.sendingChainKey,e.receivingChainKey,e.dhSelfSec,...e.skippedMessageKeys.map(e=>e.messageKey)];for(const e of n)if(null!==e&&!te(Y(e),t))throw new Error("Ratchet wrap record ID mismatch");if(null!==e.edgeCryptoState&&!te(Y(e.edgeCryptoState,null),t))throw new Error("Ratchet wrap record ID mismatch");const a=[_(e,N.RootKey,t),Q(new Uint8Array(e.rootKey)),Q(e.sendingChainKey?new Uint8Array(e.sendingChainKey):new Uint8Array),Q(e.receivingChainKey?new Uint8Array(e.receivingChainKey):new Uint8Array),Q(new Uint8Array(e.dhSelfSec))];for(const t of e.skippedMessageKeys)a.push(Q(new Uint8Array(t.messageKey)));a.push(Q(e.edgeCryptoState?new Uint8Array(e.edgeCryptoState):new Uint8Array));const r=H(a);try{return new Uint8Array(await crypto.subtle.digest("SHA-256",r))}finally{r.fill(0)}},te=(e,t)=>{if(e.byteLength!==t.byteLength)return!1;let n=0;for(let a=0;a<e.byteLength;a++)n|=e[a]^t[a];return 0===n};async function ne(){const e=await C();try{const t=await e.get("meta",P);if(t)return t;const n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),a=e.transaction("meta","readwrite"),r=a.objectStore("meta"),o=await r.get(P);return o?(await a.done,o):(await r.put(n,P),await a.done,n)}finally{e.close()}}async function ae(e,t){const n=crypto.getRandomValues(new Uint8Array(D)),a=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},e,t),r=new Uint8Array(D+a.byteLength);return r.set(n,0),r.set(new Uint8Array(a),D),r.buffer}async function re(e,t){const n=new Uint8Array(t);if(n.byteLength<28)throw new Error("Malformed wrapped secret");return crypto.subtle.decrypt({name:"AES-GCM",iv:n.subarray(0,D)},e,n.subarray(D))}const oe=()=>globalThis.navigator?.storage,ie=new Map,se=e=>e.createSyncAccessHandle,ce=(e,t,n,a)=>new File([e],t.length>0?t:n,{type:a});async function le(e,t,n,a){if(e.length!==2*v)return null;const r=ie.get(e);if(r)return r;const o=async function(e,t,n,a){try{const r=oe();if(!r||"function"!=typeof r.getDirectory)return console.warn("OPFS unavailable — reassembling file in memory"),null;const o=await r.getDirectory(),i=await o.getDirectoryHandle(A,{create:!0}),s=await i.getFileHandle(e,{create:!0}),c=await s.getFile();if(t>0&&c.size===t)return ce(c,n,e,a);const l=s.createSyncAccessHandle;if("function"!=typeof l)return console.warn("OPFS sync access handle unavailable — reassembling in memory"),null;const d=await l.call(s);let u;try{u=await C(),d.truncate(0);let t=0;const n=u.transaction("chunks","readonly"),a=n.objectStore("chunks").index("merkleRoot");let r=await a.openCursor(e);for(;r;){const e=new Uint8Array(r.value.data??new ArrayBuffer(0));let n=0;for(;n<e.length;){const a=d.write(e.subarray(n),{at:t+n});if(a<=0)throw new Error("OPFS short write");n+=a}t+=e.length,r=await r.continue()}await n.done,d.flush()}catch(t){try{d.close()}catch{}if(u)try{u.close()}catch{}throw await i.removeEntry(e).catch(()=>{}),t}return d.close(),u.close(),ce(await s.getFile(),n,e,a)}catch(e){return console.error(e),null}}(e,t,n,a);ie.set(e,o);try{return await o}finally{ie.delete(e)}}async function de(e){try{const t=oe();if(!t||"function"!=typeof t.getDirectory)return;const n=await t.getDirectory(),a=await n.getDirectoryHandle(A).catch(()=>null);a&&await a.removeEntry(e).catch(()=>{})}catch{}}const ue=new Map,ye=new Map,we=new Map,he=new Map;async function fe(e,t){const n=he.get(e)??Promise.resolve();let a;const r=new Promise(e=>{a=e}),o=n.then(()=>r);he.set(e,o),await n;try{return await t()}finally{a(),he.get(e)===o&&he.delete(e)}}async function ge(e,t){const n=ue.get(e);if(n)return n;const a=ye.get(e);if(a)return a;if(ue.size>=8)for(const t of ue.keys())if(t!==e){await be(t);break}const r=async function(e,t){const n=oe();if(!n||"function"!=typeof n.getDirectory)return null;const a=await n.getDirectory(),r=await a.getDirectoryHandle(A,{create:!0}),o=await r.getFileHandle(e,{create:!0}),i=se(o);if("function"!=typeof i)return null;const s=await i.call(o);try{return t>0&&s.getSize()!==t&&s.truncate(t),{access:s,uniformSize:0,uniformKnown:!1,maxRealLen:0,seenCount:0}}catch(e){try{s.close()}catch{}throw e}}(e,t).catch(e=>(console.error(e),null));ye.set(e,r);try{const t=await r;return t&&ue.set(e,t),t}finally{ye.delete(e)}}function me(e,t,n){const a=new Uint8Array(n);let r=0;for(;r<a.length;){const n=e.write(a.subarray(r),{at:t+r});if(n<=0)throw new Error("OPFS short write");r+=n}}async function pe(e){const{schemaVersion:t,roomId:n,fromPeerId:a,channelLabel:r,timestamp:o,merkleRoot:i,hash:s,filename:c,messageType:l,chunkIndex:d,mimeType:u,leafHash:y,realLen:w,totalSize:h,data:f,storage:g}=e;if(1!==t||!Number.isSafeInteger(l)||l<1||l>64||!Number.isSafeInteger(d)||d<0||!Number.isSafeInteger(w)||w<=0||w>x||!Number.isSafeInteger(h)||h<=0||h>10737418240||w>h||d>=h||f.byteLength!==w||!Number.isSafeInteger(o))throw new Error("invalid receive chunk bounds");const m=e=>{if(e&&(e.roomId!==n||e.fromPeerId!==a||e.hash!==s||e.totalSize!==h||e.messageType!==l||e.filename!==c||e.channelLabel!==r||e.timestamp!==o))throw new Error("receive chunk metadata conflicts with message")},p=e=>{const t=e.map(e=>({chunkIndex:e.chunkIndex,realLen:e.realLen??0})).sort((e,t)=>e.chunkIndex-t.chunkIndex);let n=0;for(let e=0;e<t.length;e++){const a=t[e];if(!Number.isSafeInteger(a.chunkIndex)||a.chunkIndex<0||!Number.isSafeInteger(a.realLen)||a.realLen<=0||a.realLen>x||a.realLen>h||a.chunkIndex>=h||e>0&&t[e-1].chunkIndex===a.chunkIndex)throw new Error("invalid receive chunk layout");if(n+=a.realLen,!Number.isSafeInteger(n)||n>h)throw new Error("receive chunks exceed declared total")}const a=t.find(e=>0===e.chunkIndex);let r;if(a)r=a.realLen;else if(t.length>=2){r=0;for(const e of t)r=Math.max(r,e.realLen)}if(void 0!==r){const e=Math.ceil(h/r),a=e-1,o=h-r*a;for(const n of t){if(n.chunkIndex>=e)throw new Error("receive chunk index exceeds canonical manifest");const t=n.chunkIndex===a?o:r;if(n.realLen!==t)throw new Error("receive chunk length conflicts with manifest")}const i=n===h&&t.length===e&&t.every((e,t)=>e.chunkIndex===t);if(n===h&&!i)throw new Error("receive completion has a chunk-index gap");return{savedSize:n,complete:i}}if(n===h)throw new Error("receive completion is missing chunk zero");return{savedSize:n,complete:!1}},b=await C();let k;try{const e=b.transaction(["chunks","messageData"],"readonly");k=await e.objectStore("chunks").count([i,d]);m(await e.objectStore("messageData").index("merkleRoot").get(i)),await e.done}finally{b.close()}if(k>0){const e=await C();try{const t=e.transaction(["chunks","messageData"],"readonly"),n=t.objectStore("chunks").index("merkleRoot"),a=[];let r=await n.openCursor(i);for(;r;)a.push(r.value),r=await r.continue();m(await t.objectStore("messageData").index("merkleRoot").get(i));const o=p(a);return await t.done,{stored:!1,...o}}finally{e.close()}}const S=await C();try{const e=S.transaction(["chunks","messageData"],"readonly"),t=[];let n=await e.objectStore("chunks").index("merkleRoot").openCursor(i);for(;n;)t.push(n.value),n=await n.continue();m(await e.objectStore("messageData").index("merkleRoot").get(i)),p([...t,{chunkIndex:d,realLen:w}]),await e.done}finally{S.close()}let I=!1;if("opfs"===g)try{I=await async function(e,t,n,a,r){const o=await ge(e,a);if(!o)return!1;if(o.maxRealLen=Math.max(o.maxRealLen,n),o.seenCount+=1,o.uniformKnown||(0===t?(n<a&&(o.uniformSize=n),o.uniformKnown=!0):o.seenCount>=2&&(o.uniformSize=o.maxRealLen,o.uniformKnown=!0)),!o.uniformKnown)return!1;const i=0===t?0:t*o.uniformSize;return me(o.access,i,r),o.access.flush(),!0}(i,d,w,h,f)}catch(e){console.error(e),I=!1}const v="opfs"===g&&I?{merkleRoot:i,hash:s,chunkIndex:d,mimeType:u,realLen:w,leafHash:y}:{merkleRoot:i,hash:s,chunkIndex:d,mimeType:u,realLen:w,leafHash:y,data:f},K=await C();try{const e=K.transaction(["chunks","messageData","uniqueRoom"],"readwrite"),t=e.objectStore("chunks"),u=await t.getKey([i,d]);let y=!1;const f=[];let g=await t.index("merkleRoot").openCursor(i);for(;g;)f.push(g.value),g=await g.continue();void 0===u&&(p([...f,{chunkIndex:d,realLen:w}]),await t.add(v),f.push(v),y=!0);const b=p(f);if(y){const t=e.objectStore("messageData"),d=await t.index("merkleRoot").get(i);m(d),await t.put({roomId:n,timestamp:d?.timestamp??o,merkleRoot:i,hash:s,fromPeerId:a,filename:c,messageType:l,savedSize:b.savedSize,totalSize:h,channelLabel:r});const u=e.objectStore("uniqueRoom"),y=await u.index("roomId").get(n);y&&y.lastMessageMerkleRoot!==i&&await u.put({...y,lastMessageMerkleRoot:i,messageCount:y.messageCount+1,updatedAt:o})}return await e.done,{stored:y,...b}}finally{K.close()}}async function be(e){const t=ye.get(e);if(t)try{await t}catch{}const n=ue.get(e);if(n){ue.delete(e);try{n.access.flush(),n.access.close()}catch{}}}async function ke(e,t,n,a){if(e.length!==2*v)return null;await be(e);const r=we.get(e);if(r)return r;const o=async function(e,t,n,a){try{const r=oe();if(!r||"function"!=typeof r.getDirectory)return null;const o=await r.getDirectory(),i=await o.getDirectoryHandle(A,{create:!0}),s=await i.getFileHandle(e,{create:!0}),c=await C(),l=[];let d=0;try{const t=c.transaction("chunks","readonly"),n=t.objectStore("chunks").index("merkleRoot");let a=await n.openCursor(e);for(;a;){const e=a.value;e.realLen&&e.realLen>d&&(d=e.realLen),e.data&&e.data.byteLength>0&&l.push(e),a=await a.continue()}await t.done}catch(e){return console.error(e),c.close(),null}const u=await s.getFile();if(t>0&&u.size===t&&0===l.length)return c.close(),ce(u,n,e,a);if(l.length>0&&(0===d||l.some(e=>null==e.realLen)))return c.close(),null;const y=se(s);if("function"!=typeof y)return c.close(),null;const w=await y.call(s);try{t>0&&w.getSize()!==t&&w.truncate(t);for(let e=0;e<l.length;e++){const t=l[e];me(w,0===t.chunkIndex?0:t.chunkIndex*d,t.data)}w.flush()}finally{w.close()}if(l.length>0)try{const e=c.transaction("chunks","readwrite"),t=e.objectStore("chunks");for(let e=0;e<l.length;e++){const n=l[e];await t.put({merkleRoot:n.merkleRoot,hash:n.hash,chunkIndex:n.chunkIndex,mimeType:n.mimeType,realLen:n.realLen,leafHash:n.leafHash})}await e.done}catch(e){console.error(e)}c.close();const h=await s.getFile();return t>0&&h.size!==t?null:ce(h,n,e,a)}catch(e){return console.error(e),null}}(e,t,n,a);we.set(e,o);try{return await o}finally{we.delete(e)}}const Se=new class{#e=new Map;#t(e,t){const n=(e=>`${String(e.roomId.length)}:${e.roomId}${String(e.peerPublicKey.length)}:${e.peerPublicKey}`)(e),a=this.#e.get(n);if(a&&e.updatedAt<a.updatedAt)throw t.fill(0),new Error("Ratchet persistence rollback detected");if(e.updatedAt===a?.updatedAt&&!te(t,a.fingerprint))throw t.fill(0),new Error("Ratchet persistence equivocation detected");!a||e.updatedAt>a.updatedAt?(a?.fingerprint.fill(0),this.#e.set(n,{updatedAt:e.updatedAt,fingerprint:t})):t.fill(0)}async acceptAuthenticated(e){const t=await ee(e);this.#t(e,t)}async rememberTrustedWrite(e){const t=await ee(e);this.#t(e,t)}forget(e,t){const n=`${String(e.length)}:${e}${String(t.length)}:${t}`;this.#e.get(n)?.fingerprint.fill(0),this.#e.delete(n)}clear(){for(const e of this.#e.values())e.fingerprint.fill(0);this.#e.clear()}},Ie=new Map,ve=new Map,xe=(e,t)=>`${String(e.length)}:${e}${String(t.length)}:${t}`,Ke=async(e,t,n)=>{const a=xe(e,t),r=ve.get(a)??Promise.resolve();let o;const i=new Promise(e=>{o=e}),s=r.catch(()=>{}).then(()=>i);ve.set(a,s),await r.catch(()=>{});try{return await n()}finally{o(),ve.get(a)===s&&ve.delete(a)}};async function Ae(e,t){return Ke(e,t,async()=>{try{const n=await C();let a;try{a=await n.get("ratchetSessions",[e,t])}finally{n.close()}if(!a)return;const r=await ne(),o=await async function(e,t,n){const a=X(e);W(a);const r=[];try{const e=Y(a.rootKey),o=await Z(t,a.rootKey,_(a,N.RootKey,e),e);r.push(o);const i=a.sendingChainKey?await Z(t,a.sendingChainKey,_(a,N.SendingChainKey,e),e):null;i&&r.push(i);const s=a.receivingChainKey?await Z(t,a.receivingChainKey,_(a,N.ReceivingChainKey,e),e):null;s&&r.push(s);const c=await Z(t,a.dhSelfSec,_(a,N.DhSelfSecret,e),e);r.push(c);const l=[];for(let n=0;n<a.skippedMessageKeys.length;n++){const o=a.skippedMessageKeys[n],i=await Z(t,o.messageKey,_(a,N.SkippedMessageKey,e,n),e);r.push(i),l.push({dhPub:o.dhPub,n:o.n,messageKey:i})}const d=a.edgeCryptoState?await Z(t,a.edgeCryptoState,_(a,N.EdgeCryptoState,e),e,null):null;return d&&r.push(d),n&&await n.acceptAuthenticated(a),{...a,rootKey:o,sendingChainKey:i,receivingChainKey:s,dhSelfSec:c,skippedMessageKeys:l,edgeCryptoState:d}}catch(e){for(const e of r)new Uint8Array(e).fill(0);throw e}finally{G(a)}}(a,r,Se),i=xe(e,t);return Ie.set(i,Math.max(Ie.get(i)??0,o.updatedAt)),o}catch(e){return void console.error(e)}})}async function Re(e){await Ke(e.roomId,e.peerPublicKey,async()=>{const t=xe(e.roomId,e.peerPublicKey),n=Ie.get(t)??0,a=Math.max(Date.now(),e.updatedAt),r=a>n?a:n+1;if(!Number.isSafeInteger(r))throw new Error("Ratchet persistence timestamp exhausted");const o={...e,updatedAt:r};try{const e=await ne(),n=await async function(e,t){const n=X(e);if(W(n),$(n.rootKey,"ratchet rootKey",32),n.sendingChainKey&&$(n.sendingChainKey,"ratchet sendingChainKey",32),n.receivingChainKey&&$(n.receivingChainKey,"ratchet receivingChainKey",32),$(n.dhSelfSec,"ratchet dhSelfSec",32),n.skippedMessageKeys.forEach((e,t)=>$(e.messageKey,`ratchet skipped[${String(t)}].messageKey`,32)),n.edgeCryptoState&&($(n.edgeCryptoState,"ratchet edge crypto state"),n.edgeCryptoState.byteLength<1||n.edgeCryptoState.byteLength>M))throw new Error("ratchet edge crypto state length is invalid");try{const e=crypto.getRandomValues(new Uint8Array(L)),a=await J(t,n.rootKey,_(n,N.RootKey,e),e),r=n.sendingChainKey?await J(t,n.sendingChainKey,_(n,N.SendingChainKey,e),e):null,o=n.receivingChainKey?await J(t,n.receivingChainKey,_(n,N.ReceivingChainKey,e),e):null,i=await J(t,n.dhSelfSec,_(n,N.DhSelfSecret,e),e),s=[];for(let a=0;a<n.skippedMessageKeys.length;a++){const r=n.skippedMessageKeys[a];s.push({dhPub:r.dhPub,n:r.n,messageKey:await J(t,r.messageKey,_(n,N.SkippedMessageKey,e,a),e)})}const c=n.edgeCryptoState?await J(t,n.edgeCryptoState,_(n,N.EdgeCryptoState,e),e,null):null;return{...n,rootKey:a,sendingChainKey:r,receivingChainKey:o,dhSelfSec:i,skippedMessageKeys:s,edgeCryptoState:c}}finally{G(n)}}(o,e),a=await C();try{await a.put("ratchetSessions",n)}finally{a.close()}await Se.rememberTrustedWrite(n),Ie.set(t,r)}finally{(e=>{new Uint8Array(e.rootKey).fill(0),e.sendingChainKey&&new Uint8Array(e.sendingChainKey).fill(0),e.receivingChainKey&&new Uint8Array(e.receivingChainKey).fill(0),new Uint8Array(e.dhSelfSec).fill(0);for(const t of e.skippedMessageKeys)new Uint8Array(t.messageKey).fill(0);e.edgeCryptoState&&new Uint8Array(e.edgeCryptoState).fill(0)})(o)}})}const Ce=/^[0-9a-f]{64}$/,Pe=(e,t)=>{if("string"!=typeof e||0===e.length)throw new Error("PIN attempt room ID must not be empty");if(void 0!==t&&!Ce.test(t))throw new Error("PIN attempt peer identity must be 32-byte lowercase hexadecimal")},De=e=>`pinAttempts:v2:${e.length}:${e}:`,Ee=(e,t)=>`${De(e)}${t}`;const Le="identityX25519";const Be="identityEd25519",je="identityEd25519Revision",Me=0xffffffffffffffffn,Ue=e=>e.slice(0),Ne=(e,t)=>{if(e.byteLength!==t.byteLength)return!1;const n=new Uint8Array(e),a=new Uint8Array(t);let r=0;for(let e=0;e<n.byteLength;e++)r|=n[e]^a[e];return 0===r},qe=(e,t)=>{if(32!==e.byteLength||64!==t.byteLength)return!1;const n=new Uint8Array(e),a=new Uint8Array(t);let r=0;for(let e=0;e<n.byteLength;e++)r|=n[e]^a[32+e];return 0===r},$e=(e,t,n)=>{if("object"!=typeof e||null===e||Array.isArray(e))throw new Error(`${n} is malformed`);const a=Reflect.ownKeys(e);if(a.length!==t.length||a.some(e=>"string"!=typeof e||!t.includes(e)))throw new Error(`${n} is malformed`);for(const a of t){const t=Object.getOwnPropertyDescriptor(e,a);if(!t||!("value"in t))throw new Error(`${n} is malformed`)}return e},Te=(e,t,n)=>{if("bigint"!=typeof e||e<0n||e>Me||!n&&0n===e)throw new Error(`${t} must be a ${n?"":"non-zero "}uint64`);return e},ze=e=>{const t=e.pub,n=e.wrappedSecret;if(!(t instanceof ArrayBuffer)||32!==t.byteLength)throw new Error("Stored Ed25519 public key has an invalid length");if(!(n instanceof ArrayBuffer)||92!==n.byteLength)throw new Error("Stored Ed25519 wrapped secret is malformed");return{pub:t,wrappedSecret:n}},Fe=e=>{if(void 0===e)return;if("object"==typeof e&&null!==e&&Object.prototype.hasOwnProperty.call(e,"version")){const t=$e(e,["version","pub","wrappedSecret","recordRevision"],"Stored Ed25519 identity");if(1!==t.version)throw new Error("Stored Ed25519 identity version is unsupported");return{kind:"current",...ze(t),recordRevision:Te(t.recordRevision,"Stored Ed25519 identity revision",!1)}}const t=$e(e,["pub","wrappedSecret"],"Legacy stored Ed25519 identity");return{kind:"legacy",...ze(t)}},Oe=e=>{if(void 0===e)return;const t=$e(e,["version","lastRevision"],"Ed25519 identity revision record");if(1!==t.version)throw new Error("Ed25519 identity revision version is unsupported");return{version:1,lastRevision:Te(t.lastRevision,"Ed25519 identity high-water revision",!0)}},He=(e,t)=>{if("legacy"===e?.kind&&void 0!==t)throw new Error("Stored Ed25519 identity revision state is inconsistent");if("current"===e?.kind&&(void 0===t||e.recordRevision!==t.lastRevision))throw new Error("Stored Ed25519 identity revision state is inconsistent")},Qe=(e,t)=>"legacy"===e?.kind?1n:t?.lastRevision??0n,Ve=e=>{if(e>=Me)throw new Error("Ed25519 identity revision overflow");return e+1n},We=async e=>{const t=e.transaction("meta","readonly"),n=t.objectStore("meta"),[a,r]=await Promise.all([n.get(Be),n.get(je)]);await t.done;const o=Fe(a),i=Oe(r);return He(o,i),{identity:o,revision:i}},Xe=async(e,t)=>{if("legacy"!==t.identity?.kind||void 0!==t.revision)throw new Error("Legacy Ed25519 migration state is inconsistent");const n=e.transaction("meta","readwrite"),a=n.objectStore("meta"),[r,o]=await Promise.all([a.get(Be),a.get(je)]),i={identity:Fe(r),revision:Oe(o)};return He(i.identity,i.revision),"legacy"===i.identity?.kind&&void 0===i.revision&&Ne(t.identity.pub,i.identity.pub)&&Ne(t.identity.wrappedSecret,i.identity.wrappedSecret)?(await Promise.all([a.put({version:1,pub:Ue(t.identity.pub),wrappedSecret:Ue(t.identity.wrappedSecret),recordRevision:1n},Be),a.put({version:1,lastRevision:1n},je)]),await n.done,!0):(await n.done,!1)};const Ge="identityDerivation",_e=e=>"random"===e||"mnemonic"===e,Ye=e=>{try{return Fe(e)?.pub}catch{return}};async function Je(){const e=await C();let t,n;try{const a=e.transaction("meta","readonly"),r=a.objectStore("meta");[t,n]=await Promise.all([r.get(Ge),r.get(Be)]),await a.done}finally{e.close()}const a=(e=>{if("object"!=typeof e||null===e||Array.isArray(e))return;const t=e;if(1!==t.version)return;if(!_e(t.derivation))return;const n=t.boundPub;return!(null===n||n instanceof ArrayBuffer)||n instanceof ArrayBuffer&&32!==n.byteLength?void 0:{version:1,derivation:t.derivation,boundPub:n}})(t);if(void 0===a)return null;if(null===a.boundPub)return a.derivation;const r=Ye(n);return void 0===r?null:Ne(a.boundPub,r)?a.derivation:null}async function Ze(){try{await async function(){const e=[...ye.values()];for(let t=0;t<e.length;t++)try{await e[t]}catch{}const t=[...ue.keys()];for(let e=0;e<t.length;e++)await be(t[e])}();const e=oe();if(e&&"function"==typeof e.getDirectory){const t=await e.getDirectory();await t.removeEntry(A,{recursive:!0}).catch(()=>{})}}catch{}await function(e,{blocked:t}={}){const n=indexedDB.deleteDatabase(e);return t&&n.addEventListener("blocked",e=>t(e.oldVersion,e)),d(n).then(()=>{})}(R,{blocked(){console.error("DB deletion BLOCKED")}}),Se.clear(),Ie.clear()}onmessage=async e=>{const t=e.data,{id:n,method:a}=t;try{let e;switch(a){case"getDBAddressBookEntry":e=await async function(e,t){if(!e&&!t)return;if(e&&e.length<10&&!t)return;if(t&&64!==t.length&&!e)return;const n=await C();try{const a=n.transaction("addressBook","readonly"),r=e?a.objectStore("addressBook").index("peerId"):a.objectStore("addressBook").index("peerPublicKey"),o=e?await r.get(e):await r.get(t??"");return await a.done,n.close(),o?{username:o.username,peerId:o.peerId,peerPublicKey:o.peerPublicKey}:void 0}catch{return void n.close()}}(...t.args);break;case"getAllDBAddressBookEntries":e=await async function(){try{const e=await C(),t=await e.getAll("addressBook");return e.close(),t}catch{return[]}}(...t.args);break;case"setDBAddressBookEntry":await async function(e,t,n){try{const a=await C(),r=a.transaction(["addressBook"],"readwrite"),o=r.objectStore("addressBook"),i=o.index("peerId"),s=o.index("peerPublicKey"),c=await i.get(t),l=await s.get(n);(!c&&!l||c&&!l||!c&&l)&&await o.put({username:e,peerId:t,peerPublicKey:n,dateAdded:Date.now()}),await r.done,a.close()}catch(e){console.error(e)}}(...t.args),e=void 0;break;case"deleteDBAddressBookEntry":e=await async function(e,t,n){const a=!e||0===e.length,r=!t||t.length<10,o=64!==n?.length;if(a&&r&&o)throw new Error("Cannot delete address book with no data");let i=t??"";try{const a=await C(),s=a.transaction("addressBook","readwrite"),c=s.objectStore("addressBook");if(r)if(o){const t=c.index("username"),n=await t.getKey(e??"");if(n){const a=await t.get(e??"");i=a?.peerId??"",await c.delete(n)}}else{const e=c.index("peerPublicKey"),t=await e.getKey(n);if(t){const a=await e.get(n);i=a?.peerId??"",await c.delete(t)}}else{const e=c.index("peerId"),n=await e.getKey(t);n&&await c.delete(n)}await s.done,a.close()}catch{}return i}(...t.args);break;case"getDBPeerIsBlacklisted":e=await async function(e,t){if(!e&&!t)return!1;if(e&&e.length<10&&!t)return!1;if(t&&64!==t.length&&!e)return!1;const n=await C();try{const a=n.transaction("blacklist","readonly"),r=e?a.objectStore("blacklist").index("peerId"):a.objectStore("blacklist").index("peerPublicKey"),o=e?await r.get(e):await r.get(t??"");return await a.done,!!o}finally{n.close()}}(...t.args);break;case"getAllDBBlacklisted":e=await async function(){const e=await C();try{const t=await e.getAll("blacklist");return e.close(),t}catch{return e.close(),[]}}(...t.args);break;case"setDBPeerInBlacklist":await async function(e,t){try{const n=await C(),a=n.transaction(["blacklist"],"readwrite"),r=a.objectStore("blacklist"),o=r.index("peerId"),i=r.index("peerPublicKey"),s=await o.get(e),c=await i.get(t);(!s&&!c||s&&!c||!s&&c)&&await r.put({peerId:e,peerPublicKey:t,dateAdded:Date.now()}),await a.done,n.close()}catch(e){throw console.error(e),e}}(...t.args),e=void 0;break;case"deleteDBPeerFromBlacklist":await async function(e,t){const n=!e||e.length<10;if(n&&64!==t?.length)throw new Error("Cannot delete blacklisted with no data");try{const a=await C(),r=a.transaction("blacklist","readwrite"),o=r.objectStore("blacklist");if(n){const e=o.index("peerPublicKey"),n=await e.getKey(t??"");n&&await o.delete(n)}else{const t=o.index("peerId"),n=await t.getKey(e);n&&await o.delete(n)}await r.done,a.close()}catch(e){console.error(e)}}(...t.args),e=void 0;break;case"getAllDBUniqueRooms":e=await async function(){const e=await C();try{const t=await e.getAll("uniqueRoom");return e.close(),t}catch{return e.close(),[]}}(...t.args);break;case"setDBUniqueRoom":await async function(e,t,n){if(32!==n.byteLength)throw new Error("Room policy must be the canonical 32-byte descriptor");const a=await C();try{const r=a.transaction(["uniqueRoom"],"readwrite"),o=r.objectStore("uniqueRoom"),i=o.index("roomUrl"),s=o.index("roomId"),c=await i.get(e),l=await s.get(t),d=c??l;if(d)await o.put({...d,roomId:t,roomUrl:e,roomPolicy:n,updatedAt:Date.now()});else{const a=Date.now();await o.put({roomId:t,roomUrl:e,roomPolicy:n,messageCount:0,lastMessageMerkleRoot:"",createdAt:a,updatedAt:a})}await r.done}finally{a.close()}}(...t.args),e=void 0;break;case"getDBMessageData":e=await async function(e,t){try{const n=await C(),a=n.transaction(["messageData"],"readonly"),r=a.objectStore("messageData"),o=r.index("merkleRoot"),i=r.index("hash");if(e?.length===2*v){const r=await o.get(e);if(r)return await a.done,n.close(),r;if(t?.length===2*v){const e=await i.get(t);return await a.done,n.close(),e}return await a.done,void n.close()}if(t?.length===2*v){const e=await i.get(t);return await a.done,n.close(),e}return await a.done,void n.close()}catch(e){return void console.error(e)}}(...t.args);break;case"getDBRoomMessageData":e=await async function(e){const t=await C(),n=await t.getAllFromIndex("messageData","roomId",e);t.close();const a=n.length,r=[];for(let t=0;t<a;t++)r.push({transferId:n[t].transferId,roomId:e,merkleRoot:n[t].merkleRoot,hash:n[t].hash,fromPeerId:n[t].fromPeerId,filename:n[t].filename,messageType:n[t].messageType,savedSize:n[t].savedSize,totalSize:n[t].totalSize,channelLabel:n[t].channelLabel,timestamp:n[t].timestamp});return r}(...t.args);break;case"getDBRoomStats":e=await async function(e){const t=await C(),n=await t.getAllFromIndex("messageData","roomId",e);t.close();const a=new Set;let r=0,o=0,i=0,s=0;for(const e of n){const t=void 0!==e.transferId,n=t?e.totalSize:e.savedSize;t?(r+=n,i+=1):(o+=n,s+=1,e.fromPeerId.length>0&&a.add(e.fromPeerId))}return{messageCount:n.length,messagesSent:i,messagesReceived:s,bytesSent:r,bytesReceived:o,bytesTotal:r+o,peerIds:[...a]}}(...t.args);break;case"setDBRoomMessageData":await async function(e,t,n,a,r,o,i,s,c,l,d){try{const u=await C(),y=u.transaction(["messageData","uniqueRoom"],"readwrite"),w=y.objectStore("messageData"),h=await w.index("merkleRoot").get(t),f=h?.savedSize??0;try{await w.put({transferId:d,roomId:e,timestamp:l,merkleRoot:t,hash:n,fromPeerId:a,filename:s,messageType:i,savedSize:f+r<=o?f+r:o,totalSize:o,channelLabel:c})}catch(e){throw await y.done,u.close(),e}const g=y.objectStore("uniqueRoom"),m=await g.index("roomId").get(e);if(m&&m.lastMessageMerkleRoot!==t)try{await g.put({...m,lastMessageMerkleRoot:t,messageCount:m.messageCount+1,updatedAt:l})}catch(e){throw await y.done,u.close(),e}await y.done,u.close()}catch(e){throw console.error(e),e}}(...t.args),e=void 0;break;case"getDBChunk":e=await async function(e,t){const n=await C(),a=await n.get("chunks",[e,t]);return n.close(),a?.data}(...t.args);break;case"existsDBChunk":e=await async function(e,t){const n=await C(),a=await n.count("chunks",[e,t]);return n.close(),a>0}(...t.args);break;case"getDBNewChunk":e=await async function(e,t){const n=await C();try{return await n.get("newChunks",[e,t])}finally{n.close()}}(...t.args);break;case"getDBNewChunkByReceipt":e=await async function(e,t){if(e.length!==2*v||t.length!==2*v)return;const n=await C();try{return await n.getFromIndex("newChunks","receiptScope",[e,t])}finally{n.close()}}(...t.args);break;case"existsDBNewChunk":e=await async function(e,t){const n=await C(),a=await n.count("newChunks",[e,t]);return n.close(),a>0}(...t.args);break;case"getDBSendQueue":e=await async function(e,t,n){const a=await C();if(n){const r=await a.get("sendQueue",[n,e,t]);return a.close(),r?[r]:[]}const r=a.transaction("sendQueue","readonly").objectStore("sendQueue").index("labelPeer"),o=IDBKeyRange.only([e,t]),i=await r.getAll(o);return a.close(),i}(...t.args);break;case"getDBAllChunks":e=await async function(e,t){try{const n=await C(),a=n.transaction("chunks","readonly"),r=a.objectStore("chunks"),o=r.index("hash"),i=r.index("merkleRoot");if(e?.length===2*v){const t=await i.getAll(e);return 0===t.length?(await a.done,n.close(),[]):(await a.done,n.close(),t)}if(t?.length===2*v){const e=await o.getAll(t);return await a.done,n.close(),e.length>0?e.filter(t=>t.merkleRoot===e[0].merkleRoot):e}return await a.done,n.close(),[]}catch(e){return console.error(e),[]}}(...t.args);break;case"getDBAllChunkLeafHashes":e=await async function(e){if(e.length!==2*v)return[];try{const t=await C(),n=t.transaction("chunks","readonly"),a=n.objectStore("chunks").index("merkleRoot"),r=[];let o=await a.openCursor(e);for(;o;)r.push({chunkIndex:o.value.chunkIndex,leafHash:o.value.leafHash}),o=await o.continue();return await n.done,t.close(),r}catch(e){return console.error(e),[]}}(...t.args);break;case"assembleToOPFS":e=await le(...t.args);break;case"getDBAllChunksCount":e=await async function(e,t){try{const n=await C(),a=n.transaction(["chunks"],"readonly"),r=a.objectStore("chunks"),o=r.index("hash"),i=r.index("merkleRoot");if(t?.length===2*v){const r=await o.count(t);if(0===r){if(e?.length===2*v){const t=await i.count(e);return await a.done,n.close(),t}return await a.done,n.close(),0}return await a.done,n.close(),r}if(e?.length===2*v){const t=await i.count(e);return await a.done,n.close(),t}return await a.done,n.close(),0}catch{return 0}}(...t.args);break;case"setDBChunk":await async function(e){const t=await C();try{await t.add("chunks",e)}finally{t.close()}}(...t.args),e=void 0;break;case"storeReceiveChunk":{const n=t.args[0];e=await fe(n.merkleRoot,()=>async function(e){return pe(e)}(n));break}case"getReceiveFile":{const n=t.args;e=await fe(n[0],()=>ke(...n));break}case"closeReceiveFile":await fe(t.args[0],()=>be(...t.args)),e=void 0;break;case"deleteReceiveTransfer":await fe(t.args[0],()=>async function(e){if(e.length!==2*v||!/^[0-9a-f]+$/.test(e))throw new Error("invalid receive transfer Merkle root");await be(e);const t=await C();try{const n=t.transaction(["chunks","messageData","uniqueRoom"],"readwrite"),a=n.objectStore("chunks"),r=n.objectStore("messageData"),o=n.objectStore("uniqueRoom"),i=await a.index("merkleRoot").getAllKeys(e);for(const e of i)await a.delete(e);const s=await r.index("merkleRoot").getAllKeys(e);let c="";for(const e of s){const t=await r.get(e);!c&&t&&(c=t.roomId),await r.delete(e)}if(c){const e=await o.index("roomId").get(c);if(e){const t=r.index("roomId"),n=await t.count(c),a=await t.openCursor(IDBKeyRange.only(c),"prev");await o.put({...e,messageCount:n,updatedAt:a?.value.timestamp??Date.now(),lastMessageMerkleRoot:a?.value.merkleRoot??""})}}await n.done}finally{t.close()}await de(e)}(...t.args)),e=void 0;break;case"getDBAllNewChunks":e=await async function(e){const{transferId:t,merkleRootHex:n,hashHex:a}=e,r=t?"transferId":n?"merkleRoot":a?"hash":void 0,o=t??n??a;if(!r||!o)return[];const i=await C();try{return await i.getAllFromIndex("newChunks",r,o)}finally{i.close()}}(...t.args);break;case"getDBAllNewChunksCount":e=await async function(e){const t=await C();try{return await t.countFromIndex("newChunks","transferId",e)}finally{t.close()}}(...t.args);break;case"setDBNewChunk":await async function(e){const t=await C();try{await t.put("newChunks",e)}finally{t.close()}}(...t.args),e=void 0;break;case"setDBSendQueue":await async function(e){const t=await C();await t.put("sendQueue",e),t.close()}(...t.args),e=void 0;break;case"countDBSendQueue":e=await async function(e,t){const n=await C(),a=n.transaction("sendQueue","readonly").objectStore("sendQueue").index("labelPeer"),r=IDBKeyRange.only([e,t]),o=await a.count(r);return n.close(),o}(...t.args);break;case"deleteDBChunk":await async function(e,t){try{const n=await C(),a=n.transaction("chunks","readwrite"),r=a.objectStore("chunks");if(void 0!==t){const n=IDBKeyRange.only([e,t]);await r.delete(n)}else{const t=r.index("hash"),n=await t.getAllKeys(e),a=n.length;if(0===a){const t=r.index("merkleRoot"),n=await t.getAllKeys(e),a=n.length;for(let e=0;e<a;e++)await r.delete(n[e])}else for(let e=0;e<a;e++)await r.delete(n[e])}await a.done,n.close()}catch{}}(...t.args),e=void 0;break;case"deleteDBNewChunk":await async function(e){const{transferId:t,merkleRootHex:n,hashHex:a,chunkIndex:r}=e,o=await C();try{const e=o.transaction("newChunks","readwrite"),i=e.objectStore("newChunks");if(t&&void 0!==r)await i.delete([t,r]);else if(t){const e=await i.index("transferId").getAllKeys(t);for(const t of e)await i.delete(t)}else if(n){const e=await i.index("merkleRoot").getAllKeys(n);for(const t of e)await i.delete(t)}else if(a){const e=await i.index("hash").getAllKeys(a);for(const t of e)await i.delete(t)}await e.done}catch(e){throw console.error(e),e}finally{o.close()}}(...t.args),e=void 0;break;case"deleteDBMessageData":await async function(e){try{const t=await C(),n=t.transaction(["messageData","uniqueRoom"],"readwrite"),a=n.objectStore("messageData"),r=n.objectStore("uniqueRoom"),o=a.index("merkleRoot"),i=await o.getAllKeys(e),s=i.length;let c="";for(let e=0;e<s;e++){if(0===e||0===c.length){const t=await a.get(i[e]);c=t?.roomId??""}await a.delete(i[e])}if(c.length>0){const e=r.index("roomId"),t=await e.get(c);if(t){const e=a.index("roomId"),n=await e.getAllKeys(c),o=n.length,i=o>0?await a.get(n[o-1]):void 0;await r.put({...t,messageCount:t.messageCount>0?t.messageCount-1:0,updatedAt:i?.timestamp??Date.now(),lastMessageMerkleRoot:i?.merkleRoot??""})}}await n.done,t.close(),await fe(e,async()=>{await be(e),await de(e)})}catch{}}(...t.args),e=void 0;break;case"deleteDBUniqueRoom":await async function(e){const t=await C();try{const n=t.transaction(["uniqueRoom","ratchetSessions"],"readwrite"),a=n.objectStore("uniqueRoom"),r=a.index("roomId"),o=await r.getKey(e);void 0!==o&&await a.delete(o);const i=n.objectStore("ratchetSessions"),s=await i.index("roomId").getAllKeys(e);for(const t of s)await i.delete(t),Array.isArray(t)&&"string"==typeof t[1]&&(Se.forget(e,t[1]),Ie.delete(xe(e,t[1])));await n.done}finally{t.close()}}(...t.args),e=void 0;break;case"deleteDBSendQueue":await async function(e,t,n){try{const a=await C();if(void 0!==n)await a.delete("sendQueue",[n,e,t]);else{const n=IDBKeyRange.only([e,t]);await a.delete("sendQueue",n)}a.close()}catch{}}(...t.args),e=void 0;break;case"getRatchetSession":e=await Ae(...t.args);break;case"setRatchetSession":await Re(...t.args),e=void 0;break;case"deleteRatchetSession":await async function(e,t){await Ke(e,t,async()=>{try{const n=await C();await n.delete("ratchetSessions",[e,t]),n.close(),Se.forget(e,t),Ie.delete(xe(e,t))}catch(e){console.error(e)}})}(...t.args),e=void 0;break;case"getPinAttemptState":e=await async function(e,t){Pe(e,t);const n=await C();try{return await n.get("meta",Ee(e,t))}finally{n.close()}}(...t.args);break;case"incrementPinAttemptState":e=await async function(e,t,n,a,r,o){if(Pe(e,t),!Number.isSafeInteger(n)||!Number.isSafeInteger(a)||!Number.isSafeInteger(r)||!Number.isSafeInteger(o)||n<0||a<1||r<1||o<r)throw new Error("Invalid PIN backoff parameters");const i=await C();try{const s=i.transaction("meta","readwrite"),c=s.objectStore("meta"),l=Ee(e,t),d=await c.get(l),u=(d?.failures??0)+1,y={failures:u,retryAfter:n+(u<a?0:Math.min(r*2**(u-a),o))};return await c.put(y,l),await s.done,y}finally{i.close()}}(...t.args);break;case"deletePinAttemptState":await async function(e,t){Pe(e,t);const n=await C();try{if(void 0!==t)return void await n.delete("meta",Ee(e,t));const a=n.transaction("meta","readwrite"),r=a.objectStore("meta"),o=De(e),i=await r.getAllKeys();await Promise.all(i.filter(e=>"string"==typeof e&&e.startsWith(o)).map(e=>r.delete(e))),await r.delete(`pinAttempts:${e}`),await a.done}finally{n.close()}}(...t.args),e=void 0;break;case"getIdentityX25519":e=await async function(){const e=await C();let t;try{t=await e.get("meta",Le)}finally{e.close()}if(!t)return;if(32!==t.pub.byteLength||64!==t.crossSig.byteLength)throw new Error("Stored X25519 identity has invalid public fields");const n=await ne(),a=await re(n,t.wrappedSecret);if(32!==a.byteLength)throw new Error("Stored X25519 identity has an invalid secret length");return{pub:t.pub,secret:a,crossSig:t.crossSig}}();break;case"setIdentityX25519":await async function(e){if(32!==e.pub.byteLength||32!==e.secret.byteLength||64!==e.crossSig.byteLength)throw new Error("X25519 identity has invalid field lengths");const t=await ne(),n=await ae(t,e.secret),a={pub:e.pub,wrappedSecret:n,crossSig:e.crossSig},r=await C();try{await r.put("meta",a,Le)}finally{r.close()}}(...t.args),e=void 0;break;case"deleteIdentityX25519":await async function(){const e=await C();try{const t=e.transaction(["meta","ratchetSessions"],"readwrite");await t.objectStore("meta").delete(Le),await t.objectStore("ratchetSessions").clear(),await t.done}finally{e.close()}Se.clear(),Ie.clear()}(),e=void 0;break;case"getIdentityEd25519":e=await async function(){const e=await C();let t;try{for(;;){const n=await We(e);if(!n.identity)return;t??=await ne();const a=await re(t,n.identity.wrappedSecret);if(64!==a.byteLength||!qe(n.identity.pub,a))throw new Uint8Array(a).fill(0),new Error("Stored Ed25519 public and secret keys are inconsistent");try{if("legacy"===n.identity.kind&&!await Xe(e,n)){new Uint8Array(a).fill(0);continue}return{pub:Ue(n.identity.pub),secret:a,recordRevision:"legacy"===n.identity.kind?1n:n.identity.recordRevision}}catch(e){throw new Uint8Array(a).fill(0),e}}}finally{e.close()}}();break;case"setIdentityEd25519":e=await async function(e){const t="object"==typeof e&&null!==e&&e.secret instanceof ArrayBuffer?new Uint8Array(e.secret):void 0;try{if(!(e.pub instanceof ArrayBuffer&&e.secret instanceof ArrayBuffer&&32===e.pub.byteLength&&64===e.secret.byteLength))throw new Error("Ed25519 identity has invalid key lengths");if(!qe(e.pub,e.secret))throw new Error("Ed25519 public key does not match the secret key");const t=Ue(e.pub),n=Ue(e.secret);let a;try{const e=await ne();a=await ae(e,n)}finally{new Uint8Array(n).fill(0)}const r=await C();try{const e=r.transaction("meta","readwrite"),n=e.objectStore("meta"),[o,i]=await Promise.all([n.get(Be),n.get(je)]),s=Fe(o),c=Oe(i);He(s,c);const l=Qe(s,c),d=Ve(l),u={version:1,pub:t,wrappedSecret:a,recordRevision:d},y={version:1,lastRevision:d};return await Promise.all([n.put(u,Be),n.put(y,je)]),await e.done,d}finally{r.close()}}finally{t?.fill(0)}}(...t.args);break;case"deleteIdentityEd25519":await async function(){const e=await C();try{const t=e.transaction(["meta","ratchetSessions"],"readwrite"),n=t.objectStore("meta"),[a,r]=await Promise.all([n.get(Be),n.get(je)]),o=Fe(a),i=Oe(r);if(He(o,i),void 0!==o){const e=Qe(o,i),t=Ve(e);await n.put({version:1,lastRevision:t},je),await n.delete(Be)}await t.objectStore("ratchetSessions").clear(),await t.done}finally{e.close()}Se.clear(),Ie.clear()}(),e=void 0;break;case"getIdentityDerivation":e=await Je();break;case"setIdentityDerivation":await async function(e){if(!_e(e))throw new Error("Identity derivation value is unsupported");const t=await C();try{const n=t.transaction("meta","readwrite"),a=n.objectStore("meta"),r=Ye(await a.get(Be));await a.put({version:1,derivation:e,boundPub:void 0===r?null:Ue(r)},Ge),await n.done}finally{t.close()}}(...t.args),e=void 0;break;case"deleteIdentityDerivation":await async function(){const e=await C();try{await e.delete("meta",Ge)}finally{e.close()}}(),e=void 0;break;case"deleteDB":await Ze(),e=void 0;break;default:return void postMessage({id:n,error:"Method not found"})}if("getIdentityEd25519"===t.method&&void 0!==e){const t=e.secret;try{globalThis.postMessage({id:n,result:e},[t])}catch(e){throw new Uint8Array(t).fill(0),e}}else postMessage({id:n,result:e})}catch(e){postMessage({id:n,error:String(e)})}};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scheduler may overlap four policy-maximal cycle generations while close
|
|
3
|
+
* events drain. Keep the receiver at that same derived geometry even when an
|
|
4
|
+
* authenticated peer opens lanes outside the agreed schedule.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_COVER_CHANNELS_PER_EDGE: number;
|
|
7
|
+
export declare const registerCoverChannel: <T>(coverChannels: Set<T>, channel: T) => void;
|
|
8
|
+
export interface CloseObservableChannel extends EventTarget {
|
|
9
|
+
readonly readyState: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Remove exactly one concrete lane from both registries. Labels repeat every
|
|
13
|
+
* cover cycle, so label-based removal can evict a replacement and leak the
|
|
14
|
+
* channel that actually closed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const unregisterCoverChannel: <T>(coverChannels: Set<T> | undefined, openChannels: T[], channel: T) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Register the lane and its exact close cleanup as one synchronous operation,
|
|
19
|
+
* before callers perform any await. Returns the idempotent cleanup closure for
|
|
20
|
+
* parse failures and higher-level teardown.
|
|
21
|
+
*/
|
|
22
|
+
export declare const registerCoverChannelUntilClose: <T extends CloseObservableChannel>(coverChannels: Set<T>, channel: T, openChannels?: T[]) => (() => void);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { setConnectingToPeers } from "../reducers/roomSlice";
|
|
2
|
+
export interface EdgeTeardownFollowUpApi {
|
|
3
|
+
getState: () => unknown;
|
|
4
|
+
dispatch: (action: ReturnType<typeof setConnectingToPeers>) => unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Re-dials a room after one of its peer edges was torn down on a path that
|
|
8
|
+
* has no re-dial of its own.
|
|
9
|
+
*
|
|
10
|
+
* disconnectFromPeer nulls every handler on the RTCPeerConnection and on its
|
|
11
|
+
* data channels before closing them, so the main-channel onclose that would
|
|
12
|
+
* normally re-request the roster never runs for a grace-timer or a
|
|
13
|
+
* failed/closed teardown. The peer usually still holds its side of the edge
|
|
14
|
+
* at that point (its ICE consent has not expired while it was frozen), so it
|
|
15
|
+
* will not offer either, and the edge stays down until an unrelated event
|
|
16
|
+
* happens to trigger a roster request.
|
|
17
|
+
*
|
|
18
|
+
* Only a connected and verified socket can carry a peers request. Without one
|
|
19
|
+
* the signaling reconnect path re-requests the roster on its own once the new
|
|
20
|
+
* socket proves identity, so there is nothing to do here.
|
|
21
|
+
*
|
|
22
|
+
* The request goes through setConnectingToPeers, whose listener debounces it
|
|
23
|
+
* under the dedicated "redial" key: a teardown that happens to land inside
|
|
24
|
+
* the "join" window of a fresh room join must still be able to re-dial.
|
|
25
|
+
*
|
|
26
|
+
* Callers decide WHEN a teardown deserves a re-dial; this helper has no
|
|
27
|
+
* backoff of its own. handleConnectToPeer re-dials after an ICE failure or
|
|
28
|
+
* grace-timer teardown only for an edge that had reached "connected" at least
|
|
29
|
+
* once (a transport that never connected would otherwise rebuild and fail
|
|
30
|
+
* again on every ICE failure interval), and after a fresh transport's first
|
|
31
|
+
* offer went unanswered for PENDING_OFFER_TIMEOUT_MS (by then the signaling
|
|
32
|
+
* server has dropped a dark peer from the roster, so a re-dial to a peer that
|
|
33
|
+
* is really gone builds nothing).
|
|
34
|
+
*/
|
|
35
|
+
export declare const requestPeersAfterEdgeTeardown: (api: EdgeTeardownFollowUpApi, roomId: string) => boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { MiddlewareAPI } from "@reduxjs/toolkit";
|
|
2
2
|
import type { KeyPair } from "../reducers/keyPairSlice";
|
|
3
|
-
|
|
3
|
+
import type { WebSocketMessageChallengeResponse } from "../utils/interfaces";
|
|
4
|
+
export declare const handleChallenge: (keyPair: KeyPair, peerId: string, challenge: string, store: MiddlewareAPI, isCurrentSocket?: () => boolean) => Promise<WebSocketMessageChallengeResponse | null>;
|
|
@@ -3,4 +3,4 @@ import type { IRTCPeerConnection, RTCPeerConnectionParams } from "../api/webrtc/
|
|
|
3
3
|
export interface IRTCPeerConnectionParams extends RTCPeerConnectionParams {
|
|
4
4
|
peerConnections: IRTCPeerConnection[];
|
|
5
5
|
}
|
|
6
|
-
export declare const handleConnectToPeer: ({ peerId, peerPublicKey, roomId, peerConnections
|
|
6
|
+
export declare const handleConnectToPeer: ({ peerId, peerPublicKey, roomId, peerConnections }: IRTCPeerConnectionParams, api: BaseQueryApi) => Promise<IRTCPeerConnection>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type PersistInitialRatchetState, type RollbackInitialRatchetState } from "./ratchetPersist";
|
|
2
2
|
import { SparsePqHealingState } from "./pqHealingRuntime";
|
|
3
|
+
import { parseFingerprintFromSdp } from "../utils/sdpFingerprint";
|
|
3
4
|
import { type PqHealingBootstrap } from "./handshakeCore";
|
|
4
5
|
import type { LibCrypto } from "../cryptography/libcrypto";
|
|
5
6
|
import type { RatchetRootSuite } from "../utils/constants";
|
|
@@ -7,12 +8,7 @@ import type { RatchetState } from "../cryptography/ratchet";
|
|
|
7
8
|
import type { RatchetGateLease } from "./ratchetGate";
|
|
8
9
|
import type { RoomPqMode } from "../roomPolicy";
|
|
9
10
|
import type { IRTCPeerConnection, IRTCDataChannel } from "../api/webrtc/interfaces";
|
|
10
|
-
|
|
11
|
-
* Raw bytes of the `a=fingerprint:sha-256 XX:XX:...` line in an SDP blob.
|
|
12
|
-
* Throws if no sha-256 fingerprint attribute is present, or if the one found
|
|
13
|
-
* doesn't decode to exactly 32 bytes.
|
|
14
|
-
*/
|
|
15
|
-
export declare const parseFingerprintFromSdp: (sdp: string) => Uint8Array;
|
|
11
|
+
export { parseFingerprintFromSdp };
|
|
16
12
|
/**
|
|
17
13
|
* Post-connect DTLS binding check (spec §5): the fingerprint declared in the
|
|
18
14
|
* SDP (and bound into the CPace channel-input via buildChannelInput) must
|
|
@@ -2,6 +2,7 @@ import type { IRTCDataChannel, IRTCPeerConnection } from "../api/webrtc/interfac
|
|
|
2
2
|
import type { LibCrypto } from "../cryptography/libcrypto";
|
|
3
3
|
import type { PqMessageKeyContext } from "../cryptography/pqMessageKey";
|
|
4
4
|
import type { RatchetHeader } from "../cryptography/ratchet";
|
|
5
|
+
import type { RatchetGateLease } from "./ratchetGate";
|
|
5
6
|
import type { BaseQueryApi } from "@reduxjs/toolkit/query";
|
|
6
7
|
export declare const SEND_RECEIPT_WINDOW: number;
|
|
7
8
|
export declare const SEND_WINDOW_STALL_MS = 10000;
|
|
@@ -21,6 +22,13 @@ export interface ReceiptWindowParams {
|
|
|
21
22
|
export declare const waitForReceiptWindow: (params: ReceiptWindowParams) => Promise<void>;
|
|
22
23
|
interface TransferCipher {
|
|
23
24
|
epc: IRTCPeerConnection;
|
|
25
|
+
/**
|
|
26
|
+
* Gate lease of the ratchet generation `messageKey`/`header` were derived
|
|
27
|
+
* from. An in-place re-bind (remoteTransportChange) keeps the
|
|
28
|
+
* RTCPeerConnection and replaces this lease, so the epc identity alone does
|
|
29
|
+
* NOT identify the crypto generation.
|
|
30
|
+
*/
|
|
31
|
+
lease?: RatchetGateLease;
|
|
24
32
|
messageKey: Uint8Array;
|
|
25
33
|
header: RatchetHeader;
|
|
26
34
|
/**
|
|
@@ -42,7 +50,7 @@ export declare const closeTransferChannel: (channel: IRTCDataChannel) => void;
|
|
|
42
50
|
* this boundary after its send/reconcile work settles.
|
|
43
51
|
*/
|
|
44
52
|
export declare const runWithTerminalChannelClose: <T>(currentChannel: () => IRTCDataChannel, operation: () => Promise<T>) => Promise<T>;
|
|
45
|
-
type WaitForRatchetGate = (roomId: string, peerId: string) => Promise<void>;
|
|
53
|
+
type WaitForRatchetGate = (roomId: string, peerId: string, signal?: AbortSignal) => Promise<void>;
|
|
46
54
|
type DurableRatchetStep = (epc: IRTCPeerConnection, roomId: string, module: LibCrypto) => Promise<{
|
|
47
55
|
messageKey: Uint8Array;
|
|
48
56
|
header: RatchetHeader;
|
|
@@ -51,17 +59,62 @@ type DurableRatchetStep = (epc: IRTCPeerConnection, roomId: string, module: LibC
|
|
|
51
59
|
/**
|
|
52
60
|
* Bind an in-flight message to the active cryptographic transport.
|
|
53
61
|
*
|
|
54
|
-
* Reopening only its DataChannel on the
|
|
55
|
-
* ratchet session, so the per-message key/header remain
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
62
|
+
* Reopening only its DataChannel on the same RTCPeerConnection AND the same
|
|
63
|
+
* ratchet generation retains the session, so the per-message key/header remain
|
|
64
|
+
* valid. Anything else — a replacement RTCPeerConnection, or the same one
|
|
65
|
+
* re-bound in place onto a new remote DTLS certificate — performs a fresh
|
|
66
|
+
* hybrid handshake and owns an unrelated ratchet. It must wait for that
|
|
67
|
+
* generation's gate, advance its ratchet once and reseal the missing plaintext
|
|
68
|
+
* chunks; replaying the old ciphertext/key/header cannot decrypt.
|
|
69
|
+
*
|
|
70
|
+
* The generation is identified by the gate lease, NOT by the connection: an
|
|
71
|
+
* in-place re-bind keeps the very same epc while wiping its ratchet, PQ
|
|
72
|
+
* runtime and message keys. Treating that as "same transport" would keep
|
|
73
|
+
* sealing with a dead key and, worse, push frames onto an edge whose gate is
|
|
74
|
+
* still closed — which the receiver can only reject as application data before
|
|
75
|
+
* peer authentication, closing the channel and cascading into the edge.
|
|
59
76
|
*
|
|
60
77
|
* Exported only so the transport-identity and key-erasure contract has a focused
|
|
61
78
|
* unit test. It is not part of the package's public root exports.
|
|
62
79
|
*/
|
|
63
80
|
export declare const bindTransferCipherToConnection: (transfer: TransferCipher, nextEpc: IRTCPeerConnection, roomId: string, module: LibCrypto, waitForGate?: WaitForRatchetGate, ratchetStep?: DurableRatchetStep, signal?: AbortSignal) => Promise<TransferCipher>;
|
|
64
|
-
|
|
81
|
+
/**
|
|
82
|
+
* True iff a per-message channel closing is the PEER cancelling this transfer.
|
|
83
|
+
*
|
|
84
|
+
* Immediate mode has no CANCEL frame: one DataChannel is one logical message,
|
|
85
|
+
* so its close IS the signal — but only when nothing on OUR side retired it.
|
|
86
|
+
* Three things must hold: the RTCPeerConnection is still connected, it is
|
|
87
|
+
* still the registered transport for this room/peer, and the ratchet
|
|
88
|
+
* generation the transfer was sealed under is still the current one.
|
|
89
|
+
*
|
|
90
|
+
* That last test is what separates a remote cancel from a local retirement.
|
|
91
|
+
* An in-place edge re-bind (remoteTransportChange) closes the generation's
|
|
92
|
+
* message channels while KEEPING the connection connected and registered, so
|
|
93
|
+
* the first two tests still pass; the gate lease it replaces is the only
|
|
94
|
+
* evidence that the close was ours. Without it the sender abandons a perfectly
|
|
95
|
+
* resumable transfer — and reports "cancelled by peer", which an application
|
|
96
|
+
* may act on by deleting its outgoing message. Mirrors the receiver's
|
|
97
|
+
* `authenticatedTransportStillAlive` test in handleOpenChannel.
|
|
98
|
+
*/
|
|
99
|
+
export declare const isAuthenticatedPeerCancel: (currentEpc: IRTCPeerConnection, peerConnections: IRTCPeerConnection[], roomId: string, peerId: string, transferGateLease: RatchetGateLease | undefined) => boolean;
|
|
100
|
+
/**
|
|
101
|
+
* A resumed transfer failed to re-bind onto the transport it resumed on: is
|
|
102
|
+
* another of its bounded resume attempts owed?
|
|
103
|
+
*
|
|
104
|
+
* The normal reason is that the transport was REPLACED before its gate could
|
|
105
|
+
* open — an unauthenticated transport is granted at most one in-place re-bind
|
|
106
|
+
* (decideRemoteTransportChangeResponse), and the replacement tears the old one
|
|
107
|
+
* down, which rejects the gate every awaiter is sitting on. The peer is coming
|
|
108
|
+
* back on the replacement, so giving up here would abandon a transfer that is
|
|
109
|
+
* one attempt away from finishing.
|
|
110
|
+
*
|
|
111
|
+
* Two failures are terminal. A cancelled transfer resumes nothing. And a
|
|
112
|
+
* gate-wait TIMEOUT means nothing replaced anything: the peer simply never
|
|
113
|
+
* opened a handshake within the budget (a stale authenticated view), so
|
|
114
|
+
* another attempt only buys another full budget of the same wait — the same
|
|
115
|
+
* reason the send's first gate wait rethrows it.
|
|
116
|
+
*/
|
|
117
|
+
export declare const isResumableRebindFailure: (error: unknown, signal?: AbortSignal) => boolean;
|
|
65
118
|
export interface PeerSendTarget {
|
|
66
119
|
peerId: string;
|
|
67
120
|
epc?: IRTCPeerConnection;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { BaseQueryApi } from "@reduxjs/toolkit/query";
|
|
2
|
-
declare const handleWebSocketMessage: (event: MessageEvent, ws: WebSocket, api: BaseQueryApi) => Promise<void>;
|
|
2
|
+
declare const handleWebSocketMessage: (event: MessageEvent, ws: WebSocket, api: BaseQueryApi, isCurrentSocket?: () => boolean, onHeartbeat?: () => void) => Promise<void>;
|
|
3
3
|
export default handleWebSocketMessage;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { LibCrypto } from "../cryptography/libcrypto";
|
|
2
2
|
import type { RatchetState } from "../cryptography/ratchet";
|
|
3
3
|
import type { RoomPqMode } from "../roomPolicy";
|
|
4
|
+
export { HANDSHAKE_PAYLOAD_LENGTHS, HANDSHAKE_STEP, MAX_HANDSHAKE_PAYLOAD_LENGTH, handshakePayloadLengthForStep, isHandshakePayloadForStep, } from "./handshakeFrame";
|
|
5
|
+
export type { HandshakeStep } from "./handshakeFrame";
|
|
4
6
|
export interface ChannelInputParams {
|
|
5
7
|
channelId: Uint8Array;
|
|
6
8
|
ikInitiator: Uint8Array;
|
|
@@ -24,13 +26,6 @@ export interface ChannelInputParams {
|
|
|
24
26
|
* while assembling the CPace transcript.
|
|
25
27
|
*/
|
|
26
28
|
export declare const buildChannelInput: (p: ChannelInputParams) => Uint8Array;
|
|
27
|
-
export type HandshakeStep = 1 | 2 | 3;
|
|
28
|
-
/**
|
|
29
|
-
* Fail-closed pre-auth framing check used before a payload enters the handshake
|
|
30
|
-
* inbox. This keeps attacker-controlled frames bounded before the crypto core
|
|
31
|
-
* consumes them; the core repeats the same checks when parsing.
|
|
32
|
-
*/
|
|
33
|
-
export declare const isHandshakePayloadForStep: (payload: Uint8Array, step: HandshakeStep, pqMode?: RoomPqMode) => boolean;
|
|
34
29
|
export interface HandshakeTransport {
|
|
35
30
|
send(bytes: Uint8Array): void | Promise<void>;
|
|
36
31
|
recv(): Promise<Uint8Array>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RoomPqMode } from "../roomPolicy";
|
|
2
|
+
/**
|
|
3
|
+
* Internal handshake sub-frame tags carried inside FRAME_TYPE_HANDSHAKE.
|
|
4
|
+
* The tag byte is also the causal step number.
|
|
5
|
+
*/
|
|
6
|
+
export declare const HANDSHAKE_STEP: Readonly<{
|
|
7
|
+
readonly HELLO: 1;
|
|
8
|
+
readonly CONFIRM: 2;
|
|
9
|
+
readonly FINISH: 3;
|
|
10
|
+
}>;
|
|
11
|
+
export type HandshakeStep = (typeof HANDSHAKE_STEP)[keyof typeof HANDSHAKE_STEP];
|
|
12
|
+
/** Exact handshake payload lengths, including the one-byte step tag. */
|
|
13
|
+
export declare const HANDSHAKE_PAYLOAD_LENGTHS: Readonly<{
|
|
14
|
+
"hybrid-mlkem512": Readonly<Record<HandshakeStep, number>>;
|
|
15
|
+
"hybrid-mlkem768": Readonly<Record<HandshakeStep, number>>;
|
|
16
|
+
"hybrid-mlkem1024": Readonly<Record<HandshakeStep, number>>;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const handshakePayloadLengthForStep: (step: HandshakeStep, pqMode?: RoomPqMode) => number;
|
|
19
|
+
/** Source-derived upper bound across every step and supported room PQ mode. */
|
|
20
|
+
export declare const MAX_HANDSHAKE_PAYLOAD_LENGTH: number;
|
|
21
|
+
/**
|
|
22
|
+
* Fail-closed pre-auth framing check used before a payload enters a handshake
|
|
23
|
+
* inbox. The crypto core repeats this check when parsing each flight.
|
|
24
|
+
*/
|
|
25
|
+
export declare const isHandshakePayloadForStep: (payload: Uint8Array, step: HandshakeStep, pqMode?: RoomPqMode) => boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const RATCHET_GATE_WAIT_TIMEOUT_MESSAGE = "Peer is not authenticated on this transport: the handshake did not complete within the wait budget";
|
|
2
|
+
export declare const isRatchetGateWaitTimeout: (error: unknown) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Await the per-(room, peer) ratchet gate with a wall-clock budget.
|
|
5
|
+
*
|
|
6
|
+
* The gate itself is unbounded by design (it is the "handshake succeeded"
|
|
7
|
+
* signal), but a send must not wait on it forever: after an edge re-dial the
|
|
8
|
+
* remote may hold a stale authenticated view and never open a fresh `main`,
|
|
9
|
+
* so its handshake never starts and the gate never settles. The transfer
|
|
10
|
+
* abort signal still wins first; a gate that opens or rejects on its own
|
|
11
|
+
* clears the timer and settles with its own outcome.
|
|
12
|
+
*/
|
|
13
|
+
export declare const waitForRatchetGate: (roomId: string, peerId: string, signal?: AbortSignal, timeoutMs?: number) => Promise<void>;
|
|
@@ -12,5 +12,29 @@ export interface PeerTransferOutcome {
|
|
|
12
12
|
/** Accurate, defensive per-peer delivery state for UI/diagnostics consumers. */
|
|
13
13
|
export declare const getPeerTransferOutcome: (roomId: string, peerId: string, transferId: string) => PeerTransferOutcome;
|
|
14
14
|
export declare const isTransferComplete: (roomId: string, peerId: string, transferId: string) => boolean;
|
|
15
|
+
export interface TransferAck {
|
|
16
|
+
readonly peerId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Distinct chunks this peer has receipted so far, counting decoys: a receipt
|
|
19
|
+
* is emitted per inbound frame, and the padding is what makes every frame
|
|
20
|
+
* indistinguishable, so excluding it would report a fraction of a denominator
|
|
21
|
+
* that includes it.
|
|
22
|
+
*/
|
|
23
|
+
readonly ackedChunks: number;
|
|
24
|
+
readonly complete: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Live outbound progress for one logical send, per peer.
|
|
28
|
+
*
|
|
29
|
+
* The sender already knows this -- every receipt lands in the have-set above --
|
|
30
|
+
* but nothing outside this module could read it, so a UI could say "sending"
|
|
31
|
+
* and then nothing at all until the transfer settled. On a large file that is
|
|
32
|
+
* a progress bar's worth of silence.
|
|
33
|
+
*
|
|
34
|
+
* Derived from the same state selective retransmit uses, so it cannot drift
|
|
35
|
+
* from what the sender actually believes was delivered. Read-only and
|
|
36
|
+
* defensively copied; polling it never affects the transfer.
|
|
37
|
+
*/
|
|
38
|
+
export declare const getTransferAcks: (roomId: string, transferId: string) => TransferAck[];
|
|
15
39
|
export declare const clearTransfer: (roomId: string, peerId: string, transferId: string) => void;
|
|
16
40
|
export declare const waitForCompletion: (roomId: string, peerId: string, transferId: string, timeoutMs: number, signal?: AbortSignal) => Promise<boolean>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AppliedRoomResponse } from "../utils/roomRequestCoordinator";
|
|
2
|
+
import type { WebSocketMessageRoomIdRequest } from "../utils/interfaces";
|
|
3
|
+
export interface RoomRequestApi {
|
|
4
|
+
getState: () => unknown;
|
|
5
|
+
}
|
|
6
|
+
export type SendRoomRequest = (request: WebSocketMessageRoomIdRequest) => Promise<unknown>;
|
|
7
|
+
export declare const MAX_ROOM_REQUEST_ATTEMPTS = 4;
|
|
8
|
+
export declare const ROOM_REQUEST_RETRY_BASE_MS = 250;
|
|
9
|
+
export declare const MAX_ROOM_REQUEST_RETRY_MS = 2000;
|
|
10
|
+
export interface RoomRequestRetryOptions {
|
|
11
|
+
lifecycleEpoch?: number;
|
|
12
|
+
maxAttempts?: number;
|
|
13
|
+
retryBaseMs?: number;
|
|
14
|
+
maxRetryMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ActiveRoomRequestOptions extends RoomRequestRetryOptions {
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare const ACTIVE_ROOM_REQUEST_CONCURRENCY = 16;
|
|
20
|
+
/**
|
|
21
|
+
* The one SDK path that emits a room request.
|
|
22
|
+
*
|
|
23
|
+
* It binds every request to a fresh wire nonce through the coordinator and
|
|
24
|
+
* refuses to issue room traffic before the current socket proves identity.
|
|
25
|
+
*/
|
|
26
|
+
export declare const requestRoomFromSignaling: (api: RoomRequestApi, roomUrl: string, sendRoomRequest: SendRoomRequest) => Promise<AppliedRoomResponse>;
|
|
27
|
+
/** Retry one room independently while it remains in this socket lifecycle. */
|
|
28
|
+
export declare const requestRoomFromSignalingWithRetry: (api: RoomRequestApi, roomUrl: string, sendRoomRequest: SendRoomRequest, options?: RoomRequestRetryOptions) => Promise<AppliedRoomResponse>;
|
|
29
|
+
/** Refresh every retained active room after a fresh socket proof. */
|
|
30
|
+
export declare const requestAllActiveRooms: (api: RoomRequestApi, sendRoomRequest: SendRoomRequest, options?: ActiveRoomRequestOptions) => Promise<void>;
|