@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.
- package/CHANGELOG.md +9 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs +6 -2
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +7 -11
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts +24 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +15 -4
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +120 -25
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.d.mts +46 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mjs +45 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mjs.map +1 -0
- 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
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +1 -0
- 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
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-v1.mjs +115 -61
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -0
- package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.d.mts +3 -3
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -0
- package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.mjs +6 -6
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -0
- 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
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +1 -0
- 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
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +447 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -0
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +22 -5
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +263 -28
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +13 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +12 -1
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +106 -12
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-types.d.mts +24 -0
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +0 -1
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/sync/sync-withid.connect.respec.mjs +3 -3
- package/dist/sync/sync-withid.connect.respec.mjs.map +1 -1
- package/dist/sync/sync-withid.pingpong.respec.d.mts +11 -0
- package/dist/sync/sync-withid.pingpong.respec.d.mts.map +1 -0
- package/dist/sync/sync-withid.pingpong.respec.mjs +199 -0
- package/dist/sync/sync-withid.pingpong.respec.mjs.map +1 -0
- package/dist/witness/space/inner-space/inner-space-v1.d.mts.map +1 -1
- package/dist/witness/space/inner-space/inner-space-v1.mjs +1 -1
- package/dist/witness/space/inner-space/inner-space-v1.mjs.map +1 -1
- package/package.json +1 -1
- package/src/sync/docs/security-3b.md +92 -0
- package/src/sync/docs/security.md +107 -39
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +6 -2
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +1 -1
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +11 -14
- package/src/sync/sync-peer/sync-peer-types.mts +28 -1
- package/src/sync/sync-peer/sync-peer-v1.mts +127 -35
- package/src/sync/sync-peer/sync-peer-websocket/README.md +42 -0
- package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-constants.mts +68 -0
- package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-types.mts +2 -2
- package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-v1.mts +128 -71
- package/src/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-websocket-peer-helpers.mts +8 -8
- package/src/sync/sync-peer/{sync-peer-websocket-sender → sync-peer-websocket/sync-peer-websocket-sender}/sync-peer-websocket-sender-types.mts +1 -1
- package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +509 -0
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +267 -36
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +14 -0
- package/src/sync/sync-saga-coordinator.mts +148 -8
- package/src/sync/sync-types.mts +28 -4
- package/src/sync/sync-withid.connect.respec.mts +3 -3
- package/src/sync/sync-withid.pingpong.respec.mts +234 -0
- package/src/witness/space/inner-space/inner-space-v1.mts +4 -5
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +0 -282
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +0 -1
- package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +0 -321
- /package/dist/sync/sync-peer/{sync-peer-websocket-receiver → sync-peer-websocket/sync-peer-websocket-receiver}/sync-peer-websocket-receiver-types.mjs +0 -0
- /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 '
|
|
9
|
-
import { SyncSagaCoordinator } from '
|
|
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
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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 '
|
|
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:
|
|
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:
|
|
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 ===
|
|
184
|
+
if (msg.type === SyncWebSocketMsgType.domain_payload) {
|
|
175
185
|
const ibGib = msg.ibGib as IbGib_V1;
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
//
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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:
|
|
197
|
-
|
|
247
|
+
type: SyncWebSocketMsgType.sync_frame_authenticated,
|
|
248
|
+
contextAddr: getIbGibAddr({ ibGib: context })
|
|
198
249
|
}));
|
|
199
250
|
} else {
|
|
200
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 ===
|
|
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:
|
|
308
|
+
type: SyncWebSocketMsgType.auth_challenge,
|
|
241
309
|
challengeUuid: this.challengeUuid,
|
|
242
310
|
demandedIds: this.demandedIds
|
|
243
311
|
}));
|
|
244
312
|
|
|
245
|
-
} else if (msg.type ===
|
|
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:
|
|
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
|
|
352
|
+
* Executes the incoming sync request through the local SyncSagaCoordinator and sends response.
|
|
284
353
|
*/
|
|
285
|
-
|
|
286
|
-
context
|
|
287
|
-
payloadIbGibsControl = [],
|
|
354
|
+
protected async executeIncomingSyncRequestAndRespond({
|
|
355
|
+
context
|
|
288
356
|
}: {
|
|
289
357
|
context: SyncSagaContextIbGib_V1;
|
|
290
|
-
|
|
291
|
-
|
|
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}
|
|
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
|
-
|
|
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}
|
|
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 '
|
|
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 '
|
|
19
|
-
import { parseKeystoneIb } from '
|
|
20
|
-
import { KeystoneStrategyFactory } from '
|
|
21
|
-
import { KeystoneService_V1 } from '
|
|
22
|
-
import { IbGibSpaceAny } from '
|
|
23
|
-
import { MetaspaceService } from '
|
|
24
|
-
import { POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT } from '
|
|
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 '
|
|
8
|
+
} from '../../sync-peer-types.mjs';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Data for the SyncPeerWebSocketSender witness.
|