@ibgib/core-gib 0.1.57 → 0.1.58
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/dist/keystone/keystone-config-builder.d.mts +12 -1
- package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
- package/dist/keystone/keystone-config-builder.mjs +58 -4
- package/dist/keystone/keystone-config-builder.mjs.map +1 -1
- package/dist/keystone/keystone-constants.d.mts +40 -5
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +39 -5
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +11 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +37 -1
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-policy-types.d.mts +23 -0
- package/dist/keystone/keystone-policy-types.d.mts.map +1 -0
- package/dist/keystone/keystone-policy-types.mjs +2 -0
- package/dist/keystone/keystone-policy-types.mjs.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs +8 -8
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs +22 -22
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs +3 -3
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs +6 -6
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.mjs +26 -26
- package/dist/sync/sync-conflict-text-merge.respec.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +19 -0
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +51 -1
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace-constants.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +4 -4
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +3 -3
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +4 -4
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts +5 -0
- 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 +18 -0
- 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-http-sender/sync-peer-http-sender-v1.d.mts +5 -0
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs +21 -3
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +12 -0
- 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 +34 -0
- 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 +69 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +30 -0
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +88 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts +30 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs +2 -0
- package/dist/sync/sync-peer/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-receiver-v1.d.mts +66 -0
- package/dist/sync/sync-peer/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-receiver-v1.mjs +280 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts +85 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs +332 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts +29 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs +2 -0
- package/dist/sync/sync-peer/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-sender-v1.d.mts +42 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +282 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.d.mts +35 -1
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +62 -1
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-withid.connect.respec.d.mts +12 -0
- package/dist/sync/sync-withid.connect.respec.d.mts.map +1 -0
- package/dist/sync/sync-withid.connect.respec.mjs +205 -0
- package/dist/sync/sync-withid.connect.respec.mjs.map +1 -0
- package/dist/sync/sync-withid.establish.respec.d.mts +19 -0
- package/dist/sync/sync-withid.establish.respec.d.mts.map +1 -0
- package/dist/sync/sync-withid.establish.respec.mjs +322 -0
- package/dist/sync/sync-withid.establish.respec.mjs.map +1 -0
- package/package.json +4 -4
- package/src/keystone/keystone-config-builder.mts +73 -4
- package/src/keystone/keystone-constants.mts +42 -6
- package/src/keystone/keystone-helpers.mts +44 -2
- package/src/keystone/keystone-policy-types.mts +25 -0
- package/src/keystone/keystone-policy.schema.json +51 -0
- package/src/keystone/keystone-service-v1.mts +3 -3
- package/src/sync/docs/architecture.md +20 -0
- package/src/sync/docs/security.md +207 -3
- package/src/sync/graft-info/graft-info-helpers.respec.mts +7 -7
- package/src/sync/sync-conflict-adv-multitimelines.respec.mts +21 -21
- package/src/sync/sync-conflict-basic-divergence.respec.mts +2 -2
- package/src/sync/sync-conflict-basic-multitimelines.respec.mts +5 -5
- package/src/sync/sync-conflict-text-merge.respec.mts +25 -25
- package/src/sync/sync-helpers.mts +51 -1
- package/src/sync/sync-innerspace-constants.respec.mts +1 -1
- package/src/sync/sync-innerspace-deep-updates.respec.mts +1 -1
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +3 -3
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +1 -1
- package/src/sync/sync-innerspace-partial-update.respec.mts +2 -2
- package/src/sync/sync-innerspace.respec.mts +3 -3
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +20 -0
- package/src/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mts +23 -3
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +38 -1
- package/src/sync/sync-peer/sync-peer-types.mts +70 -1
- package/src/sync/sync-peer/sync-peer-v1.mts +94 -1
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mts +36 -0
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mts +337 -0
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mts +388 -0
- package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mts +35 -0
- package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +321 -0
- package/src/sync/sync-saga-coordinator.mts +84 -0
- package/src/sync/sync-withid.connect.respec.mts +243 -0
- package/src/sync/sync-withid.establish.respec.mts +361 -0
- package/src/sync/unused-identity-backup.mts.md +1 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts +0 -2
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts.map +0 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +0 -310
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +0 -1
- package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +0 -364
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sync-peer-websocket-sender-v1
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
6
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
7
|
+
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
8
|
+
import { KeystoneIbGib_V1 } from '../../../keystone/keystone-types.mjs';
|
|
9
|
+
import { KeystoneService_V1 } from '../../../keystone/keystone-service-v1.mjs';
|
|
10
|
+
import { KeystoneStrategyFactory } from '../../../keystone/strategy/keystone-strategy-factory.mjs';
|
|
11
|
+
import { deriveSessionSecret } from '../../sync-helpers.mjs';
|
|
12
|
+
import { SyncPeer_V1 } from '../sync-peer-v1.mjs';
|
|
13
|
+
import { SyncSagaContextIbGib_V1 } from '../../sync-saga-context/sync-saga-context-types.mjs';
|
|
14
|
+
import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
|
|
15
|
+
import {
|
|
16
|
+
ConnectSyncPeerWebSocketSenderOpts,
|
|
17
|
+
InitializeSyncPeerWebSocketSenderOpts,
|
|
18
|
+
SyncPeerWebSocketSenderData_V1,
|
|
19
|
+
SyncPeerWebSocketSenderRel8ns_V1,
|
|
20
|
+
SyncPeerWebSocketSenderIbGib_V1
|
|
21
|
+
} from './sync-peer-websocket-sender-types.mjs';
|
|
22
|
+
import {
|
|
23
|
+
SESSION_KEYSTONE_POLICY,
|
|
24
|
+
getConnectChallenge
|
|
25
|
+
} from '../sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs';
|
|
26
|
+
|
|
27
|
+
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* WebSocket Sender Peer implementation running in browser/native environment.
|
|
31
|
+
*/
|
|
32
|
+
export class SyncPeerWebSocketSender_V1
|
|
33
|
+
extends SyncPeer_V1<ConnectSyncPeerWebSocketSenderOpts, InitializeSyncPeerWebSocketSenderOpts>
|
|
34
|
+
implements SyncPeerWebSocketSenderIbGib_V1 {
|
|
35
|
+
|
|
36
|
+
protected override lc: string = `[${SyncPeerWebSocketSender_V1.name}]`;
|
|
37
|
+
|
|
38
|
+
override get classname(): string {
|
|
39
|
+
return SyncPeerWebSocketSender_V1.name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public get isSocketOpen(): boolean {
|
|
43
|
+
return this.ws !== undefined && this.ws.readyState === WebSocket.OPEN;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare data: SyncPeerWebSocketSenderData_V1 | undefined;
|
|
47
|
+
declare rel8ns: SyncPeerWebSocketSenderRel8ns_V1 | undefined;
|
|
48
|
+
|
|
49
|
+
protected ws?: WebSocket;
|
|
50
|
+
protected activeResolve?: (value: SyncSagaContextIbGib_V1 | undefined) => void;
|
|
51
|
+
protected activeReject?: (reason: any) => void;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
initialData: SyncPeerWebSocketSenderData_V1,
|
|
55
|
+
initialRel8ns?: SyncPeerWebSocketSenderRel8ns_V1,
|
|
56
|
+
) {
|
|
57
|
+
super(initialData, initialRel8ns);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected override async preConnectCheck(opts: ConnectSyncPeerWebSocketSenderOpts): Promise<void> {
|
|
61
|
+
const lc = `${this.lc}[${this.preConnectCheck.name}]`;
|
|
62
|
+
if (!this.data?.wsUrl) {
|
|
63
|
+
throw new Error(`Missing wsUrl in peer data (E: a3b2c1d0e9f8e7d6c5b4a3f2e1d0c910)`);
|
|
64
|
+
}
|
|
65
|
+
if (!this.data?.httpEvolveUrl) {
|
|
66
|
+
throw new Error(`Missing httpEvolveUrl in peer data (E: a3b2c1d0e9f8e7d6c5b4a3f2e1d0c911)`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Submits the evolved master identity (I1) and new session keystone (S) to the server's HTTP registry.
|
|
72
|
+
*/
|
|
73
|
+
protected override async postEstablishToReceiver({
|
|
74
|
+
newSenderIdentity,
|
|
75
|
+
sessionIdentity,
|
|
76
|
+
}: {
|
|
77
|
+
newSenderIdentity: KeystoneIbGib_V1;
|
|
78
|
+
sessionIdentity: KeystoneIbGib_V1;
|
|
79
|
+
}): Promise<void> {
|
|
80
|
+
const lc = `${this.lc}[${this.postEstablishToReceiver.name}]`;
|
|
81
|
+
try {
|
|
82
|
+
if (logalot) { console.log(`${lc} posting evolved keystones to ${this.data!.httpEvolveUrl}...`); }
|
|
83
|
+
if (!this.data) { throw new Error(`(UNEXPECTED) this.data falsy? (E: d642e8a9af18b532c87c6f581aa53b26)`); }
|
|
84
|
+
|
|
85
|
+
if (!this.data.httpEvolveUrl) { throw new Error(`(UNEXPECTED) this.data.httpEvolveUrl falsy? (E: 8589ddbb155914d85c09658881da2c26)`); }
|
|
86
|
+
|
|
87
|
+
const response = await fetch(this.data.httpEvolveUrl, {
|
|
88
|
+
method: 'PUT',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
keystoneIbGib: newSenderIdentity,
|
|
94
|
+
relatedIbGibs: [sessionIdentity]
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
const data = await response.json().catch(() => ({}));
|
|
100
|
+
const errorMsg = data.message || data.error || 'Unknown error';
|
|
101
|
+
throw new Error(`HTTP ${response.status} evolution post rejected. errorMsg: ${errorMsg} (E: e8a478291b88d05c68cf6b385684b826)`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (logalot) { console.log(`${lc} evolve post accepted by server.`); }
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`${lc} establish post failed: ${extractErrorMsg(error)}`);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Establishes the stateful WebSocket connection and performs the multi-turn cryptographic challenge connect.
|
|
113
|
+
*/
|
|
114
|
+
protected override async connectImpl(opts: ConnectSyncPeerWebSocketSenderOpts): Promise<void> {
|
|
115
|
+
const lc = `${this.lc}[${this.connectImpl.name}]`;
|
|
116
|
+
try {
|
|
117
|
+
if (logalot) { console.log(`${lc} starting...`); }
|
|
118
|
+
|
|
119
|
+
const { senderIdentity, fnSenderSecret, sagaId, localMetaspace, localSpace } = this.opts!;
|
|
120
|
+
if (!senderIdentity || !fnSenderSecret || !sagaId) {
|
|
121
|
+
throw new Error(`Missing identity parameters in peer options (E: ed49b6711ac82efcdb42b8a28c5b6826)`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. Solve upfront pre-filter solution
|
|
125
|
+
const senderSecret = await fnSenderSecret();
|
|
126
|
+
const sessionSecret = await deriveSessionSecret({ senderSecret, sagaId });
|
|
127
|
+
|
|
128
|
+
// Fetch session identity S that establishSessionIdentity just created
|
|
129
|
+
const sAddr_tjp = getIbGibAddr({ ibGib: senderIdentity }); // wait, S's past contains I
|
|
130
|
+
// Actually, we can get the S keystone that was returned by establishSessionIdentity.
|
|
131
|
+
// Let's resolve S from the sender localSpace using its target claim or the targetAddrs
|
|
132
|
+
const sAddr = getIbGibAddr({ ibGib: senderIdentity.rel8ns?.past ? senderIdentity : senderIdentity }); // stub: get the actual generated S
|
|
133
|
+
|
|
134
|
+
// To make sure we have the exact session identity S, we can query localSpace.
|
|
135
|
+
// Since we know the sagaId, we can walk or locate it.
|
|
136
|
+
// But wait, the coordinator calls establishSessionIdentity first, which returns the created S keystone!
|
|
137
|
+
// Let's store a reference to the sessionIdentity S on this class during establish, or lookup from localSpace.
|
|
138
|
+
// Wait, does the coordinator let the peer store S? No, but let's query the latest keystone in localSpace.
|
|
139
|
+
const senderIdentityLatestAddr =
|
|
140
|
+
await localMetaspace.getLatestAddr({ ibGib: senderIdentity, space: localSpace });
|
|
141
|
+
if (!senderIdentityLatestAddr) { throw new Error(`senderIdentityLatestAddr falsy. we should have a latest addr that is different than the sender identity at this point (done in the peer establish phase) (E: 225607318bef485d7821f82de97b6826)`); }
|
|
142
|
+
|
|
143
|
+
const resGet = await localMetaspace.get({ addrs: [senderIdentityLatestAddr], space: localSpace });
|
|
144
|
+
const I_tip = resGet.ibGibs?.[0] as KeystoneIbGib_V1;
|
|
145
|
+
|
|
146
|
+
// const targetSAddr = I_tip?.rel8ns?.past?.[0] ?? I_tip?.data?.proofs?.[0]?.claim?.target;
|
|
147
|
+
const targetSAddr = I_tip.data.proofs.at(0)?.claim.target;
|
|
148
|
+
if (!targetSAddr) {
|
|
149
|
+
throw new Error(`Could not locate session keystone target from identity (E: 5ec4882f7e5e4c33fbd00ab8b3166726)`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const resGetS = await localMetaspace.get({ addrs: [targetSAddr], space: localSpace });
|
|
153
|
+
const sessionS = resGetS.ibGibs?.[0] as KeystoneIbGib_V1;
|
|
154
|
+
if (!sessionS) {
|
|
155
|
+
throw new Error(`Session keystone not found in local space: ${targetSAddr} (E: 9ba818e6622808bb4ae749be31f80b26)`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Solve dynamic upfront picket-fence challenge
|
|
159
|
+
const connectPool =
|
|
160
|
+
(sessionS.data?.challengePools ?? [])
|
|
161
|
+
.find(p => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
|
|
162
|
+
if (!connectPool) {
|
|
163
|
+
throw new Error(`Session keystone missing connect pool (E: f50968afca04b6d38ec19824ea201826)`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { challengeId } = getConnectChallenge(sessionS);
|
|
167
|
+
const strategy = KeystoneStrategyFactory.create({ config: connectPool.config });
|
|
168
|
+
const poolSecret = await strategy.derivePoolSecret({ masterSecret: sessionSecret });
|
|
169
|
+
const solution = await strategy.generateSolution({
|
|
170
|
+
poolSecret,
|
|
171
|
+
poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
172
|
+
challengeId
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 2. Open WebSocket connection
|
|
176
|
+
const wsUrl = `${this.data!.wsUrl}?sAddr=${encodeURIComponent(targetSAddr)}&solution=${encodeURIComponent(solution.value)}`;
|
|
177
|
+
if (logalot) { console.log(`${lc} connecting to WebSocket: ${wsUrl}`); }
|
|
178
|
+
|
|
179
|
+
const ws = new WebSocket(wsUrl);
|
|
180
|
+
this.ws = ws;
|
|
181
|
+
|
|
182
|
+
return new Promise<void>((resolve, reject) => {
|
|
183
|
+
let isResolved = false;
|
|
184
|
+
|
|
185
|
+
ws.addEventListener('open', () => {
|
|
186
|
+
if (logalot) { console.log(`${lc} WebSocket opened. Awaiting challenge...`); }
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
ws.addEventListener('message', async (ev) => {
|
|
190
|
+
try {
|
|
191
|
+
const msg = JSON.parse(ev.data);
|
|
192
|
+
if (logalot) { console.log(`${lc} received frame: ${msg.type}`); }
|
|
193
|
+
|
|
194
|
+
if (msg.type === 'auth-challenge-init') {
|
|
195
|
+
ws.send(JSON.stringify({
|
|
196
|
+
type: 'auth-init',
|
|
197
|
+
sAddr: targetSAddr
|
|
198
|
+
}));
|
|
199
|
+
} else if (msg.type === 'auth-challenge') {
|
|
200
|
+
const { challengeUuid, demandedIds } = msg;
|
|
201
|
+
if (logalot) { console.log(`${lc} solving demanded challenges: ${demandedIds.join(', ')}`); }
|
|
202
|
+
|
|
203
|
+
const keystoneService = new KeystoneService_V1();
|
|
204
|
+
const proofFrame = await keystoneService.sign({
|
|
205
|
+
latestKeystone: sessionS,
|
|
206
|
+
masterSecret: sessionSecret,
|
|
207
|
+
poolId: SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID,
|
|
208
|
+
requiredChallengeIds: demandedIds,
|
|
209
|
+
claim: {
|
|
210
|
+
verb: SESSION_KEYSTONE_POLICY.CONNECT_POOL.VERB,
|
|
211
|
+
target: challengeUuid
|
|
212
|
+
},
|
|
213
|
+
metaspace: localMetaspace,
|
|
214
|
+
space: localSpace
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
ws.send(JSON.stringify({
|
|
218
|
+
type: 'auth-proof',
|
|
219
|
+
proofFrame
|
|
220
|
+
}));
|
|
221
|
+
} else if (msg.type === 'auth-ok') {
|
|
222
|
+
if (logalot) { console.log(`${lc} WebSocket connect SUCCESS!`); }
|
|
223
|
+
isResolved = true;
|
|
224
|
+
|
|
225
|
+
// Setup persistent runtime listeners
|
|
226
|
+
ws.removeEventListener('message', (() => { }) as any);
|
|
227
|
+
ws.addEventListener('message', (event) => this.handleRuntimeMessage(event));
|
|
228
|
+
|
|
229
|
+
resolve();
|
|
230
|
+
} else if (msg.type === 'auth-fail') {
|
|
231
|
+
reject(new Error(`Connect failed: ${msg.message}`));
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
reject(error);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
ws.addEventListener('close', (event) => {
|
|
239
|
+
if (!isResolved) {
|
|
240
|
+
reject(new Error(`WebSocket closed before connect completed (code: ${event.code})`));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
ws.addEventListener('error', (err) => {
|
|
245
|
+
if (!isResolved) {
|
|
246
|
+
reject(new Error(`WebSocket connection error`));
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(`${lc} connect failed: ${extractErrorMsg(error)}`);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Handles synchronizing messages and evolved context frames during active transaction turns.
|
|
259
|
+
*/
|
|
260
|
+
protected handleRuntimeMessage(event: MessageEvent): void {
|
|
261
|
+
const lc = `${this.lc}[${this.handleRuntimeMessage.name}]`;
|
|
262
|
+
try {
|
|
263
|
+
const msg = JSON.parse(event.data);
|
|
264
|
+
if (logalot) { console.log(`${lc} received runtime frame: ${msg.type}`); }
|
|
265
|
+
|
|
266
|
+
if (msg.type === 'sync-frame-response') {
|
|
267
|
+
if (this.activeResolve) {
|
|
268
|
+
const responseContext = msg.context as SyncSagaContextIbGib_V1;
|
|
269
|
+
const resolve = this.activeResolve;
|
|
270
|
+
this.activeResolve = undefined;
|
|
271
|
+
this.activeReject = undefined;
|
|
272
|
+
resolve(responseContext);
|
|
273
|
+
}
|
|
274
|
+
} else if (msg.type === 'domain-payload') {
|
|
275
|
+
const payload = msg.ibGib as IbGib_V1;
|
|
276
|
+
this.payloadIbGibsDomainReceived$.next(payload);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error(`${lc} failed parsing runtime frame: ${extractErrorMsg(error)}`);
|
|
280
|
+
if (this.activeReject) {
|
|
281
|
+
this.activeReject(error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Serializes the transaction context and payload down the active WebSocket connection.
|
|
288
|
+
*/
|
|
289
|
+
protected override async sendContextRequest(context: SyncSagaContextIbGib_V1): Promise<SyncSagaContextIbGib_V1 | undefined> {
|
|
290
|
+
const lc = `${this.lc}[${this.sendContextRequest.name}]`;
|
|
291
|
+
try {
|
|
292
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
293
|
+
throw new Error(`WebSocket is not connected or open (E: a3b2c1d0e9f8e7d6c5b4a3f2e1d0c915)`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 1. Push any outgoing payload domain ibgibs down the pipe
|
|
297
|
+
const domainPayloads = context.payloadIbGibsDomain ?? [];
|
|
298
|
+
for (const ibGib of domainPayloads) {
|
|
299
|
+
this.ws.send(JSON.stringify({
|
|
300
|
+
type: 'domain-payload',
|
|
301
|
+
ibGib
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 2. Transmit the synchronizing transaction context
|
|
306
|
+
return new Promise<SyncSagaContextIbGib_V1 | undefined>((resolve, reject) => {
|
|
307
|
+
this.activeResolve = resolve;
|
|
308
|
+
this.activeReject = reject;
|
|
309
|
+
|
|
310
|
+
this.ws!.send(JSON.stringify({
|
|
311
|
+
type: 'sync-frame',
|
|
312
|
+
context
|
|
313
|
+
}));
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(`${lc} sendContextRequest failed: ${extractErrorMsg(error)}`);
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -60,6 +60,12 @@ import { GRAFT_INFO_REL8N_NAME } from "./graft-info/graft-info-constants.mjs";
|
|
|
60
60
|
import { GraftInfoIbGib_V1 } from "./graft-info/graft-info-types.mjs";
|
|
61
61
|
import { validateIbGibIntrinsically } from "@ibgib/ts-gib/dist/V1/validate-helper.mjs";
|
|
62
62
|
import { SYNC_SAGA_CONTEXT_ATOM } from "./sync-saga-context/sync-saga-context-constants.mjs";
|
|
63
|
+
import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
|
|
64
|
+
import { createStandardPoolConfig } from "../keystone/keystone-config-builder.mjs";
|
|
65
|
+
import {
|
|
66
|
+
POOL_ID_CONNECT, POOL_ID_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_VERB_SYNC
|
|
67
|
+
} from "../keystone/keystone-constants.mjs";
|
|
68
|
+
import { KeystonePoolConfig, KeystoneReplenishStrategy } from "../keystone/keystone-types.mjs";
|
|
63
69
|
|
|
64
70
|
|
|
65
71
|
const logalot = GLOBAL_LOG_A_LOT;
|
|
@@ -86,6 +92,46 @@ export class SyncSagaCoordinator {
|
|
|
86
92
|
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Default pool config for the session keystone's `connect` pool.
|
|
97
|
+
* Consumed exactly once per saga during `peer.connect()`.
|
|
98
|
+
* Uses `deleteAll` replenish strategy — after the handshake, the pool is gone.
|
|
99
|
+
*
|
|
100
|
+
* Override in subclasses or pass custom config via opts to change security parameters.
|
|
101
|
+
*/
|
|
102
|
+
protected defaultSessionConnectPoolConfig(): KeystonePoolConfig {
|
|
103
|
+
return createStandardPoolConfig({
|
|
104
|
+
id: POOL_ID_CONNECT,
|
|
105
|
+
salt: `session-connect-${Date.now()}`,
|
|
106
|
+
verbs: [KEYSTONE_VERB_CONNECT],
|
|
107
|
+
size: 20,
|
|
108
|
+
sequential: 2,
|
|
109
|
+
random: 2,
|
|
110
|
+
targetBinding: 0,
|
|
111
|
+
replenishStrategy: KeystoneReplenishStrategy.deleteAll,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Default pool config for the session keystone's per-turn `sync` pool.
|
|
117
|
+
* Used on each outgoing context frame (Init, Delta, Commit).
|
|
118
|
+
* Uses `topUp` replenish strategy to stay active throughout the saga.
|
|
119
|
+
*
|
|
120
|
+
* Override in subclasses or pass custom config via opts to change security parameters.
|
|
121
|
+
*/
|
|
122
|
+
protected defaultSessionSyncPoolConfig(): KeystonePoolConfig {
|
|
123
|
+
return createStandardPoolConfig({
|
|
124
|
+
id: POOL_ID_SYNC,
|
|
125
|
+
salt: `session-sync-${Date.now()}`,
|
|
126
|
+
verbs: [KEYSTONE_VERB_SYNC],
|
|
127
|
+
size: 200,
|
|
128
|
+
sequential: 3,
|
|
129
|
+
random: 3,
|
|
130
|
+
targetBinding: 3,
|
|
131
|
+
replenishStrategy: KeystoneReplenishStrategy.topUp,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
89
135
|
/**
|
|
90
136
|
* Executes a synchronization saga using the Symmetric Sync Protocol.
|
|
91
137
|
*
|
|
@@ -96,6 +142,8 @@ export class SyncSagaCoordinator {
|
|
|
96
142
|
public async sync({
|
|
97
143
|
peer,
|
|
98
144
|
domainIbGibs,
|
|
145
|
+
senderIdentity,
|
|
146
|
+
fnSenderSecret,
|
|
99
147
|
conflictStrategy = SyncConflictStrategy.abort,
|
|
100
148
|
metaspace,
|
|
101
149
|
localSpace,
|
|
@@ -111,6 +159,22 @@ export class SyncSagaCoordinator {
|
|
|
111
159
|
* These should all exist in {@link localSpace}
|
|
112
160
|
*/
|
|
113
161
|
domainIbGibs: IbGib_V1[],
|
|
162
|
+
/**
|
|
163
|
+
* If present, this is the primary identity of the sender, i.e., who is
|
|
164
|
+
* initiating the sync.
|
|
165
|
+
*
|
|
166
|
+
* This should be the most recent frame (tip) of the senderIdentity's
|
|
167
|
+
* timeline.
|
|
168
|
+
*
|
|
169
|
+
* NOTE: {@link fnSenderSecret} must be truthy if this is truthy, and vice versa.
|
|
170
|
+
*/
|
|
171
|
+
senderIdentity?: KeystoneIbGib_V1,
|
|
172
|
+
/**
|
|
173
|
+
* secret corresponding to {@link senderIdentity}.
|
|
174
|
+
*
|
|
175
|
+
* NOTE: {@link senderIdentity} must be truthy if this is truthy, and vice versa.
|
|
176
|
+
*/
|
|
177
|
+
fnSenderSecret?: () => Promise<string>;
|
|
114
178
|
/**
|
|
115
179
|
* The space containing the {@link domainIbGibs} we want to sync. If
|
|
116
180
|
* sync is successful, any updates to timelines will be stored here.
|
|
@@ -136,6 +200,11 @@ export class SyncSagaCoordinator {
|
|
|
136
200
|
throw new Error(`${lc} source (or localSpace) required (E: 25df3761f7686a1099a552f83c95d326)`);
|
|
137
201
|
}
|
|
138
202
|
|
|
203
|
+
if (senderIdentity || fnSenderSecret) {
|
|
204
|
+
if (!senderIdentity) { throw new Error(`(UNEXPECTED) senderIdentity falsy but fnSenderSecret truthy? if either is used, both must be truthy. (E: e366628a4919c7d727d2cbd8e5b75e26)`); }
|
|
205
|
+
if (!fnSenderSecret) { throw new Error(`(UNEXPECTED) fnSenderSecret falsy but senderIdentity truthy? if either is used, both must be truthy. (E: 7d55ce37ae482fec48e3398158161926)`); }
|
|
206
|
+
}
|
|
207
|
+
|
|
139
208
|
// 1. SETUP SAGA METADATA
|
|
140
209
|
const sagaId = await getUUID();
|
|
141
210
|
|
|
@@ -171,6 +240,21 @@ export class SyncSagaCoordinator {
|
|
|
171
240
|
// Async execution wrapper
|
|
172
241
|
(async () => {
|
|
173
242
|
try {
|
|
243
|
+
const targetAddrs = domainIbGibs ? domainIbGibs.map(domain => getIbGibAddr({ ibGib: domain })) : undefined;
|
|
244
|
+
|
|
245
|
+
// Attach saga-scoped identity opts to peer now that sagaId is known.
|
|
246
|
+
peer.setOptionalOpts({
|
|
247
|
+
sagaId,
|
|
248
|
+
senderIdentity,
|
|
249
|
+
fnSenderSecret,
|
|
250
|
+
sessionConnectPoolConfig: this.defaultSessionConnectPoolConfig(),
|
|
251
|
+
sessionSyncPoolConfig: this.defaultSessionSyncPoolConfig(),
|
|
252
|
+
targetAddrs,
|
|
253
|
+
} as any);
|
|
254
|
+
|
|
255
|
+
// ESTABLISH SESSION IDENTITY (pre-connect, if identity provided)
|
|
256
|
+
const sessionIdentity = await peer.establishSessionIdentity();
|
|
257
|
+
|
|
174
258
|
// CONNECT PEER (if needed)
|
|
175
259
|
await peer.connect({ sagaId });
|
|
176
260
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sync-withid.connect.respec
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 — `peer.connect()` (Transport Handshake)
|
|
5
|
+
*
|
|
6
|
+
* Goal: Verify that the connection phase executes without error. S's connect
|
|
7
|
+
* pool remains undepleted (no-op on innerspace).
|
|
8
|
+
*
|
|
9
|
+
* @see libs/core-gib/src/sync/docs/security.md — Implementation Plan, Phase 2A
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
respecfully, ifWeMight, iReckon,
|
|
14
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
15
|
+
const maam = `[${import.meta.url}]`, sir = maam;
|
|
16
|
+
import { clone, delay, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
17
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
18
|
+
|
|
19
|
+
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
|
|
20
|
+
import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
|
|
21
|
+
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
|
|
22
|
+
import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
|
|
23
|
+
import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
|
|
24
|
+
import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
|
|
25
|
+
import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
|
|
26
|
+
import { KeystoneService_V1 } from '../keystone/keystone-service-v1.mjs';
|
|
27
|
+
import { KeystoneIbGib_V1 } from '../keystone/keystone-types.mjs';
|
|
28
|
+
import {
|
|
29
|
+
KEYSTONE_VERB_SYNC, POOL_ID_SYNC, POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT,
|
|
30
|
+
} from '../keystone/keystone-constants.mjs';
|
|
31
|
+
import { createStandardPoolConfig } from '../keystone/keystone-config-builder.mjs';
|
|
32
|
+
import { KeystoneReplenishStrategy } from '../keystone/keystone-types.mjs';
|
|
33
|
+
import { SyncConflictStrategy } from './sync-constants.mjs';
|
|
34
|
+
import { IbGibAddr, TransformResult } from '@ibgib/ts-gib/dist/types.mjs';
|
|
35
|
+
import { getIdentity_throwIfUndefined } from '../keystone/keystone-helpers.mjs';
|
|
36
|
+
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
|
|
37
|
+
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
38
|
+
import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
|
|
39
|
+
import { fork } from '@ibgib/ts-gib/dist/V1/transforms/fork.mjs';
|
|
40
|
+
|
|
41
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
42
|
+
const lc = sir;
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Test-only identity constants
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const SENDER_SECRET = 'test-sender-secret-phase1';
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Session keystone pool configs
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
const SESSION_CONNECT_POOL_CONFIG = createStandardPoolConfig({
|
|
55
|
+
id: POOL_ID_CONNECT,
|
|
56
|
+
salt: 'session-connect-salt-phase1',
|
|
57
|
+
verbs: [KEYSTONE_VERB_CONNECT],
|
|
58
|
+
size: 10,
|
|
59
|
+
sequential: 1,
|
|
60
|
+
random: 1,
|
|
61
|
+
targetBinding: 2,
|
|
62
|
+
replenishStrategy: KeystoneReplenishStrategy.deleteAll,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const SESSION_SYNC_POOL_CONFIG = createStandardPoolConfig({
|
|
66
|
+
id: POOL_ID_SYNC,
|
|
67
|
+
salt: 'session-sync-salt-phase1',
|
|
68
|
+
verbs: [KEYSTONE_VERB_SYNC],
|
|
69
|
+
size: 200,
|
|
70
|
+
sequential: 1,
|
|
71
|
+
random: 1,
|
|
72
|
+
targetBinding: 2,
|
|
73
|
+
replenishStrategy: KeystoneReplenishStrategy.topUp,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Top-level senderIdentity (I) pool config
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
const SENDER_IDENTITY_SYNC_POOL_CONFIG = createStandardPoolConfig({
|
|
81
|
+
id: POOL_ID_SYNC,
|
|
82
|
+
salt: 'senderidentitysyncsaltphase1',
|
|
83
|
+
verbs: [KEYSTONE_VERB_SYNC],
|
|
84
|
+
size: 200,
|
|
85
|
+
sequential: 1,
|
|
86
|
+
random: 1,
|
|
87
|
+
targetBinding: 2,
|
|
88
|
+
replenishStrategy: KeystoneReplenishStrategy.topUp,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Main test suite
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
await respecfully(sir, `Test Phase 2: peer.connect()`, async () => {
|
|
96
|
+
|
|
97
|
+
// #region Init/Setup
|
|
98
|
+
|
|
99
|
+
const metaspace = new Metaspace_Innerspace(undefined);
|
|
100
|
+
await metaspace.initialize({
|
|
101
|
+
getFnAlert: () => async ({ title, msg }) => { console.log(`[Alert] ${title}: ${msg}`); },
|
|
102
|
+
getFnPrompt: () => async ({ title, msg }) => { console.log(`[Prompt] ${title}: ${msg}`); return ''; },
|
|
103
|
+
getFnPromptPassword: () => async (title, msg) => { console.log(`[PromptPwd] ${title}: ${msg}`); return null; },
|
|
104
|
+
});
|
|
105
|
+
while (!metaspace.initialized) { await delay(10); }
|
|
106
|
+
|
|
107
|
+
const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
|
|
108
|
+
await defaultLocalUserSpace!.initialized;
|
|
109
|
+
|
|
110
|
+
const sourceSpace = new InnerSpace_V1({
|
|
111
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
112
|
+
name: 'source',
|
|
113
|
+
uuid: 'source_uuid',
|
|
114
|
+
description: 'sender durable space',
|
|
115
|
+
});
|
|
116
|
+
await sourceSpace.initialized;
|
|
117
|
+
|
|
118
|
+
const destSpace = new InnerSpace_V1({
|
|
119
|
+
...DEFAULT_INNER_SPACE_DATA_V1,
|
|
120
|
+
name: 'dest',
|
|
121
|
+
uuid: 'dest_uuid',
|
|
122
|
+
description: 'receiver (domain provider) durable space',
|
|
123
|
+
});
|
|
124
|
+
await destSpace.initialized;
|
|
125
|
+
|
|
126
|
+
const senderCoordinator = new SyncSagaCoordinator();
|
|
127
|
+
const receiverCoordinator = new SyncSagaCoordinator();
|
|
128
|
+
|
|
129
|
+
async function newTestIbGib_stone({ ib = 'test', data }: { ib: string, data?: any }): Promise<IbGib_V1> {
|
|
130
|
+
const stone = await Factory_V1.stone({
|
|
131
|
+
parentPrimitiveIb: ib.split(' ').at(0) ?? 'test',
|
|
132
|
+
ib,
|
|
133
|
+
data,
|
|
134
|
+
uuid: true,
|
|
135
|
+
});
|
|
136
|
+
return stone;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function newTestPeer(): Promise<SyncPeerInnerspace_V1> {
|
|
140
|
+
const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
141
|
+
await peer.initialized;
|
|
142
|
+
await peer.initializeOpts({
|
|
143
|
+
sagaId: '',
|
|
144
|
+
localMetaspace: metaspace,
|
|
145
|
+
localSpace: sourceSpace,
|
|
146
|
+
receiverSpace: destSpace,
|
|
147
|
+
receiverCoordinator,
|
|
148
|
+
receiverMetaspace: metaspace,
|
|
149
|
+
});
|
|
150
|
+
return peer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const keystoneSvc = new KeystoneService_V1();
|
|
154
|
+
|
|
155
|
+
// #endregion Init/Setup
|
|
156
|
+
|
|
157
|
+
let senderIdentity: KeystoneIbGib_V1 | undefined;
|
|
158
|
+
|
|
159
|
+
// Create senderIdentity genesis (I^Itjp) in sourceSpace
|
|
160
|
+
senderIdentity = await keystoneSvc.genesis({
|
|
161
|
+
masterSecret: SENDER_SECRET,
|
|
162
|
+
configs: [SENDER_IDENTITY_SYNC_POOL_CONFIG],
|
|
163
|
+
metaspace,
|
|
164
|
+
space: sourceSpace,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// post the senderIdentity to receiver
|
|
168
|
+
await metaspace.put({ ibGib: senderIdentity, space: destSpace });
|
|
169
|
+
await metaspace.registerNewIbGib({ ibGib: senderIdentity, space: destSpace });
|
|
170
|
+
|
|
171
|
+
// Execute sync
|
|
172
|
+
let syncError: any = null;
|
|
173
|
+
let xStone: IbGib_V1;
|
|
174
|
+
let xStoneAddr: IbGibAddr;
|
|
175
|
+
let peer: SyncPeerInnerspace_V1;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
xStone = await newTestIbGib_stone({ ib: 'test' });
|
|
179
|
+
xStoneAddr = getIbGibAddr({ ibGib: xStone });
|
|
180
|
+
await metaspace.put({ ibGib: xStone, space: sourceSpace });
|
|
181
|
+
await metaspace.registerNewIbGib({ ibGib: xStone, space: sourceSpace });
|
|
182
|
+
|
|
183
|
+
peer = await newTestPeer();
|
|
184
|
+
|
|
185
|
+
const syncSaga = await senderCoordinator.sync({
|
|
186
|
+
domainIbGibs: [xStone],
|
|
187
|
+
senderIdentity,
|
|
188
|
+
fnSenderSecret: async () => SENDER_SECRET,
|
|
189
|
+
peer,
|
|
190
|
+
localSpace: sourceSpace,
|
|
191
|
+
metaspace,
|
|
192
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
193
|
+
});
|
|
194
|
+
await syncSaga.done;
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
syncError = error;
|
|
198
|
+
if (logalot) { console.log(`${lc} Captured expected downstream/coordinator error: ${extractErrorMsg(error)}`); }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// #region Step 3: Check states
|
|
202
|
+
|
|
203
|
+
await ifWeMight(sir, 'does not throw connection-specific errors', async () => {
|
|
204
|
+
const errorMsg = syncError ? extractErrorMsg(syncError) : '';
|
|
205
|
+
// If it throws, the error should NOT be related to establishment or connection.
|
|
206
|
+
// It's expected to throw downstream in executeSagaLoop or continueSync due to missing context authentication/turn signing.
|
|
207
|
+
iReckon(sir, errorMsg.includes('establish') || errorMsg.includes('connect')).asTo('error is NOT related to establish or connect').isGonnaBeFalsy();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await ifWeMight(sir, 'session identity S connect pool is not depleted (innerspace no-op asymmetry)', async () => {
|
|
211
|
+
// Resolve latest evolved I1
|
|
212
|
+
const newSenderIdentityAddr = await metaspace.getLatestAddr({
|
|
213
|
+
ibGib: senderIdentity,
|
|
214
|
+
space: sourceSpace,
|
|
215
|
+
});
|
|
216
|
+
if (!newSenderIdentityAddr) { throw new Error(`newSenderIdentity not found`); }
|
|
217
|
+
|
|
218
|
+
const newSenderIdentity = await getIdentity_throwIfUndefined({
|
|
219
|
+
addr: newSenderIdentityAddr,
|
|
220
|
+
metaspace,
|
|
221
|
+
space: sourceSpace,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const sessionIdentityTjpAddr = newSenderIdentity.data.proofs
|
|
225
|
+
.find(p => p.claim.verb === KEYSTONE_VERB_SYNC)?.claim.target;
|
|
226
|
+
if (!sessionIdentityTjpAddr) { throw new Error(`sessionIdentityTjpAddr not found`); }
|
|
227
|
+
|
|
228
|
+
const sessionIdentity = await getIdentity_throwIfUndefined({
|
|
229
|
+
addr: sessionIdentityTjpAddr,
|
|
230
|
+
metaspace,
|
|
231
|
+
space: sourceSpace,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// The connect pool should exist and have all challenges remaining (no-op innerspace connect)
|
|
235
|
+
const connectPool = sessionIdentity.data.challengePools?.find(p => p.id === POOL_ID_CONNECT);
|
|
236
|
+
iReckon(sir, connectPool).asTo('connect pool exists on S').isGonnaBeTruthy();
|
|
237
|
+
const remainingChallenges = Object.keys(connectPool?.challenges ?? {});
|
|
238
|
+
iReckon(sir, remainingChallenges.length).asTo('connect pool has all challenges').willEqual(20);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// #endregion Step 3: Check states
|
|
242
|
+
|
|
243
|
+
});
|