@dxos/client-services 0.1.35 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/lib/browser/{chunk-H5IXSSI7.mjs → chunk-2AYYVPWN.mjs} +835 -950
  2. package/dist/lib/browser/chunk-2AYYVPWN.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +9 -11
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/packlets/testing/index.mjs +212 -74
  6. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  7. package/dist/lib/node/index.cjs +942 -1058
  8. package/dist/lib/node/index.cjs.map +4 -4
  9. package/dist/lib/node/meta.json +1 -1
  10. package/dist/lib/node/packlets/testing/index.cjs +1089 -1067
  11. package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
  12. package/dist/types/src/packlets/identity/identity-manager.d.ts +1 -0
  13. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  14. package/dist/types/src/packlets/identity/identity.d.ts +3 -3
  15. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  16. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +18 -0
  17. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -0
  18. package/dist/types/src/packlets/invitations/device-invitation-protocol.test.d.ts +2 -0
  19. package/dist/types/src/packlets/invitations/device-invitation-protocol.test.d.ts.map +1 -0
  20. package/dist/types/src/packlets/invitations/index.d.ts +4 -4
  21. package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +12 -0
  23. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -0
  24. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +38 -0
  25. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -0
  26. package/dist/types/src/packlets/invitations/invitations-service.d.ts +13 -15
  27. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  28. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +22 -0
  29. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -0
  30. package/dist/types/src/packlets/invitations/space-invitation-protocol.test.d.ts +2 -0
  31. package/dist/types/src/packlets/invitations/space-invitation-protocol.test.d.ts.map +1 -0
  32. package/dist/types/src/packlets/services/service-context.d.ts +6 -4
  33. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  35. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +2 -0
  36. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  37. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  38. package/dist/types/src/packlets/testing/invitation-utils.d.ts +31 -2
  39. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  40. package/dist/types/src/packlets/testing/test-builder.d.ts +3 -3
  41. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  42. package/dist/types/src/packlets/tests/invitations.test.d.ts +2 -0
  43. package/dist/types/src/packlets/tests/invitations.test.d.ts.map +1 -0
  44. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -0
  45. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  46. package/package.json +30 -30
  47. package/src/packlets/identity/identity-manager.ts +2 -1
  48. package/src/packlets/identity/identity.ts +3 -3
  49. package/src/packlets/invitations/{device-invitations-handler.test.ts → device-invitation-protocol.test.ts} +3 -3
  50. package/src/packlets/invitations/device-invitation-protocol.ts +83 -0
  51. package/src/packlets/invitations/index.ts +4 -4
  52. package/src/packlets/invitations/invitation-protocol.ts +21 -0
  53. package/src/packlets/invitations/invitations-handler.ts +462 -0
  54. package/src/packlets/invitations/invitations-service.ts +127 -104
  55. package/src/packlets/invitations/space-invitation-protocol.test.ts +221 -0
  56. package/src/packlets/invitations/space-invitation-protocol.ts +117 -0
  57. package/src/packlets/services/service-context.test.ts +11 -4
  58. package/src/packlets/services/service-context.ts +40 -19
  59. package/src/packlets/services/service-host.ts +4 -12
  60. package/src/packlets/services/service-registry.test.ts +23 -25
  61. package/src/packlets/spaces/data-space-manager.ts +5 -0
  62. package/src/packlets/spaces/spaces-service.ts +7 -2
  63. package/src/packlets/storage/storage.ts +2 -0
  64. package/src/packlets/testing/invitation-utils.ts +219 -31
  65. package/src/packlets/testing/test-builder.ts +33 -22
  66. package/src/packlets/tests/client-services.test.ts +33 -76
  67. package/src/packlets/tests/halo-profile.test.ts +2 -28
  68. package/src/packlets/tests/invitations.test.ts +389 -0
  69. package/src/packlets/tests/spaces-invitations.test.ts +12 -30
  70. package/src/packlets/tests/spaces.test.ts +30 -28
  71. package/src/packlets/vault/worker-runtime.ts +4 -0
  72. package/dist/lib/browser/chunk-H5IXSSI7.mjs.map +0 -7
  73. package/dist/types/src/packlets/invitations/device-invitations-handler.d.ts +0 -30
  74. package/dist/types/src/packlets/invitations/device-invitations-handler.d.ts.map +0 -1
  75. package/dist/types/src/packlets/invitations/device-invitations-handler.test.d.ts +0 -2
  76. package/dist/types/src/packlets/invitations/device-invitations-handler.test.d.ts.map +0 -1
  77. package/dist/types/src/packlets/invitations/device-invitations-service.d.ts +0 -13
  78. package/dist/types/src/packlets/invitations/device-invitations-service.d.ts.map +0 -1
  79. package/dist/types/src/packlets/invitations/device-invitations-service.test.d.ts +0 -2
  80. package/dist/types/src/packlets/invitations/device-invitations-service.test.d.ts.map +0 -1
  81. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +0 -26
  82. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +0 -1
  83. package/dist/types/src/packlets/invitations/space-invitations-handler.test.d.ts +0 -2
  84. package/dist/types/src/packlets/invitations/space-invitations-handler.test.d.ts.map +0 -1
  85. package/dist/types/src/packlets/invitations/space-invitations-service.d.ts +0 -16
  86. package/dist/types/src/packlets/invitations/space-invitations-service.d.ts.map +0 -1
  87. package/dist/types/src/packlets/invitations/space-invitations-service.test.d.ts +0 -2
  88. package/dist/types/src/packlets/invitations/space-invitations-service.test.d.ts.map +0 -1
  89. package/src/packlets/invitations/device-invitations-handler.ts +0 -370
  90. package/src/packlets/invitations/device-invitations-service.test.ts +0 -96
  91. package/src/packlets/invitations/device-invitations-service.ts +0 -24
  92. package/src/packlets/invitations/space-invitations-handler.test.ts +0 -246
  93. package/src/packlets/invitations/space-invitations-handler.ts +0 -463
  94. package/src/packlets/invitations/space-invitations-service.test.ts +0 -129
  95. package/src/packlets/invitations/space-invitations-service.ts +0 -35
