@dxos/client-services 0.1.22 → 0.1.24-next.6b1f434

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 (129) hide show
  1. package/dist/lib/browser/chunk-ZPZBLOQW.mjs +4086 -0
  2. package/dist/lib/browser/chunk-ZPZBLOQW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +68 -3027
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +198 -0
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
  8. package/dist/lib/node/index.cjs +1694 -667
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/packlets/testing/index.cjs +3806 -0
  12. package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
  13. package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
  14. package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
  15. package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
  16. package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
  17. package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
  18. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  19. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  20. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  21. package/dist/types/src/packlets/devtools/network.d.ts +3 -3
  22. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  23. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/authenticator.d.ts +9 -9
  25. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  26. package/dist/types/src/packlets/identity/identity-manager.d.ts +8 -2
  27. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity.d.ts +5 -4
  29. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
  31. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
  33. package/dist/types/src/packlets/invitations/invitations.d.ts.map +1 -1
  34. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +1 -1
  35. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-context.d.ts +3 -1
  37. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
  39. package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
  40. package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
  41. package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
  42. package/dist/types/src/packlets/services/service-host.d.ts +9 -6
  43. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  44. package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
  45. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  46. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
  47. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  48. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts +2 -0
  49. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts.map +1 -0
  50. package/dist/types/src/packlets/spaces/data-space.d.ts +20 -4
  51. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  52. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
  53. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
  54. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
  55. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
  56. package/dist/types/src/packlets/spaces/spaces-service.d.ts +3 -2
  57. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  58. package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
  59. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
  60. package/dist/types/src/packlets/testing/index.d.ts +2 -0
  61. package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
  62. package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
  63. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
  64. package/dist/types/src/packlets/testing/test-builder.d.ts +31 -2
  65. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  66. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +35 -0
  67. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
  68. package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +9 -4
  69. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
  70. package/dist/types/src/packlets/vault/index.d.ts +6 -1
  71. package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
  72. package/dist/types/src/packlets/vault/services.d.ts +9 -1
  73. package/dist/types/src/packlets/vault/services.d.ts.map +1 -1
  74. package/dist/types/src/packlets/vault/shell-controller.d.ts +24 -0
  75. package/dist/types/src/packlets/vault/shell-controller.d.ts.map +1 -0
  76. package/dist/types/src/packlets/vault/shell-runtime.d.ts +45 -0
  77. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -0
  78. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
  79. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
  80. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -1
  81. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  82. package/dist/types/src/packlets/vault/worker-session.d.ts +7 -4
  83. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  84. package/package.json +30 -29
  85. package/src/packlets/deprecated/profile.ts +20 -2
  86. package/src/packlets/deprecated/space.ts +41 -37
  87. package/src/packlets/deprecated/system.ts +26 -13
  88. package/src/packlets/devtools/devtools.ts +4 -4
  89. package/src/packlets/devtools/feeds.ts +11 -6
  90. package/src/packlets/devtools/network.ts +20 -9
  91. package/src/packlets/devtools/spaces.ts +8 -7
  92. package/src/packlets/identity/authenticator.test.ts +4 -4
  93. package/src/packlets/identity/authenticator.ts +27 -17
  94. package/src/packlets/identity/identity-manager.test.ts +25 -16
  95. package/src/packlets/identity/identity-manager.ts +25 -13
  96. package/src/packlets/identity/identity.test.ts +22 -23
  97. package/src/packlets/identity/identity.ts +15 -10
  98. package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
  99. package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
  100. package/src/packlets/invitations/invitations.ts +2 -1
  101. package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
  102. package/src/packlets/invitations/space-invitations-handler.ts +9 -12
  103. package/src/packlets/services/service-context.test.ts +56 -0
  104. package/src/packlets/services/service-context.ts +62 -10
  105. package/src/packlets/services/service-definitions.ts +1 -1
  106. package/src/packlets/services/service-host.test.ts +74 -5
  107. package/src/packlets/services/service-host.ts +86 -24
  108. package/src/packlets/services/service-registry.test.ts +2 -1
  109. package/src/packlets/services/service-registry.ts +5 -1
  110. package/src/packlets/spaces/data-space-manager.test.ts +131 -0
  111. package/src/packlets/spaces/data-space-manager.ts +61 -15
  112. package/src/packlets/spaces/data-space.ts +125 -14
  113. package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
  114. package/src/packlets/spaces/notarization-plugin.ts +211 -0
  115. package/src/packlets/spaces/spaces-service.ts +10 -2
  116. package/src/packlets/testing/credential-utils.ts +28 -0
  117. package/src/packlets/testing/index.ts +2 -0
  118. package/src/packlets/testing/invitation-utils.ts +44 -0
  119. package/src/packlets/testing/test-builder.ts +96 -14
  120. package/src/packlets/vault/iframe-host-runtime.ts +131 -0
  121. package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +18 -3
  122. package/src/packlets/vault/index.ts +6 -1
  123. package/src/packlets/vault/services.ts +17 -1
  124. package/src/packlets/vault/shell-controller.ts +77 -0
  125. package/src/packlets/vault/shell-runtime.ts +133 -0
  126. package/src/packlets/vault/vault-resource-lock.ts +76 -0
  127. package/src/packlets/vault/worker-runtime.ts +16 -4
  128. package/src/packlets/vault/worker-session.ts +36 -26
  129. package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
