p2party 0.14.7 → 0.14.10

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.
Files changed (181) hide show
  1. package/README.md +26 -23
  2. package/THIRD_PARTY_NOTICES.md +33 -6
  3. package/docs/getting-started.md +103 -4
  4. package/docs/protocol-v4-security.md +57 -7
  5. package/docs/references.md +1 -1
  6. package/docs/session-api.md +9 -0
  7. package/docs/wire-format.md +102 -14
  8. package/lib/api/signalingServerApi.d.ts +38 -0
  9. package/lib/api/webrtc/interfaces.d.ts +33 -1
  10. package/lib/api/webrtc/peerSocketGeneration.d.ts +68 -0
  11. package/lib/cryptography/coverCell.d.ts +7 -0
  12. package/lib/cryptography/emscripten.d.ts +101 -0
  13. package/lib/cryptography/libcrypto.d.ts +181 -0
  14. package/lib/db/outboxTypes.d.ts +135 -0
  15. package/lib/db/types.d.ts +29 -3
  16. package/lib/db.worker.js +1 -1
  17. package/lib/handlers/coverRuntime.d.ts +14 -1
  18. package/lib/handlers/coverScheduler.d.ts +59 -1
  19. package/lib/handlers/coverTransfer.d.ts +169 -3
  20. package/lib/handlers/handleSendMessage.d.ts +79 -3
  21. package/lib/handlers/outbox.d.ts +47 -0
  22. package/lib/handlers/ratchetGate.d.ts +9 -0
  23. package/lib/handlers/reconcile.d.ts +5 -0
  24. package/lib/index.d.ts +5 -0
  25. package/lib/index.js +1 -1
  26. package/lib/index.min.js +1 -1
  27. package/lib/index.mjs +1 -1
  28. package/lib/reducers/roomSlice.d.ts +9 -2
  29. package/lib/session.js +1 -1
  30. package/lib/session.mjs +1 -1
  31. package/lib/utils/constants.d.ts +7 -0
  32. package/lib/utils/interfaces.d.ts +11 -0
  33. package/lib/utils/signalingReconnectController.d.ts +44 -0
  34. package/package.json +13 -15
  35. package/lib/api/webrtc/applyIceConfiguration.d.ts +0 -9
  36. package/lib/api/webrtc/dataChannelHandler.d.ts +0 -10
  37. package/lib/api/webrtc/deleteRoomData.d.ts +0 -13
  38. package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +0 -9
  39. package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts +0 -7
  40. package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts +0 -8
  41. package/lib/api/webrtc/disconnectFromPeerQuery.d.ts +0 -9
  42. package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +0 -9
  43. package/lib/api/webrtc/disconnectQuery.d.ts +0 -9
  44. package/lib/api/webrtc/iceGeneration.d.ts +0 -17
  45. package/lib/api/webrtc/iceRepair.d.ts +0 -12
  46. package/lib/api/webrtc/index.d.ts +0 -17
  47. package/lib/api/webrtc/negotiationLock.d.ts +0 -36
  48. package/lib/api/webrtc/openChannelQuery.d.ts +0 -8
  49. package/lib/api/webrtc/pendingIceCandidates.d.ts +0 -14
  50. package/lib/api/webrtc/pendingOffer.d.ts +0 -29
  51. package/lib/api/webrtc/rebindDeadline.d.ts +0 -61
  52. package/lib/api/webrtc/remoteTransportChange.d.ts +0 -142
  53. package/lib/api/webrtc/retiredRemoteTransports.d.ts +0 -70
  54. package/lib/api/webrtc/roomEdgeBudget.d.ts +0 -10
  55. package/lib/api/webrtc/roomPeer.d.ts +0 -11
  56. package/lib/api/webrtc/sendMessageQuery.d.ts +0 -14
  57. package/lib/api/webrtc/setCandidateQuery.d.ts +0 -8
  58. package/lib/api/webrtc/setDescriptionQuery.d.ts +0 -16
  59. package/lib/api/webrtc/settleOnClose.d.ts +0 -36
  60. package/lib/blindRendezvous/admissionInput.d.ts +0 -11
  61. package/lib/blindRendezvous/admissionPolicy.d.ts +0 -217
  62. package/lib/blindRendezvous/canonicalSchema.d.ts +0 -139
  63. package/lib/blindRendezvous/carrierEnvelope.d.ts +0 -105
  64. package/lib/blindRendezvous/coreReduce.d.ts +0 -199
  65. package/lib/blindRendezvous/derivations.d.ts +0 -381
  66. package/lib/blindRendezvous/identityHelloAdmission.d.ts +0 -89
  67. package/lib/blindRendezvous/identityHelloAdmissionPersistence.d.ts +0 -133
  68. package/lib/blindRendezvous/identityHelloHistoricalEvidence.d.ts +0 -52
  69. package/lib/blindRendezvous/identityHelloOpen.d.ts +0 -167
  70. package/lib/blindRendezvous/identityHelloTrial.d.ts +0 -271
  71. package/lib/blindRendezvous/localAdmissibleProject.d.ts +0 -101
  72. package/lib/blindRendezvous/localPresenceKeyBinding.d.ts +0 -108
  73. package/lib/blindRendezvous/localStableIdentityCustody.d.ts +0 -46
  74. package/lib/blindRendezvous/normativeKernel.d.ts +0 -222
  75. package/lib/blindRendezvous/operation.d.ts +0 -137
  76. package/lib/blindRendezvous/operationPayload.d.ts +0 -134
  77. package/lib/blindRendezvous/pairBootstrapSuite.d.ts +0 -93
  78. package/lib/blindRendezvous/pairCrypto.d.ts +0 -122
  79. package/lib/blindRendezvous/pairLedger.d.ts +0 -154
  80. package/lib/blindRendezvous/pairOpenBudget.d.ts +0 -67
  81. package/lib/blindRendezvous/pairOpenReservation.d.ts +0 -157
  82. package/lib/blindRendezvous/pairPlaintext.d.ts +0 -240
  83. package/lib/blindRendezvous/pairStepEnvelope.d.ts +0 -60
  84. package/lib/blindRendezvous/plannerState.d.ts +0 -61
  85. package/lib/blindRendezvous/projections.d.ts +0 -71
  86. package/lib/blindRendezvous/publicWindow.d.ts +0 -330
  87. package/lib/blindRendezvous/retainedOperationStore.d.ts +0 -187
  88. package/lib/blindRendezvous/roomInviteV2.d.ts +0 -92
  89. package/lib/blindRendezvous/roomPolicyV4.d.ts +0 -90
  90. package/lib/blindRendezvous/schemas.d.ts +0 -221
  91. package/lib/blindRendezvous/storeDescriptor.d.ts +0 -160
  92. package/lib/cryptography/aeadWasm.d.ts +0 -18
  93. package/lib/cryptography/byteInput.d.ts +0 -12
  94. package/lib/cryptography/carrierAead.d.ts +0 -23
  95. package/lib/cryptography/chacha20poly1305.d.ts +0 -7
  96. package/lib/cryptography/cpace.d.ts +0 -41
  97. package/lib/cryptography/ed25519.d.ts +0 -11
  98. package/lib/cryptography/fips202.d.ts +0 -10
  99. package/lib/cryptography/hashStream.d.ts +0 -15
  100. package/lib/cryptography/hkdf.d.ts +0 -3
  101. package/lib/cryptography/hpke.d.ts +0 -84
  102. package/lib/cryptography/hpkeSuite.d.ts +0 -39
  103. package/lib/cryptography/hybridKem.d.ts +0 -42
  104. package/lib/cryptography/hybridKemSuite.d.ts +0 -22
  105. package/lib/cryptography/identityCrossSig.d.ts +0 -7
  106. package/lib/cryptography/memory.d.ts +0 -14
  107. package/lib/cryptography/merkle.d.ts +0 -42
  108. package/lib/cryptography/ownedEd25519KeyMaterial.d.ts +0 -27
  109. package/lib/cryptography/pqHealingFrame.d.ts +0 -39
  110. package/lib/cryptography/random.d.ts +0 -22
  111. package/lib/cryptography/testModule.d.ts +0 -7
  112. package/lib/cryptography/utils.d.ts +0 -13
  113. package/lib/cryptography/webCryptoSha256.d.ts +0 -20
  114. package/lib/cryptography/x25519.d.ts +0 -33
  115. package/lib/cryptography/x3dh.d.ts +0 -9
  116. package/lib/cryptography/xchacha20poly1305.d.ts +0 -7
  117. package/lib/db/api.d.ts +0 -70
  118. package/lib/db/db.worker.d.ts +0 -1
  119. package/lib/db/identityEd25519ClientBoundary.d.ts +0 -13
  120. package/lib/db/ratchetWrap.d.ts +0 -36
  121. package/lib/db/src/getDB.d.ts +0 -81
  122. package/lib/handlers/chunkFrame.d.ts +0 -13
  123. package/lib/handlers/connectionSignal.d.ts +0 -26
  124. package/lib/handlers/coverChannelRegistry.d.ts +0 -22
  125. package/lib/handlers/coverEdge.d.ts +0 -42
  126. package/lib/handlers/edgeTeardownFollowUp.d.ts +0 -35
  127. package/lib/handlers/frameType.d.ts +0 -10
  128. package/lib/handlers/handleChallenge.d.ts +0 -4
  129. package/lib/handlers/handleConnectToPeer.d.ts +0 -6
  130. package/lib/handlers/handleHandshake.d.ts +0 -70
  131. package/lib/handlers/handleMessageQueueing.d.ts +0 -54
  132. package/lib/handlers/handleOpenChannel.d.ts +0 -12
  133. package/lib/handlers/handleQueuedIceCandidates.d.ts +0 -2
  134. package/lib/handlers/handleReadReceipt.d.ts +0 -17
  135. package/lib/handlers/handleReceiveMessage.d.ts +0 -32
  136. package/lib/handlers/handleWebSocketMessage.d.ts +0 -3
  137. package/lib/handlers/messageChunkCrypto.d.ts +0 -70
  138. package/lib/handlers/peerRosterDelta.d.ts +0 -12
  139. package/lib/handlers/pqHealingOrchestrator.d.ts +0 -46
  140. package/lib/handlers/ratchetGateWait.d.ts +0 -13
  141. package/lib/handlers/ratchetPersist.d.ts +0 -53
  142. package/lib/handlers/receiptFrame.d.ts +0 -18
  143. package/lib/handlers/receiveMessageKeyLifetime.d.ts +0 -24
  144. package/lib/handlers/requestRoom.d.ts +0 -30
  145. package/lib/handlers/roomResponse.d.ts +0 -53
  146. package/lib/handlers/transferAbort.d.ts +0 -42
  147. package/lib/middleware/keyPairListenerMiddleware.d.ts +0 -2
  148. package/lib/middleware/roomListenerMiddleware.d.ts +0 -5
  149. package/lib/roomInvite.d.ts +0 -18
  150. package/lib/roomPinAttempts.d.ts +0 -12
  151. package/lib/roomPinVault.d.ts +0 -26
  152. package/lib/utils/channelLabel.d.ts +0 -9
  153. package/lib/utils/chunkBounds.d.ts +0 -9
  154. package/lib/utils/correlationId.d.ts +0 -4
  155. package/lib/utils/drainAndClose.d.ts +0 -5
  156. package/lib/utils/identityRole.d.ts +0 -19
  157. package/lib/utils/leafHash.d.ts +0 -13
  158. package/lib/utils/metadata.d.ts +0 -22
  159. package/lib/utils/mutex.d.ts +0 -24
  160. package/lib/utils/protocolVersion.d.ts +0 -5
  161. package/lib/utils/receiptToken.d.ts +0 -7
  162. package/lib/utils/roomConnectAdmission.d.ts +0 -15
  163. package/lib/utils/roomRequestCoordinator.d.ts +0 -104
  164. package/lib/utils/roomRosterReconciler.d.ts +0 -83
  165. package/lib/utils/sdpFingerprint.d.ts +0 -6
  166. package/lib/utils/sendQueueKey.d.ts +0 -2
  167. package/lib/utils/signalingAttemptLifecycle.d.ts +0 -33
  168. package/lib/utils/signalingAuth.d.ts +0 -27
  169. package/lib/utils/signalingBounds.d.ts +0 -15
  170. package/lib/utils/signalingFrame.d.ts +0 -21
  171. package/lib/utils/signalingHeartbeatWatchdog.d.ts +0 -45
  172. package/lib/utils/signalingIngressQueue.d.ts +0 -49
  173. package/lib/utils/signalingPeerRequestDebouncer.d.ts +0 -13
  174. package/lib/utils/signalingRoomLifecycle.d.ts +0 -7
  175. package/lib/utils/signalingRoomOperationLifecycle.d.ts +0 -14
  176. package/lib/utils/signalingServerBoundary.d.ts +0 -11
  177. package/lib/utils/splitToChunks.d.ts +0 -30
  178. package/lib/utils/transportIdentity.d.ts +0 -5
  179. package/lib/utils/uint8array.d.ts +0 -16
  180. package/lib/utils/waitForOpen.d.ts +0 -3
  181. package/lib/utils/zeroFree.d.ts +0 -10
