@dxos/client-services 0.1.39 → 0.1.40
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-2DGNNQWS.mjs → chunk-LGMYP2WL.mjs} +679 -569
- package/dist/lib/browser/chunk-LGMYP2WL.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +14 -14
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +721 -611
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +725 -621
- package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts +54 -0
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +8 -3
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -1
- package/package.json +30 -30
- package/src/packlets/invitations/invitation-extension.ts +211 -0
- package/src/packlets/invitations/invitations-handler.ts +54 -123
- package/src/packlets/invitations/invitations-service.ts +106 -137
- package/src/packlets/services/utils.ts +3 -3
- package/src/packlets/testing/test-builder.ts +9 -7
- package/src/packlets/vault/iframe-host-runtime.ts +3 -3
- package/src/packlets/vault/iframe-proxy-runtime.ts +11 -3
- package/src/packlets/vault/worker-runtime.ts +3 -3
- package/dist/lib/browser/chunk-2DGNNQWS.mjs.map +0 -7
|
@@ -6,31 +6,24 @@ import assert from 'node:assert';
|
|
|
6
6
|
|
|
7
7
|
import { PushStream, scheduleTask, sleep, TimeoutError, Trigger } from '@dxos/async';
|
|
8
8
|
import {
|
|
9
|
+
AuthenticatingInvitationObservable,
|
|
9
10
|
AUTHENTICATION_CODE_LENGTH,
|
|
10
11
|
CancellableInvitationObservable,
|
|
11
12
|
INVITATION_TIMEOUT,
|
|
12
|
-
ON_CLOSE_DELAY
|
|
13
|
-
AuthenticatingInvitationObservable
|
|
13
|
+
ON_CLOSE_DELAY
|
|
14
14
|
} from '@dxos/client';
|
|
15
15
|
import { Context } from '@dxos/context';
|
|
16
16
|
import { generatePasscode } from '@dxos/credentials';
|
|
17
|
+
import { InvalidInvitationExtensionRoleError } from '@dxos/errors';
|
|
17
18
|
import { PublicKey } from '@dxos/keys';
|
|
18
19
|
import { log } from '@dxos/log';
|
|
19
20
|
import { createTeleportProtocolFactory, NetworkManager, StarTopology } from '@dxos/network-manager';
|
|
20
|
-
import {
|
|
21
|
+
import { trace } from '@dxos/protocols';
|
|
21
22
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
22
23
|
import { ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
|
-
import {
|
|
24
|
-
AuthenticationRequest,
|
|
25
|
-
AuthenticationResponse,
|
|
26
|
-
IntroductionRequest,
|
|
27
|
-
IntroductionResponse,
|
|
28
|
-
AdmissionRequest,
|
|
29
|
-
AdmissionResponse,
|
|
30
|
-
InvitationHostService
|
|
31
|
-
} from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
32
|
-
import { ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
24
|
+
import { AuthenticationResponse } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
33
25
|
|
|
26
|
+
import { InvitationGuestExtension, InvitationHostExtension } from './invitation-extension';
|
|
34
27
|
import { InvitationProtocol } from './invitation-protocol';
|
|
35
28
|
|
|
36
29
|
const MAX_OTP_ATTEMPTS = 3;
|
|
@@ -219,6 +212,18 @@ export class InvitationsHandler {
|
|
|
219
212
|
}
|
|
220
213
|
}
|
|
221
214
|
});
|
|
215
|
+
},
|
|
216
|
+
onError: (err) => {
|
|
217
|
+
if (err instanceof InvalidInvitationExtensionRoleError) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (err instanceof TimeoutError) {
|
|
221
|
+
log('timeout', { ...protocol.toJSON() });
|
|
222
|
+
stream.next({ ...invitation, state: Invitation.State.TIMEOUT });
|
|
223
|
+
} else {
|
|
224
|
+
log.error('failed', err);
|
|
225
|
+
stream.error(err);
|
|
226
|
+
}
|
|
222
227
|
}
|
|
223
228
|
});
|
|
224
229
|
extension._traceParent = this._traceParent;
|
|
@@ -260,12 +265,19 @@ export class InvitationsHandler {
|
|
|
260
265
|
|
|
261
266
|
const authenticated = new Trigger<string>();
|
|
262
267
|
|
|
268
|
+
let currentState: Invitation.State;
|
|
263
269
|
const stream = new PushStream<Invitation>();
|
|
270
|
+
const setState = (newData: Partial<Invitation>) => {
|
|
271
|
+
assert(newData.state !== undefined);
|
|
272
|
+
currentState = newData.state;
|
|
273
|
+
stream.next({ ...invitation, ...newData });
|
|
274
|
+
};
|
|
275
|
+
|
|
264
276
|
const ctx = new Context({
|
|
265
277
|
onError: (err) => {
|
|
266
278
|
if (err instanceof TimeoutError) {
|
|
267
279
|
log('timeout', { ...protocol.toJSON() });
|
|
268
|
-
|
|
280
|
+
setState({ state: Invitation.State.TIMEOUT });
|
|
269
281
|
} else {
|
|
270
282
|
log.warn('auth failed', err);
|
|
271
283
|
stream.error(err);
|
|
@@ -283,7 +295,14 @@ export class InvitationsHandler {
|
|
|
283
295
|
let connectionCount = 0;
|
|
284
296
|
|
|
285
297
|
const extension = new InvitationGuestExtension({
|
|
286
|
-
onOpen: () => {
|
|
298
|
+
onOpen: (extensionCtx) => {
|
|
299
|
+
extensionCtx.onDispose(async () => {
|
|
300
|
+
log('extension disposed', { currentState });
|
|
301
|
+
if (currentState !== Invitation.State.SUCCESS) {
|
|
302
|
+
stream.error(new Error('Remote peer disconnected.'));
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
287
306
|
scheduleTask(ctx, async () => {
|
|
288
307
|
const traceId = PublicKey.random().toHex();
|
|
289
308
|
try {
|
|
@@ -299,7 +318,7 @@ export class InvitationsHandler {
|
|
|
299
318
|
scheduleTask(ctx, () => ctx.raise(new TimeoutError(timeout)), timeout);
|
|
300
319
|
|
|
301
320
|
log('connected', { ...protocol.toJSON() });
|
|
302
|
-
|
|
321
|
+
setState({ state: Invitation.State.CONNECTED });
|
|
303
322
|
|
|
304
323
|
// 1. Introduce guest to host.
|
|
305
324
|
log('introduce', { ...protocol.toJSON() });
|
|
@@ -316,11 +335,11 @@ export class InvitationsHandler {
|
|
|
316
335
|
if (isAuthenticationRequired(invitation)) {
|
|
317
336
|
for (let attempt = 1; attempt <= MAX_OTP_ATTEMPTS; attempt++) {
|
|
318
337
|
log('guest waiting for authentication code...');
|
|
319
|
-
|
|
338
|
+
setState({ state: Invitation.State.READY_FOR_AUTHENTICATION });
|
|
320
339
|
const authCode = await authenticated.wait({ timeout });
|
|
321
340
|
|
|
322
341
|
log('sending authentication request');
|
|
323
|
-
|
|
342
|
+
setState({ state: Invitation.State.AUTHENTICATING });
|
|
324
343
|
const response = await extension.rpc.InvitationHostService.authenticate({ authCode });
|
|
325
344
|
if (response.status === undefined || response.status === AuthenticationResponse.Status.OK) {
|
|
326
345
|
break;
|
|
@@ -337,7 +356,7 @@ export class InvitationsHandler {
|
|
|
337
356
|
}
|
|
338
357
|
} else {
|
|
339
358
|
// Notify that introduction is complete even if auth is not required.
|
|
340
|
-
|
|
359
|
+
setState({ state: Invitation.State.READY_FOR_AUTHENTICATION });
|
|
341
360
|
}
|
|
342
361
|
|
|
343
362
|
// 3. Send admission credentials to host (with local space keys).
|
|
@@ -350,12 +369,12 @@ export class InvitationsHandler {
|
|
|
350
369
|
|
|
351
370
|
// 5. Success.
|
|
352
371
|
log('admitted by host', { ...protocol.toJSON() });
|
|
353
|
-
|
|
372
|
+
setState({ ...result, state: Invitation.State.SUCCESS });
|
|
354
373
|
log.trace('dxos.sdk.invitations-handler.guest.onOpen', trace.end({ id: traceId }));
|
|
355
374
|
} catch (err: any) {
|
|
356
375
|
if (err instanceof TimeoutError) {
|
|
357
376
|
log('timeout', { ...protocol.toJSON() });
|
|
358
|
-
|
|
377
|
+
setState({ state: Invitation.State.TIMEOUT });
|
|
359
378
|
} else {
|
|
360
379
|
log('auth failed', err);
|
|
361
380
|
stream.error(err);
|
|
@@ -365,6 +384,18 @@ export class InvitationsHandler {
|
|
|
365
384
|
await ctx.dispose();
|
|
366
385
|
}
|
|
367
386
|
});
|
|
387
|
+
},
|
|
388
|
+
onError: (err) => {
|
|
389
|
+
if (err instanceof InvalidInvitationExtensionRoleError) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (err instanceof TimeoutError) {
|
|
393
|
+
log('timeout', { ...protocol.toJSON() });
|
|
394
|
+
setState({ state: Invitation.State.TIMEOUT });
|
|
395
|
+
} else {
|
|
396
|
+
log('auth failed', err);
|
|
397
|
+
stream.error(err);
|
|
398
|
+
}
|
|
368
399
|
}
|
|
369
400
|
});
|
|
370
401
|
|
|
@@ -384,14 +415,14 @@ export class InvitationsHandler {
|
|
|
384
415
|
});
|
|
385
416
|
ctx.onDispose(() => swarmConnection.close());
|
|
386
417
|
|
|
387
|
-
|
|
418
|
+
setState({ state: Invitation.State.CONNECTING });
|
|
388
419
|
});
|
|
389
420
|
|
|
390
421
|
const observable = new AuthenticatingInvitationObservable({
|
|
391
422
|
initialInvitation: invitation,
|
|
392
423
|
subscriber: stream.observable,
|
|
393
424
|
onCancel: async () => {
|
|
394
|
-
|
|
425
|
+
setState({ state: Invitation.State.CANCELLED });
|
|
395
426
|
await ctx.dispose();
|
|
396
427
|
},
|
|
397
428
|
onAuthenticate: async (code: string) => {
|
|
@@ -405,103 +436,3 @@ export class InvitationsHandler {
|
|
|
405
436
|
}
|
|
406
437
|
|
|
407
438
|
const isAuthenticationRequired = (invitation: Invitation) => invitation.authMethod !== Invitation.AuthMethod.NONE;
|
|
408
|
-
|
|
409
|
-
type InvitationHostExtensionCallbacks = {
|
|
410
|
-
// Deliberately not async to not block the extensions opening.
|
|
411
|
-
onOpen: () => void;
|
|
412
|
-
|
|
413
|
-
introduce: (request: IntroductionRequest) => Promise<IntroductionResponse>;
|
|
414
|
-
authenticate: (request: AuthenticationRequest) => Promise<AuthenticationResponse>;
|
|
415
|
-
admit: (request: AdmissionRequest) => Promise<AdmissionResponse>;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Host's side for a connection to a concrete peer in p2p network during invitation.
|
|
420
|
-
*/
|
|
421
|
-
class InvitationHostExtension extends RpcExtension<{}, { InvitationHostService: InvitationHostService }> {
|
|
422
|
-
/**
|
|
423
|
-
* @internal
|
|
424
|
-
*/
|
|
425
|
-
public _traceParent?: string;
|
|
426
|
-
|
|
427
|
-
constructor(private readonly _callbacks: InvitationHostExtensionCallbacks) {
|
|
428
|
-
super({
|
|
429
|
-
exposed: {
|
|
430
|
-
InvitationHostService: schema.getService('dxos.halo.invitations.InvitationHostService')
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
protected override async getHandlers(): Promise<{ InvitationHostService: InvitationHostService }> {
|
|
436
|
-
return {
|
|
437
|
-
// TODO(dmaretskyi): For now this is just forwarding the data to callbacks since we don't have session-specific logic.
|
|
438
|
-
// Perhaps in the future we will have more complex logic here.
|
|
439
|
-
InvitationHostService: {
|
|
440
|
-
introduce: async (request) => {
|
|
441
|
-
const traceId = PublicKey.random().toHex();
|
|
442
|
-
log.trace(
|
|
443
|
-
'dxos.sdk.invitation-handler.host.introduce',
|
|
444
|
-
trace.begin({ id: traceId, parentId: this._traceParent })
|
|
445
|
-
);
|
|
446
|
-
const response = await this._callbacks.introduce(request);
|
|
447
|
-
log.trace('dxos.sdk.invitation-handler.host.introduce', trace.end({ id: traceId }));
|
|
448
|
-
return response;
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
authenticate: async (request) => {
|
|
452
|
-
const traceId = PublicKey.random().toHex();
|
|
453
|
-
log.trace(
|
|
454
|
-
'dxos.sdk.invitation-handler.host.authenticate',
|
|
455
|
-
trace.begin({ id: traceId, parentId: this._traceParent })
|
|
456
|
-
);
|
|
457
|
-
const response = await this._callbacks.authenticate(request);
|
|
458
|
-
log.trace('dxos.sdk.invitation-handler.host.authenticate', trace.end({ id: traceId, data: { ...response } }));
|
|
459
|
-
return response;
|
|
460
|
-
},
|
|
461
|
-
|
|
462
|
-
admit: async (request) => {
|
|
463
|
-
const traceId = PublicKey.random().toHex();
|
|
464
|
-
log.trace(
|
|
465
|
-
'dxos.sdk.invitation-handler.host.admit',
|
|
466
|
-
trace.begin({ id: traceId, parentId: this._traceParent })
|
|
467
|
-
);
|
|
468
|
-
const response = await this._callbacks.admit(request);
|
|
469
|
-
log.trace('dxos.sdk.invitation-handler.host.admit', trace.end({ id: traceId }));
|
|
470
|
-
return response;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
override async onOpen(context: ExtensionContext) {
|
|
477
|
-
await super.onOpen(context);
|
|
478
|
-
this._callbacks.onOpen();
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
type InvitationGuestExtensionCallbacks = {
|
|
483
|
-
// Deliberately not async to not block the extensions opening.
|
|
484
|
-
onOpen: () => void;
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Guest's side for a connection to a concrete peer in p2p network during invitation.
|
|
489
|
-
*/
|
|
490
|
-
class InvitationGuestExtension extends RpcExtension<{ InvitationHostService: InvitationHostService }, {}> {
|
|
491
|
-
constructor(private readonly _callbacks: InvitationGuestExtensionCallbacks) {
|
|
492
|
-
super({
|
|
493
|
-
requested: {
|
|
494
|
-
InvitationHostService: schema.getService('dxos.halo.invitations.InvitationHostService')
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
protected override async getHandlers() {
|
|
500
|
-
return {};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
override async onOpen(context: ExtensionContext) {
|
|
504
|
-
await super.onOpen(context);
|
|
505
|
-
this._callbacks.onOpen();
|
|
506
|
-
}
|
|
507
|
-
}
|
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import assert from 'node:assert';
|
|
6
6
|
|
|
7
|
+
import { Event } from '@dxos/async';
|
|
7
8
|
import { AuthenticatingInvitationObservable, CancellableInvitationObservable } from '@dxos/client';
|
|
8
9
|
import { Stream } from '@dxos/codec-protobuf';
|
|
9
10
|
import { log } from '@dxos/log';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AuthenticationRequest,
|
|
13
|
+
Invitation,
|
|
14
|
+
InvitationsService,
|
|
15
|
+
QueryInvitationsResponse
|
|
16
|
+
} from '@dxos/protocols/proto/dxos/client/services';
|
|
11
17
|
|
|
12
18
|
import { InvitationProtocol } from './invitation-protocol';
|
|
13
19
|
import { InvitationsHandler } from './invitations-handler';
|
|
@@ -18,6 +24,10 @@ import { InvitationsHandler } from './invitations-handler';
|
|
|
18
24
|
export class InvitationsServiceImpl implements InvitationsService {
|
|
19
25
|
private readonly _createInvitations = new Map<string, CancellableInvitationObservable>();
|
|
20
26
|
private readonly _acceptInvitations = new Map<string, AuthenticatingInvitationObservable>();
|
|
27
|
+
private readonly _invitationCreated = new Event<Invitation>();
|
|
28
|
+
private readonly _invitationAccepted = new Event<Invitation>();
|
|
29
|
+
private readonly _removedCreated = new Event<Invitation>();
|
|
30
|
+
private readonly _removedAccepted = new Event<Invitation>();
|
|
21
31
|
|
|
22
32
|
constructor(
|
|
23
33
|
private readonly _invitationsHandler: InvitationsHandler,
|
|
@@ -31,160 +41,61 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
31
41
|
};
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
createInvitation(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
createInvitation(options: Invitation): Stream<Invitation> {
|
|
45
|
+
let invitation: CancellableInvitationObservable;
|
|
46
|
+
|
|
47
|
+
const existingInvitation = this._createInvitations.get(options.invitationId);
|
|
48
|
+
if (existingInvitation) {
|
|
49
|
+
invitation = existingInvitation;
|
|
50
|
+
} else {
|
|
51
|
+
const handler = this._getHandler(options);
|
|
52
|
+
invitation = this._invitationsHandler.createInvitation(handler, options);
|
|
53
|
+
this._createInvitations.set(invitation.get().invitationId, invitation);
|
|
54
|
+
this._invitationCreated.emit(invitation.get());
|
|
55
|
+
}
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
observable.subscribe(
|
|
57
|
+
return new Stream<Invitation>(({ next, close }) => {
|
|
58
|
+
invitation.subscribe(
|
|
42
59
|
(invitation) => {
|
|
43
|
-
|
|
44
|
-
case Invitation.State.CONNECTING: {
|
|
45
|
-
assert(invitation.invitationId);
|
|
46
|
-
invitationId = invitation.invitationId;
|
|
47
|
-
this._createInvitations.set(invitation.invitationId, observable);
|
|
48
|
-
invitation.state = Invitation.State.CONNECTING;
|
|
49
|
-
next(invitation);
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
case Invitation.State.CONNECTED: {
|
|
53
|
-
assert(invitation.invitationId);
|
|
54
|
-
invitation.state = Invitation.State.CONNECTED;
|
|
55
|
-
next(invitation);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
case Invitation.State.READY_FOR_AUTHENTICATION: {
|
|
59
|
-
assert(invitation.invitationId);
|
|
60
|
-
invitation.state = Invitation.State.READY_FOR_AUTHENTICATION;
|
|
61
|
-
next(invitation);
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
case Invitation.State.AUTHENTICATING: {
|
|
65
|
-
assert(invitation.invitationId);
|
|
66
|
-
invitation.state = Invitation.State.AUTHENTICATING;
|
|
67
|
-
next(invitation);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
case Invitation.State.SUCCESS: {
|
|
71
|
-
assert(invitation.invitationId);
|
|
72
|
-
invitation.state = Invitation.State.SUCCESS;
|
|
73
|
-
next(invitation);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
case Invitation.State.CANCELLED: {
|
|
77
|
-
assert(invitationId);
|
|
78
|
-
invitation.invitationId = invitationId;
|
|
79
|
-
invitation.state = Invitation.State.CANCELLED;
|
|
80
|
-
next(invitation);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
case Invitation.State.TIMEOUT: {
|
|
84
|
-
assert(invitationId);
|
|
85
|
-
invitation.invitationId = invitationId;
|
|
86
|
-
invitation.state = Invitation.State.TIMEOUT;
|
|
87
|
-
next(invitation);
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
60
|
+
next(invitation);
|
|
91
61
|
},
|
|
92
62
|
(err: Error) => {
|
|
93
63
|
close(err);
|
|
94
64
|
},
|
|
95
65
|
() => {
|
|
96
66
|
close();
|
|
67
|
+
this._createInvitations.delete(invitation.get().invitationId);
|
|
97
68
|
}
|
|
98
69
|
);
|
|
99
|
-
|
|
100
|
-
return (err?: Error) => {
|
|
101
|
-
const context = this.getLoggingContext();
|
|
102
|
-
if (err) {
|
|
103
|
-
log.warn('stream closed', { ...context, err });
|
|
104
|
-
} else {
|
|
105
|
-
log('stream closed', context);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
this._createInvitations.delete(invitation.invitationId!);
|
|
109
|
-
};
|
|
110
70
|
});
|
|
111
71
|
}
|
|
112
72
|
|
|
113
|
-
acceptInvitation(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
73
|
+
acceptInvitation(options: Invitation): Stream<Invitation> {
|
|
74
|
+
let invitation: AuthenticatingInvitationObservable;
|
|
75
|
+
|
|
76
|
+
const existingInvitation = this._acceptInvitations.get(options.invitationId);
|
|
77
|
+
if (existingInvitation) {
|
|
78
|
+
invitation = existingInvitation;
|
|
79
|
+
} else {
|
|
80
|
+
const handler = this._getHandler(options);
|
|
81
|
+
invitation = this._invitationsHandler.acceptInvitation(handler, options);
|
|
82
|
+
this._acceptInvitations.set(invitation.get().invitationId, invitation);
|
|
83
|
+
this._invitationAccepted.emit(invitation.get());
|
|
84
|
+
}
|
|
117
85
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
observable.subscribe(
|
|
86
|
+
return new Stream<Invitation>(({ next, close }) => {
|
|
87
|
+
invitation.subscribe(
|
|
121
88
|
(invitation) => {
|
|
122
|
-
|
|
123
|
-
case Invitation.State.CONNECTING: {
|
|
124
|
-
assert(invitation.invitationId);
|
|
125
|
-
invitationId = invitation.invitationId;
|
|
126
|
-
this._acceptInvitations.set(invitation.invitationId, observable);
|
|
127
|
-
invitation.state = Invitation.State.CONNECTING;
|
|
128
|
-
next(invitation);
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case Invitation.State.CONNECTED: {
|
|
132
|
-
assert(invitation.invitationId);
|
|
133
|
-
invitation.state = Invitation.State.CONNECTED;
|
|
134
|
-
next(invitation);
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
case Invitation.State.READY_FOR_AUTHENTICATION: {
|
|
138
|
-
assert(invitation.invitationId);
|
|
139
|
-
invitation.state = Invitation.State.READY_FOR_AUTHENTICATION;
|
|
140
|
-
next(invitation);
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
case Invitation.State.AUTHENTICATING: {
|
|
144
|
-
assert(invitation.invitationId);
|
|
145
|
-
invitation.state = Invitation.State.AUTHENTICATING;
|
|
146
|
-
next(invitation);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
case Invitation.State.SUCCESS: {
|
|
150
|
-
invitation.state = Invitation.State.SUCCESS;
|
|
151
|
-
next(invitation);
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case Invitation.State.CANCELLED: {
|
|
155
|
-
assert(invitationId);
|
|
156
|
-
invitation.invitationId = invitationId;
|
|
157
|
-
invitation.state = Invitation.State.CANCELLED;
|
|
158
|
-
next(invitation);
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
case Invitation.State.TIMEOUT: {
|
|
162
|
-
assert(invitationId);
|
|
163
|
-
invitation.invitationId = invitationId;
|
|
164
|
-
invitation.state = Invitation.State.TIMEOUT;
|
|
165
|
-
next(invitation);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
89
|
+
next(invitation);
|
|
169
90
|
},
|
|
170
91
|
(err: Error) => {
|
|
171
92
|
close(err);
|
|
172
93
|
},
|
|
173
94
|
() => {
|
|
174
95
|
close();
|
|
96
|
+
this._acceptInvitations.delete(invitation.get().invitationId);
|
|
175
97
|
}
|
|
176
98
|
);
|
|
177
|
-
|
|
178
|
-
return (err?: Error) => {
|
|
179
|
-
const context = this.getLoggingContext();
|
|
180
|
-
if (err) {
|
|
181
|
-
log.warn('stream closed', { ...context, err });
|
|
182
|
-
} else {
|
|
183
|
-
log('stream closed', context);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this._acceptInvitations.delete(invitation.invitationId!);
|
|
187
|
-
};
|
|
188
99
|
});
|
|
189
100
|
}
|
|
190
101
|
|
|
@@ -200,13 +111,71 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
200
111
|
}
|
|
201
112
|
|
|
202
113
|
async cancelInvitation({ invitationId }: { invitationId: string }): Promise<void> {
|
|
203
|
-
log('
|
|
114
|
+
log('deleting...');
|
|
204
115
|
assert(invitationId);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
116
|
+
const created = this._createInvitations.get(invitationId);
|
|
117
|
+
const accepted = this._acceptInvitations.get(invitationId);
|
|
118
|
+
if (created) {
|
|
119
|
+
await created.cancel();
|
|
120
|
+
this._createInvitations.delete(invitationId);
|
|
121
|
+
this._removedCreated.emit(created.get());
|
|
122
|
+
} else if (accepted) {
|
|
123
|
+
await accepted.cancel();
|
|
124
|
+
this._acceptInvitations.delete(invitationId);
|
|
125
|
+
this._removedAccepted.emit(accepted.get());
|
|
208
126
|
} else {
|
|
209
|
-
|
|
127
|
+
log.warn('invalid invitation', { invitationId });
|
|
210
128
|
}
|
|
211
129
|
}
|
|
130
|
+
|
|
131
|
+
queryInvitations(): Stream<QueryInvitationsResponse> {
|
|
132
|
+
return new Stream<QueryInvitationsResponse>(({ next, ctx }) => {
|
|
133
|
+
// Push added invitations to the stream.
|
|
134
|
+
this._invitationCreated.on(ctx, (invitation) => {
|
|
135
|
+
next({
|
|
136
|
+
action: QueryInvitationsResponse.Action.ADDED,
|
|
137
|
+
type: QueryInvitationsResponse.Type.CREATED,
|
|
138
|
+
invitations: [invitation]
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this._invitationAccepted.on(ctx, (invitation) => {
|
|
143
|
+
next({
|
|
144
|
+
action: QueryInvitationsResponse.Action.ADDED,
|
|
145
|
+
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
146
|
+
invitations: [invitation]
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Push removed invitations to the stream.
|
|
151
|
+
this._removedCreated.on(ctx, (invitation) => {
|
|
152
|
+
next({
|
|
153
|
+
action: QueryInvitationsResponse.Action.REMOVED,
|
|
154
|
+
type: QueryInvitationsResponse.Type.CREATED,
|
|
155
|
+
invitations: [invitation]
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this._removedAccepted.on(ctx, (invitation) => {
|
|
160
|
+
next({
|
|
161
|
+
action: QueryInvitationsResponse.Action.REMOVED,
|
|
162
|
+
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
163
|
+
invitations: [invitation]
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Push existing invitations to the stream.
|
|
168
|
+
next({
|
|
169
|
+
action: QueryInvitationsResponse.Action.ADDED,
|
|
170
|
+
type: QueryInvitationsResponse.Type.CREATED,
|
|
171
|
+
invitations: Array.from(this._createInvitations.values()).map((invitation) => invitation.get())
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
next({
|
|
175
|
+
action: QueryInvitationsResponse.Action.ADDED,
|
|
176
|
+
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
177
|
+
invitations: Array.from(this._acceptInvitations.values()).map((invitation) => invitation.get())
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
212
181
|
}
|
|
@@ -30,11 +30,11 @@ export const fromHost = (config: Config = new Config()): ClientServicesProvider
|
|
|
30
30
|
*/
|
|
31
31
|
// TODO(burdon): Move to client-services and remove dependencies from here.
|
|
32
32
|
const createNetworkManager = (config: Config, options: Partial<NetworkManagerOptions> = {}): NetworkManager => {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
33
|
+
const signals = config.get('runtime.services.signaling');
|
|
34
|
+
if (signals) {
|
|
35
35
|
const {
|
|
36
36
|
log = true,
|
|
37
|
-
signalManager = new WebsocketSignalManager(
|
|
37
|
+
signalManager = new WebsocketSignalManager(signals),
|
|
38
38
|
transportFactory = createWebRTCTransportFactory({
|
|
39
39
|
iceServers: config.get('runtime.services.ice')
|
|
40
40
|
})
|
|
@@ -23,10 +23,12 @@ export const testConfigWithLocalSignal = new Config({
|
|
|
23
23
|
version: 1,
|
|
24
24
|
runtime: {
|
|
25
25
|
services: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
signaling: [
|
|
27
|
+
{
|
|
28
|
+
// TODO(burdon): Port numbers and global consts?
|
|
29
|
+
server: 'ws://localhost:4000/.well-known/dx/signal'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
});
|
|
@@ -56,11 +58,11 @@ export class TestBuilder {
|
|
|
56
58
|
* Get network manager using local shared memory or remote signal manager.
|
|
57
59
|
*/
|
|
58
60
|
get networkManager() {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
+
const signals = this._config.get('runtime.services.signaling');
|
|
62
|
+
if (signals) {
|
|
61
63
|
return new NetworkManager({
|
|
62
64
|
log: true,
|
|
63
|
-
signalManager: new WebsocketSignalManager(
|
|
65
|
+
signalManager: new WebsocketSignalManager(signals),
|
|
64
66
|
transportFactory: createWebRTCTransportFactory({
|
|
65
67
|
iceServers: this._config.get('runtime.services.ice')
|
|
66
68
|
})
|
|
@@ -75,14 +75,14 @@ export class IFrameHostRuntime {
|
|
|
75
75
|
log('starting...');
|
|
76
76
|
try {
|
|
77
77
|
this._config = await getAsyncValue(this._configProvider);
|
|
78
|
-
const
|
|
78
|
+
const signals = this._config.get('runtime.services.signaling');
|
|
79
79
|
this._clientServices = new LocalClientServices({
|
|
80
80
|
lockKey: LOCK_KEY,
|
|
81
81
|
config: this._config,
|
|
82
82
|
networkManager: new NetworkManager({
|
|
83
83
|
log: true,
|
|
84
|
-
signalManager:
|
|
85
|
-
? new WebsocketSignalManager(
|
|
84
|
+
signalManager: signals
|
|
85
|
+
? new WebsocketSignalManager(signals)
|
|
86
86
|
: new MemorySignalManager(new MemorySignalManagerContext()), // TODO(dmaretskyi): Inject this context.
|
|
87
87
|
transportFactory: this._transportFactory
|
|
88
88
|
})
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { iframeServiceBundle, workerServiceBundle, WorkerServiceBundle } from '@dxos/client';
|
|
6
|
+
import { RemoteServiceConnectionError } from '@dxos/errors';
|
|
7
|
+
import { log } from '@dxos/log';
|
|
6
8
|
import { WebRTCTransportService } from '@dxos/network-manager';
|
|
7
9
|
import { BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
8
10
|
import { createProtoRpcPeer, ProtoRpcPeer, RpcPort } from '@dxos/rpc';
|
|
@@ -54,7 +56,7 @@ export class IFrameProxyRuntime {
|
|
|
54
56
|
}
|
|
55
57
|
},
|
|
56
58
|
port: this._systemPort,
|
|
57
|
-
timeout:
|
|
59
|
+
timeout: 1000
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
this._workerAppPort.subscribe((msg) => this._windowAppPort.send(msg));
|
|
@@ -70,8 +72,14 @@ export class IFrameProxyRuntime {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
async open(origin: string) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
try {
|
|
76
|
+
await this._systemRpc.open();
|
|
77
|
+
await this._systemRpc.rpc.WorkerService.start({ origin });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.catch(err);
|
|
80
|
+
throw new RemoteServiceConnectionError('Failed to connect to worker');
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
await this._shellRuntime?.open();
|
|
76
84
|
}
|
|
77
85
|
|