@@ -2,7 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import assert from 'node:assert';
6
+
5
7
  import { Trigger } from '@dxos/async';
8
+ import { Invitation } from '@dxos/client';
6
9
  import { CredentialConsumer, getCredentialAssertion } from '@dxos/credentials';
7
10
  import { failUndefined } from '@dxos/debug';
8
11
  import {
@@ -25,7 +28,12 @@ import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
25
28
  import { Storage } from '@dxos/random-access-storage';
26
29
 
27
30
  import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
28
- import { DeviceInvitationsHandler, SpaceInvitationsHandler } from '../invitations';
31
+ import {
32
+ DeviceInvitationProtocol,
33
+ InvitationsHandler,
34
+ InvitationProtocol,
35
+ SpaceInvitationProtocol
36
+ } from '../invitations';
29
37
  import { DataSpaceManager } from '../spaces';
30
38
 
31
39
  /**
@@ -41,13 +49,17 @@ export class ServiceContext {
41
49
  public readonly keyring: Keyring;
42
50
  public readonly spaceManager: SpaceManager;
43
51
  public readonly identityManager: IdentityManager;
44
- public readonly deviceInvitations: DeviceInvitationsHandler;
45
-
46
- private _deviceSpaceSync?: CredentialConsumer<any>;
52
+ public readonly invitations: InvitationsHandler;
47
53
 
48
54
  // Initialized after identity is initialized.
49
55
  public dataSpaceManager?: DataSpaceManager;
50
- public spaceInvitations?: SpaceInvitationsHandler;
56
+
57
+ private readonly _handlerFactories = new Map<
58
+ Invitation.Kind,
59
+ (invitation: Partial<Invitation>) => InvitationProtocol
60
+ >();
61
+
62
+ private _deviceSpaceSync?: CredentialConsumer<any>;
51
63
 
52
64
  private readonly _instanceId = PublicKey.random().toHex();
53
65
 
@@ -57,6 +69,7 @@ export class ServiceContext {
57
69
  public readonly networkManager: NetworkManager,
58
70
  public readonly modelFactory: ModelFactory
59
71
  ) {
72
+ networkManager._traceParent = this._instanceId;
60
73
  // TODO(burdon): Move strings to constants.
61
74
  this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
62
75
  this.snapshotStore = new SnapshotStore(storage.createDirectory('snapshots'));
@@ -76,6 +89,7 @@ export class ServiceContext {
76
89
  feedStore: this.feedStore,
77
90
  networkManager: this.networkManager
78
91
  });
92
+ this.spaceManager._traceParent = this._instanceId;
79
93
 
80
94
  this.identityManager = new IdentityManager(
81
95
  this.metadataStore,
@@ -83,19 +97,21 @@ export class ServiceContext {
83
97
  this.feedStore,
84
98
  this.spaceManager
85
99
  );
100
+ this.identityManager._traceParent = this._instanceId;
101
+
102
+ this.invitations = new InvitationsHandler(this.networkManager);
86
103
 
87
104
  // TODO(burdon): _initialize called in multiple places.
88
105
  // TODO(burdon): Call _initialize on success.
89
- this.deviceInvitations = new DeviceInvitationsHandler({
90
- networkManager: this.networkManager,
91
- getIdentity: () => this.identityManager.identity ?? failUndefined(),
92
- acceptIdentity: this._acceptIdentity.bind(this),
93
- keyring: this.keyring
94
- });
106
+ this._handlerFactories.set(Invitation.Kind.DEVICE, () => new DeviceInvitationProtocol(
107
+ this.keyring,
108
+ () => this.identityManager.identity ?? failUndefined(),
109
+ this._acceptIdentity.bind(this)
110
+ ));
95
111
  }
96
112
 
97
113
  async open() {
98
- log.trace('dxos.sdk.client-services', trace.begin({ id: this._instanceId }));
114
+ log.trace('dxos.sdk.service-context', trace.begin({ id: this._instanceId }));
99
115
 
100
116
  log('opening...');
101
117
  await this.networkManager.open();
@@ -118,7 +134,7 @@ export class ServiceContext {
118
134
  this.dataServiceSubscriptions.clear();
119
135
  log('closed');
120
136
 
121
- log.trace('dxos.sdk.client-services', trace.end({ id: this._instanceId }));
137
+ log.trace('dxos.sdk.service-context', trace.end({ id: this._instanceId }));
122
138
  }
123
139
 
124
140
  async reset() {
@@ -135,6 +151,12 @@ export class ServiceContext {
135
151
  return identity;
136
152
  }
137
153
 
154
+ getInvitationHandler(invitation: Partial<Invitation> & Pick<Invitation, 'kind'>): InvitationProtocol {
155
+ const factory = this._handlerFactories.get(invitation.kind);
156
+ assert(factory, `Unknown invitation kind: ${invitation.kind}`);
157
+ return factory(invitation);
158
+ }
159
+
138
160
  private async _acceptIdentity(params: JoinIdentityParams) {
139
161
  const identity = await this.identityManager.acceptIdentity(params);
140
162
 
@@ -166,14 +188,13 @@ export class ServiceContext {
166
188
  this.feedStore,
167
189
  this.snapshotStore
168
190
  );
191
+ this.dataSpaceManager._traceParent = this._instanceId;
169
192
  await this.dataSpaceManager.open();
170
193
 
171
- this.spaceInvitations = new SpaceInvitationsHandler(
172
- this.networkManager,
173
- this.dataSpaceManager,
174
- signingContext,
175
- this.keyring
176
- );
194
+ this._handlerFactories.set(Invitation.Kind.SPACE, (invitation) => {
195
+ assert(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
196
+ return new SpaceInvitationProtocol(this.dataSpaceManager, signingContext, this.keyring, invitation.spaceKey);
197
+ });
177
198
  this.initialized.wake();
178
199
 
179
200
  this._deviceSpaceSync = identity.space.spaceState.registerProcessor({
@@ -7,7 +7,6 @@ import assert from 'node:assert';
7
7
  import { Event } from '@dxos/async';
8
8
  import { clientServiceBundle, ClientServices, createDefaultModelFactory, PublicKey } from '@dxos/client';
9
9
  import { Config } from '@dxos/config';
10
- import { raise } from '@dxos/debug';
11
10
  import { DataServiceImpl } from '@dxos/echo-pipeline';
12
11
  import { log } from '@dxos/log';
13
12
  import { ModelFactory } from '@dxos/model-factory';
@@ -20,7 +19,7 @@ import { TracingServiceImpl } from '../deprecated';
20
19
  import { DevicesServiceImpl } from '../devices';
21
20
  import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
22
21
  import { IdentityServiceImpl } from '../identity';
23
- import { DeviceInvitationsServiceImpl, SpaceInvitationsServiceImpl } from '../invitations';
22
+ import { InvitationsServiceImpl } from '../invitations';
24
23
  import { NetworkServiceImpl } from '../network';
25
24
  import { SpacesServiceImpl } from '../spaces';
26
25
  import { createStorageObjects } from '../storage';
@@ -185,18 +184,11 @@ export class ClientServicesHost {
185
184
 
186
185
  IdentityService: new IdentityServiceImpl(this._serviceContext),
187
186
 
188
- DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
189
-
190
- DeviceInvitationsService: new DeviceInvitationsServiceImpl(
191
- this._serviceContext.identityManager,
192
- this._serviceContext.deviceInvitations
187
+ InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitations, (invitation) =>
188
+ this._serviceContext.getInvitationHandler(invitation)
193
189
  ),
194
190
 
195
- SpaceInvitationsService: new SpaceInvitationsServiceImpl(
196
- this._serviceContext.identityManager,
197
- () => this._serviceContext.spaceInvitations ?? raise(new Error('SpaceInvitations not initialized')),
198
- () => this._serviceContext.dataSpaceManager ?? raise(new Error('SpaceManager not initialized'))
199
- ),
191
+ DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
200
192
 
201
193
  SpacesService: new SpacesServiceImpl(
202
194
  this._serviceContext.identityManager,
@@ -5,25 +5,25 @@
5
5
  import assert from 'node:assert';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
- import { ClientServices, SpaceInvitationsProxy } from '@dxos/client';
8
+ import { ClientServices, InvitationsProxy } from '@dxos/client';
9
9
  import { log } from '@dxos/log';
10
10
  import { schema } from '@dxos/protocols';
11
- import { Invitation, SpaceInvitationsService } from '@dxos/protocols/proto/dxos/client/services';
11
+ import { Invitation, InvitationsService } from '@dxos/protocols/proto/dxos/client/services';
12
12
  import { createLinkedPorts, createProtoRpcPeer, createServiceBundle } from '@dxos/rpc';
13
13
  import { describe, test } from '@dxos/test';
14
14
 
15
- import { SpaceInvitationsServiceImpl } from '../invitations';
15
+ import { InvitationsServiceImpl } from '../invitations';
16
16
  import { createServiceContext } from '../testing';
17
17
  import { ServiceRegistry } from './service-registry';
18
18
 
19
19
  // TODO(burdon): Create TestService (that doesn't require peers).
20
20
 
21
21
  type TestServices = {
22
- SpaceInvitationsService: SpaceInvitationsService;
22
+ InvitationsService: InvitationsService;
23
23
  };
24
24
 
25
25
  const serviceBundle = createServiceBundle<TestServices>({
26
- SpaceInvitationsService: schema.getService('dxos.client.services.SpaceInvitationsService')
26
+ InvitationsService: schema.getService('dxos.client.services.InvitationsService')
27
27
  });
28
28
 
29
29
  describe('service registry', () => {
@@ -33,14 +33,11 @@ describe('service registry', () => {
33
33
  await serviceContext.createIdentity();
34
34
 
35
35
  assert(serviceContext.dataSpaceManager);
36
- assert(serviceContext.spaceInvitations);
37
36
  const space = await serviceContext.dataSpaceManager.createSpace();
38
37
 
39
38
  const serviceRegistry = new ServiceRegistry(serviceBundle, {
40
- SpaceInvitationsService: new SpaceInvitationsServiceImpl(
41
- serviceContext.identityManager,
42
- () => serviceContext.spaceInvitations!,
43
- () => serviceContext.dataSpaceManager!
39
+ InvitationsService: new InvitationsServiceImpl(serviceContext.invitations, (invitation) =>
40
+ serviceContext.getInvitationHandler(invitation)
44
41
  )
45
42
  });
46
43
 
@@ -66,21 +63,22 @@ describe('service registry', () => {
66
63
  const done = new Trigger(); // createRpcServer
67
64
 
68
65
  {
69
- const spaceInvitationsProxy = new SpaceInvitationsProxy(proxy.rpc.SpaceInvitationsService);
70
- const observer = spaceInvitationsProxy.createInvitation(space.key);
71
- observer.subscribe({
72
- onConnecting: (invitation: Invitation) => {
73
- log('connecting', invitation);
74
- void observer.cancel();
75
- },
76
- onCancelled: () => {
77
- done.wake();
78
- },
79
- onSuccess: (_invitation: Invitation) => {
80
- throw new Error('Not not implemented.');
81
- },
82
- onError: () => {
83
- throw new Error('Not not implemented.');
66
+ const spaceInvitationsProxy = new InvitationsProxy(proxy.rpc.InvitationsService, () => ({
67
+ kind: Invitation.Kind.SPACE,
68
+ spaceKey: space.key
69
+ }));
70
+ const observer = spaceInvitationsProxy.createInvitation();
71
+ observer.subscribe((invitation: Invitation) => {
72
+ switch (invitation.state) {
73
+ case Invitation.State.CONNECTING: {
74
+ log('connecting', invitation);
75
+ void observer.cancel();
76
+ break;
77
+ }
78
+
79
+ case Invitation.State.CANCELLED: {
80
+ done.wake();
81
+ }
84
82
  }
85
83
  });
86
84
  }
@@ -23,6 +23,7 @@ import { Keyring } from '@dxos/keyring';
23
23
  import { PublicKey } from '@dxos/keys';
24
24
  import { log } from '@dxos/log';
25
25
  import { ModelFactory } from '@dxos/model-factory';
26
+ import { trace } from '@dxos/protocols';
26
27
  import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
27
28
  import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
28
29
  import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
@@ -58,6 +59,8 @@ export class DataSpaceManager {
58
59
  private readonly _spaces = new ComplexMap<PublicKey, DataSpace>(PublicKey.hash);
59
60
 
60
61
  private _isOpen = false;
62
+ private readonly _instanceId = PublicKey.random().toHex();
63
+ public _traceParent?: string;
61
64
 
62
65
  constructor(
63
66
  private readonly _spaceManager: SpaceManager,
@@ -78,6 +81,7 @@ export class DataSpaceManager {
78
81
  @synchronized
79
82
  async open() {
80
83
  log('open');
84
+ log.trace('dxos.echo.data-space-manager', trace.begin({ id: this._instanceId, parentId: this._traceParent }));
81
85
  await this._metadataStore.load();
82
86
  log('metadata loaded', { spaces: this._metadataStore.spaces.length });
83
87
 
@@ -99,6 +103,7 @@ export class DataSpaceManager {
99
103
  for (const space of this._spaces.values()) {
100
104
  await space.close();
101
105
  }
106
+ log.trace('dxos.echo.data-space-manager', trace.end({ id: this._instanceId }));
102
107
  }
103
108
 
104
109
  /**
@@ -149,10 +149,15 @@ export class SpacesServiceImpl implements SpacesService {
149
149
  spaceKey: space.key,
150
150
  state: space.state,
151
151
  pipeline: {
152
- targetControlTimeframe: space.inner.controlPipeline.state.targetTimeframe,
152
+ controlFeeds: space.inner.controlPipeline.state.feeds.map((feed) => feed.key),
153
153
  currentControlTimeframe: space.inner.controlPipeline.state.timeframe,
154
+ targetControlTimeframe: space.inner.controlPipeline.state.targetTimeframe,
155
+ totalControlTimeframe: space.inner.controlPipeline.state.endTimeframe,
156
+
157
+ dataFeeds: space.dataPipeline.pipelineState?.feeds.map((feed) => feed.key) ?? [],
154
158
  currentDataTimeframe: space.dataPipeline.pipelineState?.timeframe,
155
- targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe
159
+ targetDataTimeframe: space.dataPipeline.pipelineState?.targetTimeframe,
160
+ totalDataTimeframe: space.dataPipeline.pipelineState?.endTimeframe
156
161
  },
157
162
  members: Array.from(space.inner.spaceState.members.values()).map((member) => ({
158
163
  identity: {
@@ -54,6 +54,8 @@ const toStorageType = (type: StorageDriver | undefined): StorageType | undefined
54
54
  return StorageType.IDB;
55
55
  case StorageDriver.NODE:
56
56
  return StorageType.NODE;
57
+ case StorageDriver.WEBFS:
58
+ return StorageType.WEBFS;
57
59
  default:
58
60
  throw new Error(`Invalid storage type: ${StorageDriver[type]}`);
59
61
  }
@@ -5,39 +5,227 @@
5
5
  import assert from 'node:assert';
6
6
 
7
7
  import { Trigger } from '@dxos/async';
8
- import { InvitationsHandler } from '@dxos/client';
9
- import { raise } from '@dxos/debug';
8
+ import {
9
+ AuthenticatingInvitationObservable,
10
+ CancellableInvitationObservable,
11
+ Client,
12
+ EchoProxy,
13
+ HaloProxy,
14
+ InvitationsProxy,
15
+ SpaceProxy
16
+ } from '@dxos/client';
10
17
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
11
18
 
12
- export const performInvitation = async <T>(host: InvitationsHandler<T>, guest: InvitationsHandler<T>, context: T) => {
13
- const done1 = new Trigger<Invitation>(); // peer 1 connected.
14
- const done2 = new Trigger<Invitation>(); // peer 2 connected.
15
- const observable1 = await host.createInvitation(context, { type: Invitation.Type.INTERACTIVE_TESTING });
16
- observable1.subscribe({
17
- onConnecting: async (invitation1: Invitation) => {
18
- const observable2 = await guest.acceptInvitation(invitation1, { type: Invitation.Type.INTERACTIVE_TESTING });
19
- observable2.subscribe({
20
- onConnecting: async (invitation2: Invitation) => {
21
- assert(invitation1.swarmKey!.equals(invitation2.swarmKey!));
22
- },
23
- onConnected: async (invitation2: Invitation) => {},
24
- onSuccess: (invitation) => {
25
- done2.wake(invitation);
26
- },
27
- onCancelled: () => raise(new Error()),
28
- onTimeout: (err: Error) => raise(err),
29
- onError: (err: Error) => raise(err)
30
- });
31
- },
32
- onConnected: async (invitation1: Invitation) => {},
33
- onCancelled: () => raise(new Error('cancelled')),
34
- onSuccess: (invitation) => {
35
- done1.wake(invitation);
19
+ import { ServiceContext } from '../services';
20
+
21
+ /**
22
+ * Strip secrets from invitation before giving it to the peer.
23
+ */
24
+ export const sanitizeInvitation = (invitation: Invitation): Invitation => {
25
+ return {
26
+ invitationId: invitation.invitationId,
27
+ type: invitation.type,
28
+ kind: invitation.kind,
29
+ authMethod: invitation.authMethod,
30
+ swarmKey: invitation.swarmKey,
31
+ state: invitation.state,
32
+ timeout: invitation.timeout
33
+ };
34
+ };
35
+
36
+ export type PerformInvitationCallbacks<T> = {
37
+ onConnecting?: (value: T) => boolean | void;
38
+ onConnected?: (value: T) => boolean | void;
39
+ onReady?: (value: T) => boolean | void;
40
+ onAuthenticating?: (value: T) => boolean | void;
41
+ onSuccess?: (value: T) => boolean | void;
42
+ onCancelled?: (value: T) => boolean | void;
43
+ onTimeout?: (value: T) => boolean | void;
44
+ onError?: (value: T) => boolean | void;
45
+ };
46
+
47
+ export type PerformInvitationParams = {
48
+ host: ServiceContext | InvitationsProxy | HaloProxy | SpaceProxy;
49
+ guest: ServiceContext | InvitationsProxy | HaloProxy | EchoProxy | Client;
50
+ options?: Partial<Invitation>;
51
+ hooks?: {
52
+ host?: PerformInvitationCallbacks<CancellableInvitationObservable>;
53
+ guest?: PerformInvitationCallbacks<AuthenticatingInvitationObservable>;
54
+ };
55
+ };
56
+
57
+ export type Result = { invitation?: Invitation; error?: Error };
58
+
59
+ export const performInvitation = ({
60
+ host,
61
+ guest,
62
+ options,
63
+ hooks
64
+ }: PerformInvitationParams): [Promise<Result>, Promise<Result>] => {
65
+ const hostComplete = new Trigger<Result>();
66
+ const guestComplete = new Trigger<Result>();
67
+ const authCode = new Trigger<string>();
68
+
69
+ const hostObservable = createInvitation(host, options);
70
+ hostObservable.subscribe(
71
+ async (hostInvitation: Invitation) => {
72
+ switch (hostInvitation.state) {
73
+ case Invitation.State.CONNECTING: {
74
+ if (hooks?.host?.onConnecting?.(hostObservable)) {
75
+ break;
76
+ }
77
+ const guestObservable = acceptInvitation(guest, hostInvitation);
78
+ guestObservable.subscribe(
79
+ async (guestInvitation: Invitation) => {
80
+ switch (guestInvitation.state) {
81
+ case Invitation.State.CONNECTING: {
82
+ if (hooks?.guest?.onConnecting?.(guestObservable)) {
83
+ break;
84
+ }
85
+ assert(hostInvitation.swarmKey!.equals(guestInvitation.swarmKey!));
86
+ break;
87
+ }
88
+
89
+ case Invitation.State.CONNECTED: {
90
+ hooks?.guest?.onConnected?.(guestObservable);
91
+ break;
92
+ }
93
+
94
+ case Invitation.State.READY_FOR_AUTHENTICATION: {
95
+ if (hooks?.guest?.onReady?.(guestObservable)) {
96
+ break;
97
+ }
98
+ await guestObservable.authenticate(await authCode.wait());
99
+ break;
100
+ }
101
+
102
+ case Invitation.State.AUTHENTICATING: {
103
+ hooks?.guest?.onAuthenticating?.(guestObservable);
104
+ break;
105
+ }
106
+
107
+ case Invitation.State.SUCCESS: {
108
+ if (hooks?.guest?.onSuccess?.(guestObservable)) {
109
+ break;
110
+ }
111
+ guestComplete.wake({ invitation: guestInvitation });
112
+ break;
113
+ }
114
+
115
+ case Invitation.State.CANCELLED: {
116
+ if (hooks?.guest?.onCancelled?.(guestObservable)) {
117
+ break;
118
+ }
119
+ guestComplete.wake({ invitation: guestInvitation });
120
+ break;
121
+ }
122
+
123
+ case Invitation.State.TIMEOUT: {
124
+ if (hooks?.guest?.onTimeout?.(guestObservable)) {
125
+ return;
126
+ }
127
+ guestComplete.wake({ invitation: guestInvitation });
128
+ }
129
+ }
130
+ },
131
+ (error: Error) => {
132
+ if (hooks?.guest?.onError?.(guestObservable)) {
133
+ return;
134
+ }
135
+ guestComplete.wake({ error });
136
+ }
137
+ );
138
+ break;
139
+ }
140
+
141
+ case Invitation.State.CONNECTED: {
142
+ hooks?.host?.onConnected?.(hostObservable);
143
+ break;
144
+ }
145
+
146
+ case Invitation.State.READY_FOR_AUTHENTICATION: {
147
+ if (hooks?.host?.onReady?.(hostObservable)) {
148
+ break;
149
+ }
150
+ if (hostInvitation.authCode) {
151
+ authCode.wake(hostInvitation.authCode);
152
+ }
153
+ break;
154
+ }
155
+
156
+ case Invitation.State.AUTHENTICATING: {
157
+ hooks?.host?.onAuthenticating?.(hostObservable);
158
+ break;
159
+ }
160
+
161
+ case Invitation.State.SUCCESS: {
162
+ if (hooks?.host?.onSuccess?.(hostObservable)) {
163
+ break;
164
+ }
165
+ hostComplete.wake({ invitation: hostInvitation });
166
+ break;
167
+ }
168
+
169
+ case Invitation.State.CANCELLED: {
170
+ if (hooks?.host?.onCancelled?.(hostObservable)) {
171
+ break;
172
+ }
173
+ hostComplete.wake({ invitation: hostInvitation });
174
+ break;
175
+ }
176
+
177
+ case Invitation.State.TIMEOUT: {
178
+ if (hooks?.host?.onTimeout?.(hostObservable)) {
179
+ break;
180
+ }
181
+ hostComplete.wake({ invitation: hostInvitation });
182
+ break;
183
+ }
184
+ }
36
185
  },
37
- onTimeout: (err: Error) => raise(err),
38
- onError: (err: Error) => raise(err)
39
- });
186
+ (error: Error) => {
187
+ if (hooks?.host?.onError?.(hostObservable)) {
188
+ return;
189
+ }
190
+ hostComplete.wake({ error });
191
+ }
192
+ );
193
+
194
+ return [hostComplete.wait(), guestComplete.wait()];
195
+ };
196
+
197
+ const createInvitation = (
198
+ host: ServiceContext | InvitationsProxy | HaloProxy | SpaceProxy,
199
+ options?: Partial<Invitation>
200
+ ): CancellableInvitationObservable => {
201
+ options ??= {
202
+ authMethod: Invitation.AuthMethod.NONE,
203
+ ...(options ?? {})
204
+ };
205
+
206
+ if (host instanceof InvitationsProxy || host instanceof HaloProxy || host instanceof SpaceProxy) {
207
+ return host.createInvitation(options);
208
+ }
209
+
210
+ const hostHandler = host.getInvitationHandler({ kind: Invitation.Kind.SPACE, ...options });
211
+ return host.invitations.createInvitation(hostHandler, options);
212
+ };
213
+
214
+ const acceptInvitation = (
215
+ guest: ServiceContext | InvitationsProxy | HaloProxy | EchoProxy | Client,
216
+ invitation: Invitation
217
+ ): AuthenticatingInvitationObservable => {
218
+ invitation = sanitizeInvitation(invitation);
219
+
220
+ if (
221
+ guest instanceof InvitationsProxy ||
222
+ guest instanceof HaloProxy ||
223
+ guest instanceof EchoProxy ||
224
+ guest instanceof Client
225
+ ) {
226
+ return guest.acceptInvitation(invitation);
227
+ }
40
228
 
41
- await done1.wait();
42
- await done2.wait();
229
+ const guestHandler = guest.getInvitationHandler({ kind: invitation.kind });
230
+ return guest.invitations.acceptInvitation(guestHandler, invitation);
43
231
  };