package/README.md CHANGED
@@ -9,7 +9,8 @@
9
9
  Protocol-v4 end-to-end encryption and reliable file transfer over a WebRTC
10
10
  room mesh.
11
11
 
12
- Apache-2.0 · [LICENSE.md](LICENSE.md)
12
+ Apache-2.0 · [License](LICENSE.md) · [Getting started](docs/getting-started.md) ·
13
+ [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md)
13
14
 
14
15
  > Status: protocol v4 is an intentional wire break — v3 peers and persisted v3
15
16
  > crypto rows are not resumed. The current code has not completed an
@@ -33,7 +34,7 @@ Apache-2.0 · [LICENSE.md](LICENSE.md)
33
34
  decoy slots can hide a message's exact payload length within its transfer.
34
35
  - Each outbound message has its own transfer identity and data channel, a
35
36
  cancellable handle, authenticated receipts, selective retransmission, and
36
- reconnect resume.
37
+ reconnect and sender-reload recovery from a bounded durable outbox.
37
38
  - Text and files up to the enforced 10 GiB application limit are supported.
38
39
  Browser builds use IndexedDB and, where available, OPFS for disk-backed large
39
40
  file receipt.
@@ -248,15 +249,14 @@ reconverge and exchange byte-exact messages 0.5–6.5 s after wake, and four
248
249
  in-flight 8 MiB transfer configurations finish byte-exact with the sender
