@ibgib/core-gib 0.1.58 → 0.1.60

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 (96) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts.map +1 -1
  3. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs +6 -2
  4. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs.map +1 -1
  5. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +1 -1
  6. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +1 -1
  7. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  8. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +7 -11
  9. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  10. package/dist/sync/sync-peer/sync-peer-types.d.mts +24 -1
  11. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  12. package/dist/sync/sync-peer/sync-peer-v1.d.mts +15 -4
  13. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  14. package/dist/sync/sync-peer/sync-peer-v1.mjs +120 -25
  15. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  16. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.d.mts +46 -0
  17. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.d.mts.map +1 -0
  18. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mjs +45 -0
  19. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mjs.map +1 -0
  20. package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-types.d.mts +2 -2
  21. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +1 -0
  22. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +1 -0
  23. package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-v1.d.mts +11 -8
  24. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +1 -0
  25. package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-v1.mjs +115 -61
  26. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -0
  27. package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.d.mts +3 -3
  28. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -0
  29. package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.mjs +6 -6
  30. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -0
  31. package/dist/sync/sync-peer/{sync-peer-websocket-sender → sync-peer-websocket/sync-peer-websocket-sender}/sync-peer-websocket-sender-types.d.mts +1 -1
  32. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +1 -0
  33. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +1 -0
  34. package/dist/sync/sync-peer/{sync-peer-websocket-sender → sync-peer-websocket/sync-peer-websocket-sender}/sync-peer-websocket-sender-v1.d.mts +22 -4
  35. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -0
  36. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +447 -0
  37. package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -0
  38. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +22 -5
  39. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  40. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +263 -28
  41. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  42. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +13 -0
  43. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  44. package/dist/sync/sync-saga-coordinator.d.mts +12 -1
  45. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  46. package/dist/sync/sync-saga-coordinator.mjs +106 -12
  47. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  48. package/dist/sync/sync-types.d.mts +24 -0
  49. package/dist/sync/sync-types.d.mts.map +1 -1
  50. package/dist/sync/sync-types.mjs +0 -1
  51. package/dist/sync/sync-types.mjs.map +1 -1
  52. package/dist/sync/sync-withid.connect.respec.mjs +3 -3
  53. package/dist/sync/sync-withid.connect.respec.mjs.map +1 -1
  54. package/dist/sync/sync-withid.pingpong.respec.d.mts +11 -0
  55. package/dist/sync/sync-withid.pingpong.respec.d.mts.map +1 -0
  56. package/dist/sync/sync-withid.pingpong.respec.mjs +199 -0
  57. package/dist/sync/sync-withid.pingpong.respec.mjs.map +1 -0
  58. package/dist/witness/space/inner-space/inner-space-v1.d.mts.map +1 -1
  59. package/dist/witness/space/inner-space/inner-space-v1.mjs +1 -1
  60. package/dist/witness/space/inner-space/inner-space-v1.mjs.map +1 -1
  61. package/package.json +1 -1
  62. package/src/sync/docs/security-3b.md +92 -0
  63. package/src/sync/docs/security.md +107 -39
  64. package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +6 -2
  65. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +1 -1
  66. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +11 -14
  67. package/src/sync/sync-peer/sync-peer-types.mts +28 -1
  68. package/src/sync/sync-peer/sync-peer-v1.mts +127 -35
  69. package/src/sync/sync-peer/sync-peer-websocket/README.md +42 -0
  70. package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mts +68 -0
  71. package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-types.mts +2 -2
  72. package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-v1.mts +128 -71
  73. package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.mts +8 -8
  74. package/src/sync/sync-peer/{sync-peer-websocket-sender → sync-peer-websocket/sync-peer-websocket-sender}/sync-peer-websocket-sender-types.mts +1 -1
  75. package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +509 -0
  76. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +267 -36
  77. package/src/sync/sync-saga-context/sync-saga-context-types.mts +14 -0
  78. package/src/sync/sync-saga-coordinator.mts +148 -8
  79. package/src/sync/sync-types.mts +28 -4
  80. package/src/sync/sync-withid.connect.respec.mts +3 -3
  81. package/src/sync/sync-withid.pingpong.respec.mts +234 -0
  82. package/src/witness/space/inner-space/inner-space-v1.mts +4 -5
  83. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +0 -1
  84. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +0 -1
  85. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +0 -1
  86. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +0 -1
  87. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +0 -1
  88. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +0 -1
  89. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +0 -1
  90. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +0 -1
  91. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +0 -1
  92. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +0 -282
  93. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +0 -1
  94. package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +0 -321
  95. /package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-types.mjs +0 -0
  96. /package/dist/sync/sync-peer/{sync-peer-websocket-sender → sync-peer-websocket/sync-peer-websocket-sender}/sync-peer-websocket-sender-types.mjs +0 -0
