@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
@@ -7,13 +7,12 @@ import assert from 'node:assert';
7
7
  import waitForExpect from 'wait-for-expect';
8
8
 
9
9
  import { Trigger } from '@dxos/async';
10
- import { Client, Space } from '@dxos/client';
11
- import { raise } from '@dxos/debug';
10
+ import { Client, Space, SpaceProxy } from '@dxos/client';
12
11
  import { log } from '@dxos/log';
13
12
  import { Invitation, SpaceMember } from '@dxos/protocols/proto/dxos/client/services';
14
13
  import { describe, test, afterTest } from '@dxos/test';
15
14
 
16
- import { syncItems, TestBuilder } from '../testing';
15
+ import { performInvitation, syncItems, TestBuilder } from '../testing';
17
16
 
18
17
  // TODO(burdon): Use as set-up for test suite.
19
18
  // TODO(burdon): Timeouts and progress callback/events.
@@ -116,48 +115,20 @@ describe('Client services', () => {
116
115
  await client1.halo.createIdentity();
117
116
  }
118
117
 
119
- const success1 = new Trigger<Invitation>();
120
- const success2 = new Trigger<Invitation>();
121
-
122
- const authenticationCode = new Trigger<string>();
123
-
124
- {
125
- const observable1 = client1.halo.createInvitation();
126
- observable1.subscribe({
127
- onConnecting: (invitation) => {
128
- const observable2 = client2.halo.acceptInvitation(invitation);
129
- observable2.subscribe({
130
- onAuthenticating: async () => {
131
- await observable2.authenticate(await authenticationCode.wait());
132
- },
133
- onSuccess: (invitation: Invitation) => {
134
- // TODO(burdon): No device.
135
- // expect(guest.identityManager.identity!.authorizedDeviceKeys.size).to.eq(1);
136
- success2.wake(invitation);
137
- },
138
- onError: (err: Error) => raise(new Error(err.message))
139
- });
140
- },
141
- onConnected: (invitation: Invitation) => {
142
- assert(invitation.authenticationCode);
143
- authenticationCode.wake(invitation.authenticationCode);
144
- },
145
- onSuccess: (invitation: Invitation) => {
146
- success1.wake(invitation);
147
- },
148
- onCancelled: () => raise(new Error()),
149
- onTimeout: (err: Error) => raise(new Error(err.message)),
150
- onError: (err: Error) => raise(new Error(err.message))
151
- });
152
- }
118
+ const [{ invitation: hostInvitation }, { invitation: guestInvitation }] = await Promise.all(
119
+ performInvitation({
120
+ host: client1.halo,
121
+ guest: client2.halo,
122
+ options: { authMethod: Invitation.AuthMethod.SHARED_SECRET }
123
+ })
124
+ );
153
125
 
154
126
  // Check same identity.
155
- const [invitation1, invitation2] = await Promise.all([success1.wait(), success2.wait()]);
156
- expect(invitation1.identityKey).not.to.exist;
157
- expect(invitation2.identityKey).to.deep.eq(client1.halo.identity.get()!.identityKey);
158
- expect(invitation2.identityKey).to.deep.eq(client2.halo.identity.get()!.identityKey);
159
- expect(invitation1.state).to.eq(Invitation.State.SUCCESS);
160
- expect(invitation2.state).to.eq(Invitation.State.SUCCESS);
127
+ expect(hostInvitation!.identityKey).not.to.exist;
128
+ expect(guestInvitation?.identityKey).to.deep.eq(client1.halo.identity.get()!.identityKey);
129
+ expect(guestInvitation?.identityKey).to.deep.eq(client2.halo.identity.get()!.identityKey);
130
+ expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
131
+ expect(guestInvitation?.state).to.eq(Invitation.State.SUCCESS);
161
132
 
162
133
  // Check devices.
163
134
  // TODO(burdon): Incorrect number of devices.
@@ -194,48 +165,34 @@ describe('Client services', () => {
194
165
  afterTest(() => Promise.all([client1.destroy(), server1.close(), peer1.close()]));
195
166
  afterTest(() => Promise.all([client2.destroy(), server2.close(), peer2.close()]));
196
167
 
197
- const success1 = new Trigger<Invitation>();
198
- const success2 = new Trigger<Invitation>();
199
-
200
- const space1 = await client1.createSpace();
201
- log('createSpace', { key: space1.key });
202
- const observable1 = space1.createInvitation({ type: Invitation.Type.INTERACTIVE_TESTING });
203
-
204
- observable1.subscribe({
205
- onConnecting: (invitation) => {
206
- const observable2 = client2.acceptInvitation(invitation);
207
- observable2.subscribe({
208
- onSuccess: (invitation: Invitation) => {
209
- success2.wake(invitation);
210
- },
211
- onError: (err: Error) => raise(err)
212
- });
213
- },
214
- onSuccess: (invitation) => {
215
- log('onSuccess');
216
- success1.wake(invitation);
217
- },
218
- onError: (err) => raise(err)
219
- });
168
+ const hostSpace = await client1.createSpace();
169
+ log('createSpace', { key: hostSpace.key });
170
+ const [{ invitation: hostInvitation }, { invitation: guestInvitation }] = await Promise.all(
171
+ performInvitation({
172
+ host: hostSpace as SpaceProxy,
173
+ guest: client2,
174
+ options: { authMethod: Invitation.AuthMethod.SHARED_SECRET }
175
+ })
176
+ );
220
177
 
221
- const [invitation1, invitation2] = await Promise.all([success1.wait(), success2.wait()]);
222
- expect(invitation1.spaceKey).to.deep.eq(invitation2.spaceKey);
223
- expect(invitation1.state).to.eq(Invitation.State.SUCCESS);
178
+ expect(guestInvitation?.spaceKey).to.deep.eq(hostSpace.key);
179
+ expect(hostInvitation?.spaceKey).to.deep.eq(guestInvitation?.spaceKey);
180
+ expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
224
181
 
225
182
  log('Invitation complete');
226
183
 
227
184
  // TODO(burdon): Space should now be available?
228
185
  const trigger = new Trigger<Space>();
229
186
  await waitForExpect(() => {
230
- const space2 = client2.getSpace(invitation2.spaceKey!);
231
- assert(space2);
232
- expect(space2).to.exist;
233
- trigger.wake(space2);
187
+ const guestSpace = client2.getSpace(guestInvitation!.spaceKey!);
188
+ assert(guestSpace);
189
+ expect(guestSpace).to.exist;
190
+ trigger.wake(guestSpace);
234
191
  });
235
192
 
236
- const space2 = await trigger.wait();
193
+ const guestSpace = await trigger.wait();
237
194
 
238
- for (const space of [space1, space2]) {
195
+ for (const space of [hostSpace, guestSpace]) {
239
196
  await waitForExpect(() => {
240
197
  expect(space.members.get()).to.deep.equal([
241
198
  {
@@ -260,6 +217,6 @@ describe('Client services', () => {
260
217
  }, 3_000);
261
218
  }
262
219
 
263
- await syncItems(space1.internal.db, space2.internal.db);
220
+ await syncItems(hostSpace.internal.db, guestSpace.internal.db);
264
221
  });
265
222
  });
@@ -4,12 +4,10 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
- import { Trigger } from '@dxos/async';
8
7
  import { Client } from '@dxos/client';
9
- import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
10
8
  import { describe, test, afterTest } from '@dxos/test';
11
9
 
12
- import { TestBuilder } from '../testing';
10
+ import { performInvitation, TestBuilder } from '../testing';
13
11
 
14
12
  describe('Halo', () => {
15
13
  test('creates a identity', async () => {
@@ -42,31 +40,7 @@ describe('Halo', () => {
42
40
  afterTest(() => client2.destroy());
43
41
  await client2.initialize();
44
42
 
45
- const done1 = new Trigger();
46
- const done2 = new Trigger();
47
- const invitation = client1.halo.createInvitation({ type: Invitation.Type.INTERACTIVE_TESTING });
48
- invitation.subscribe({
49
- onConnecting: (invitation) => {
50
- const invitation2 = client2.halo.acceptInvitation(invitation, { type: Invitation.Type.INTERACTIVE_TESTING });
51
- invitation2.subscribe({
52
- onSuccess: () => {
53
- done2.wake();
54
- },
55
- onError: (error) => {
56
- throw error;
57
- }
58
- });
59
- },
60
- onSuccess: async (invitation) => {
61
- done1.wake();
62
- },
63
- onError: (error) => {
64
- throw error;
65
- }
66
- });
67
-
68
- await done1.wait();
69
- await done2.wait();
43
+ await Promise.all(performInvitation({ host: client1.halo, guest: client2.halo }));
70
44
 
71
45
  expect(await client1.halo.devices.get()).to.have.lengthOf(2);
72
46
  expect(await client2.halo.devices.get()).to.have.lengthOf(2);
@@ -0,0 +1,389 @@
1
+ //
2
+ // Copyright 2021 DXOS.org
3
+ //
4
+
5
+ import chai, { expect } from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
7
+ import assert from 'node:assert';
8
+ import waitForExpect from 'wait-for-expect';
9
+
10
+ import { asyncChain, asyncTimeout } from '@dxos/async';
11
+ import { Client, InvitationsProxy, Space, SpaceProxy } from '@dxos/client';
12
+ import { ConnectionState, Invitation } from '@dxos/protocols/proto/dxos/client/services';
13
+ import { afterTest, describe, test } from '@dxos/test';
14
+
15
+ import { InvitationsServiceImpl } from '../invitations';
16
+ import { LocalClientServices, ServiceContext } from '../services';
17
+ import { DataSpace } from '../spaces';
18
+ import {
19
+ createIdentity,
20
+ createPeers,
21
+ performInvitation,
22
+ PerformInvitationParams,
23
+ Result,
24
+ TestBuilder
25
+ } from '../testing';
26
+
27
+ chai.use(chaiAsPromised);
28
+
29
+ const closeAfterTest = async (peer: ServiceContext) => {
30
+ afterTest(() => peer.close());
31
+ return peer;
32
+ };
33
+
34
+ const successfulInvitation = async ({
35
+ host,
36
+ guest,
37
+ hostResult: { invitation: hostInvitation, error: hostError },
38
+ guestResult: { invitation: guestInvitation, error: guestError }
39
+ }: {
40
+ host: ServiceContext;
41
+ guest: ServiceContext;
42
+ hostResult: Result;
43
+ guestResult: Result;
44
+ }) => {
45
+ expect(hostError).to.be.undefined;
46
+ expect(guestError).to.be.undefined;
47
+ expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
48
+ expect(guestInvitation?.state).to.eq(Invitation.State.SUCCESS);
49
+
50
+ switch (hostInvitation!.kind) {
51
+ case Invitation.Kind.SPACE:
52
+ expect(guestInvitation!.spaceKey).to.exist;
53
+ expect(hostInvitation!.spaceKey).to.deep.eq(guestInvitation!.spaceKey);
54
+ break;
55
+
56
+ case Invitation.Kind.DEVICE:
57
+ expect(hostInvitation!.identityKey).not.to.exist;
58
+ expect(guestInvitation!.identityKey).to.deep.eq(host.identityManager.identity!.identityKey);
59
+ expect(guestInvitation!.identityKey).to.deep.eq(guest.identityManager.identity!.identityKey);
60
+
61
+ // Check devices.
62
+ // TODO(burdon): Incorrect number of devices.
63
+ await waitForExpect(() => {
64
+ expect(host.identityManager.identity!.authorizedDeviceKeys.size).to.eq(2);
65
+ expect(guest.identityManager.identity!.authorizedDeviceKeys.size).to.eq(2);
66
+ });
67
+ // console.log(host.identityManager.identity!.authorizedDeviceKeys.size);
68
+ // console.log(guest.identityManager.identity!.authorizedDeviceKeys.size);
69
+ break;
70
+ }
71
+ };
72
+
73
+ const testSuite = (getParams: () => PerformInvitationParams, getPeers: () => [ServiceContext, ServiceContext]) => {
74
+ test('no auth', async () => {
75
+ const [host, guest] = getPeers();
76
+ const [hostResult, guestResult] = await Promise.all(performInvitation(getParams()));
77
+ await successfulInvitation({ host, guest, hostResult, guestResult });
78
+ });
79
+
80
+ test('with shared secret', async () => {
81
+ const [host, guest] = getPeers();
82
+ const params = getParams();
83
+ const [hostResult, guestResult] = await Promise.all(
84
+ performInvitation({
85
+ ...params,
86
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET }
87
+ })
88
+ );
89
+
90
+ await successfulInvitation({ host, guest, hostResult, guestResult });
91
+ });
92
+
93
+ test('invalid auth code', async () => {
94
+ const [host, guest] = getPeers();
95
+ const params = getParams();
96
+ let attempt = 1;
97
+ const [hostResult, guestResult] = await Promise.all(
98
+ performInvitation({
99
+ ...params,
100
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET },
101
+ hooks: {
102
+ guest: {
103
+ onReady: (invitation) => {
104
+ if (attempt === 0) {
105
+ // Force retry.
106
+ void invitation.authenticate('000000');
107
+ attempt++;
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ }
113
+ }
114
+ }
115
+ })
116
+ );
117
+
118
+ expect(attempt).to.eq(1);
119
+ await successfulInvitation({ host, guest, hostResult, guestResult });
120
+ });
121
+
122
+ test('max auth code retries', async () => {
123
+ const params = getParams();
124
+ let attempt = 0;
125
+ const [hostPromise, guestPromise] = performInvitation({
126
+ ...params,
127
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET },
128
+ hooks: {
129
+ guest: {
130
+ onReady: (invitation) => {
131
+ // Force retry.
132
+ void invitation.authenticate('000000');
133
+ attempt++;
134
+ return true;
135
+ }
136
+ }
137
+ }
138
+ });
139
+ const guestResult = await guestPromise;
140
+
141
+ expect(attempt).to.eq(3);
142
+ expect(guestResult.error).to.exist;
143
+ await expect(asyncTimeout(hostPromise, 100)).to.be.rejected;
144
+ });
145
+
146
+ test('invitation timeout', async () => {
147
+ const params = getParams();
148
+ const [hostResult, guestResult] = await Promise.all(
149
+ performInvitation({
150
+ ...params,
151
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET, timeout: 1 }
152
+ })
153
+ );
154
+
155
+ expect(hostResult.invitation?.state).to.eq(Invitation.State.TIMEOUT);
156
+ expect(guestResult.invitation?.state).to.eq(Invitation.State.TIMEOUT);
157
+ });
158
+
159
+ test('host cancels invitation', async () => {
160
+ const params = getParams();
161
+ const [hostResult, guestResult] = await Promise.all(
162
+ performInvitation({
163
+ ...params,
164
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET },
165
+ hooks: {
166
+ host: {
167
+ onConnected: (invitation) => {
168
+ void invitation.cancel();
169
+ return true;
170
+ }
171
+ }
172
+ }
173
+ })
174
+ );
175
+
176
+ expect(hostResult.invitation?.state).to.eq(Invitation.State.CANCELLED);
177
+ expect(guestResult.error).to.exist;
178
+ });
179
+
180
+ test('guest cancels invitation', async () => {
181
+ const params = getParams();
182
+ const [hostPromise, guestPromise] = performInvitation({
183
+ ...params,
184
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET },
185
+ hooks: {
186
+ guest: {
187
+ onConnected: (invitation) => {
188
+ void invitation.cancel();
189
+ return true;
190
+ }
191
+ }
192
+ }
193
+ });
194
+ const guestResult = await guestPromise;
195
+
196
+ expect(guestResult.invitation?.state).to.eq(Invitation.State.CANCELLED);
197
+ await expect(asyncTimeout(hostPromise, 100)).to.be.rejected;
198
+ });
199
+
200
+ test('network error', async () => {
201
+ const [, guest] = getPeers();
202
+ const params = getParams();
203
+ const [hostPromise, guestPromise] = performInvitation({
204
+ ...params,
205
+ options: { ...params.options, authMethod: Invitation.AuthMethod.SHARED_SECRET },
206
+ hooks: {
207
+ guest: {
208
+ onConnected: () => {
209
+ void guest.networkManager.setConnectionState(ConnectionState.OFFLINE);
210
+ return true;
211
+ }
212
+ }
213
+ }
214
+ });
215
+ const guestResult = await guestPromise;
216
+
217
+ expect(guestResult.error).to.exist;
218
+ await expect(asyncTimeout(hostPromise, 100)).to.be.rejected;
219
+
220
+ // Test cleanup fails if the guest is offline.
221
+ await guest.networkManager.setConnectionState(ConnectionState.ONLINE);
222
+ });
223
+ };
224
+
225
+ describe('Invitations', () => {
226
+ describe('ServiceContext', () => {
227
+ describe('space', () => {
228
+ let host: ServiceContext;
229
+ let guest: ServiceContext;
230
+ let space: DataSpace;
231
+
232
+ beforeEach(async () => {
233
+ const peers = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
234
+ host = peers[0];
235
+ guest = peers[1];
236
+ space = await host.dataSpaceManager!.createSpace();
237
+ });
238
+
239
+ testSuite(
240
+ () => ({
241
+ host,
242
+ guest,
243
+ options: { kind: Invitation.Kind.SPACE, spaceKey: space.key }
244
+ }),
245
+ () => [host, guest]
246
+ );
247
+ });
248
+
249
+ describe('device', () => {
250
+ let host: ServiceContext;
251
+ let guest: ServiceContext;
252
+
253
+ beforeEach(async () => {
254
+ const peers = await asyncChain<ServiceContext>([closeAfterTest])(createPeers(2));
255
+ host = peers[0];
256
+ guest = peers[1];
257
+ await host.createIdentity();
258
+ });
259
+
260
+ testSuite(
261
+ () => ({ host, guest, options: { kind: Invitation.Kind.DEVICE } }),
262
+ () => [host, guest]
263
+ );
264
+ });
265
+ });
266
+
267
+ describe('InvitationsProxy', () => {
268
+ describe('space', () => {
269
+ let hostContext: ServiceContext;
270
+ let guestContext: ServiceContext;
271
+ let host: InvitationsProxy;
272
+ let guest: InvitationsProxy;
273
+ let space: DataSpace;
274
+
275
+ beforeEach(async () => {
276
+ const peers = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(2));
277
+ hostContext = peers[0];
278
+ guestContext = peers[1];
279
+ assert(hostContext.dataSpaceManager);
280
+ assert(guestContext.dataSpaceManager);
281
+
282
+ const hostService = new InvitationsServiceImpl(hostContext.invitations, (invitation) =>
283
+ hostContext.getInvitationHandler(invitation)
284
+ );
285
+ const guestService = new InvitationsServiceImpl(guestContext.invitations, (invitation) =>
286
+ guestContext.getInvitationHandler(invitation)
287
+ );
288
+
289
+ space = await hostContext.dataSpaceManager.createSpace();
290
+ host = new InvitationsProxy(hostService, () => ({ kind: Invitation.Kind.SPACE, spaceKey: space.key }));
291
+ guest = new InvitationsProxy(guestService, () => ({ kind: Invitation.Kind.SPACE }));
292
+
293
+ afterTest(() => space.close());
294
+ });
295
+
296
+ testSuite(
297
+ () => ({ host, guest }),
298
+ () => [hostContext, guestContext]
299
+ );
300
+ });
301
+
302
+ describe('device', () => {
303
+ let hostContext: ServiceContext;
304
+ let guestContext: ServiceContext;
305
+ let host: InvitationsProxy;
306
+ let guest: InvitationsProxy;
307
+
308
+ beforeEach(async () => {
309
+ const peers = await asyncChain<ServiceContext>([closeAfterTest])(createPeers(2));
310
+ hostContext = peers[0];
311
+ guestContext = peers[1];
312
+
313
+ await hostContext.identityManager.createIdentity();
314
+
315
+ const hostService = new InvitationsServiceImpl(hostContext.invitations, (invitation) =>
316
+ hostContext.getInvitationHandler(invitation)
317
+ );
318
+
319
+ const guestService = new InvitationsServiceImpl(guestContext.invitations, (invitation) =>
320
+ guestContext.getInvitationHandler(invitation)
321
+ );
322
+
323
+ host = new InvitationsProxy(hostService, () => ({ kind: Invitation.Kind.DEVICE }));
324
+ guest = new InvitationsProxy(guestService, () => ({ kind: Invitation.Kind.DEVICE }));
325
+ });
326
+
327
+ testSuite(
328
+ () => ({ host, guest }),
329
+ () => [hostContext, guestContext]
330
+ );
331
+ });
332
+ });
333
+
334
+ describe('HaloProxy', () => {
335
+ let host: Client;
336
+ let guest: Client;
337
+
338
+ beforeEach(async () => {
339
+ const testBuilder = new TestBuilder();
340
+
341
+ host = new Client({ services: testBuilder.createLocal() });
342
+ guest = new Client({ services: testBuilder.createLocal() });
343
+ await host.initialize();
344
+ await guest.initialize();
345
+
346
+ await host.halo.createIdentity({ displayName: 'Peer' });
347
+
348
+ afterTest(() => Promise.all([host.destroy()]));
349
+ afterTest(() => Promise.all([guest.destroy()]));
350
+ });
351
+
352
+ testSuite(
353
+ () => ({ host: host.halo, guest: guest.halo }),
354
+ () => [
355
+ (host.services as LocalClientServices).host._serviceContext,
356
+ (guest.services as LocalClientServices).host._serviceContext
357
+ ]
358
+ );
359
+ });
360
+
361
+ describe('EchoProxy', () => {
362
+ let host: Client;
363
+ let guest: Client;
364
+ let space: Space;
365
+ beforeEach(async () => {
366
+ const testBuilder = new TestBuilder();
367
+
368
+ host = new Client({ services: testBuilder.createLocal() });
369
+ guest = new Client({ services: testBuilder.createLocal() });
370
+ await host.initialize();
371
+ await guest.initialize();
372
+ await host.halo.createIdentity({ displayName: 'Peer 1' });
373
+ await guest.halo.createIdentity({ displayName: 'Peer 2' });
374
+
375
+ afterTest(() => Promise.all([host.destroy()]));
376
+ afterTest(() => Promise.all([guest.destroy()]));
377
+
378
+ space = await host.createSpace();
379
+ });
380
+
381
+ testSuite(
382
+ () => ({ host: space as SpaceProxy, guest }),
383
+ () => [
384
+ (host.services as LocalClientServices).host._serviceContext,
385
+ (guest.services as LocalClientServices).host._serviceContext
386
+ ]
387
+ );
388
+ });
389
+ });
@@ -4,13 +4,11 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
- import { Trigger } from '@dxos/async';
8
- import { Client, Invitation } from '@dxos/client';
9
- import { raise } from '@dxos/debug';
7
+ import { Client, Invitation, SpaceProxy } from '@dxos/client';
10
8
  import { log } from '@dxos/log';
11
9
  import { afterTest, describe, test } from '@dxos/test';
12
10
 
13
- import { TestBuilder, testSpace } from '../testing';
11
+ import { performInvitation, TestBuilder, testSpace } from '../testing';
14
12
 
15
13
  describe('Spaces/invitations', () => {
16
14
  test('creates a space and invites a peer', async () => {
@@ -28,36 +26,20 @@ describe('Spaces/invitations', () => {
28
26
  afterTest(() => Promise.all([client1.destroy()]));
29
27
  afterTest(() => Promise.all([client2.destroy()]));
30
28
 
31
- const success1 = new Trigger<Invitation>();
32
- const success2 = new Trigger<Invitation>();
33
-
34
29
  const space1 = await client1.createSpace();
35
30
  log('createSpace', { key: space1.key });
36
- const observable1 = space1.createInvitation({ type: Invitation.Type.INTERACTIVE_TESTING });
37
-
38
- observable1.subscribe({
39
- onConnecting: (invitation) => {
40
- const observable2 = client2.acceptInvitation(invitation);
41
- observable2.subscribe({
42
- onSuccess: (invitation: Invitation) => {
43
- success2.wake(invitation);
44
- },
45
- onError: (err: Error) => raise(err)
46
- });
47
- },
48
- onSuccess: (invitation) => {
49
- log('onSuccess');
50
- success1.wake(invitation);
51
- },
52
- onError: (err) => raise(err)
53
- });
54
-
55
- const [invitation1, invitation2] = await Promise.all([success1.wait(), success2.wait()]);
56
- expect(invitation1.spaceKey).to.deep.eq(invitation2.spaceKey);
57
- expect(invitation1.state).to.eq(Invitation.State.SUCCESS);
31
+ const [{ invitation: hostInvitation }, { invitation: guestInvitation }] = await Promise.all(
32
+ performInvitation({
33
+ host: space1 as SpaceProxy,
34
+ guest: client2
35
+ })
36
+ );
37
+ expect(guestInvitation?.spaceKey).to.deep.eq(space1.key);
38
+ expect(hostInvitation?.spaceKey).to.deep.eq(guestInvitation?.spaceKey);
39
+ expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
58
40
 
59
41
  {
60
- const space = await client2.getSpace(invitation2.spaceKey!)!.waitUntilReady();
42
+ const space = await client2.getSpace(guestInvitation!.spaceKey!)!.waitUntilReady();
61
43
  await testSpace(space.internal.db);
62
44
  }
63
45
  });