@@ -3,33 +3,32 @@
3
3
  //
4
4
 
5
5
  import { Trigger } from '@dxos/async';
6
+ import { CredentialConsumer, getCredentialAssertion } from '@dxos/credentials';
6
7
  import { failUndefined } from '@dxos/debug';
7
8
  import {
8
- MOCK_AUTH_PROVIDER,
9
- MOCK_AUTH_VERIFIER,
10
9
  valueEncoding,
11
- DataServiceSubscriptions,
12
10
  MetadataStore,
13
11
  SpaceManager,
14
12
  SigningContext,
13
+ DataServiceSubscriptions,
15
14
  SnapshotStore
16
- } from '@dxos/echo-db';
15
+ } from '@dxos/echo-pipeline';
17
16
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
18
17
  import { Keyring } from '@dxos/keyring';
19
18
  import { log } from '@dxos/log';
20
19
  import { ModelFactory } from '@dxos/model-factory';
21
20
  import { NetworkManager } from '@dxos/network-manager';
22
21
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
22
+ import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
23
23
  import { Storage } from '@dxos/random-access-storage';
24
24
 
25
- import { CreateIdentityOptions, IdentityManager } from '../identity';
25
+ import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
26
26
  import { HaloInvitationsHandler, SpaceInvitationsHandler } from '../invitations';
27
27
  import { DataSpaceManager } from '../spaces/data-space-manager';
28
28
 
29
29
  /**
30
30
  * Shared backend for all client services.
31
31
  */
32
-
33
32
  // TODO(burdon): Rename/break-up into smaller components. And/or make members private.
34
33
  export class ServiceContext {
35
34
  public readonly initialized = new Trigger();
@@ -42,6 +41,8 @@ export class ServiceContext {
42
41
  public readonly identityManager: IdentityManager;
43
42
  public readonly haloInvitations: HaloInvitationsHandler;
44
43
 
44
+ private _deviceSpaceSync?: CredentialConsumer<any>;
45
+
45
46
  // Initialized after identity is initialized.
46
47
  public dataSpaceManager?: DataSpaceManager;
47
48
  public spaceInvitations?: SpaceInvitationsHandler;
@@ -66,19 +67,27 @@ export class ServiceContext {
66
67
  }
67
68
  })
68
69
  });
70
+
69
71
  this.spaceManager = new SpaceManager({
70
72
  feedStore: this.feedStore,
71
73
  networkManager: this.networkManager
72
74
  });