@@ -0,0 +1,42 @@
1
+ # WebSocket Sync Peers
2
+
3
+ This directory contains the WebSocket client (sender) and server (receiver) peer implementations.
4
+
5
+ ## WebSocket Resource Management & Teardown
6
+
7
+ Since WebSockets manage active OS-level TCP connections, they must be cleanly closed to prevent socket descriptor leaks on the client and resource exhaustion (DoS) on the server.
8
+
9
+ ### Analysis (2026-06-12T09:45:21-05:00)
10
+
11
+ Our analysis highlights several areas where WebSocket connections are currently left open:
12
+
13
+ 1. **Sender Connect Failures**: If connection handshake fails or times out, the client rejects the promise but does not close the socket.
14
+ 2. **Sender Runtime Failures**: If runtime message validation fails, we throw an error but leave the socket open.
15
+ 3. **Sender Saga Completion**: When the sync loop finishes successfully, the socket is left open indefinitely because there is no `disconnect` method.
16
+ 4. **Receiver Message Handling Failures**: If incoming messages fail validation/authentication, the server sends a `sync_error` or `auth_fail` frame, but relies on the client to close the connection. A buggy/malicious client can leave it open forever.
17
+
18
+ #### Design Questions & Ideas
19
+ * **Idempotent `disconnect()`**: The disconnect method on the sender should clear `this.handshakeMessageListener` and set it to `undefined` so that multiple calls to `disconnect()` are safe.
20
+ * **Reuse `disconnect()`**: In connection failure handlers (like `handleHandshakeAuthFail`), we can call `this.disconnect()` to ensure uniform cleanup.
21
+ * **Server-side Close on Error (best-effort `send`)**: If a message fails, we want to notify the client first and then close the connection. If the `send()` itself throws, we want a safe cleanup. In JavaScript, we can nest `try..catch..finally` inside a `catch` block, or use a short `setTimeout` to push the socket close operation to the next event loop tick, ensuring the outgoing frame is written before the socket is terminated.
22
+
23
+ ---
24
+
25
+ ### Implementation TODOs
26
+
27
+ - [x] **Implement `disconnect` method on `SyncPeerWebSocketSender_V1`**
28
+ - Implement a synchronous or asynchronous `disconnect()` method.
29
+ - Safely remove the message event listener `this.handshakeMessageListener`.
30
+ - Set `this.handshakeMessageListener = undefined` to make it idempotent.
31
+ - Close the WebSocket `this.ws.close()`.
32
+
33
+ - [x] **Clean up socket on Sender connection failures**
34
+ - Call `this.disconnect()` inside `handleHandshakeAuthFail`.
35
+ - Call `this.disconnect()` inside `handleHandshakeSyncError`.
36
+
37
+ - [x] **Close socket on Sender runtime message validation failures**
38
+ - In `handleRuntimeMessage` and `handleRuntimeSyncFrameResponse`, catch validation errors, trigger `this.disconnect()`, and rethrow.
39
+
40
+ - [x] **Close socket on Receiver message handling failures**
41
+ - In the catch block of `handleIncomingMessage`, call `this.socketWrapper?.close()` (or equivalent wrapper cleanup) to force-close connections after sending error frames.
42
+ - Wrap the `send()` call in a nested try/catch or use `setTimeout` to ensure best-effort message transmission before terminating the socket wrapper.
@@ -0,0 +1,68 @@
1
+ // #region SyncWebSocketMsgType enum
2
+
3
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE_INIT = 'auth-challenge-init';
4
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_INIT = 'auth-init';
5
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE = 'auth-challenge';
6
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_PROOF = 'auth-proof';
7
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_OK = 'auth-ok';
8
+ export const SYNC_WEB_SOCKET_MSG_TYPE_AUTH_FAIL = 'auth-fail';
9
+ export const SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME = 'sync-frame';
10
+ export const SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE = 'sync-frame-response';
11
+ export const SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_AUTHENTICATED = 'sync-frame-authenticated';
12
+ export const SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE_AUTHENTICATED = 'sync-frame-response-authenticated';
13
+ export const SYNC_WEB_SOCKET_MSG_TYPE_DOMAIN_PAYLOAD = 'domain-payload';
14
+ export const SYNC_WEB_SOCKET_MSG_TYPE_SYNC_ERROR = 'sync-error';
15
+
16
+ /**
17
+ * Message types transmitted over the stateful WebSocket connection
18
+ * during a sync saga session handshake and execution.
19
+ */
20
+ export type SyncWebSocketMsgType =
21
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE_INIT
22
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_INIT
23
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE
24
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_PROOF
25
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_OK
26
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_AUTH_FAIL
27
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME
28
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE
29
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_AUTHENTICATED
30
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE_AUTHENTICATED
31
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_DOMAIN_PAYLOAD
32
+ | typeof SYNC_WEB_SOCKET_MSG_TYPE_SYNC_ERROR
33
+ ;
34
+
35
+ export const SyncWebSocketMsgType = {
36
+ /** Sent by server/receiver to trigger connection authentication. */
37
+ auth_challenge_init: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE_INIT,
38
+ /** Sent by client/sender to start the authentication handshake with the target session address. */
39
+ auth_init: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_INIT,
40
+ /** Sent by server/receiver with dynamic connect challenges to be solved. */
41
+ auth_challenge: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_CHALLENGE,
42
+ /** Sent by client/sender with proof of session keystone evolution solving challenges. */
43
+ auth_proof: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_PROOF,
44
+ /** Sent by server/receiver signaling successful challenge resolution and upgrade to active sync. */
45
+ auth_ok: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_OK,
46
+ /** Sent when authentication fails. */
47
+ auth_fail: SYNC_WEB_SOCKET_MSG_TYPE_AUTH_FAIL,
48
+ /** Sent by client/sender to transmit the next sync transaction context without payload ibgibs. */
49
+ sync_frame: SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME,
50
+ /** Sent by server/receiver responding with the next sync transaction context. */
51
+ sync_frame_response: SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE,
52
+ /** Sent by server/receiver after context is validated/authenticated to signal payload transmission. */
53
+ sync_frame_authenticated: SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_AUTHENTICATED,
54
+ /** Sent by client/sender after response context is validated/authenticated to signal payload transmission. */
55
+ sync_frame_response_authenticated: SYNC_WEB_SOCKET_MSG_TYPE_SYNC_FRAME_RESPONSE_AUTHENTICATED,
56
+ /** Sent to stream a single domain payload ibgib. */
57
+ domain_payload: SYNC_WEB_SOCKET_MSG_TYPE_DOMAIN_PAYLOAD,
58
+ /** Sent when a sync runtime execution error occurs. */
59
+ sync_error: SYNC_WEB_SOCKET_MSG_TYPE_SYNC_ERROR,
60
+ } satisfies { readonly [key: string]: SyncWebSocketMsgType };
61
+
62
+ export const SYNC_WEB_SOCKET_MSG_TYPE_VALID_VALUES: SyncWebSocketMsgType[] = Object.values(SyncWebSocketMsgType);
63
+
64
+ export function isSyncWebSocketMsgType(value: any): value is SyncWebSocketMsgType {
65
+ return SYNC_WEB_SOCKET_MSG_TYPE_VALID_VALUES.includes(value);
66
+ }
67
+
68
+ // #endregion SyncWebSocketMsgType enum
@@ -5,8 +5,8 @@ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
5
5
  import {
6
6
  SyncPeerData_V1, SyncPeerRel8ns_V1, SyncPeerWitness,
7
7
  InitializeSyncPeerOpts, ConnectSyncPeerOpts
8
- } from '../sync-peer-types.mjs';
9
- import { SyncSagaCoordinator } from '../../sync-saga-coordinator.mjs';
8
+ } from '../../sync-peer-types.mjs';
9
+ import { SyncSagaCoordinator } from '../../../sync-saga-coordinator.mjs';
10
10
 