249
250
  reporting `delivered`.
250
251
 
251
- Two limits, since they are as real as the fixes. Resume follows a live page: a
252
- send in flight does not survive reloading the sending page, though a reloaded
253
- receiver replays receipts for what it already holds so a still-live sender
254
- resends only the gaps. And when a frozen identity responder is carrying an
255
- in-flight transfer, reconvergence takes about 37 s rather than the usual 5–7 s
256
- the surviving SCTP association accepts our new streams but not the peer's, so
257
- recovery waits out the 30 s handshake step timeout. The transfer completes
258
- correctly; the latency is a known open item on the
259
- [roadmap](ROADMAP.md#near-term).
252
+ Prepared sends now survive sender reloads in the same browser storage and
253
+ identity. Pending state and retry/cancel APIs are described in
254
+ [durable transfer recovery](docs/getting-started.md). A failed `handle.done`
255
+ reports the outcome of that attempt; it does not mean its pending data was
256
+ removed. The outbox retains prepared sends for up to 24 hours and cannot recover
257
+ an upload whose preparation never finished or whose browser storage was erased.
258
+ Current measured recovery results and remaining compatibility limits are in
259
+ [the browser report](docs/e2e/README.md).
260
260
 
261
261
  ## Wire format
262
262
 
@@ -795,14 +795,9 @@ is what this package compiles.
795
795
 
796
796
  ## No build step: a script tag and the CDN
797
797
 
798
- > **`0.14.7` is not on the CDN yet.** The release pipeline uploads its objects
799
- > from the tarball it publishes, and that pipeline has not run for this
800
- > version: every path under `https://cdn.p2party.com/@0.14.7/` currently
801
- > answers 404, and npm still resolves `p2party` to an older release. The
802
- > snippet below is generated from this build and is what the release will
803
- > serve — until then, either pin the version `npm view p2party version` reports
804
- > (its objects are live) or self-host the bundle, which works today and is
805
- > described in the next section.
798
+ > **`0.14.10` is a local release candidate.** This work does not publish npm
799
+ > packages or CDN assets. The snippet names the candidate's intended release
800
+ > path; use the generated local bundle until that version is published.
806
801
 
807
802
  A release publishes its browser bundle, its database worker and its
808
803
  cryptographic module as immutable, versioned CDN objects. The version is in the
@@ -815,8 +810,8 @@ script in and `window.p2party` is there — no npm, no bundler, no build:
815
810
  <title>p2party in one file</title>
816
811
 
817
812
  <script
818
- src="https://cdn.p2party.com/@0.14.7/p2party.min.js"
819
- integrity="sha384-Wh2Pxka9YN2oAZmzYgaoyLmfKQMXsMO4AfUmshluGmYjfS0+ahOZGCTBGHtKTcC9"
813
+ src="https://cdn.p2party.com/@0.14.10/p2party.min.js"
814
+ integrity="sha384-z5PjsRDKjtPcQ9JhEwkPyr/7zthzQgjjh6pSTdjTmhZrwVhhhFS/KhRkGtmJwqJX"
820
815
  crossorigin="anonymous"
821
816
  ></script>
822
817
 
@@ -855,7 +850,7 @@ https://cdn.p2party.com/@<version>/libcrypto.wasm the cryptographic module
855
850
  The `integrity` value above is this release's bundle, and the release build
856
851
  fails if the README and the built artifact ever disagree — so it is safe to
857
852
  copy verbatim. The worker, if you host it yourself, is
858
- `integrity=sha384-BudWf1U+vDU/j4pf2sX+bLogS/7MuPyzKRA9AfLoZqdbduUCwYr42p38rEoQRXa8`.
853
+ `integrity=sha384-iZfBJVoMgpK1+s2shZQucC0OUSBaX4eHMTjHj4NqzGApUodsP9T16+EHgyDKTVQP`.
859
854
 
860
855
  The WASM is integrity-checked whether or not you pin the script: that hash is
861
856
  compiled into the bundle and cannot be turned off.
@@ -894,7 +889,7 @@ immutable, versioned path, so a URL names exactly one build and is safe to
894
889
  cache forever:
895
890
 
896
891
  ```sh
897
- curl -O "https://cdn.p2party.com/@$(npm view p2party version)/libcrypto.wasm"
892
+ curl --compressed -O "https://cdn.p2party.com/@$(npm view p2party version)/libcrypto.wasm"
898
893
  ```
899
894
 
900
895
  Two caveats, both current as of this release. The CDN carries only three
@@ -905,6 +900,14 @@ the tarball. And nothing is uploaded for this release yet, which is why the
905
900
  command above asks npm for a version that is actually published instead of
906
901
  naming this one.
907
902
 
903
+ New CDN uploads gzip all three objects; WASM keeps `Content-Type:
904
+ application/wasm` and uses `Content-Encoding: gzip`. Browsers decode it before
905
+ SRI verification, and `curl --compressed` saves those same decoded bytes for
906
+ the checks below. Existing raw WASM objects remain untouched when their decoded
907
+ content matches; enabling compression never overwrites a published version.
908
+ Upload preparation compresses the validated artifacts already in `lib/`; it
909
+ does not replace them from a local development crypto build.
910
+
908
911
  Check what you downloaded before you serve it. The SHA-256 and the SRI value
909
912
  are both recorded in the provenance file:
910
913
 
@@ -68,7 +68,7 @@ SOFTWARE.
68
68
 
69
69
  ## Emscripten-generated runtime
70
70
 
71
- The WebAssembly and JavaScript glue are built with Emscripten 6.0.2.
71
+ The WebAssembly and JavaScript glue are built with Emscripten 6.0.9.
72
72
 
73
73
  Verbatim upstream license:
74
74
 
@@ -416,11 +416,6 @@ The browser bundle and/or installed runtime dependency graph includes:
416
416
  - `redux-thunk` 3.1.0 — Copyright (c) 2015-present Dan Abramov
417
417
  - `reselect` 5.1.1 — Copyright (c) 2015-2018 Reselect Contributors
418
418
  - `immer` 11.1.15 — Copyright (c) 2017 Michel Weststrate
419
- - `class-validator` 0.15.1 — Copyright (c) 2015-2020 TypeStack
420
- - `validator` 13.15.23 — Copyright (c) 2018 Chris O'Hara
421
- `<cohara87@gmail.com>`
422
- - `libphonenumber-js` 1.12.27 — Copyright (c) 2016 @catamphetamine
423
- `<purecatamphetamine@gmail.com>`
424
419
 
425
420
  Each component above is MIT-licensed. Verbatim grant and disclaimer:
426
421
 
@@ -456,3 +451,35 @@ Permission to use, copy, modify, and/or distribute this software for any purpose
456
451
 
457
452
  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
458
453
  ```
454
+
455
+ ## Emscripten TypeScript declarations
456
+
457
+ The module-scoped `EmscriptenModule` and factory types are adapted from
458
+ `@types/emscripten` 1.41.5 in DefinitelyTyped. All inherited LibCrypto members
459
+ are retained; unrelated global declarations are not included.
460
+
461
+ Verbatim upstream license:
462
+
463
+ ```text
464
+ MIT License
465
+
466
+ Copyright (c) Microsoft Corporation.
467
+
468
+ Permission is hereby granted, free of charge, to any person obtaining a copy
469
+ of this software and associated documentation files (the "Software"), to deal
470
+ in the Software without restriction, including without limitation the rights
471
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
472
+ copies of the Software, and to permit persons to whom the Software is
473
+ furnished to do so, subject to the following conditions:
474
+
475
+ The above copyright notice and this permission notice shall be included in all
476
+ copies or substantial portions of the Software.
477
+
478
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
479
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
480
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
481
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
482
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
483
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
484
+ SOFTWARE
485
+ ```
@@ -109,6 +109,14 @@ bytes are deliberately absent from the public policy, Redux, persistent room
109
109
  records, and logs. PIN mode adds CPace authentication to the identity and
110
110
  ML-KEM handshake; it does not replace identity possession.
111
111
 
112
+ The SDK retries an unexpectedly closed signaling socket with bounded backoff.
113
+ Let that controller own recovery: do not call `connect()` on every disconnected
114
+ render or browser focus event. Call it for initial admission and a deliberate
115
+ retry, such as entering a corrected PIN. Terminal PIN or policy failures stay
116
+ visible and do not trigger automatic peer discovery or transport retries.
117
+ A fresh connection request from the other peer can retry that peer's failed
118
+ edge; the existing PIN-attempt throttle still applies.
119
+
112
120
  Scheduled timing cover is wired as of 0.14: a policy may pin `coverMode:
113
121
  "scheduled"` with a cadence, lane count, and frames per cell, and every edge in
114
122
  the room then emits fixed-size cells on that schedule whether or not data is
@@ -117,8 +125,10 @@ their live transport wiring is not complete.
117
125
 
118
126
  ## Send, cancel, and read
119
127
 
120
- Wait until the room has peers before sending. Each logical send has a random
121
- transfer ID and opens a per-message data channel on every eligible peer edge.
128
+ Wait until the room has known recipient identities before sending. Each logical
129
+ send has a random transfer ID. Immediate mode opens a per-message data channel
130
+ on each eligible edge; scheduled mode places its chunks into the existing
131
+ fixed-cadence cover lanes.
122
132
 
123
133
  ```ts
124
134
  const handle = p2party.sendMessage("hello room", "chat", room.id);
@@ -147,8 +157,97 @@ void cancel; // Remove when a UI event uses it.
147
157
 
148
158
  `sendMessage()` returns a `MessageTransferHandle`, not a promise. `done`
149
159
  settles after all started peer sends and cleanup and reports ordered per-peer
150
- outcomes. A peer may be delivered, failed during setup/transfer, or skipped
151
- because it is disconnected, unauthenticated, or the transfer was cancelled.
160
+ outcomes for that attempt. A peer may be delivered, failed during setup/transfer,
161
+ or skipped because it is disconnected, unauthenticated, or the transfer was
162
+ cancelled. A rejected attempt can still have a durable pending transfer: do not
163
+ label it delivered, or assume that its resend source was discarded.
164
+
165
+ The sender publishes its outbox record only after every staged chunk, the
166
+ Merkle tree, and its local message copy have committed. A reload before that
167
+ point cannot resume preparation; select the original file again. After
168
+ publication, a reload or an exhausted reconnect budget retains the original
169
+ transfer ID, Merkle root and staged bytes for retry. Nothing is rehashed or
170
+ randomly repadded on a resumed send.
171
+
172
+ ```ts
173
+ import type { PendingMessage } from "p2party";
174
+
175
+ const pending: PendingMessage[] = await p2party.listPendingMessages(room.id);
176
+ for (const message of pending) {
177
+ console.log(message.filename, message.phase, message.remainingRecipients);
178
+ }
179
+
180
+ // Use a deliberate retry button. It may take the room's scheduled delivery
181
+ // time to settle; leave cancellation available while this promise is pending.
182
+ try {
183
+ const attempts = await p2party.resumePendingMessages(room.id);
184
+ for (const attempt of attempts) console.table(attempt.outcomes);
185
+ } catch (error) {
186
+ // AggregateError.errors contains failed attempts; their pending records stay
187
+ // available unless explicitly cancelled or expired.
188
+ console.error(error);
189
+ }
190
+
191
+ // Exact logical-send cancellation also works after the original handle was
192
+ // lost to a reload. It fences queued writes before removing retained staging.
193
+ if (pending[0])
194
+ await p2party.cancelPendingMessage(room.id, pending[0].transferId);
195
+ ```
196
+
197
+ Authenticated-peer events trigger a serialized retry pass, including a bounded
198
+ claim after an old tab's lease expires. A failed pass does not start an endless
199
+ retry timer. Explicit retry requires verified signaling and a matching
200
+ currently authenticated recipient; it never calls `connect()`, prompts for a
201
+ passkey, or supplies a PIN. A PIN room must first be admitted with the user's
202
+ current in-memory PIN. PIN and policy failures remain terminal until a deliberate
203
+ authentication retry.
204
+
205
+ The immutable outbox binds the sender's public key and durable identity
206
+ revision, room capability and canonical policy, channel, and recipient public
207
+ keys. Session peer IDs may change. Only one tab can own a transfer's 30-second
208
+ lease at a time; it renews every 10 seconds. A stale tab cannot publish queued
209
+ writes or renew a replacement owner's lease. Terminal signaling refusal stops
210
+ its current owners, and switching identity invalidates the old outbox.
211
+
212
+ Confirmed recipients are recorded separately and never retried. A recipient's
213
+ explicit cancellation also ends retries to that recipient without cancelling
214
+ other recipients. Immediate retries rebuild the have-set from authenticated
215
+ receipt replay, so older receivers that discarded a closed channel's partial
216
+ can still recover. Scheduled retries preserve the receipt bitmap and send
217
+ missing chunks plus at most one already-received real chunk as a completion
218
+ probe. The fixed cover cadence and lane count remain unchanged.
219
+
220
+ Retention and admission have explicit limits:
221
+
222
+ - Published pending transfers expire 24 hours after publication. Active owners
223
+ are protected while they run; a completed recipient is never reported as a
224
+ new delivery merely because staging remains.
225
+ - Interrupted preparation expires after one hour. Its `phase` remains
226
+ `"staging"`; an expired `retryAfter` means its owner is gone, not that the
227
+ unfinished file can resume.
228
+ - At most 32 pending transfers and 64 recipients per transfer are admitted.
229
+ Their aggregate storage reservation is capped at 12 GiB and charges padded
230
+ cells, metadata, proofs and the sender's local copy. Browser quota can impose
231
+ a smaller limit than the 10 GiB per-message protocol maximum. Admission
232
+ refuses excess work rather than evicting another pending send.
233
+ - Incomplete received transfers expire after 24 hours without local receive
234
+ activity. Cleanup uses the device's activity clock, not the sender's message
235
+ timestamp. It protects active writes, completed messages and sender copies,
236
+ checks the room/receive owner, and fences writes queued before cancellation.
237
+
238
+ Storage sweeps run when pending transfers are listed or an authentication event
239
+ checks for resumable work. Maintenance runs immediately on the first check,
240
+ then at most once every 30 seconds, and defers while receive queues are busy.
241
+ Committed expiry also removes the old room view and retires its mapped receive
242
+ key, preserving newer receives and connection generations.
243
+ Cancelling or deleting a message removes its pending
244
+ outbox; leaving a room discards that room's pending work. Successful completion
245
+ removes resend staging while retaining completed local history.
246
+
247
+ The outbox reuses the already-staged message bytes in browser storage; it is
248
+ not a new encryption-at-rest guarantee for files or message history. It stores
249
+ no PIN, passkey assertion, plaintext ratchet key or message key. A resumed send
250
+ derives fresh message encryption state from the currently authenticated edge.
152
251
 
153
252
  For an inbound message, take `merkleRootHex` (and, if needed, `sha512Hex`) from
154
253
  the room's exported `messages` state:
@@ -17,9 +17,9 @@ There are three different acknowledgements in the system:
17
17
  2. The protocol-v4 handshake runs over the open main channel. After HELLO,
18
18
  responder CONFIRM, initiator CONFIRM, and responder FINISH, the peers have
19
19
  authenticated the same hybrid root and initial ratchet keys.
20
- 3. Authenticated 65-byte receipt frames acknowledge message chunks and final
21
- transfer completion. They are delivery/reconciliation signals, not handshake
22
- establishment.
20
+ 3. Immediate 65-byte receipts on authenticated message channels, or encrypted
21
+ scheduled control cells, acknowledge chunks and transfer completion. They
22
+ are delivery/reconciliation signals, not handshake establishment.
23
23
 
24
24
  An open data channel therefore is necessary transport readiness, not an
25
25
  established p2party crypto session.
@@ -66,15 +66,65 @@ byte so the sparse post-quantum healing epoch cannot wrap.
66
66
 
67
67
  The fixed frame geometry absorbs the ratchet and AEAD overhead into the cell
68
68
  budget. Random padding and decoy slots can hide the exact payload length within
69
- one transfer's chosen number of frames. Receipts have a distinct fixed 65-byte
70
- geometry. Handshake flights are suite- and step-dependent rather than
71
- 65,490-byte cells.
69
+ one transfer's chosen number of frames. Immediate receipts have a distinct
70
+ 65-byte geometry and immediate pause/cancel controls are 66 bytes. Scheduled
71
+ receipt batches, terminal receipts and CANCELs occupy existing 65,490-byte
72
+ cover slots. Handshake flights are suite- and step-dependent.
72
73
 
73
74
  Per-message channels give each transfer an independent lifecycle for
74
75
  cancellation, bounded channel accounting, receipts, selective retransmission,
75
76
  and reconnect resume. Channel isolation is a UX and reliability property; it
76
77
  does not make timing or channel count invisible.
77
78
 
79
+ ### Durable selective resume and cancellation
80
+
81
+ A chunk acknowledgement becomes live only after its per-recipient outbox
82
+ update commits. Receipt acceptance binds room, peer ID, peer public key,
83
+ random transfer ID, Merkle root and original chunk index. Scheduled cells
84
+ add direction, PQ epoch, AEAD and counter replay checks; their terminal token
85
+ must equal the transfer root. Stale asynchronous storage responses cannot
86
+ advance a replacement owner's live state. Recipient refusal is persisted
87
+ separately from completion and stops only that recipient's retry.
88
+
89
+ Scheduled chunk receipts use a separate encrypted subtype with up to 64 tokens
90
+ per root. Existing subtype 3 remains terminal-only: older clients interpreted
91
+ any token in that subtype as completion. Older protocol-v4 clients drop the
92
+ new subtype and retain terminal-only scheduled delivery, including full replay
93
+ when necessary. Selective scheduled resume therefore requires supporting peers.
94
+
95
+ Have-set membership is O(1) in memory and does not allocate a set copy per
96
+ scheduled slot. A resume scans indices once, sends missing real chunks and
97
+ may send one completion probe. Storage lookup costs, initial file preparation
98
+ and scanning remain; this is not a claim of sublinear total file processing.
99
+ Batching and selective resume reduce real work within the existing schedule,
100
+ whose observable cell count remains fixed.
101
+
102
+ Immediate resume uses the channel protocol `p2party-resume-v1` and a capability
103
+ echo to distinguish pause from explicit cancellation. Type-6 controls are
104
+ authenticated by the concrete DTLS/SCTP channel and current identity gate,
105
+ with an exact-root check; they do not have a separate application AEAD.
106
+ Legacy peers retain close-as-cancel behavior. A fresh immediate attempt
107
+ rebuilds its have-set from paced receiver receipts instead of assuming a
108
+ legacy receiver retained its partial. Remote cancellation drains pending
109
+ writes, then atomically checks room, sender and incomplete status before
110
+ deleting receiver data; completed files and sender self-copies are preserved.
111
+
112
+ ### Sparse-PQ healing during scheduled sends
113
+
114
+ Local healing waits for queued or admitted real jobs, message channels,
115
+ derive-to-enqueue reservations and queued receive work to drain. An atomic
116
+ application reservation closes the race between deriving a message key and
117
+ admitting its scheduled producer. Dummy cover does not block healing.
118
+
119
+ An incoming exchange blocks new admission and drains reservations, then
120
+ persists its new epoch before adopting it. Epoch adoption invalidates old-key
121
+ real producers on the same scheduler and wakes bounded sender recovery.
122
+ Recovery requires an actual epoch advance when reusing that runtime and
123
+ re-derives the missing chunks. If sealing was already in flight, its retired
124
+ cell is wiped and the slot remains dummy; pending explicit CANCEL is resealed
125
+ under the new epoch. Lane timing continues throughout this transition.
126
+ Persistence failure leaves the prior live epoch unchanged for exact retry.
127
+
78
128
  ## Guarantees, assuming authenticated peer keys
79
129
 
80
130
  With a correctly pinned peer Ed25519 identity, uncompromised endpoints, matching
@@ -128,7 +178,7 @@ receives its normalized value. A fragment is not a server-blind meeting point.
128
178
 
129
179
  ## Signaling and TURN correctness boundary
130
180
 
131
- SDK 0.14.7 makes legacy signaling state transitions correlated and bounded; it
181
+ SDK 0.14.10 makes legacy signaling state transitions correlated and bounded; it
132
182
  does not make that signaling path blind.
133
183
 
134
184
  - A room response is accepted only for the exact current socket, room
@@ -172,6 +172,6 @@ third-party security audit. Cite the implementation:
172
172
  @software{p2party,
173
173
  title = {p2party: protocol-v4 end-to-end encryption over a WebRTC room mesh},
174
174
  url = {https://github.com/p2party/p2party-js},
175
- note = {Version 0.14.7}
175
+ note = {Version 0.14.10}
176
176
  }
177
177
  ```
@@ -381,6 +381,15 @@ The bootstrap ML-KEM exchange protects the initial root. Healing periodically
381
381
  re-runs it so a later post-quantum compromise cannot unwind an old session.
382
382
  The session owns the state machine; the caller owns scheduling and transport.
383
383
 
384
+ The browser SDK separately coordinates queued scheduled producers with epoch
385
+ changes, persists per-recipient chunk receipts, and implements WebRTC pause and
386
+ CANCEL controls. Those transport/outbox services are not part of
387
+ `p2party/session`. A session integrator must coordinate its own pending output
388
+ and recovery when `pqEpoch` changes; the session does not selectively resend
389
+ previously returned frames or interpret cover receipt subtypes. See the
390
+ [wire format](wire-format.md#scheduled-control-cells--65490-bytes) for the browser
391
+ contract.
392
+
384
393
  Control frames are the same 65,490-byte size as chunk frames, so the outer
385
394
  framing MUST record which kind a record is — the session will reject a control
386
395
  frame handed to `decrypt()` and vice versa.
@@ -1,26 +1,29 @@
1
1
  # Wire format
2
2
 
3
- Every byte here is derived from
4
- [`src/utils/constants.ts`](../src/utils/constants.ts), which is the single
5
- source of truth and is byte-matched in `utils.h`. For what these frames do and
6
- do not protect, read the
3
+ Chunk geometry is defined in
4
+ [`src/utils/constants.ts`](../src/utils/constants.ts) and byte-matched in
5
+ `utils.h`. Scheduled control subtypes are defined in
6
+ [`coverCell.ts`](../src/cryptography/coverCell.ts); immediate transfer controls
7
+ are defined in [`immediateDisposition.ts`](../src/handlers/immediateDisposition.ts).
8
+ For what these frames do and do not protect, read the
7
9
  [protocol-v4 security boundary](protocol-v4-security.md).
8
10
 
9
-
10
11
  ## Outer frame types
11
12
 
12
13
  One tag byte leads every frame on a data channel.
13
14
 
14
- | Tag | Name | Size on the wire | Carries |
15
- | --- | ------------ | ---------------- | ------------------------------- |
16
- | 1 | `HANDSHAKE` | step-dependent | HELLO, CONFIRM, FINISH |
17
- | 2 | `CHUNK` | 65,490 B | one fixed application cell |
18
- | 3 | `RECEIPT` | 65 B | SHA-512 acknowledgement token |
19
- | 4 | `COVER` | 65,490 B | a scheduled cell, real or decoy |
20
- | 5 | `PQ_CONTROL` | 65,490 B | sparse-PQ OFFER / ADVANCE / ACK |
15
+ | Tag | Name | Size on the wire | Carries |
16
+ | --- | ----------------------- | ---------------- | ------------------------------------- |
17
+ | 1 | `HANDSHAKE` | step-dependent | HELLO, CONFIRM, FINISH |
18
+ | 2 | `CHUNK` | 65,490 B | one fixed application cell |
19
+ | 3 | `RECEIPT` | 65 B | SHA-512 acknowledgement token |
20
+ | 4 | `COVER` | 65,490 B | scheduled dummy or encrypted control |
21
+ | 5 | `PQ_CONTROL` | 65,490 B | sparse-PQ OFFER / ADVANCE / ACK |
22
+ | 6 | `IMMEDIATE_DISPOSITION` | 66 B | immediate resume capability or CANCEL |
21
23
 
22
- Tags 2, 4, and 5 are deliberately identical in size. An observer cannot tell an
23
- application cell from a decoy or from a healing exchange by looking at the wire.
24
+ Tags 2, 4, and 5 have equal payload lengths. The frame tags travel inside
25
+ WebRTC's DTLS transport; packetization, timing and total traffic remain
26
+ observable. Scheduled real data uses tag 2 in a scheduled lane slot.
24
27
 
25
28
  ## Chunk frame — 65,490 bytes
26
29
 
@@ -52,6 +55,81 @@ application cell from a decoy or from a healing exchange by looking at the wire.
52
55
 
53
56
  Both per-chunk acknowledgements and the terminal content-hash acknowledgement
54
57
  use this exact geometry, so a completion is not distinguishable by size.
58
+ Chunk tokens bind the transfer Merkle root, original chunk index and leaf hash.
59
+ The sender resolves them through its staged chunk index and persists the
60
+ per-recipient acknowledgement before updating the live have-set. A terminal
61
+ immediate receipt must equal the message's content hash. These frames use the
62
+ authenticated concrete message channel and its current identity gate.
63
+
64
+ ## Scheduled control cells — 65,490 bytes
65
+
66
+ ```text
67
+ type=4(1) || edge binding(32) || counter(8) || reserved zero(8) ||
68
+ PQ epoch(8) || nonce(12) || ciphertext(65,405) || AEAD tag(16)
69
+ ```
70
+
71
+ All 69 header bytes are authenticated. The key binds the room's suite, edge,
72
+ direction and PQ epoch. After authentication and counter replay rejection,
73
+ the plaintext is `subtype(1) || payload length(4, BE) || payload || zero pad`:
74
+
75
+ | Subtype | Meaning | Payload |
76
+ | ------- | ---------------- | ---------------------------------------------------------------------- |
77
+ | 1 | Dummy | Empty |
78
+ | 2 | CANCEL | Transfer Merkle root (64 B) |
79
+ | 3 | Terminal receipt | Transfer Merkle root (64 B), followed by the same root as token (64 B) |
80
+ | 4 | Chunk receipts | Transfer Merkle root (64 B), followed by 1–64 chunk tokens (64 B each) |
81
+
82
+ Subtype 4's authenticated payload length determines the token count; partial
83
+ tokens, empty batches and batches over 64 tokens are rejected. Only tokens for
84
+ one transfer root share a batch. A subtype 3 token unequal to its root cannot
85
+ complete a send. Each chunk token must resolve to the tracked transfer, root
86
+ and a valid original index. The durable owner also binds room, recipient ID
87
+ and public key; a stale owner cannot advance live state after a storage await.
88
+ A recipient's CANCEL records refusal only for that recipient.
89
+
90
+ Receipts and CANCELs replace dummy slots in the existing schedule. The pending
91
+ outbound control queue is bounded to 4,096 token/control units; dropping a
92
+ queued acknowledgement may require a later retransmission. A terminal receipt
93
+ supersedes unsent chunk acknowledgements for the same root, preserving the
94
+ first available reverse slot for completion even at long cadences. Other roots
95
+ and queued CANCEL controls retain their order. Live have-set
96
+ membership uses an O(1) `Set` lookup without copying the set per slot. A resumed
97
+ producer scans original indices once and emits missing chunks. It also selects
98
+ index zero once per attempt to recover a lost terminal receipt: if that index
99
+ was already acknowledged, this costs one extra completion probe. Index scanning
100
+ and durable token lookups do not imply globally sublinear processing, and fewer
101
+ real chunks do not reduce the fixed scheduled cell count.
102
+
103
+ Subtype 4 is a protocol-v4 wire addition. Earlier clients reject and drop this
104
+ unknown subtype and keep using subtype 3 completion. They cannot selectively
105
+ resume scheduled chunks; a missing terminal receipt can require whole-transfer
106
+ replay. Subtype 3 is never reused for chunk tokens, because earlier clients
107
+ treated any subtype 3 receipt for a tracked root as completion. These additions
108
+ do not introduce a fallback to an earlier cryptographic protocol version.
109
+
110
+ ## Immediate pause and cancellation — 66 bytes
111
+
112
+ ```text
113
+ type=6(1) || kind(1) || transfer Merkle root(64)
114
+ ```
115
+
116
+ Kind 0 announces resume support; kind 1 is explicit CANCEL. An immediate
117
+ message channel opened with DCEP protocol `p2party-resume-v1` promises that a
118
+ bare close may pause the transfer. A supporting receiver echoes kind 0 after
119
+ the identity gate opens; the opener then also treats a bare remote close as
120
+ pause. CANCEL is accepted once per concrete channel, only under its current
121
+ authenticated gate and exact root. Malformed, unknown, stale and cross-root
122
+ controls have no effect. This control relies on the authenticated DTLS/SCTP
123
+ channel; it has no separate application AEAD envelope.
124
+
125
+ An explicit cancellation sends kind 1 before closing. A transient failed
126
+ attempt closes without CANCEL, allowing a supporting receiver to retain its
127
+ partial data for bounded resume. Earlier clients ignore type 6 and retain
128
+ their close-as-cancel behavior. At a fresh immediate attempt the new sender
129
+ starts a fresh live chunk have-set and rebuilds it from the receiver's paced
130
+ receipt replay, so deleted legacy partials cause full resend
131
+ instead of trusting stale acknowledgements. The store-free session API does
132
+ not implement this WebRTC channel contract.
55
133
 
56
134
  ## Handshake ladder
57
135
 
@@ -88,6 +166,16 @@ rather than something both sides have to reconcile. Each side persists its
88
166
  mutated state **before** dispatching, and application traffic is blocked while
89
167
  an epoch is in flight.
90
168
 
169
+ The browser scheduler starts a local exchange only when no real producer,
170
+ derive-to-enqueue reservation, message channel or queued receive work remains.
171
+ Dummy/control cover alone does not hold it back. A remote exchange blocks new
172
+ application admission and drains reservations before the durable transition;
173
+ it does not wait for a long scheduled send to finish. A committed new epoch
174
+ retires old-key real producers and publishes a cover generation change on the
175
+ same runtime. The sender re-derives the remaining transfer under that epoch;
176
+ fixed lane timing continues with dummy tails. An unrelated generation notice
177
+ cannot authorize reuse of the same runtime without an actual PQ epoch advance.
178
+
91
179
  ## Room policy — 32 bytes
92
180
 
93
181
  A room's policy is a fixed 32-byte record (magic `"P2RP"`) that pins the ML-KEM
@@ -8,6 +8,44 @@ export interface WebSocketParams {
8
8
  export interface WebSocketMessage {
9
9
  content: WebSocketMessagePongResponse | WebSocketMessageCandidateSend | WebSocketMessageDescriptionSend | WebSocketMessageChallengeResponse | WebSocketMessageRoomIdRequest | WebSocketMessageLeaveRoomRequest | WebSocketMessagePeersRequest | WebSocketMessageConnectionResponse | WebSocketMessagePeerConnectionRequest;
10
10
  }
11
+ /**
12
+ * The host the reconnect controller reads Redux state from and dispatches
13
+ * through. Bound on every connect attempt; the tests rebind it directly. It is
14
+ * NOT the store import, so this module keeps its existing dependency edges.
15
+ */
16
+ type SignalingReconnectHost = {
17
+ dispatch: (action: unknown) => unknown;
18
+ getState: () => unknown;
19
+ };
20
+ export declare const setSignalingReconnectHost: (host: SignalingReconnectHost | null) => void;
21
+ /**
22
+ * Record how a socket ended, so the reconnect ladder can refuse a close the
23
+ * server means as final. `selfInitiated` is what keeps the SDK's own protective
24
+ * closes out of the policy: `onOverflow` closes with 1008 when this tab's
25
+ * ingress backlog is exceeded, and local overload is exactly what bounded
26
+ * backoff is for.
27
+ */
28
+ export declare const noteSignalingSocketClose: (close: Readonly<{
29
+ code: number;
30
+ selfInitiated: boolean;
31
+ }>) => void;
32
+ /**
33
+ * A ladder rung is a floor, never shortened, and at most 25 % longer. Without
34
+ * this every client a server restart disconnected walks the identical
35
+ * 1/2/4/8 s ladder and re-handshakes in lockstep, which is the same stampede
36
+ * the restart caused. The spread lives here rather than in the controller
37
+ * because the controller's ladder is exact by contract and its tests assert
38
+ * exact delays; the scheduler this module injects is the seam that perturbs it.
39
+ */
40
+ export declare const signalingReconnectDelayWithJitter: (delayMs: number) => number;
41
+ /**
42
+ * C4: a socket close that carries no browser wake event is otherwise terminal.
43
+ * `settleClosedSocket` detects; this reconnects. Shared by the close handler,
44
+ * the error handler and the heartbeat watchdog, so a peer whose socket died
45
+ * while its edges stayed authenticated is found again within 1-30 s instead of
46
+ * never.
47
+ */
48
+ export declare const signalingReconnect: import("../utils/signalingReconnectController").SignalingReconnectController;
11
49
  type CurrentSocketApi = Pick<BaseQueryApi, "dispatch" | "getState">;
12
50
  /**
13
51
  * Send a correlated leave on the exact authenticated socket and wait for its