@@ -9,7 +9,7 @@ import { Client, ClientServices, ClientServicesProxy, createDefaultModelFactory,
9
9
  import { Config } from '@dxos/config';
10
10
  import { raise } from '@dxos/debug';
11
11
  import { DocumentModel } from '@dxos/document-model';
12
- import { DatabaseBackendProxy, genesisMutation } from '@dxos/echo-db';
12
+ import { DatabaseProxy, genesisMutation } from '@dxos/echo-db';
13
13
  import { PublicKey } from '@dxos/keys';
14
14
  import { log } from '@dxos/log';
15
15
  import { MemorySignalManager, MemorySignalManagerContext, WebsocketSignalManager } from '@dxos/messaging';
@@ -116,7 +116,7 @@ export class TestBuilder {
116
116
  }
117
117
  }
118
118
 
119
- export const testSpace = async (create: DatabaseBackendProxy, check: DatabaseBackendProxy = create) => {
119
+ export const testSpace = async (create: DatabaseProxy, check: DatabaseProxy = create) => {
120
120
  const objectId = PublicKey.random().toHex();
121
121
 
122
122
  const result = create.mutate(genesisMutation(objectId, DocumentModel.meta.type));
@@ -133,7 +133,7 @@ export const testSpace = async (create: DatabaseBackendProxy, check: DatabaseBac
133
133
  return result;
134
134
  };
135
135
 
136
- export const syncItems = async (db1: DatabaseBackendProxy, db2: DatabaseBackendProxy) => {
136
+ export const syncItems = async (db1: DatabaseProxy, db2: DatabaseProxy) => {
137
137
  // Check item replicated from 1 => 2.
138
138
  await testSpace(db1, db2);
139
139
 
@@ -150,27 +150,38 @@ export const joinCommonSpace = async ([initialPeer, ...peers]: Client[], spaceKe
150
150
  const hostDone = new Trigger<Invitation>();
151
151
  const guestDone = new Trigger<Invitation>();
152
152
 
153
- const hostObservabli = rootSpace.createInvitation({ type: Invitation.Type.INTERACTIVE_TESTING });
153
+ const hostObservable = rootSpace.createInvitation({ authMethod: Invitation.AuthMethod.NONE });
154
154
  log('invitation created');
155
- hostObservabli.subscribe({
156
- onConnecting: (invitation) => {
157
- const guestObservable = peer.acceptInvitation(invitation);
158
- log('invitation accepted');
159
-
160
- guestObservable.subscribe({
161
- onSuccess: (invitation: Invitation) => {
162
- guestDone.wake(invitation);
163
- log('invitation guestDone');
164
- },
165
- onError: (err: Error) => raise(err)
166
- });
155
+ hostObservable.subscribe(
156
+ (hostInvitation) => {
157
+ switch (hostInvitation.state) {
158
+ case Invitation.State.CONNECTING: {
159
+ const guestObservable = peer.acceptInvitation(hostInvitation);
160
+ log('invitation accepted');
161
+
162
+ guestObservable.subscribe(
163
+ (guestInvitation) => {
164
+ switch (guestInvitation.state) {
165
+ case Invitation.State.SUCCESS: {
166
+ guestDone.wake(guestInvitation);
167
+ log('invitation guestDone');
168
+ break;
169
+ }
170
+ }
171
+ },
172
+ (err) => raise(err)
173
+ );
174
+ break;
175
+ }
176
+
177
+ case Invitation.State.SUCCESS: {
178
+ hostDone.wake(hostInvitation);
179
+ log('invitation hostDone');
180
+ }
181
+ }
167
182
  },
168
- onSuccess: (invitation) => {
169
- hostDone.wake(invitation);
170
- log('invitation hostDone');
171
- },
172
- onError: (err) => raise(err)
173
- });
183
+ (err) => raise(err)
184
+ );
174
185
 
175
186
  await Promise.all([hostDone.wait(), guestDone.wait()]);
176
187
  })