11
11
  /**
12
12
  * Data for the SyncPeerWebSocketReceiver witness.
@@ -5,21 +5,25 @@
5
5
  import { extractErrorMsg, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
6
6
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
7
7
  import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
8
-
9
- import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
10
- import { SyncPeer_V1 } from '../sync-peer-v1.mjs';
11
- import { SyncSagaContextIbGib_V1 } from '../../sync-saga-context/sync-saga-context-types.mjs';
12
- import { authenticateContext } from '../../sync-saga-context/sync-saga-context-helpers.mjs';
13
- import { IbGibSpaceAny } from '../../../witness/space/space-base-v1.mjs';
14
- import { putInSpace, registerNewIbGib } from '../../../witness/space/space-helper.mjs';
8
+ import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
9
+
10
+ import { GLOBAL_LOG_A_LOT } from '../../../../core-constants.mjs';
11
+ import { SyncPeer_V1 } from '../../sync-peer-v1.mjs';
12
+ import { SyncSagaContextIbGib_V1 } from '../../../sync-saga-context/sync-saga-context-types.mjs';
13
+ import { authenticateContextIntrinsically } from '../../../sync-saga-context/sync-saga-context-helpers.mjs';
14
+ import { IbGibSpaceAny } from '../../../../witness/space/space-base-v1.mjs';
15
+ import { putInSpace, registerNewIbGib } from '../../../../witness/space/space-helper.mjs';
15
16
  import {
16
17
  ConnectSyncPeerWebSocketReceiverOpts,
17
18
  InitializeSyncPeerWebSocketReceiverOpts,
18
19
  SyncPeerWebSocketReceiverData_V1, SyncPeerWebSocketReceiverRel8ns_V1,
19
20
  SyncPeerWebSocketReceiverIbGib_V1
20
21
  } from './sync-peer-websocket-receiver-types.mjs';
21
- import { KeystoneIbGib_V1 } from '../../../keystone/keystone-types.mjs';
22
+ import { KeystoneIbGib_V1 } from '../../../../keystone/keystone-types.mjs';
23
+ import { SyncWebSocketMsgType } from '../sync-peer-websocket-constants.mjs';
22
24
  import { SESSION_KEYSTONE_POLICY, verifyConnectProof } from './sync-websocket-peer-helpers.mjs';
25
+ import { getFullSyncSagaHistory } from '../../../sync-helpers.mjs';
26
+ import { toDto } from '../../../../common/other/ibgib-helper.mjs';
23
27
 
24
28
  const logalot = GLOBAL_LOG_A_LOT || true;
25
29
 
@@ -30,6 +34,7 @@ export interface IWebSocketWrapper {
30
34
  send(data: string): void;
31
35
  onMessage(callback: (data: string) => void): void;
32
36
  onClose(callback: () => void): void;
37
+ close(): void;
33
38
  }
34
39
 
35
40
  /**
@@ -59,6 +64,11 @@ export class SyncPeerWebSocketReceiver_V1
59
64
  protected demandedIds?: string[];
60
65
  protected sessionS_tjpAddr?: string;
61
66
 
67
+ // Runtime state variables for delayed payload streaming
68
+ protected pendingContext?: SyncSagaContextIbGib_V1;
69
+ protected pendingPayloadAddrs?: Set<string>;
70
+ protected pendingResponsePayloadsToSend: IbGib_V1[] = [];
71
+
62
72
  constructor(
63
73
  initialData: SyncPeerWebSocketReceiverData_V1,
64
74
  initialRel8ns?: SyncPeerWebSocketReceiverRel8ns_V1,
@@ -79,13 +89,13 @@ export class SyncPeerWebSocketReceiver_V1
79
89
  try {
80
90
  this.challengeUuid = await getUUID();
81
91
  socketWrapper.send(JSON.stringify({
82
- type: 'auth-challenge-init',
92
+ type: SyncWebSocketMsgType.auth_challenge_init,
83
93
  challengeUuid: this.challengeUuid
84
94
  }));
85
95
  } catch (error) {
86
96
  console.error(`${lc} failed triggering challenge init: ${extractErrorMsg(error)}`);
87
97
  socketWrapper.send(JSON.stringify({
88
- type: 'auth-fail',
98
+ type: SyncWebSocketMsgType.auth_fail,
89
99
  message: 'Internal server connect error'
90
100
  }));
91
101
  }
@@ -171,41 +181,99 @@ export class SyncPeerWebSocketReceiver_V1
171
181
  }
172
182
 
173
183
  // 2. Authenticated Runtime Sync Route
174
- if (msg.type === 'domain-payload') {
184
+ if (msg.type === SyncWebSocketMsgType.domain_payload) {
175
185
  const ibGib = msg.ibGib as IbGib_V1;
176
- const tempSpace = await this.ensureLocalTempSpace();
177
- await putInSpace({ space: tempSpace, ibGibs: [ibGib] });
178
- } else if (msg.type === 'sync-frame') {
186
+ const validationErrors =
187
+ await validateIbGibIntrinsically({ ibGib }) ?? [];
188
+ if (validationErrors.length > 0) {
189
+ throw new Error(`controlIbGibs invalid intrinsically. validationErrors: ${validationErrors.join('|')} (E: 5ee1787d4cc53d3d2c55f3d4f2865226)`);
190
+ }
191
+
192
+ if (this.pendingContext && this.pendingPayloadAddrs) {
193
+ const addr = getIbGibAddr({ ibGib });
194
+ if (this.pendingPayloadAddrs.has(addr)) {
195
+ const tempSpace = await this.ensureLocalTempSpace();
196
+ await putInSpace({ space: tempSpace, ibGibs: [ibGib] });
197
+ this.pendingPayloadAddrs.delete(addr);
198
+
199
+ // If all expected payloads for the pending context are received
200
+ if (this.pendingPayloadAddrs.size === 0) {
201
+ const context = this.pendingContext;
202
+ this.pendingContext = undefined;
203
+ this.pendingPayloadAddrs = undefined;
204
+
205
+ await this.executeIncomingSyncRequestAndRespond({ context });
206
+ }
207
+ } else {
208
+ console.warn(`${lc} received payload not in expected list: ${addr}`);
209
+ }
210
+ }
211
+ } else if (msg.type === SyncWebSocketMsgType.sync_frame) {
179
212
  const context = msg.context as SyncSagaContextIbGib_V1;
180
213
 
181
- // Process turn through coordinator
182
- const responseCtx = await this.handleIncomingSyncRequest({ context });
183
-
184
- if (responseCtx) {
185
- // Send outgoing payload domain ibgibs first
186
- const responsePayloads = responseCtx.payloadIbGibsDomain ?? [];
187
- for (const ibGib of responsePayloads) {
188
- this.socketWrapper.send(JSON.stringify({
189
- type: 'domain-payload',
190
- ibGib
191
- }));
214
+ // First, validate and authenticate the context
215
+ const allControlIbGibs: IbGib_V1[] = [
216
+ toDto({ ibGib: context }),
217
+ context.sagaFrame,
218
+ context.sagaFrameMsg
219
+ ];
220
+ if (context.signedSessionIdentity) {
221
+ allControlIbGibs.push(context.signedSessionIdentity);
222
+ }
223
+
224
+ for (const controlIbGib of allControlIbGibs) {
225
+ const validationErrors =
226
+ await validateIbGibIntrinsically({ ibGib: controlIbGib }) ?? [];
227
+ if (validationErrors.length > 0) {
228
+ throw new Error(`controlIbGibs invalid intrinsically. validationErrors: ${validationErrors.join('|')} (E: d40dfa87265a0b73c8ef784d1265ea26)`);
192
229
  }
230
+ }
231
+
232
+ await this.authenticateAndValidate({ context });
233
+
234
+ // Put control ibgibs into durable space immediately for audit trail
235
+ for (const ibGib of allControlIbGibs) {
236
+ await putInSpace({ space: this.opts!.localSpace, ibGibs: [ibGib] });
237
+ await registerNewIbGib({ space: this.opts!.localSpace, ibGib });
238
+ }
193
239
 
194
- // Send evolved context turn response
240
+ // Check if payloads are expected
241
+ const expectedPayloadAddrs = context.data?.['@payloadAddrsDomain'] || [];
242
+ if (expectedPayloadAddrs.length > 0) {
243
+ // Set up wait state and request payloads
244
+ this.pendingContext = context;
245
+ this.pendingPayloadAddrs = new Set(expectedPayloadAddrs);
195
246
  this.socketWrapper.send(JSON.stringify({
196
- type: 'sync-frame-response',
197
- context: responseCtx
247
+ type: SyncWebSocketMsgType.sync_frame_authenticated,
248
+ contextAddr: getIbGibAddr({ ibGib: context })
198
249
  }));
199
250
  } else {
200
- if (logalot) { console.log(`${lc} synchronization session completed successfully.`); }
251
+ // No payloads expected, process immediately
252
+ await this.executeIncomingSyncRequestAndRespond({ context });
253
+ }
254
+ } else if (msg.type === SyncWebSocketMsgType.sync_frame_response_authenticated) {
255
+ // Alice authenticated our response context, stream our payloads now
256
+ const payloads = this.pendingResponsePayloadsToSend || [];
257
+ this.pendingResponsePayloadsToSend = [];
258
+ for (const ibGib of payloads) {
259
+ this.socketWrapper.send(JSON.stringify({
260
+ type: SyncWebSocketMsgType.domain_payload,
261
+ ibGib
262
+ }));
201
263
  }
202
264
  }
203
265
  } catch (error) {
204
266
  console.error(`${lc} message frame handling failed: ${extractErrorMsg(error)}`);
205
- this.socketWrapper?.send(JSON.stringify({
206
- type: this.isAuthenticated ? 'sync-error' : 'auth-fail',
207
- message: extractErrorMsg(error)
208
- }));
267
+ try {
268
+ this.socketWrapper?.send(JSON.stringify({
269
+ type: this.isAuthenticated ? SyncWebSocketMsgType.sync_error : SyncWebSocketMsgType.auth_fail,
270
+ message: extractErrorMsg(error)
271
+ }));
272
+ } catch (nestedError) {
273
+ console.error(`${lc}[nested catch] failed to send error frame: ${extractErrorMsg(nestedError)}`);
274
+ } finally {
275
+ this.socketWrapper?.close();
276
+ }
209
277
  }
210
278
  }
211
279
 
@@ -217,7 +285,7 @@ export class SyncPeerWebSocketReceiver_V1
217
285
  const metaspace = this.opts!.localMetaspace;
218
286
  const space = this.opts!.localSpace;
219
287
 
220
- if (msg.type === 'auth-init') {
288
+ if (msg.type === SyncWebSocketMsgType.auth_init) {
221
289
  const { sAddr } = msg;
222
290
  if (logalot) { console.log(`${lc} auth-init for ${sAddr}`); }
223
291
 
@@ -237,12 +305,12 @@ export class SyncPeerWebSocketReceiver_V1
237
305
  this.sessionS_tjpAddr = (past && past.length > 0) ? past[0] : getIbGibAddr({ ibGib: authorizedS });
238
306
 
239
307
  this.socketWrapper!.send(JSON.stringify({
240
- type: 'auth-challenge',
308
+ type: SyncWebSocketMsgType.auth_challenge,
241
309
  challengeUuid: this.challengeUuid,
242
310
  demandedIds: this.demandedIds
243
311
  }));
244
312
 
245
- } else if (msg.type === 'auth-proof') {
313
+ } else if (msg.type === SyncWebSocketMsgType.auth_proof) {
246
314
  const { proofFrame } = msg;
247
315
  if (logalot) { console.log(`${lc} verifying auth-proof...`); }
248
316
 
@@ -267,12 +335,13 @@ export class SyncPeerWebSocketReceiver_V1
267
335
 
268
336
  // Persist the newly validated evolved session keystone tip
269
337
  await metaspace.put({ ibGibs: [proofFrame], space });
338
+ await metaspace.registerNewIbGib({ ibGib: proofFrame, space });
270
339
 
271
340
  if (logalot) { console.log(`${lc} connect validation successful! Connection upgraded to active sync session.`); }
272
341
  this.isAuthenticated = true;
273
342
 
274
343
  this.socketWrapper!.send(JSON.stringify({
275
- type: 'auth-ok'
344
+ type: SyncWebSocketMsgType.auth_ok
276
345
  }));
277
346
  } else {
278
347
  throw new Error(`Unexpected message type ${msg.type} during connect phase (E: f67a0f47f8426c2b01af5bc3d0146b26)`);
@@ -280,57 +349,45 @@ export class SyncPeerWebSocketReceiver_V1
280
349
  }
281
350
 
282
351
  /**
283
- * Executes the transaction turn through the local SyncSagaCoordinator.
352
+ * Executes the incoming sync request through the local SyncSagaCoordinator and sends response.
284
353
  */