75
+
73
76
  this.identityManager = new IdentityManager(
74
77
  this.metadataStore,
75
78
  this.keyring,
79
+ this.feedStore,
76
80
  this.spaceManager
77
81
  );
78
82
 
79
83
  // TODO(burdon): _initialize called in multiple places.
80
84
  // TODO(burdon): Call _initialize on success.
81
- this.haloInvitations = new HaloInvitationsHandler(this.networkManager, this.identityManager);
85
+ this.haloInvitations = new HaloInvitationsHandler({
86
+ networkManager: this.networkManager,
87
+ getIdentity: () => this.identityManager.identity ?? failUndefined(),
88
+ acceptIdentity: this._acceptIdentity.bind(this),
89
+ keyring: this.keyring
90
+ });
82
91
  }
83
92
 
84
93
  async open() {
@@ -94,6 +103,7 @@ export class ServiceContext {
94
103
 
95
104
  async close() {
96
105
  log('closing...');
106
+ await this._deviceSpaceSync?.close();
97
107
  await this.dataSpaceManager?.close();
98
108
  await this.identityManager.close();
99
109
  await this.spaceManager.close();
@@ -117,17 +127,25 @@ export class ServiceContext {
117
127
  return identity;
118
128
  }
119
129
 
130
+ private async _acceptIdentity(params: JoinIdentityParams) {
131
+ const identity = await this.identityManager.acceptIdentity(params);
132
+
133
+ await this._initialize();
134
+ return identity;
135
+ }
136
+
120
137
  // Called when identity is created.
121
138
  private async _initialize() {
122
139
  log('initializing spaces...');
123
140
  const identity = this.identityManager.identity ?? failUndefined();
124
141
  const signingContext: SigningContext = {
125
- credentialProvider: MOCK_AUTH_PROVIDER,
126
- credentialAuthenticator: MOCK_AUTH_VERIFIER,
127
142
  credentialSigner: identity.getIdentityCredentialSigner(),
128
143
  identityKey: identity.identityKey,
129
144
  deviceKey: identity.deviceKey,
130
- profile: identity.profileDocument
145
+ profile: identity.profileDocument,
146
+ recordCredential: async (credential) => {
147
+ await identity.controlPipeline.writer.write({ credential: { credential } });
148
+ }
131
149
  };
132
150
 
133
151
  this.dataSpaceManager = new DataSpaceManager(
@@ -137,9 +155,11 @@ export class ServiceContext {
137
155
  this.keyring,
138
156
  signingContext,
139
157
  this.modelFactory,
158
+ this.feedStore,
140
159
  this.snapshotStore
141
160
  );
142
161
  await this.dataSpaceManager.open();
162
+
143
163
  this.spaceInvitations = new SpaceInvitationsHandler(
144
164
  this.networkManager,
145
165
  this.dataSpaceManager,
@@ -147,5 +167,37 @@ export class ServiceContext {
147
167
  this.keyring
148
168
  );
149
169
  this.initialized.wake();
170
+
171
+ this._deviceSpaceSync = identity.space.spaceState.registerProcessor({
172
+ process: async (credential: Credential) => {
173
+ const assertion = getCredentialAssertion(credential);
174
+ if (assertion['@type'] !== 'dxos.halo.credentials.SpaceMember') {
175
+ return;
176
+ }
177
+ if (assertion.spaceKey.equals(identity.space.key)) {
178
+ // ignore halo space
179
+ return;
180
+ }
181
+ if (!this.dataSpaceManager) {
182
+ log('dataSpaceManager not initialized yet, ignoring space admission', { details: assertion });
183
+ return;
184
+ }
185
+ if (this.dataSpaceManager.spaces.has(assertion.spaceKey)) {
186
+ log('space already exists, ignoring space admission', { details: assertion });
187
+ return;
188
+ }
189
+
190
+ try {
191
+ log('accepting space recorded in halo', { details: assertion });
192
+ await this.dataSpaceManager.acceptSpace({
193
+ spaceKey: assertion.spaceKey,
194
+ genesisFeedKey: assertion.genesisFeedKey
195
+ });
196
+ } catch (err) {
197
+ log.catch(err);
198
+ }
199
+ }
200
+ });
201
+ await this._deviceSpaceSync.open();
150
202
  }
151
203
  }
@@ -42,7 +42,7 @@ export type ClientServices = {
42
42
  */
43
43
  export interface ClientServicesProvider {
44
44
  descriptors: ServiceBundle<ClientServices>;
45
- services: ClientServices;
45
+ services: Partial<ClientServices>;
46
46
 
47
47
  open(): Promise<void>;
48
48
  close(): Promise<void>;
@@ -2,12 +2,17 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { latch } from '@dxos/async';
5
+ import { expect } from 'chai';
6
+
7
+ import { latch, Trigger } from '@dxos/async';
6
8
  import { Config } from '@dxos/config';
9
+ import { verifyPresentation } from '@dxos/credentials';
10
+ import { PublicKey } from '@dxos/keys';
7
11
  import { MemorySignalManagerContext } from '@dxos/messaging';
12
+ import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
8
13
  import { afterTest, describe, test } from '@dxos/test';
9
14
 
10
- import { createServiceHost } from '../testing';
15
+ import { createMockCredential, createServiceHost } from '../testing';
11
16
 
12
17
  describe('ClientServicesHost', () => {
13
18
  test('queryCredentials', async () => {
@@ -15,10 +20,10 @@ describe('ClientServicesHost', () => {
15
20
  await host.open();
16
21
  afterTest(() => host.close());
17
22
 
18
- await host.services.ProfileService.createProfile({});
19
- const { publicKey: spaceKey } = await host.services.SpaceService.createSpace();
23
+ await host.services.ProfileService!.createProfile({});
24
+ const { publicKey: spaceKey } = await host.services.SpaceService!.createSpace();
20
25
 
21
- const stream = host.services.SpacesService.queryCredentials({ spaceKey });
26
+ const stream = host.services.SpacesService!.queryCredentials({ spaceKey });
22
27
  const [done, tick] = latch({ count: 3 });
23
28
  stream.subscribe((credential) => {
24
29
  tick();
@@ -28,4 +33,68 @@ describe('ClientServicesHost', () => {
28
33
 
29
34
  await done();
30
35
  });
36
+
37
+ test('write and query credentials', async () => {
38
+ const host = createServiceHost(new Config(), new MemorySignalManagerContext());
39
+ await host.open();
40
+ afterTest(() => host.close());
41
+
42
+ await host.services.ProfileService!.createProfile({});
43
+
44
+ const testCredential = await createMockCredential({
45
+ signer: host._serviceContext.keyring,
46
+ issuer: host._serviceContext.identityManager.identity!.deviceKey
47
+ });
48
+
49
+ // Test if Profile exposes haloSpace key.
50
+ const haloSpace = new Trigger<PublicKey>();
51
+ host.services.ProfileService!.subscribeProfile()!.subscribe(({ profile }) => {
52
+ if (profile?.haloSpace) {
53
+ haloSpace.wake(profile.haloSpace);
54
+ }
55
+ });
56
+
57
+ await host.services.SpacesService?.writeCredentials({
58
+ spaceKey: await haloSpace.wait(),
59
+ credentials: [testCredential]
60
+ });
61
+
62
+ const credentials = host.services.SpacesService!.queryCredentials({ spaceKey: await haloSpace.wait() });
63
+ const queriedCredential = new Trigger<Credential>();
64
+ credentials.subscribe((credential) => {
65
+ if (credential.subject.id.equals(testCredential.subject.id)) {
66
+ queriedCredential.wake(credential);
67
+ }
68
+ });
69
+ afterTest(() => credentials.close());
70
+
71
+ await queriedCredential.wait();
72
+ });
73
+
74
+ test('sign presentation', async () => {
75
+ const host = createServiceHost(new Config(), new MemorySignalManagerContext());
76
+ await host.open();
77
+ afterTest(() => host.close());
78
+
79
+ await host.services.ProfileService!.createProfile({});
80
+
81
+ const testCredential = await createMockCredential({
82
+ signer: host._serviceContext.keyring,
83
+ issuer: host._serviceContext.identityManager.identity!.deviceKey
84
+ });
85
+
86
+ const nonce = new Uint8Array([0, 0, 0, 0]);
87
+
88
+ const presentation = await host.services.ProfileService!.signPresentation({
89
+ presentation: {
90
+ credentials: [testCredential]
91
+ },
92
+ nonce
93
+ });
94
+
95
+ expect(presentation.proofs?.[0].nonce).to.deep.equal(nonce);
96
+ expect(await verifyPresentation(presentation)).to.deep.equal({
97
+ kind: 'pass'
98
+ });
99
+ });
31
100
  });
@@ -4,21 +4,23 @@
4
4
 
5
5
  import { Config } from '@dxos/config';
6
6
  import { raise } from '@dxos/debug';
7
- import { DataServiceImpl } from '@dxos/echo-db';
7
+ import { DocumentModel } from '@dxos/document-model';
8
+ import { DataServiceImpl } from '@dxos/echo-pipeline';
8
9
  import { log } from '@dxos/log';
9
10
  import { ModelFactory } from '@dxos/model-factory';
10
11
  import { NetworkManager } from '@dxos/network-manager';
11
- import { ObjectModel } from '@dxos/object-model';
12
+ import { Status } from '@dxos/protocols/proto/dxos/client';
12
13
  import { Storage } from '@dxos/random-access-storage';
13
14
  import { TextModel } from '@dxos/text-model';
14
15
 
15
- import { SpaceServiceImpl, ProfileServiceImpl, SystemServiceImpl, TracingServiceImpl } from '../deprecated';
16
+ import { SpaceServiceImpl, ProfileServiceImpl, TracingServiceImpl, SystemServiceImpl } from '../deprecated';
16
17
  import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
17
18
  import { DevicesServiceImpl } from '../identity/devices-service-impl';
18
19
  import { HaloInvitationsServiceImpl, SpaceInvitationsServiceImpl } from '../invitations';
19
20
  import { NetworkServiceImpl } from '../network';
20
21
  import { SpacesServiceImpl } from '../spaces';
21
22
  import { createStorageObjects } from '../storage';
23
+ import { VaultResourceLock } from '../vault';
22
24
  import { ServiceContext } from './service-context';
23
25
  import { ClientServicesProvider, ClientServices, clientServiceBundle } from './service-definitions';
24
26
  import { ServiceRegistry } from './service-registry';
@@ -26,7 +28,7 @@ import { ServiceRegistry } from './service-registry';
26
28
  // TODO(burdon): Factor out to spaces.
27
29
  // TODO(burdon): Defaults (with TextModel).
28
30
  export const createDefaultModelFactory = () => {
29
- return new ModelFactory().registerModel(ObjectModel).registerModel(TextModel);
31
+ return new ModelFactory().registerModel(DocumentModel).registerModel(TextModel);
30
32
  };
31
33
 
32
34
  type ClientServicesHostParams = {
@@ -34,31 +36,77 @@ type ClientServicesHostParams = {
34
36
  modelFactory?: ModelFactory;
35
37
  networkManager: NetworkManager;
36
38
  storage?: Storage;
39
+ lockKey?: string;
37
40
  };
38
41
 
39
42
  /**
40
43
  * Remote service implementation.
41
44
  */
42
45
  export class ClientServicesHost implements ClientServicesProvider {
43
- private _serviceContext!: ServiceContext;
44
- private _serviceRegistry!: ServiceRegistry<ClientServices>;
46
+ private readonly _resourceLock?: VaultResourceLock;
47
+ private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
48
+ private readonly _systemService: SystemServiceImpl;
45
49
 
46
50
  private readonly _config: Config;
47
51
  private readonly _modelFactory: ModelFactory;
48
52
  private readonly _networkManager: NetworkManager;
49
- private _storage: Storage;
53
+ private readonly _storage: Storage;
54
+
55
+ /**
56
+ * @internal
57
+ */
58
+ _serviceContext!: ServiceContext;
59
+ private _open = false;
50
60
 
51
61
  constructor({
52
62
  config,
53
63
  modelFactory = createDefaultModelFactory(),
54
64
  // TODO(burdon): Create ApolloLink abstraction (see Client).
55
65
  networkManager,
56
- storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage
66
+ storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage,
67
+ lockKey
57
68
  }: ClientServicesHostParams) {
58
69
  this._config = config;
59
70
  this._modelFactory = modelFactory;
60
71
  this._networkManager = networkManager;
61
72
  this._storage = storage;
73
+
74
+ this._resourceLock = lockKey
75
+ ? new VaultResourceLock({
76
+ lockKey,
77
+ onAcquire: () => this.open(),
78
+ onRelease: () => this.close()
79
+ })
80
+ : undefined;
81
+
82
+ this._systemService = new SystemServiceImpl({
83
+ config: this._config,
84
+
85
+ onInit: async () => {
86
+ await this._resourceLock?.acquire();
87
+ },
88
+
89
+ onStatus: async () => {
90
+ if (!this.isOpen) {
91
+ return { status: Status.INACTIVE };
92
+ }
93
+
94
+ return { status: Status.ACTIVE };
95
+ },
96
+
97
+ onReset: async () => {
98
+ await this._serviceContext?.reset();
99
+ }
100
+ });
101
+
102
+ // TODO(burdon): Start to think of DMG (dynamic services).
103
+ this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
104
+ SystemService: this._systemService
105
+ });
106
+ }
107
+
108
+ get isOpen() {
109
+ return this._open;
62
110
  }
63
111
 
64
112
  get descriptors() {
@@ -70,27 +118,21 @@ export class ClientServicesHost implements ClientServicesProvider {
70
118
  }
71
119
 
72
120
  async open() {
73
- log('opening...');
74
- await this._initialize();
75
- await this._serviceContext.open();
76
- const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
77
- log('opened', { deviceKey });
78
- }
121
+ if (this._open) {
122
+ return;
123
+ }
79
124
 
80
- async close() {
81
- const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
82
- log('closing...', { deviceKey });
83
- await this._serviceContext.close();
84
- log('closed', { deviceKey });
85
- }
125
+ log('opening...');
86
126
 
87
- // TODO(burdon): Move into open.
88
- async _initialize() {
127
+ // TODO(wittjosiah): Make re-entrant.
89
128
  // TODO(burdon): Break into components.
90
129
  this._serviceContext = new ServiceContext(this._storage, this._networkManager, this._modelFactory);
91
130
 
92
131
  // TODO(burdon): Start to think of DMG (dynamic services).
93
- this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
132
+ this._serviceRegistry.setServices({
133
+ // TODO(burdon): Move to new protobuf definitions.
134
+ SystemService: this._systemService,
135
+
94
136
  HaloInvitationsService: new HaloInvitationsServiceImpl(
95
137
  this._serviceContext.identityManager,
96
138
  this._serviceContext.haloInvitations
@@ -112,8 +154,10 @@ export class ClientServicesHost implements ClientServicesProvider {
112
154
 
113
155
  // TODO(burdon): Move to new protobuf definitions.
114
156
  ProfileService: new ProfileServiceImpl(this._serviceContext),
157
+
158
+ // TODO(burdon): Port old SubscribeSpaces to QueryServices>
115
159
  SpaceService: new SpaceServiceImpl(this._serviceContext),
116
- SystemService: new SystemServiceImpl(this._config, this._serviceContext),
160
+
117
161
  TracingService: new TracingServiceImpl(this._config),
118
162
  DevtoolsHost: new DevtoolsServiceImpl({
119
163
  events: new DevtoolsHostEvents(),
@@ -121,5 +165,23 @@ export class ClientServicesHost implements ClientServicesProvider {
121
165
  context: this._serviceContext
122
166
  })
123
167
  });
168
+
169
+ await this._serviceContext.open();
170
+ this._open = true;
171
+ const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
172
+ log('opened', { deviceKey });
173
+ }
174
+
175
+ async close() {
176
+ if (!this._open) {
177
+ return;
178
+ }
179
+
180
+ const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
181
+ log('closing...', { deviceKey });
182
+ this._serviceRegistry.setServices({ SystemService: this._systemService });
183
+ await this._serviceContext.close();
184
+ this._open = false;
185
+ log('closed', { deviceKey });
124
186
  }
125
187
  }
@@ -13,6 +13,7 @@ import { describe, test } from '@dxos/test';
13
13
 
14
14
  import { SpaceInvitationsProxy, SpaceInvitationsServiceImpl } from '../invitations';
15
15
  import { createServiceContext } from '../testing';
16
+ import { ClientServices } from './service-definitions';
16
17
  import { ServiceRegistry } from './service-registry';
17
18
 
18
19
  // TODO(burdon): Create TestService (that doesn't require peers).
@@ -54,7 +55,7 @@ describe('service registry', () => {
54
55
 
55
56
  const server = createProtoRpcPeer({
56
57
  exposed: serviceRegistry.descriptors,
57
- handlers: serviceRegistry.services,
58
+ handlers: serviceRegistry.services as ClientServices,
58
59
  port: serverPort
59
60
  });
60
61
 
@@ -11,7 +11,7 @@ export class ServiceRegistry<Services> {
11
11
  // prettier-ignore
12
12
  constructor (
13
13
  private readonly _serviceBundle: ServiceBundle<Services>,
14
- private readonly _handlers: Services
14
+ private _handlers: Partial<Services>
15
15
  ) {}
16
16
 
17
17
  get descriptors() {
@@ -21,4 +21,8 @@ export class ServiceRegistry<Services> {
21
21
  get services() {
22
22
  return this._handlers;
23
23
  }
24
+
25
+ setServices(services: Partial<Services>) {
26
+ this._handlers = services;
27
+ }
24
28
  }
@@ -0,0 +1,131 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+
7
+ import { createAdmissionCredentials } from '@dxos/credentials';
8
+ import { DocumentModel } from '@dxos/document-model';
9
+ import { AuthStatus, DataServiceSubscriptions } from '@dxos/echo-pipeline';
10
+ import { writeMessages } from '@dxos/feed-store';
11
+ import { log } from '@dxos/log';
12
+ import { ModelFactory } from '@dxos/model-factory';
13
+ import { test, describe } from '@dxos/test';
14
+
15
+ import { createSigningContext, syncItems, TestBuilder } from '../testing';
16
+ import { DataSpaceManager } from './data-space-manager';
17
+
18
+ describe('DataSpaceManager', () => {
19
+ test('create space', async () => {
20
+ const builder = new TestBuilder();
21
+
22
+ const peer = builder.createPeer();
23
+ const identity = await createSigningContext(peer.keyring);
24
+ const dataSpaceManager = new DataSpaceManager(
25
+ peer.spaceManager,
26
+ peer.metadataStore,
27
+ new DataServiceSubscriptions(),
28
+ peer.keyring,
29
+ identity,
30
+ new ModelFactory().registerModel(DocumentModel),
31
+ peer.feedStore,
32
+ peer.snapshotStore
33
+ );
34
+ const space = await dataSpaceManager.createSpace();
35
+
36
+ // Process all written mutations.
37
+ await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
38
+
39
+ expect(space.inner.spaceState.genesisCredential).to.exist;
40
+ expect(space.inner.spaceState.members.size).to.equal(1);
41
+ expect(space.inner.spaceState.feeds.size).to.equal(2);
42
+ expect(space.inner.protocol.feeds.size).to.equal(2);
43
+ });
44
+
45
+ test('sync between peers', async () => {
46
+ const builder = new TestBuilder();
47
+
48
+ const peer1 = builder.createPeer();
49
+ const identity1 = await createSigningContext(peer1.keyring);
50
+ const dataSpaceManager1 = new DataSpaceManager(
51
+ peer1.spaceManager,
52
+ peer1.metadataStore,
53
+ new DataServiceSubscriptions(),
54
+ peer1.keyring,
55
+ identity1,
56
+ new ModelFactory().registerModel(DocumentModel),
57
+ peer1.feedStore,
58
+ peer1.snapshotStore
59
+ );
60
+
61
+ const peer2 = builder.createPeer();
62
+ const identity2 = await createSigningContext(peer2.keyring);
63
+ const dataSpaceManager2 = new DataSpaceManager(
64
+ peer2.spaceManager,
65
+ peer2.metadataStore,
66
+ new DataServiceSubscriptions(),
67
+ peer2.keyring,
68
+ identity2,
69
+ new ModelFactory().registerModel(DocumentModel),
70
+ peer2.feedStore,
71
+ peer1.snapshotStore
72
+ );
73
+
74
+ const space1 = await dataSpaceManager1.createSpace();
75
+ await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
76
+
77
+ // Admit peer2 to space1.
78
+ await writeMessages(
79
+ space1.inner.controlPipeline.writer,
80
+ await createAdmissionCredentials(
81
+ identity1.credentialSigner,
82
+ identity2.identityKey,
83
+ space1.key,
84
+ space1.inner.genesisFeedKey
85
+ )
86
+ );
87
+
88
+ // Accept must be called after admission so that the peer can authenticate for notarization.
89
+ const space2 = await dataSpaceManager2.acceptSpace({
90
+ spaceKey: space1.key,
91
+ genesisFeedKey: space1.inner.genesisFeedKey
92
+ });
93
+
94
+ log('', {
95
+ peer1: {
96
+ identity: identity1.identityKey,
97
+ device: identity1.deviceKey,
98
+ control: space1.inner.controlFeedKey,
99
+ data: space1.inner.dataFeedKey
100
+ },
101
+ peer2: {
102
+ identity: identity2.identityKey,
103
+ device: identity2.deviceKey,
104
+ control: space2.inner.controlFeedKey,
105
+ data: space2.inner.dataFeedKey
106
+ }
107
+ });
108
+
109
+ await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
110
+ await space2.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
111
+
112
+ log('', {
113
+ space1: {
114
+ timeframe: space1.inner.controlPipeline.state.timeframe,
115
+ endTimeframe: space1.inner.controlPipeline.state.endTimeframe
116
+ },
117
+ space2: {
118
+ timeframe: space2.inner.controlPipeline.state.timeframe,
119
+ endTimeframe: space2.inner.controlPipeline.state.endTimeframe
120
+ }
121
+ });
122
+ log.break();
123
+
124
+ await syncItems(space1.dataPipelineController, space2.dataPipelineController);
125
+
126
+ expect(space1.inner.protocol.sessions.get(identity2.deviceKey)).to.exist;
127
+ expect(space1.inner.protocol.sessions.get(identity2.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
128
+ expect(space2.inner.protocol.sessions.get(identity1.deviceKey)).to.exist;
129
+ expect(space2.inner.protocol.sessions.get(identity1.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
130
+ });
131
+ });