@dxos/client-services 0.1.30-next.7c60cd3 → 0.1.31-next.ea8f0bc

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 (148) hide show
  1. package/dist/lib/browser/{chunk-JLLUQQXD.mjs → chunk-ZIQUYEJS.mjs} +898 -1132
  2. package/dist/lib/browser/chunk-ZIQUYEJS.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +5 -41
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/packlets/testing/index.mjs +163 -50
  6. package/dist/lib/browser/packlets/testing/index.mjs.map +4 -4
  7. package/dist/lib/node/index.cjs +785 -1043
  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 +2812 -2686
  11. package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
  12. package/dist/types/src/packlets/invitations/device-invitations-handler.d.ts +1 -2
  13. package/dist/types/src/packlets/invitations/device-invitations-handler.d.ts.map +1 -1
  14. package/dist/types/src/packlets/invitations/device-invitations-service.d.ts +1 -1
  15. package/dist/types/src/packlets/invitations/device-invitations-service.d.ts.map +1 -1
  16. package/dist/types/src/packlets/invitations/index.d.ts +0 -6
  17. package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
  18. package/dist/types/src/packlets/invitations/invitations-service.d.ts +2 -3
  19. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  20. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +2 -4
  21. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
  22. package/dist/types/src/packlets/invitations/space-invitations-service.d.ts +2 -3
  23. package/dist/types/src/packlets/invitations/space-invitations-service.d.ts.map +1 -1
  24. package/dist/types/src/packlets/services/client-rpc-server.d.ts +22 -0
  25. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -0
  26. package/dist/types/src/packlets/services/index.d.ts +2 -3
  27. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  28. package/dist/types/src/packlets/services/local-client-services.d.ts +3 -2
  29. package/dist/types/src/packlets/services/local-client-services.d.ts.map +1 -1
  30. package/dist/types/src/packlets/services/service-context.d.ts +1 -1
  31. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  32. package/dist/types/src/packlets/services/service-host.d.ts +22 -8
  33. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  34. package/dist/types/src/packlets/services/service-registry.d.ts +3 -1
  35. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/utils.d.ts +7 -0
  37. package/dist/types/src/packlets/services/utils.d.ts.map +1 -0
  38. package/dist/types/src/packlets/spaces/index.d.ts +2 -0
  39. package/dist/types/src/packlets/spaces/index.d.ts.map +1 -1
  40. package/dist/types/src/packlets/system/system-service.d.ts +2 -2
  41. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  42. package/dist/types/src/packlets/testing/host-test-builder.d.ts +43 -0
  43. package/dist/types/src/packlets/testing/host-test-builder.d.ts.map +1 -0
  44. package/dist/types/src/packlets/testing/index.d.ts +1 -0
  45. package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
  46. package/dist/types/src/packlets/testing/invitation-utils.d.ts +1 -1
  47. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  48. package/dist/types/src/packlets/testing/test-builder.d.ts +30 -34
  49. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  50. package/dist/types/src/packlets/tests/client-services.test.d.ts +2 -0
  51. package/dist/types/src/packlets/tests/client-services.test.d.ts.map +1 -0
  52. package/dist/types/src/packlets/tests/client.test.d.ts +2 -0
  53. package/dist/types/src/packlets/tests/client.test.d.ts.map +1 -0
  54. package/dist/types/src/packlets/tests/halo-credentials.test.d.ts +2 -0
  55. package/dist/types/src/packlets/tests/halo-credentials.test.d.ts.map +1 -0
  56. package/dist/types/src/packlets/tests/halo-persistent.test.d.ts +2 -0
  57. package/dist/types/src/packlets/tests/halo-persistent.test.d.ts.map +1 -0
  58. package/dist/types/src/packlets/tests/halo-profile.test.d.ts +2 -0
  59. package/dist/types/src/packlets/tests/halo-profile.test.d.ts.map +1 -0
  60. package/dist/types/src/packlets/tests/shared-worker.test.d.ts +2 -0
  61. package/dist/types/src/packlets/tests/shared-worker.test.d.ts.map +1 -0
  62. package/dist/types/src/packlets/tests/spaces-invitations.test.d.ts +2 -0
  63. package/dist/types/src/packlets/tests/spaces-invitations.test.d.ts.map +1 -0
  64. package/dist/types/src/packlets/tests/spaces.test.d.ts +2 -0
  65. package/dist/types/src/packlets/tests/spaces.test.d.ts.map +1 -0
  66. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +0 -1
  67. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
  68. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -1
  69. package/dist/types/src/packlets/vault/index.d.ts +0 -2
  70. package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
  71. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  72. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  73. package/dist/types/src/packlets/vault/worker-session.d.ts +4 -4
  74. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  75. package/package.json +30 -30
  76. package/src/packlets/invitations/device-invitations-handler.ts +10 -9
  77. package/src/packlets/invitations/device-invitations-service.test.ts +1 -1
  78. package/src/packlets/invitations/device-invitations-service.ts +1 -1
  79. package/src/packlets/invitations/index.ts +0 -6
  80. package/src/packlets/invitations/invitations-service.ts +7 -2
  81. package/src/packlets/invitations/space-invitations-handler.test.ts +3 -3
  82. package/src/packlets/invitations/space-invitations-handler.ts +11 -11
  83. package/src/packlets/invitations/space-invitations-service.test.ts +1 -1
  84. package/src/packlets/invitations/space-invitations-service.ts +2 -3
  85. package/src/packlets/services/client-rpc-server.ts +91 -0
  86. package/src/packlets/services/index.ts +2 -3
  87. package/src/packlets/services/local-client-services.ts +5 -1
  88. package/src/packlets/services/service-context.test.ts +4 -4
  89. package/src/packlets/services/service-context.ts +1 -1
  90. package/src/packlets/services/service-host.ts +52 -19
  91. package/src/packlets/services/service-registry.test.ts +2 -2
  92. package/src/packlets/services/service-registry.ts +9 -1
  93. package/src/packlets/services/utils.ts +53 -0
  94. package/src/packlets/spaces/data-space-manager.test.ts +4 -4
  95. package/src/packlets/spaces/index.ts +2 -0
  96. package/src/packlets/system/system-service.ts +3 -3
  97. package/src/packlets/testing/host-test-builder.ts +163 -0
  98. package/src/packlets/testing/index.ts +1 -0
  99. package/src/packlets/testing/invitation-utils.ts +1 -2
  100. package/src/packlets/testing/test-builder.ts +118 -140
  101. package/src/packlets/tests/client-services.test.ts +252 -0
  102. package/src/packlets/tests/client.test.ts +143 -0
  103. package/src/packlets/tests/halo-credentials.test.ts +94 -0
  104. package/src/packlets/tests/halo-persistent.test.ts +53 -0
  105. package/src/packlets/tests/halo-profile.test.ts +74 -0
  106. package/src/packlets/tests/shared-worker.test.ts +93 -0
  107. package/src/packlets/tests/spaces-invitations.test.ts +24 -0
  108. package/src/packlets/tests/spaces.test.ts +68 -0
  109. package/src/packlets/vault/iframe-host-runtime.ts +26 -34
  110. package/src/packlets/vault/iframe-proxy-runtime.ts +1 -1
  111. package/src/packlets/vault/index.ts +0 -2
  112. package/src/packlets/vault/shell-runtime.ts +1 -2
  113. package/src/packlets/vault/worker-runtime.ts +5 -15
  114. package/src/packlets/vault/worker-session.ts +39 -23
  115. package/dist/lib/browser/chunk-JLLUQQXD.mjs.map +0 -7
  116. package/dist/types/src/packlets/invitations/device-invitations-proxy.d.ts +0 -8
  117. package/dist/types/src/packlets/invitations/device-invitations-proxy.d.ts.map +0 -1
  118. package/dist/types/src/packlets/invitations/encoder.d.ts +0 -9
  119. package/dist/types/src/packlets/invitations/encoder.d.ts.map +0 -1
  120. package/dist/types/src/packlets/invitations/encoder.test.d.ts +0 -2
  121. package/dist/types/src/packlets/invitations/encoder.test.d.ts.map +0 -1
  122. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +0 -56
  123. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +0 -1
  124. package/dist/types/src/packlets/invitations/invitations-proxy.d.ts +0 -18
  125. package/dist/types/src/packlets/invitations/invitations-proxy.d.ts.map +0 -1
  126. package/dist/types/src/packlets/invitations/invitations.d.ts +0 -55
  127. package/dist/types/src/packlets/invitations/invitations.d.ts.map +0 -1
  128. package/dist/types/src/packlets/invitations/space-invitations-proxy.d.ts +0 -11
  129. package/dist/types/src/packlets/invitations/space-invitations-proxy.d.ts.map +0 -1
  130. package/dist/types/src/packlets/services/service-definitions.d.ts +0 -30
  131. package/dist/types/src/packlets/services/service-definitions.d.ts.map +0 -1
  132. package/dist/types/src/packlets/services/service-proxy.d.ts +0 -17
  133. package/dist/types/src/packlets/services/service-proxy.d.ts.map +0 -1
  134. package/dist/types/src/packlets/vault/services.d.ts +0 -21
  135. package/dist/types/src/packlets/vault/services.d.ts.map +0 -1
  136. package/dist/types/src/packlets/vault/shell-controller.d.ts +0 -18
  137. package/dist/types/src/packlets/vault/shell-controller.d.ts.map +0 -1
  138. package/src/packlets/invitations/device-invitations-proxy.ts +0 -14
  139. package/src/packlets/invitations/encoder.test.ts +0 -23
  140. package/src/packlets/invitations/encoder.ts +0 -26
  141. package/src/packlets/invitations/invitations-handler.ts +0 -68
  142. package/src/packlets/invitations/invitations-proxy.ts +0 -159
  143. package/src/packlets/invitations/invitations.ts +0 -113
  144. package/src/packlets/invitations/space-invitations-proxy.ts +0 -16
  145. package/src/packlets/services/service-definitions.ts +0 -68
  146. package/src/packlets/services/service-proxy.ts +0 -54
  147. package/src/packlets/vault/services.ts +0 -42
  148. package/src/packlets/vault/shell-controller.ts +0 -56