285
- public async handleIncomingSyncRequest({
286
- context,
287
- payloadIbGibsControl = [],
354
+ protected async executeIncomingSyncRequestAndRespond({
355
+ context
288
356
  }: {
289
357
  context: SyncSagaContextIbGib_V1;
290
- payloadIbGibsControl?: IbGib_V1[];
291
- }): Promise<SyncSagaContextIbGib_V1 | undefined> {
292
- const lc = `${this.lc}[${this.handleIncomingSyncRequest.name}]`;
358
+ }): Promise<void> {
359
+ const lc = `${this.lc}[${this.executeIncomingSyncRequestAndRespond.name}]`;
293
360
  try {
294
- if (logalot) { console.log(`${lc} starting incoming sync turn...`); }
361
+ if (logalot) { console.log(`${lc} executing incoming sync turn...`); }
295
362
 
296
363
  if (!this.opts) { throw new Error(`opts not initialized. (E: 0c98186714e85b9a08bb9d98daada826)`); }
297
364
  const { localCoordinator, localMetaspace, localSpace } = this.opts;
298
365
  const localTempSpace = await this.ensureLocalTempSpace();
299
366
 
300
- // Put control ibgibs into durable space
301
- const allControlIbGibs = [context, ...payloadIbGibsControl];
302
- for (const ibGib of allControlIbGibs) {
303
- await putInSpace({ space: localSpace, ibGibs: [ibGib] });
304
- await registerNewIbGib({ space: localSpace, ibGib });
305
- }
306
-
307
- // Authenticate context signature
308
- const authErrors = await authenticateContext({
309
- context,
310
- space: localSpace,
311
- });
312
- if (authErrors.length > 0) {
313
- throw new Error(`Context authentication failed: ${authErrors.join(', ')} (E: 424bd9b03ff8a42df8b1a438ed393726)`);
314
- }
315
-
316
- // Put incoming domain payloads into temp space
317
- if (context.payloadIbGibsDomain && context.payloadIbGibsDomain.length > 0) {
318
- for (const ibGib of context.payloadIbGibsDomain) {
319
- await putInSpace({ space: localTempSpace, ibGibs: [ibGib] });
320
- }
321
- }
322
-
323
- // Evolve frame and run next coordinator sync turn
324
367
  const responseCtx = await localCoordinator.continueSync({
325
368
  sagaContext: context,
326
369
  metaspace: localMetaspace,
327
370
  mySpace: localSpace,
328
371
  myTempSpace: localTempSpace,
372
+ peer: this,
329
373
  });
330
374
 
331
- return responseCtx || undefined;
375
+ if (responseCtx) {
376
+ const responsePayloads = responseCtx.payloadIbGibsDomain ?? [];
377
+ delete responseCtx.payloadIbGibsDomain;
378
+
379
+ // Save payloads to stream once authenticated by Alice
380
+ this.pendingResponsePayloadsToSend = responsePayloads;
381
+
382
+ this.socketWrapper!.send(JSON.stringify({
383
+ type: SyncWebSocketMsgType.sync_frame_response,
384
+ context: responseCtx
385
+ }));
386
+ } else {
387
+ if (logalot) { console.log(`${lc} synchronization session completed successfully.`); }
388
+ }
332
389
  } catch (error) {
333
- console.error(`${lc} handleIncomingSyncRequest turn execution failed: ${extractErrorMsg(error)}`);
390
+ console.error(`${lc} executeIncomingSyncRequestAndRespond failed: ${extractErrorMsg(error)}`);
334
391
  throw error;
335
392
  }
336
393
  }
@@ -11,17 +11,17 @@ import { validateIbGibAddr, validateIbGibIntrinsically } from '@ibgib/ts-gib/dis
11
11
  import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
12
12
  import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
13
13
 
14
- import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
14
+ import { GLOBAL_LOG_A_LOT } from '../../../../core-constants.mjs';
15
15
  import {
16
16
  KeystoneReplenishStrategy, KeystoneChallengeType, KeystoneIbGib_V1,
17
17
  KeystoneSolution, KeystoneChallenge
18
- } from '../../../keystone/keystone-types.mjs';
19
- import { parseKeystoneIb } from '../../../keystone/keystone-helpers.mjs';
20
- import { KeystoneStrategyFactory } from '../../../keystone/strategy/keystone-strategy-factory.mjs';
21
- import { KeystoneService_V1 } from '../../../keystone/keystone-service-v1.mjs';
22
- import { IbGibSpaceAny } from '../../../witness/space/space-base-v1.mjs';
23
- import { MetaspaceService } from '../../../witness/space/metaspace/metaspace-types.mjs';
24
- import { POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT } from '../../../keystone/keystone-constants.mjs';
18
+ } from '../../../../keystone/keystone-types.mjs';
19
+ import { parseKeystoneIb } from '../../../../keystone/keystone-helpers.mjs';
20
+ import { KeystoneStrategyFactory } from '../../../../keystone/strategy/keystone-strategy-factory.mjs';
21
+ import { KeystoneService_V1 } from '../../../../keystone/keystone-service-v1.mjs';
22
+ import { IbGibSpaceAny } from '../../../../witness/space/space-base-v1.mjs';
23
+ import { MetaspaceService } from '../../../../witness/space/metaspace/metaspace-types.mjs';
24
+ import { POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT } from '../../../../keystone/keystone-constants.mjs';
25
25
 
26
26
  const logalot = GLOBAL_LOG_A_LOT
27
27
 
@@ -5,7 +5,7 @@ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
5
5
  import {
6
6
  SyncPeerData_V1, SyncPeerRel8ns_V1, SyncPeerWitness,
7
7
  InitializeSyncPeerOpts, ConnectSyncPeerOpts
8
- } from '../sync-peer-types.mjs';
8
+ } from '../../sync-peer-types.mjs';
9
9
 
10
10
  /**
11
11
  * Data for the SyncPeerWebSocketSender witness.