@dxos/client-services 0.6.14-main.8b352a0 → 0.6.14-staging.3e2eaca
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/lib/browser/{chunk-Y5D7T3I7.mjs → chunk-GQWG2754.mjs} +169 -142
- package/dist/lib/browser/chunk-GQWG2754.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +9 -2
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-DARYUWYV.cjs → chunk-4SK56Z44.cjs} +177 -150
- package/dist/lib/node/chunk-4SK56Z44.cjs.map +7 -0
- package/dist/lib/node/index.cjs +47 -47
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +16 -9
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node-esm/{chunk-RVNJ6XZO.mjs → chunk-POX3HG6I.mjs} +169 -142
- package/dist/lib/node-esm/chunk-POX3HG6I.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +9 -2
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/packlets/devices/devices-service.d.ts +3 -1
- package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -0
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +1 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +38 -38
- package/src/packlets/devices/devices-service.ts +18 -7
- package/src/packlets/identity/identity.test.ts +16 -5
- package/src/packlets/invitations/edge-invitation-handler.ts +6 -3
- package/src/packlets/invitations/invitation-guest-extenstion.ts +5 -5
- package/src/packlets/invitations/invitation-host-extension.ts +7 -10
- package/src/packlets/invitations/invitations-handler.test.ts +3 -6
- package/src/packlets/invitations/invitations-handler.ts +34 -28
- package/src/packlets/invitations/invitations-manager.ts +5 -3
- package/src/packlets/invitations/invitations-service.ts +5 -1
- package/src/packlets/services/service-context.ts +2 -2
- package/src/packlets/services/service-host.ts +1 -1
- package/src/packlets/spaces/edge-feed-replicator.test.ts +12 -7
- package/src/packlets/spaces/edge-feed-replicator.ts +9 -5
- package/src/packlets/testing/invitation-utils.ts +7 -0
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-Y5D7T3I7.mjs.map +0 -7
- package/dist/lib/node/chunk-DARYUWYV.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVNJ6XZO.mjs.map +0 -7
|
@@ -125,7 +125,7 @@ export class InvitationHostExtension
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
log('guest introduced themselves', { guestProfile: profile });
|
|
128
|
+
log.verbose('guest introduced themselves', { guestProfile: profile });
|
|
129
129
|
this.guestProfile = profile;
|
|
130
130
|
this._callbacks.onStateUpdate(Invitation.State.READY_FOR_AUTHENTICATION);
|
|
131
131
|
this._challenge =
|
|
@@ -143,7 +143,7 @@ export class InvitationHostExtension
|
|
|
143
143
|
log.trace('dxos.sdk.invitation-handler.host.authenticate', trace.begin({ id: traceId }));
|
|
144
144
|
|
|
145
145
|
const invitation = this._requireActiveInvitation();
|
|
146
|
-
log('received authentication request', { authCode: code });
|
|
146
|
+
log.verbose('received authentication request', { authCode: code });
|
|
147
147
|
let status = AuthenticationResponse.Status.OK;
|
|
148
148
|
|
|
149
149
|
this._assertInvitationState([Invitation.State.AUTHENTICATING, Invitation.State.READY_FOR_AUTHENTICATION]);
|
|
@@ -236,18 +236,15 @@ export class InvitationHostExtension
|
|
|
236
236
|
await super.onOpen(context);
|
|
237
237
|
|
|
238
238
|
try {
|
|
239
|
-
log('host acquire lock');
|
|
239
|
+
log.verbose('host acquire lock');
|
|
240
240
|
this._invitationFlowLock = await tryAcquireBeforeContextDisposed(this._ctx, this._invitationFlowMutex);
|
|
241
|
-
log('host lock acquired');
|
|
242
|
-
const lastState = this._requireActiveInvitation().state;
|
|
241
|
+
log.verbose('host lock acquired');
|
|
243
242
|
this._callbacks.onStateUpdate(Invitation.State.CONNECTING);
|
|
244
243
|
await this.rpc.InvitationHostService.options({ role: InvitationOptions.Role.HOST });
|
|
245
|
-
log('options sent');
|
|
244
|
+
log.verbose('options sent');
|
|
246
245
|
await cancelWithContext(this._ctx, this._remoteOptionsTrigger.wait({ timeout: OPTIONS_TIMEOUT }));
|
|
247
|
-
log('options received');
|
|
246
|
+
log.verbose('options received');
|
|
248
247
|
if (this._remoteOptions?.role !== InvitationOptions.Role.GUEST) {
|
|
249
|
-
// we connected with another host, restore previous real invitation flow status
|
|
250
|
-
this._callbacks.onStateUpdate(lastState);
|
|
251
248
|
throw new InvalidInvitationExtensionRoleError(undefined, {
|
|
252
249
|
expected: InvitationOptions.Role.GUEST,
|
|
253
250
|
remoteOptions: this._remoteOptions,
|
|
@@ -299,7 +296,7 @@ export class InvitationHostExtension
|
|
|
299
296
|
if (this._invitationFlowLock != null) {
|
|
300
297
|
this._invitationFlowLock?.release();
|
|
301
298
|
this._invitationFlowLock = null;
|
|
302
|
-
log('invitation flow lock released');
|
|
299
|
+
log.verbose('invitation flow lock released');
|
|
303
300
|
}
|
|
304
301
|
}
|
|
305
302
|
}
|
|
@@ -71,7 +71,7 @@ describe('InvitationHandler', () => {
|
|
|
71
71
|
|
|
72
72
|
await guest.sink.waitFor(Invitation.State.READY_FOR_AUTHENTICATION);
|
|
73
73
|
await sleep(200);
|
|
74
|
-
await host.sink.waitFor(Invitation.State.
|
|
74
|
+
await host.sink.waitFor(Invitation.State.CONNECTING);
|
|
75
75
|
await guest.sink.waitFor(Invitation.State.TIMEOUT);
|
|
76
76
|
|
|
77
77
|
await sleep(10);
|
|
@@ -91,7 +91,7 @@ describe('InvitationHandler', () => {
|
|
|
91
91
|
|
|
92
92
|
await guest.sink.waitFor(Invitation.State.READY_FOR_AUTHENTICATION);
|
|
93
93
|
await guest.peer.networkManager.close();
|
|
94
|
-
await host.sink.waitFor(Invitation.State.
|
|
94
|
+
await host.sink.waitFor(Invitation.State.CONNECTING);
|
|
95
95
|
|
|
96
96
|
await sleep(10);
|
|
97
97
|
expect(host.ctx.disposed).to.be.false;
|
|
@@ -134,7 +134,7 @@ describe('InvitationHandler', () => {
|
|
|
134
134
|
const host = await createPeer();
|
|
135
135
|
const invitation = await createInvitation(host, { multiUse: true });
|
|
136
136
|
await hostInvitation(host, invitation);
|
|
137
|
-
|
|
137
|
+
await createNewHost(invitation);
|
|
138
138
|
|
|
139
139
|
const guest = await createPeer(host.spaceKey);
|
|
140
140
|
const codeInput = await failAuth(guest, invitation);
|
|
@@ -143,9 +143,6 @@ describe('InvitationHandler', () => {
|
|
|
143
143
|
await sleep(10);
|
|
144
144
|
}
|
|
145
145
|
await guest.sink.waitFor(Invitation.State.SUCCESS);
|
|
146
|
-
|
|
147
|
-
const hostFailed = [host, anotherHost].map((h) => h.sink.hasState(0, Invitation.State.ERROR));
|
|
148
|
-
expect(hostFailed.sort()).to.deep.eq([false, true]);
|
|
149
146
|
});
|
|
150
147
|
|
|
151
148
|
test('single guest - many hosts', async () => {
|
|
@@ -86,7 +86,7 @@ export class InvitationsHandler {
|
|
|
86
86
|
kind: invitation.kind,
|
|
87
87
|
type: invitation.type,
|
|
88
88
|
});
|
|
89
|
-
metrics.increment('dxos.invitation.
|
|
89
|
+
metrics.increment('dxos.invitation.host');
|
|
90
90
|
const guardedState = createGuardedInvitationState(ctx, invitation, stream);
|
|
91
91
|
// Called for every connecting peer.
|
|
92
92
|
const createExtension = (): InvitationHostExtension => {
|
|
@@ -96,7 +96,9 @@ export class InvitationsHandler {
|
|
|
96
96
|
},
|
|
97
97
|
|
|
98
98
|
onStateUpdate: (newState: Invitation.State): Invitation => {
|
|
99
|
-
|
|
99
|
+
if (newState !== Invitation.State.ERROR && newState !== Invitation.State.TIMEOUT) {
|
|
100
|
+
guardedState.set(extension, newState);
|
|
101
|
+
}
|
|
100
102
|
return guardedState.current;
|
|
101
103
|
},
|
|
102
104
|
|
|
@@ -125,7 +127,7 @@ export class InvitationsHandler {
|
|
|
125
127
|
let admitted = false;
|
|
126
128
|
connectionCtx.onDispose(() => {
|
|
127
129
|
if (!admitted) {
|
|
128
|
-
guardedState.
|
|
130
|
+
guardedState.set(extension, Invitation.State.CONNECTING);
|
|
129
131
|
}
|
|
130
132
|
});
|
|
131
133
|
|
|
@@ -133,9 +135,9 @@ export class InvitationsHandler {
|
|
|
133
135
|
const traceId = PublicKey.random().toHex();
|
|
134
136
|
try {
|
|
135
137
|
log.trace('dxos.sdk.invitations-handler.host.onOpen', trace.begin({ id: traceId }));
|
|
136
|
-
log('connected', { ...protocol.toJSON() });
|
|
138
|
+
log.verbose('connected', { ...protocol.toJSON() });
|
|
137
139
|
const deviceKey = await extension.completedTrigger.wait({ timeout: invitation.timeout });
|
|
138
|
-
log('admitted guest', { guest: deviceKey, ...protocol.toJSON() });
|
|
140
|
+
log.verbose('admitted guest', { guest: deviceKey, ...protocol.toJSON() });
|
|
139
141
|
guardedState.set(extension, Invitation.State.SUCCESS);
|
|
140
142
|
metrics.increment('dxos.invitation.success');
|
|
141
143
|
log.trace('dxos.sdk.invitations-handler.host.onOpen', trace.end({ id: traceId }));
|
|
@@ -145,13 +147,14 @@ export class InvitationsHandler {
|
|
|
145
147
|
await ctx.dispose();
|
|
146
148
|
}
|
|
147
149
|
} catch (err: any) {
|
|
150
|
+
const stateChanged = guardedState.set(extension, Invitation.State.CONNECTING);
|
|
148
151
|
if (err instanceof TimeoutError) {
|
|
149
|
-
if (
|
|
152
|
+
if (stateChanged) {
|
|
150
153
|
metrics.increment('dxos.invitation.timeout');
|
|
151
|
-
log('timeout', { ...protocol.toJSON() });
|
|
154
|
+
log.verbose('timeout', { ...protocol.toJSON() });
|
|
152
155
|
}
|
|
153
156
|
} else {
|
|
154
|
-
if (
|
|
157
|
+
if (stateChanged) {
|
|
155
158
|
metrics.increment('dxos.invitation.failed');
|
|
156
159
|
log.error('failed', err);
|
|
157
160
|
}
|
|
@@ -163,17 +166,18 @@ export class InvitationsHandler {
|
|
|
163
166
|
});
|
|
164
167
|
},
|
|
165
168
|
onError: (err) => {
|
|
169
|
+
const stateChanged = guardedState.set(extension, Invitation.State.CONNECTING);
|
|
166
170
|
if (err instanceof InvalidInvitationExtensionRoleError) {
|
|
167
171
|
log('invalid role', { ...err.context });
|
|
168
172
|
return;
|
|
169
173
|
}
|
|
170
174
|
if (err instanceof TimeoutError) {
|
|
171
|
-
if (
|
|
175
|
+
if (stateChanged) {
|
|
172
176
|
metrics.increment('dxos.invitation.timeout');
|
|
173
|
-
log('timeout', { err });
|
|
177
|
+
log.verbose('timeout', { err });
|
|
174
178
|
}
|
|
175
179
|
} else {
|
|
176
|
-
if (
|
|
180
|
+
if (stateChanged) {
|
|
177
181
|
metrics.increment('dxos.invitation.failed');
|
|
178
182
|
log.error('failed', err);
|
|
179
183
|
}
|
|
@@ -187,19 +191,21 @@ export class InvitationsHandler {
|
|
|
187
191
|
if (invitation.lifetime && invitation.created) {
|
|
188
192
|
if (invitation.created.getTime() + invitation.lifetime * 1000 < Date.now()) {
|
|
189
193
|
log.warn('invitation has already expired');
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
async () => {
|
|
194
|
-
// ensure the swarm is closed before changing state and closing the stream.
|
|
195
|
-
await swarmConnection.close();
|
|
196
|
-
guardedState.set(null, Invitation.State.EXPIRED);
|
|
197
|
-
metrics.increment('dxos.invitation.expired');
|
|
198
|
-
await ctx.dispose();
|
|
199
|
-
},
|
|
200
|
-
invitation.created.getTime() + invitation.lifetime * 1000 - Date.now(),
|
|
201
|
-
);
|
|
194
|
+
guardedState.set(null, Invitation.State.EXPIRED);
|
|
195
|
+
void ctx.dispose().catch((err) => log.catch(err));
|
|
196
|
+
return;
|
|
202
197
|
}
|
|
198
|
+
scheduleTask(
|
|
199
|
+
ctx,
|
|
200
|
+
async () => {
|
|
201
|
+
// ensure the swarm is closed before changing state and closing the stream.
|
|
202
|
+
await swarmConnection.close();
|
|
203
|
+
guardedState.set(null, Invitation.State.EXPIRED);
|
|
204
|
+
metrics.increment('dxos.invitation.expired');
|
|
205
|
+
await ctx.dispose();
|
|
206
|
+
},
|
|
207
|
+
invitation.created.getTime() + invitation.lifetime * 1000 - Date.now(),
|
|
208
|
+
);
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
let swarmConnection: SwarmConnection;
|
|
@@ -262,7 +268,7 @@ export class InvitationsHandler {
|
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
connectionCtx.onDispose(async () => {
|
|
265
|
-
log('extension disposed', { admitted, currentState: guardedState.current.state });
|
|
271
|
+
log.verbose('extension disposed', { admitted, currentState: guardedState.current.state });
|
|
266
272
|
if (!admitted) {
|
|
267
273
|
guardedState.error(extension, new ContextDisposedError());
|
|
268
274
|
if (shouldCancelInvitationFlow(extension)) {
|
|
@@ -353,10 +359,10 @@ export class InvitationsHandler {
|
|
|
353
359
|
log.trace('dxos.sdk.invitations-handler.guest.onOpen', trace.end({ id: traceId }));
|
|
354
360
|
} catch (err: any) {
|
|
355
361
|
if (err instanceof TimeoutError) {
|
|
356
|
-
log('timeout', { ...protocol.toJSON() });
|
|
362
|
+
log.verbose('timeout', { ...protocol.toJSON() });
|
|
357
363
|
guardedState.set(extension, Invitation.State.TIMEOUT);
|
|
358
364
|
} else {
|
|
359
|
-
log('auth failed', err);
|
|
365
|
+
log.verbose('auth failed', err);
|
|
360
366
|
guardedState.error(extension, err);
|
|
361
367
|
}
|
|
362
368
|
extensionCtx.close(err);
|
|
@@ -369,10 +375,10 @@ export class InvitationsHandler {
|
|
|
369
375
|
return;
|
|
370
376
|
}
|
|
371
377
|
if (err instanceof TimeoutError) {
|
|
372
|
-
log('timeout', { ...protocol.toJSON() });
|
|
378
|
+
log.verbose('timeout', { ...protocol.toJSON() });
|
|
373
379
|
guardedState.set(extension, Invitation.State.TIMEOUT);
|
|
374
380
|
} else {
|
|
375
|
-
log('auth failed', err);
|
|
381
|
+
log.verbose('auth failed', err);
|
|
376
382
|
guardedState.error(extension, err);
|
|
377
383
|
}
|
|
378
384
|
},
|
|
@@ -190,7 +190,7 @@ export class InvitationsManager {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
private _createInvitation(protocol: InvitationProtocol,
|
|
193
|
+
private _createInvitation(protocol: InvitationProtocol, _options?: Partial<Invitation>): Invitation {
|
|
194
194
|
const {
|
|
195
195
|
invitationId = PublicKey.random().toHex(),
|
|
196
196
|
type = Invitation.Type.INTERACTIVE,
|
|
@@ -198,13 +198,14 @@ export class InvitationsManager {
|
|
|
198
198
|
state = Invitation.State.INIT,
|
|
199
199
|
timeout = INVITATION_TIMEOUT,
|
|
200
200
|
swarmKey = PublicKey.random(),
|
|
201
|
-
persistent =
|
|
201
|
+
persistent = _options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
|
|
202
202
|
created = new Date(),
|
|
203
203
|
guestKeypair = undefined,
|
|
204
204
|
role = SpaceMember.Role.ADMIN,
|
|
205
205
|
lifetime = 86400, // 1 day,
|
|
206
206
|
multiUse = false,
|
|
207
|
-
|
|
207
|
+
...options
|
|
208
|
+
} = _options ?? {};
|
|
208
209
|
const authCode =
|
|
209
210
|
options?.authCode ??
|
|
210
211
|
(authMethod === Invitation.AuthMethod.SHARED_SECRET ? generatePasscode(AUTHENTICATION_CODE_LENGTH) : undefined);
|
|
@@ -225,6 +226,7 @@ export class InvitationsManager {
|
|
|
225
226
|
role,
|
|
226
227
|
multiUse,
|
|
227
228
|
delegationCredentialId: options?.delegationCredentialId,
|
|
229
|
+
...options,
|
|
228
230
|
...protocol.getInvitationContext(),
|
|
229
231
|
} satisfies Invitation;
|
|
230
232
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type InvitationsService,
|
|
11
11
|
QueryInvitationsResponse,
|
|
12
12
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
13
|
+
import { trace } from '@dxos/tracing';
|
|
13
14
|
|
|
14
15
|
import { type InvitationsManager } from './invitations-manager';
|
|
15
16
|
|
|
@@ -30,7 +31,10 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
30
31
|
return new Stream<Invitation>(({ next, close }) => {
|
|
31
32
|
void this._invitationsManager
|
|
32
33
|
.createInvitation(options)
|
|
33
|
-
.then((invitation) =>
|
|
34
|
+
.then((invitation) => {
|
|
35
|
+
trace.metrics.increment('dxos.invitation.created');
|
|
36
|
+
invitation.subscribe(next, close, close);
|
|
37
|
+
})
|
|
34
38
|
.catch(close);
|
|
35
39
|
});
|
|
36
40
|
}
|
|
@@ -381,12 +381,12 @@ export class ServiceContext extends Resource {
|
|
|
381
381
|
identity: identity.identityKey.toHex(),
|
|
382
382
|
swarms: this.networkManager.topics,
|
|
383
383
|
});
|
|
384
|
-
if (params?.deviceCredential) {
|
|
384
|
+
if (params?.deviceCredential || Boolean((globalThis as any)?.DEVICE_INVITE_INTEROP)) {
|
|
385
385
|
edgeIdentity = await createChainEdgeIdentity(
|
|
386
386
|
identity.signer,
|
|
387
387
|
identity.identityKey,
|
|
388
388
|
identity.deviceKey,
|
|
389
|
-
{ credential: params.deviceCredential },
|
|
389
|
+
params?.deviceCredential && { credential: params.deviceCredential },
|
|
390
390
|
[], // TODO(dmaretskyi): Service access credentials.
|
|
391
391
|
);
|
|
392
392
|
} else {
|
|
@@ -318,7 +318,7 @@ export class ClientServicesHost {
|
|
|
318
318
|
|
|
319
319
|
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
320
320
|
|
|
321
|
-
DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
|
|
321
|
+
DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager, this._edgeConnection),
|
|
322
322
|
|
|
323
323
|
SpacesService: new SpacesServiceImpl(
|
|
324
324
|
this._serviceContext.identityManager,
|
|
@@ -65,15 +65,16 @@ describe('EdgeFeedReplicator', () => {
|
|
|
65
65
|
|
|
66
66
|
test('re-requests metadata on reconnect', async () => {
|
|
67
67
|
const { endpoint, admitConnection, messageSink } = await createEdge();
|
|
68
|
-
const { messenger } = await createClient(endpoint);
|
|
68
|
+
const { messenger, reconnectTrigger } = await createClient(endpoint);
|
|
69
69
|
|
|
70
70
|
await attachReplicator(messenger);
|
|
71
71
|
|
|
72
72
|
admitConnection.wake();
|
|
73
73
|
await expect.poll(() => messageSink.length).toEqual(1);
|
|
74
74
|
|
|
75
|
+
reconnectTrigger.reset();
|
|
75
76
|
await updateIdentity(messenger);
|
|
76
|
-
await
|
|
77
|
+
await reconnectTrigger.wait();
|
|
77
78
|
|
|
78
79
|
await expect.poll(() => messageSink.length).toEqual(2);
|
|
79
80
|
expect(messageSink[1].type).toEqual('get-metadata');
|
|
@@ -100,7 +101,7 @@ describe('EdgeFeedReplicator', () => {
|
|
|
100
101
|
|
|
101
102
|
test('recovers after response sending failure during identity change', async () => {
|
|
102
103
|
const { endpoint, admitConnection, messageSink, sendResponseMessage } = await createEdge();
|
|
103
|
-
const { messenger, sendSpy } = await createClient(endpoint);
|
|
104
|
+
const { messenger, sendSpy, reconnectTrigger } = await createClient(endpoint);
|
|
104
105
|
|
|
105
106
|
const { feed } = await attachReplicator(messenger);
|
|
106
107
|
await appendMessage(feed);
|
|
@@ -117,9 +118,11 @@ describe('EdgeFeedReplicator', () => {
|
|
|
117
118
|
await expect.poll(() => sendSpy.mock.calls.length).toEqual(2);
|
|
118
119
|
sendSpy.mockRestore();
|
|
119
120
|
expect(messageSink.length).toEqual(0);
|
|
121
|
+
|
|
122
|
+
reconnectTrigger.reset();
|
|
120
123
|
await updateIdentity(messenger);
|
|
124
|
+
await reconnectTrigger.wait();
|
|
121
125
|
|
|
122
|
-
await messenger.reconnect.waitForCount(1);
|
|
123
126
|
await expect.poll(() => messageSink.find((msg) => msg.type === 'data')).toBeDefined();
|
|
124
127
|
});
|
|
125
128
|
|
|
@@ -151,8 +154,8 @@ describe('EdgeFeedReplicator', () => {
|
|
|
151
154
|
await sleep(100);
|
|
152
155
|
admitConnection.wake();
|
|
153
156
|
|
|
154
|
-
await expect.poll(() => messageSink.length).toEqual(
|
|
155
|
-
expect(messageSink.map((m) => m.type)).toStrictEqual(range(
|
|
157
|
+
await expect.poll(() => messageSink.length).toEqual(1);
|
|
158
|
+
expect(messageSink.map((m) => m.type)).toStrictEqual(range(1, () => 'get-metadata'));
|
|
156
159
|
});
|
|
157
160
|
|
|
158
161
|
test('block appended during reconnect', async () => {
|
|
@@ -213,10 +216,12 @@ describe('EdgeFeedReplicator', () => {
|
|
|
213
216
|
};
|
|
214
217
|
|
|
215
218
|
const createClient = async (endpoint: string) => {
|
|
219
|
+
const reconnectTrigger = new Trigger();
|
|
216
220
|
const messenger = new EdgeClient(await createEphemeralEdgeIdentity(), { socketEndpoint: endpoint });
|
|
221
|
+
messenger.onReconnected(() => reconnectTrigger.wake());
|
|
217
222
|
const sendSpy = vi.spyOn(messenger, 'send');
|
|
218
223
|
await openAndClose(messenger);
|
|
219
|
-
return { messenger, sendSpy };
|
|
224
|
+
return { messenger, sendSpy, reconnectTrigger };
|
|
220
225
|
};
|
|
221
226
|
|
|
222
227
|
const attachReplicator = async (messenger: EdgeClient, options?: { skipOpen?: boolean }) => {
|
|
@@ -56,7 +56,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
56
56
|
log('open');
|
|
57
57
|
// TODO: handle reconnects
|
|
58
58
|
this._ctx.onDispose(
|
|
59
|
-
this._messenger.
|
|
59
|
+
this._messenger.onMessage((message: RouterMessage) => {
|
|
60
60
|
if (!message.serviceId) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
@@ -77,11 +77,15 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
77
77
|
}),
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
this._ctx.onDispose(
|
|
81
|
+
this._messenger.onReconnected(() => {
|
|
82
|
+
scheduleMicroTask(this._ctx, () => this._handleReconnect());
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
84
86
|
|
|
87
|
+
private async _handleReconnect() {
|
|
88
|
+
await this._resetConnection();
|
|
85
89
|
if (this._messenger.isConnected) {
|
|
86
90
|
this._startReplication();
|
|
87
91
|
}
|
|
@@ -62,6 +62,7 @@ export const performInvitation = ({
|
|
|
62
62
|
}: PerformInvitationParams): [Promise<Result>, Promise<Result>] => {
|
|
63
63
|
let guestError = false;
|
|
64
64
|
let guestConnected = false;
|
|
65
|
+
let wereConnected = false;
|
|
65
66
|
const hostComplete = new Trigger<Result>();
|
|
66
67
|
const guestComplete = new Trigger<Result>();
|
|
67
68
|
const authCode = new Trigger<string>();
|
|
@@ -72,12 +73,17 @@ export const performInvitation = ({
|
|
|
72
73
|
switch (hostInvitation.state) {
|
|
73
74
|
case Invitation.State.CONNECTING: {
|
|
74
75
|
if (guestConnected) {
|
|
76
|
+
if (wereConnected) {
|
|
77
|
+
hostComplete.wake({ invitation: hostInvitation });
|
|
78
|
+
}
|
|
75
79
|
break;
|
|
76
80
|
}
|
|
81
|
+
|
|
77
82
|
guestConnected = true;
|
|
78
83
|
if (hooks?.host?.onConnecting?.(hostObservable)) {
|
|
79
84
|
break;
|
|
80
85
|
}
|
|
86
|
+
|
|
81
87
|
const guestObservable = acceptInvitation(guest, hostInvitation, guestDeviceProfile);
|
|
82
88
|
guestObservable.subscribe(
|
|
83
89
|
async (guestInvitation: Invitation) => {
|
|
@@ -153,6 +159,7 @@ export const performInvitation = ({
|
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
case Invitation.State.CONNECTED: {
|
|
162
|
+
wereConnected = true;
|
|
156
163
|
hooks?.host?.onConnected?.(hostObservable);
|
|
157
164
|
break;
|
|
158
165
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DXOS_VERSION = "0.6.14-
|
|
1
|
+
export const DXOS_VERSION = "0.6.14-staging.3e2eaca";
|