@@ -1,162 +1,140 @@
1
1
  //
2
- // Copyright 2022 DXOS.org
2
+ // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- import { Config } from '@dxos/config';
6
- import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
7
- import {
8
- SnapshotStore,
9
- DataPipelineControllerImpl,
10
- MetadataStore,
11
- SigningContext,
12
- SpaceManager,
13
- valueEncoding
14
- } from '@dxos/echo-pipeline';
15
- import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
16
- import { FeedFactory, FeedStore } from '@dxos/feed-store';
17
- import { Keyring } from '@dxos/keyring';
18
- import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
19
- import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
20
- import { createStorage, Storage, StorageType } from '@dxos/random-access-storage';
21
-
22
- import { createDefaultModelFactory, ClientServicesHost, ServiceContext } from '../services';
23
-
24
- //
25
- // TODO(burdon): Replace with test builder.
26
- //
27
-
28
- export const createServiceHost = (config: Config, signalManagerContext: MemorySignalManagerContext) => {
29
- const networkManager = new NetworkManager({
30
- signalManager: new MemorySignalManager(signalManagerContext),
31
- transportFactory: MemoryTransportFactory
32
- });
33
-
34
- return new ClientServicesHost({
35
- config,
36
- networkManager
37
- });
38
- };
39
-
40
- export const createServiceContext = ({
41
- signalContext = new MemorySignalManagerContext(),
42
- storage = createStorage({ type: StorageType.RAM })
43
- }: {
44
- signalContext?: MemorySignalManagerContext;
45
- storage?: Storage;
46
- } = {}) => {
47
- const networkManager = new NetworkManager({
48
- signalManager: new MemorySignalManager(signalContext),
49
- transportFactory: MemoryTransportFactory
50
- });
51
-
52
- const modelFactory = createDefaultModelFactory();
53
- return new ServiceContext(storage, networkManager, modelFactory);
54
- };
55
-
56
- export const createPeers = async (numPeers: number) => {
57
- const signalContext = new MemorySignalManagerContext();
58
-
59
- return await Promise.all(
60
- Array.from(Array(numPeers)).map(async () => {
61
- const peer = createServiceContext({ signalContext });
62
- await peer.open();
63
- return peer;
64
- })
65
- );
66
- };
67
-
68
- export const createIdentity = async (peer: ServiceContext) => {
69
- await peer.createIdentity();
70
- return peer;
71
- };
72
-
73
- // TODO(burdon): Remove @dxos/client-testing.
74
- // TODO(burdon): Create builder and make configurable.
75
- export const syncItems = async (db1: DataPipelineControllerImpl, db2: DataPipelineControllerImpl) => {
76
- await testLocalDatabase(db1, db2);
77
- await testLocalDatabase(db2, db1);
78
- };
79
-
80
- export class TestBuilder {
81
- public readonly signalContext = new MemorySignalManagerContext();
5
+ import assert from 'node:assert';
82
6
 
83
- createPeer(): TestPeer {
84
- return new TestPeer(this.signalContext);
7
+ import { asyncTimeout } from '@dxos/async';
8
+ import { Client, ClientServices, ClientServicesProxy, createDefaultModelFactory } from '@dxos/client';
9
+ import { Config } from '@dxos/config';
10
+ import { DocumentModel } from '@dxos/document-model';
11
+ import { DatabaseBackendProxy, genesisMutation } from '@dxos/echo-db';
12
+ import { PublicKey } from '@dxos/keys';
13
+ import { MemorySignalManager, MemorySignalManagerContext, WebsocketSignalManager } from '@dxos/messaging';
14
+ import { createWebRTCTransportFactory, MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
15
+ import { Storage } from '@dxos/random-access-storage';
16
+ import { createLinkedPorts, createProtoRpcPeer, ProtoRpcPeer } from '@dxos/rpc';
17
+
18
+ import { ClientServicesHost, LocalClientServices } from '../services';
19
+
20
+ export const testConfigWithLocalSignal = new Config({
21
+ version: 1,
22
+ runtime: {
23
+ services: {
24
+ signal: {
25
+ // TODO(burdon): Port numbers and global consts?
26
+ server: 'ws://localhost:4000/.well-known/dx/signal'
27
+ }
28
+ }
85
29
  }
86
- }
30
+ });
87
31
 
88
- export type TestPeerProps = {
89
- storage?: Storage;
90
- feedStore?: FeedStore<any>;
91
- metadataStore?: MetadataStore;
92
- keyring?: Keyring;
93
- networkManager?: NetworkManager;
94
- spaceManager?: SpaceManager;
95
- snapshotStore?: SnapshotStore;
96
- };
97
-
98
- export class TestPeer {
99
- private _props: TestPeerProps = {};
32
+ /**
33
+ * Client builder supports different configurations, incl. signaling, transports, storage.
34
+ */
35
+ export class TestBuilder {
36
+ private readonly _config: Config;
100
37
 
101
- constructor(private readonly signalContext: MemorySignalManagerContext) {}
38
+ public storage?: Storage;
102
39
 
103
- get storage() {
104
- return (this._props.storage ??= createStorage({ type: StorageType.RAM }));
40
+ // prettier-ignore
41
+ constructor (
42
+ config?: Config,
43
+ private readonly _modelFactory = createDefaultModelFactory(),
44
+ private readonly _signalManagerContext = new MemorySignalManagerContext()
45
+ ) {
46
+ this._config = config ?? new Config();
105
47
  }
106
48
 
107
- get keyring() {
108
- return (this._props.keyring ??= new Keyring(this.storage.createDirectory('keyring')));
49
+ get config(): Config {
50
+ return this._config;
109
51
  }
110
52
 
111
- get feedStore() {
112
- return (this._props.feedStore ??= new FeedStore({
113
- factory: new FeedFactory({
114
- root: this.storage.createDirectory('feeds'),
115
- signer: this.keyring,
116
- hypercore: {
117
- valueEncoding
118
- }
119
- })
120
- }));
53
+ /**
54
+ * Get network manager using local shared memory or remote signal manager.
55
+ */
56
+ get networkManager() {
57
+ const signalServer = this._config.get('runtime.services.signal.server');
58
+ if (signalServer) {
59
+ return new NetworkManager({
60
+ log: true,
61
+ signalManager: new WebsocketSignalManager([signalServer]),
62
+ transportFactory: createWebRTCTransportFactory({
63
+ iceServers: this._config.get('runtime.services.ice')
64
+ })
65
+ });
66
+ }
67
+
68
+ // Memory transport with shared context.
69
+ return new NetworkManager({
70
+ log: true,
71
+ signalManager: new MemorySignalManager(this._signalManagerContext),
72
+ transportFactory: MemoryTransportFactory
73
+ });
121
74
  }
122
75
 
123
- get metadataStore() {
124
- return (this._props.metadataStore ??= new MetadataStore(this.storage.createDirectory('metadata')));
76
+ /**
77
+ * Create backend service handlers.
78
+ */
79
+ createClientServicesHost() {
80
+ return new ClientServicesHost({
81
+ config: this._config,
82
+ modelFactory: this._modelFactory,
83
+ networkManager: this.networkManager,
84
+ storage: this.storage
85
+ });
125
86
  }
126
87
 
127
- get snapshotStore() {
128
- return (this._props.snapshotStore ??= new SnapshotStore(this.storage.createDirectory('snapshots')));
88
+ /**
89
+ * Create local services host.
90
+ */
91
+ createLocal() {
92
+ return new LocalClientServices({
93
+ config: this._config,
94
+ modelFactory: this._modelFactory,
95
+ networkManager: this.networkManager,
96
+ storage: this.storage
97
+ });
129
98
  }
130
99
 
131
- get networkManager() {
132
- return (this._props.networkManager ??= new NetworkManager({
133
- signalManager: new MemorySignalManager(this.signalContext),
134
- transportFactory: MemoryTransportFactory
135
- }));
136
- }
137
-
138
- get spaceManager() {
139
- return (this._props.spaceManager ??= new SpaceManager({
140
- feedStore: this.feedStore,
141
- networkManager: this.networkManager
142
- }));
100
+ /**
101
+ * Create client/server.
102
+ */
103
+ createClientServer(host: ClientServicesHost = this.createClientServicesHost()): [Client, ProtoRpcPeer<{}>] {
104
+ const [proxyPort, hostPort] = createLinkedPorts();
105
+ const server = createProtoRpcPeer({
106
+ exposed: host.descriptors,
107
+ handlers: host.services as ClientServices,
108
+ port: hostPort
109
+ });
110
+ // TODO(dmaretskyi): Refactor.
111
+
112
+ const client = new Client({ services: new ClientServicesProxy(proxyPort) });
113
+ return [client, server];
143
114
  }
144
115
  }
145
116
 
146
- export const createSigningContext = async (keyring: Keyring): Promise<SigningContext> => {
147
- const identityKey = await keyring.createKey();
148
- const deviceKey = await keyring.createKey();
149
-
150
- return {
151
- identityKey,
152
- deviceKey,
153
- credentialSigner: createCredentialSignerWithChain(
154
- keyring,
155
- {
156
- credential: await new CredentialGenerator(keyring, identityKey, deviceKey).createDeviceAuthorization(deviceKey)
157
- },
158
- deviceKey
159
- ),
160
- recordCredential: async () => {} // No-op.
161
- };
117
+ export const testSpace = async (create: DatabaseBackendProxy, check: DatabaseBackendProxy = create) => {
118
+ const objectId = PublicKey.random().toHex();
119
+
120
+ const result = create.mutate(genesisMutation(objectId, DocumentModel.meta.type));
121
+
122
+ await result.getReceipt();
123
+ // TODO(dmaretskiy): await result.waitToBeProcessed()
124
+ assert(create._itemManager.entities.has(result.objectsCreated[0].id));
125
+
126
+ await asyncTimeout(
127
+ check._itemManager.update.waitForCondition(() => check._itemManager.entities.has(objectId)),
128
+ 1000
129
+ );
130
+
131
+ return result;
132
+ };
133
+
134
+ export const syncItems = async (db1: DatabaseBackendProxy, db2: DatabaseBackendProxy) => {
135
+ // Check item replicated from 1 => 2.
136
+ await testSpace(db1, db2);
137
+
138
+ // Check item replicated from 2 => 1.
139
+ await testSpace(db2, db1);
162
140
  };
@@ -0,0 +1,252 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+ import assert from 'node:assert';
7
+ import waitForExpect from 'wait-for-expect';
8
+
9
+ import { Trigger } from '@dxos/async';
10
+ import { Space } from '@dxos/client';
11
+ import { raise } from '@dxos/debug';
12
+ import { log } from '@dxos/log';
13
+ import { Invitation, SpaceMember } from '@dxos/protocols/proto/dxos/client/services';
14
+ import { describe, test, afterTest } from '@dxos/test';
15
+
16
+ import { syncItems, TestBuilder } from '../testing';
17
+
18
+ // TODO(burdon): Use as set-up for test suite.
19
+ // TODO(burdon): Timeouts and progress callback/events.
20
+
21
+ describe('Client services', () => {
22
+ test('creates client with remote server', async () => {
23
+ const testBuilder = new TestBuilder();
24
+
25
+ const peer = testBuilder.createClientServicesHost();
26
+ await peer.open();
27
+ afterTest(() => peer.close());
28
+
29
+ const [client, server] = testBuilder.createClientServer(peer);
30
+ void server.open();
31
+ afterTest(() => server.close());
32
+
33
+ await client.initialize();
34
+ afterTest(() => client.destroy());
35
+ expect(client.initialized).to.be.true;
36
+ });
37
+
38
+ test('creates clients with multiple peers connected via memory transport', async () => {
39
+ const testBuilder = new TestBuilder();
40
+
41
+ {
42
+ const peer1 = testBuilder.createClientServicesHost();
43
+ await peer1.open();
44
+ afterTest(() => peer1.close());
45
+
46
+ {
47
+ const [client1a, server1a] = testBuilder.createClientServer(peer1);
48
+ void server1a.open();
49
+ await client1a.initialize();
50
+ afterTest(() => Promise.all([client1a.destroy(), server1a.close()]));
51
+ expect(client1a.initialized).to.be.true;
52
+
53
+ await client1a.halo.createIdentity();
54
+ }
55
+ {
56
+ const [client1b, server1b] = testBuilder.createClientServer(peer1);
57
+ void server1b.open();
58
+ await client1b.initialize();
59
+ afterTest(() => Promise.all([client1b.destroy(), server1b.close()]));
60
+ expect(client1b.initialized).to.be.true;
61
+
62
+ // TODO(burdon): Test profile is available.
63
+ }
64
+ }
65
+
66
+ {
67
+ const peer2 = testBuilder.createClientServicesHost();
68
+ await peer2.open();
69
+ afterTest(() => peer2.close());
70
+
71
+ {
72
+ const [client2a, server2a] = testBuilder.createClientServer(peer2);
73
+ void server2a.open();
74
+ await client2a.initialize();
75
+ afterTest(() => Promise.all([client2a.destroy(), server2a.close()]));
76
+ expect(client2a.initialized).to.be.true;
77
+
78
+ await client2a.halo.createIdentity();
79
+ }
80
+ }
81
+ });
82
+
83
+ test('creates identity and invites peer', async () => {
84
+ const testBuilder = new TestBuilder();
85
+
86
+ const peer1 = testBuilder.createClientServicesHost();
87
+ const peer2 = testBuilder.createClientServicesHost();
88
+
89
+ await peer1.open();
90
+ await peer2.open();
91
+
92
+ const [client1, server1] = testBuilder.createClientServer(peer1);
93
+ const [client2, server2] = testBuilder.createClientServer(peer2);
94
+
95
+ // Don't wait (otherwise will block).
96
+ {
97
+ void server1.open();
98
+ void server2.open();
99
+
100
+ await client1.initialize();
101
+ await client2.initialize();
102
+
103
+ await client1.halo.createIdentity();
104
+ }
105
+
106
+ const success1 = new Trigger<Invitation>();
107
+ const success2 = new Trigger<Invitation>();
108
+
109
+ const authenticationCode = new Trigger<string>();
110
+
111
+ {
112
+ const observable1 = client1.halo.createInvitation();
113
+ observable1.subscribe({
114
+ onConnecting: (invitation) => {
115
+ const observable2 = client2.halo.acceptInvitation(invitation);
116
+ observable2.subscribe({
117
+ onAuthenticating: async () => {
118
+ await observable2.authenticate(await authenticationCode.wait());
119
+ },
120
+ onSuccess: (invitation: Invitation) => {
121
+ // TODO(burdon): No device.
122
+ // expect(guest.identityManager.identity!.authorizedDeviceKeys.size).to.eq(1);
123
+ success2.wake(invitation);
124
+ },
125
+ onError: (err: Error) => raise(new Error(err.message))
126
+ });
127
+ },
128
+ onConnected: (invitation: Invitation) => {
129
+ assert(invitation.authenticationCode);
130
+ authenticationCode.wake(invitation.authenticationCode);
131
+ },
132
+ onSuccess: (invitation: Invitation) => {
133
+ success1.wake(invitation);
134
+ },
135
+ onCancelled: () => raise(new Error()),
136
+ onTimeout: (err: Error) => raise(new Error(err.message)),
137
+ onError: (err: Error) => raise(new Error(err.message))
138
+ });
139
+ }
140
+
141
+ // Check same identity.
142
+ const [invitation1, invitation2] = await Promise.all([success1.wait(), success2.wait()]);
143
+ expect(invitation1.identityKey).not.to.exist;
144
+ expect(invitation2.identityKey).to.deep.eq(client1.halo.identity!.identityKey);
145
+ expect(invitation2.identityKey).to.deep.eq(client2.halo.identity!.identityKey);
146
+ expect(invitation1.state).to.eq(Invitation.State.SUCCESS);
147
+ expect(invitation2.state).to.eq(Invitation.State.SUCCESS);
148
+
149
+ // Check devices.
150
+ // TODO(burdon): Incorrect number of devices.
151
+ await waitForExpect(async () => {
152
+ expect(client1.halo.getDevices()).to.have.lengthOf(2);
153
+ expect(client2.halo.getDevices()).to.have.lengthOf(2);
154
+ });
155
+ });
156
+
157
+ test('synchronizes data between two spaces after completing invitation', async () => {
158
+ const testBuilder = new TestBuilder();
159
+
160
+ const peer1 = testBuilder.createClientServicesHost();
161
+ const peer2 = testBuilder.createClientServicesHost();
162
+
163
+ await peer1.open();
164
+ await peer2.open();
165
+
166
+ const [client1, server1] = testBuilder.createClientServer(peer1);
167
+ const [client2, server2] = testBuilder.createClientServer(peer2);
168
+
169
+ // Don't wait (otherwise will block).
170
+ {
171
+ void server1.open();
172
+ void server2.open();
173
+
174
+ await client1.initialize();
175
+ await client2.initialize();
176
+ await client1.halo.createIdentity({ displayName: 'Peer 1' });
177
+ await client2.halo.createIdentity({ displayName: 'Peer 2' });
178
+ }
179
+ log('initialized');
180
+
181
+ afterTest(() => Promise.all([client1.destroy(), server1.close(), peer1.close()]));
182
+ afterTest(() => Promise.all([client2.destroy(), server2.close(), peer2.close()]));
183
+
184
+ const success1 = new Trigger<Invitation>();
185
+ const success2 = new Trigger<Invitation>();
186
+
187
+ const space1 = await client1.echo.createSpace();
188
+ log('createSpace', { key: space1.key });
189
+ const observable1 = space1.createInvitation({ type: Invitation.Type.INTERACTIVE_TESTING });
190
+
191
+ observable1.subscribe({
192
+ onConnecting: (invitation) => {
193
+ const observable2 = client2.echo.acceptInvitation(invitation);
194
+ observable2.subscribe({
195
+ onSuccess: (invitation: Invitation) => {
196
+ success2.wake(invitation);
197
+ },
198
+ onError: (err: Error) => raise(err)
199
+ });
200
+ },
201
+ onSuccess: (invitation) => {
202
+ log('onSuccess');
203
+ success1.wake(invitation);
204
+ },
205
+ onError: (err) => raise(err)
206
+ });
207
+
208
+ const [invitation1, invitation2] = await Promise.all([success1.wait(), success2.wait()]);
209
+ expect(invitation1.spaceKey).to.deep.eq(invitation2.spaceKey);
210
+ expect(invitation1.state).to.eq(Invitation.State.SUCCESS);
211
+
212
+ log('Invitation complete');
213
+
214
+ // TODO(burdon): Space should now be available?
215
+ const trigger = new Trigger<Space>();
216
+ await waitForExpect(() => {
217
+ const space2 = client2.echo.getSpace(invitation2.spaceKey!);
218
+ assert(space2);
219
+ expect(space2).to.exist;
220
+ trigger.wake(space2);
221
+ });
222
+
223
+ const space2 = await trigger.wait();
224
+
225
+ for (const space of [space1, space2]) {
226
+ await waitForExpect(() => {
227
+ expect(space.getMembers()).to.deep.equal([
228
+ {
229
+ identity: {
230
+ identityKey: client1.halo.identity!.identityKey,
231
+ profile: {
232
+ displayName: 'Peer 1'
233
+ }
234
+ },
235
+ presence: SpaceMember.PresenceState.ONLINE
236
+ },
237
+ {
238
+ identity: {
239
+ identityKey: client2.halo.identity!.identityKey,
240
+ profile: {
241
+ displayName: 'Peer 2'
242
+ }
243
+ },
244
+ presence: SpaceMember.PresenceState.ONLINE
245
+ }
246
+ ]);
247
+ }, 3_000);
248
+ }
249
+
250
+ await syncItems(space1.internal.db, space2.internal.db);
251
+ });
252
+ });
@@ -0,0 +1,143 @@
1
+ //
2
+ // Copyright 2020 DXOS.org
3
+ //
4
+
5
+ import chai, { expect } from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
7
+
8
+ import { Client } from '@dxos/client';
9
+ import { Config } from '@dxos/config';
10
+ import { Runtime } from '@dxos/protocols/proto/dxos/config';
11
+ import { createStorage, StorageType } from '@dxos/random-access-storage';
12
+ import { describe, test, afterTest } from '@dxos/test';
13
+
14
+ import { TestBuilder } from '../testing';
15
+
16
+ chai.use(chaiAsPromised);
17
+
18
+ describe('Client', () => {
19
+ test('creates client with embedded services', async () => {
20
+ const testBuilder = new TestBuilder();
21
+
22
+ const client = new Client({ services: testBuilder.createLocal() });
23
+ await client.initialize();
24
+ afterTest(() => client.destroy());
25
+ expect(client.initialized).to.be.true;
26
+ });
27
+
28
+ test('initialize and destroy multiple times', async () => {
29
+ const testBuilder = new TestBuilder();
30
+
31
+ const client = new Client({ services: testBuilder.createLocal() });
32
+ await client.initialize();
33
+ await client.initialize();
34
+ expect(client.initialized).to.be.true;
35
+
36
+ await client.destroy();
37
+ await client.destroy();
38
+ expect(client.initialized).to.be.false;
39
+ });
40
+
41
+ test('closes and reopens', async () => {
42
+ const testBuilder = new TestBuilder();
43
+
44
+ const client = new Client({ services: testBuilder.createLocal() });
45
+ await client.initialize();
46
+ expect(client.initialized).to.be.true;
47
+
48
+ await client.destroy();
49
+ expect(client.initialized).to.be.false;
50
+
51
+ await client.initialize();
52
+ expect(client.initialized).to.be.true;
53
+
54
+ await client.destroy();
55
+ expect(client.initialized).to.be.false;
56
+ });
57
+
58
+ test('create space before identity', async () => {
59
+ const testBuilder = new TestBuilder();
60
+
61
+ const client = new Client({ services: testBuilder.createLocal() });
62
+ await client.initialize();
63
+ afterTest(() => client.destroy());
64
+ await expect(client.echo.createSpace()).to.eventually.be.rejectedWith(
65
+ 'This device has no HALO identity available.'
66
+ );
67
+ }).timeout(1_000);
68
+
69
+ // TODO(burdon): Memory store is reset on close (feed store is closed).
70
+ test.skip('creates identity then resets the memory storage', async () => {
71
+ const config = new Config();
72
+ const testBuilder = new TestBuilder(config);
73
+ await runTest(testBuilder);
74
+ });
75
+
76
+ test('creates identity then resets the node storage', async () => {
77
+ const config = await getNodeConfig(true);
78
+ const testBuilder = new TestBuilder(config);
79
+ await runTest(testBuilder);
80
+ }).onlyEnvironments('nodejs');
81
+ });
82
+
83
+ // TODO(burdon): Factor out.
84
+ const getNodeConfig = async (reset = false) => {
85
+ const path = '/tmp/dxos/client/test/packlets/test';
86
+ if (reset) {
87
+ // TODO(burdon): Reconcile StorageType and ConfigProto types.
88
+ const storage = createStorage({ type: StorageType.NODE, root: path });
89
+ await storage.reset();
90
+ }
91
+
92
+ // TODO(burdon): Update API.
93
+ // TODO(burdon): Config not required if remote.
94
+ return new Config({
95
+ version: 1,
96
+ runtime: {
97
+ client: {
98
+ storage: {
99
+ persistent: true,
100
+ storageType: Runtime.Client.Storage.StorageDriver.NODE,
101
+ path // TODO(burdon): Change to root.
102
+ }
103
+ }
104
+ }
105
+ });
106
+ };
107
+
108
+ // TODO(burdon): Develop test-suite.
109
+ const runTest = async (testBuilder: TestBuilder) => {
110
+ const client = new Client({ services: testBuilder.createLocal() });
111
+ const displayName = 'test-user';
112
+
113
+ {
114
+ // Create identity.
115
+ await client.initialize();
116
+ expect(client.halo.identity).not.to.exist;
117
+ const identity = await client.halo.createIdentity({ displayName });
118
+ expect(client.halo.identity).to.deep.eq(identity);
119
+ await client.destroy();
120
+ }
121
+
122
+ {
123
+ // Should throw trying to create another.
124
+ await client.initialize();
125
+ expect(client.halo.identity).to.exist;
126
+ // TODO(burdon): Error type.
127
+ await expect(client.halo.createIdentity({ displayName })).to.be.rejected;
128
+ }
129
+
130
+ {
131
+ // Reset storage.
132
+ // TODO(burdon): Reset doesn't work in memory (create store test).
133
+ await client.reset();
134
+ }
135
+
136
+ {
137
+ // Start again.
138
+ await client.initialize();
139
+ await client.halo.createIdentity({ displayName });
140
+ expect(client.halo.identity).to.exist;
141
+ await client.destroy();
142
+ }
143
+ };