@dxos/client-services 0.1.52 → 0.1.53-main.01cbc6b

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 (64) hide show
  1. package/dist/lib/browser/{chunk-C3SPTMW3.mjs → chunk-EYXOVYW5.mjs} +817 -694
  2. package/dist/lib/browser/chunk-EYXOVYW5.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +101 -120
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +24 -7
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +884 -782
  9. package/dist/lib/node/index.cjs.map +3 -3
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/packlets/testing/index.cjs +801 -664
  12. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  13. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  14. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  15. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  16. package/dist/types/src/packlets/services/service-host.d.ts +7 -1
  17. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  18. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  19. package/dist/types/src/packlets/spaces/data-space.d.ts +11 -2
  20. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  21. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +2 -1
  22. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  23. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  24. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  25. package/dist/types/src/packlets/testing/test-builder.d.ts +7 -1
  26. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  27. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +1 -1
  28. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
  29. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts +1 -1
  30. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -1
  31. package/dist/types/src/packlets/vault/shell-runtime.d.ts +1 -21
  32. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  33. package/package.json +33 -32
  34. package/src/packlets/devtools/devtools.ts +2 -2
  35. package/src/packlets/identity/identity-manager.test.ts +1 -1
  36. package/src/packlets/identity/identity-manager.ts +8 -8
  37. package/src/packlets/identity/identity-service.ts +2 -2
  38. package/src/packlets/identity/identity.test.ts +1 -1
  39. package/src/packlets/identity/identity.ts +22 -27
  40. package/src/packlets/invitations/device-invitation-protocol.ts +4 -4
  41. package/src/packlets/invitations/invitation-extension.ts +5 -5
  42. package/src/packlets/invitations/invitations-handler.ts +6 -6
  43. package/src/packlets/invitations/invitations-service.ts +3 -3
  44. package/src/packlets/invitations/space-invitation-protocol.ts +9 -9
  45. package/src/packlets/locks/node.ts +2 -2
  46. package/src/packlets/logging/logging-service.ts +4 -4
  47. package/src/packlets/services/service-context.test.ts +1 -1
  48. package/src/packlets/services/service-context.ts +12 -10
  49. package/src/packlets/services/service-host.test.ts +50 -2
  50. package/src/packlets/services/service-host.ts +28 -10
  51. package/src/packlets/spaces/data-space-manager.test.ts +72 -118
  52. package/src/packlets/spaces/data-space-manager.ts +18 -9
  53. package/src/packlets/spaces/data-space.ts +66 -20
  54. package/src/packlets/spaces/notarization-plugin.test.ts +1 -1
  55. package/src/packlets/spaces/notarization-plugin.ts +9 -5
  56. package/src/packlets/spaces/spaces-service.ts +46 -32
  57. package/src/packlets/testing/invitation-utils.ts +2 -2
  58. package/src/packlets/testing/test-builder.ts +36 -4
  59. package/src/packlets/vault/iframe-host-runtime.ts +2 -2
  60. package/src/packlets/vault/iframe-proxy-runtime.ts +2 -2
  61. package/src/packlets/vault/shell-runtime.ts +3 -49
  62. package/src/packlets/vault/worker-runtime.ts +1 -1
  63. package/src/packlets/vault/worker-session.ts +2 -2
  64. package/dist/lib/browser/chunk-C3SPTMW3.mjs.map +0 -7
@@ -5,38 +5,30 @@
5
5
  import { expect } from 'chai';
6
6
  import path from 'node:path';
7
7
 
8
- import { latch } from '@dxos/async';
8
+ import { asyncTimeout, latch } from '@dxos/async';
9
9
  import { SpecificCredential, createAdmissionCredentials } from '@dxos/credentials';
10
- import { AuthStatus, DataServiceSubscriptions } from '@dxos/echo-pipeline';
10
+ import { AuthStatus } from '@dxos/echo-pipeline';
11
11
  import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
12
12
  import { writeMessages } from '@dxos/feed-store';
13
13
  import { PublicKey } from '@dxos/keys';
14
14
  import { log } from '@dxos/log';
15
+ import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
15
16
  import { Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
16
17
  import { StorageType } from '@dxos/random-access-storage';
17
- import { afterTest, describe, test } from '@dxos/test';
18
+ import { afterTest, describe, openAndClose, test } from '@dxos/test';
18
19
  import { range } from '@dxos/util';
19
20
 
20
- import { createSigningContext, TestBuilder, syncItemsLocal } from '../testing';
21
- import { DataSpaceManager } from './data-space-manager';
21
+ import { TestBuilder, syncItemsLocal } from '../testing';
22
22
 
23
23
  describe('DataSpaceManager', () => {
24
24
  test('create space', async () => {
25
25
  const builder = new TestBuilder();
26
26
 
27
27
  const peer = builder.createPeer();
28
- const identity = await createSigningContext(peer.keyring);
29
- const dataSpaceManager = new DataSpaceManager(
30
- peer.spaceManager,
31
- peer.metadataStore,
32
- new DataServiceSubscriptions(),
33
- peer.keyring,
34
- identity,
35
- peer.feedStore,
36
- );
37
- await dataSpaceManager.open();
38
- afterTest(() => dataSpaceManager.close());
39
- const space = await dataSpaceManager.createSpace();
28
+ await peer.createIdentity();
29
+ await openAndClose(peer.dataSpaceManager);
30
+
31
+ const space = await peer.dataSpaceManager.createSpace();
40
32
 
41
33
  // Process all written mutations.
42
34
  await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
@@ -51,62 +43,44 @@ describe('DataSpaceManager', () => {
51
43
  const builder = new TestBuilder();
52
44
 
53
45
  const peer1 = builder.createPeer();
54
- const identity1 = await createSigningContext(peer1.keyring);
55
- const dataSpaceManager1 = new DataSpaceManager(
56
- peer1.spaceManager,
57
- peer1.metadataStore,
58
- new DataServiceSubscriptions(),
59
- peer1.keyring,
60
- identity1,
61
- peer1.feedStore,
62
- );
63
- await dataSpaceManager1.open();
64
- afterTest(() => dataSpaceManager1.close());
46
+ await peer1.createIdentity();
65
47
 
66
48
  const peer2 = builder.createPeer();
67
- const identity2 = await createSigningContext(peer2.keyring);
68
- const dataSpaceManager2 = new DataSpaceManager(
69
- peer2.spaceManager,
70
- peer2.metadataStore,
71
- new DataServiceSubscriptions(),
72
- peer2.keyring,
73
- identity2,
74
- peer2.feedStore,
75
- );
76
- await dataSpaceManager2.open();
77
- afterTest(() => dataSpaceManager2.close());
49
+ await peer2.createIdentity();
50
+
51
+ await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
78
52
 
79
- const space1 = await dataSpaceManager1.createSpace();
53
+ const space1 = await peer1.dataSpaceManager.createSpace();
80
54
  await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
81
55
 
82
56
  // Admit peer2 to space1.
83
57
  await writeMessages(
84
58
  space1.inner.controlPipeline.writer,
85
59
  await createAdmissionCredentials(
86
- identity1.credentialSigner,
87
- identity2.identityKey,
60
+ peer1.identity.credentialSigner,
61
+ peer2.identity.identityKey,
88
62
  space1.key,
89
63
  space1.inner.genesisFeedKey,
90
64
  ),
91
65
  );
92
66
 
93
67
  // Accept must be called after admission so that the peer can authenticate for notarization.
94
- const space2 = await dataSpaceManager2.acceptSpace({
68
+ const space2 = await peer2.dataSpaceManager.acceptSpace({
95
69
  spaceKey: space1.key,
96
70
  genesisFeedKey: space1.inner.genesisFeedKey,
97
71
  });
98
- await dataSpaceManager2.waitUntilSpaceReady(space2.key);
72
+ await peer2.dataSpaceManager.waitUntilSpaceReady(space2.key);
99
73
 
100
74
  log('', {
101
75
  peer1: {
102
- identity: identity1.identityKey,
103
- device: identity1.deviceKey,
76
+ identity: peer1.identity.identityKey,
77
+ device: peer1.identity.deviceKey,
104
78
  control: space1.inner.controlFeedKey,
105
79
  data: space1.inner.dataFeedKey,
106
80
  },
107
81
  peer2: {
108
- identity: identity2.identityKey,
109
- device: identity2.deviceKey,
82
+ identity: peer2.identity.identityKey,
83
+ device: peer2.identity.deviceKey,
110
84
  control: space2.inner.controlFeedKey,
111
85
  data: space2.inner.dataFeedKey,
112
86
  },
@@ -129,64 +103,47 @@ describe('DataSpaceManager', () => {
129
103
 
130
104
  await syncItemsLocal(space1.dataPipeline, space2.dataPipeline);
131
105
 
132
- expect(space1.inner.protocol.sessions.get(identity2.deviceKey)).to.exist;
133
- expect(space1.inner.protocol.sessions.get(identity2.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
134
- expect(space2.inner.protocol.sessions.get(identity1.deviceKey)).to.exist;
135
- expect(space2.inner.protocol.sessions.get(identity1.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
106
+ expect(space1.inner.protocol.sessions.get(peer2.identity.deviceKey)).to.exist;
107
+ expect(space1.inner.protocol.sessions.get(peer2.identity.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
108
+ expect(space2.inner.protocol.sessions.get(peer1.identity.deviceKey)).to.exist;
109
+ expect(space2.inner.protocol.sessions.get(peer1.identity.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
136
110
  });
137
111
 
138
112
  test('pub/sub API', async () => {
139
113
  const builder = new TestBuilder();
140
114
 
141
115
  const peer1 = builder.createPeer();
142
- const identity1 = await createSigningContext(peer1.keyring);
143
- const dataSpaceManager1 = new DataSpaceManager(
144
- peer1.spaceManager,
145
- peer1.metadataStore,
146
- new DataServiceSubscriptions(),
147
- peer1.keyring,
148
- identity1,
149
- peer1.feedStore,
150
- );
151
- await dataSpaceManager1.open();
152
- afterTest(() => dataSpaceManager1.close());
116
+ await peer1.createIdentity();
153
117
 
154
118
  const peer2 = builder.createPeer();
155
- const identity2 = await createSigningContext(peer2.keyring);
156
- const dataSpaceManager2 = new DataSpaceManager(
157
- peer2.spaceManager,
158
- peer2.metadataStore,
159
- new DataServiceSubscriptions(),
160
- peer2.keyring,
161
- identity2,
162
- peer2.feedStore,
163
- );
164
- await dataSpaceManager2.open();
165
- afterTest(() => dataSpaceManager2.close());
119
+ await peer2.createIdentity();
120
+ await peer2.dataSpaceManager.open();
121
+
122
+ await openAndClose(peer1.dataSpaceManager, peer2.dataSpaceManager);
166
123
 
167
- const space1 = await dataSpaceManager1.createSpace();
124
+ const space1 = await peer1.dataSpaceManager.createSpace();
168
125
  await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
169
126
 
170
127
  // Admit peer2 to space1.
171
128
  await writeMessages(
172
129
  space1.inner.controlPipeline.writer,
173
130
  await createAdmissionCredentials(
174
- identity1.credentialSigner,
175
- identity2.identityKey,
131
+ peer1.identity.credentialSigner,
132
+ peer2.identity.identityKey,
176
133
  space1.key,
177
134
  space1.inner.genesisFeedKey,
178
135
  ),
179
136
  );
180
137
 
181
138
  // Accept must be called after admission so that the peer can authenticate for notarization.
182
- const space2 = await dataSpaceManager2.acceptSpace({
139
+ const space2 = await peer2.dataSpaceManager.acceptSpace({
183
140
  spaceKey: space1.key,
184
141
  genesisFeedKey: space1.inner.genesisFeedKey,
185
142
  });
186
143
 
187
144
  // Coincidentally, this also waits until a P2P connection is established between peers.
188
145
  // TODO(dmaretskyi): Refine this to wait for connection specifically.
189
- await dataSpaceManager2.waitUntilSpaceReady(space2.key);
146
+ await peer2.dataSpaceManager.waitUntilSpaceReady(space2.key);
190
147
 
191
148
  const [receivedMessage, inc] = latch({ count: 1 });
192
149
  space2.listen('test', (message) => {
@@ -206,18 +163,9 @@ describe('DataSpaceManager', () => {
206
163
  const peer = builder.createPeer({
207
164
  storageType: typeof window === 'undefined' ? StorageType.NODE : StorageType.WEBFS,
208
165
  });
209
- const identity = await createSigningContext(peer.keyring);
210
- const dataSpaceManager = new DataSpaceManager(
211
- peer.spaceManager,
212
- peer.metadataStore,
213
- new DataServiceSubscriptions(),
214
- peer.keyring,
215
- identity,
216
- peer.feedStore,
217
- );
218
- await dataSpaceManager.open();
219
- afterTest(() => dataSpaceManager.close());
220
- const space = await dataSpaceManager.createSpace();
166
+ await peer.createIdentity();
167
+ await openAndClose(peer.dataSpaceManager);
168
+ const space = await peer.dataSpaceManager.createSpace();
221
169
  await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
222
170
 
223
171
  const feedDataPath = path.join(space.inner.dataPipeline.pipelineState!.feeds[0].key.toHex(), 'data');
@@ -241,24 +189,14 @@ describe('DataSpaceManager', () => {
241
189
  afterTest(async () => builder.destroy());
242
190
 
243
191
  const peer = builder.createPeer();
244
- const identity = await createSigningContext(peer.keyring);
192
+ await peer.createIdentity();
245
193
  const epochsNumber = 10;
246
- const dataService = new DataServiceSubscriptions();
247
194
  let spaceKey: PublicKey;
248
195
  {
249
196
  // Create space and create epochs in it.s
250
- const dataSpaceManager = new DataSpaceManager(
251
- peer.spaceManager,
252
- peer.metadataStore,
253
- dataService,
254
- peer.keyring,
255
- identity,
256
- peer.feedStore,
257
- );
258
- await dataSpaceManager.open();
259
- afterTest(() => dataSpaceManager.close());
260
-
261
- const space = await dataSpaceManager.createSpace();
197
+ await openAndClose(peer.dataSpaceManager);
198
+
199
+ const space = await peer.dataSpaceManager.createSpace();
262
200
  spaceKey = space.key;
263
201
  await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
264
202
 
@@ -267,22 +205,14 @@ describe('DataSpaceManager', () => {
267
205
  await space.createEpoch();
268
206
  }
269
207
  await space.close();
270
- await dataSpaceManager.close();
208
+ await peer.dataSpaceManager.close();
209
+ peer.props.dataSpaceManager = undefined;
271
210
  }
272
211
  {
273
212
  // Load same space and check if it loads only last epoch.s
274
- const dataSpaceManager = new DataSpaceManager(
275
- peer.spaceManager,
276
- peer.metadataStore,
277
- dataService,
278
- peer.keyring,
279
- identity,
280
- peer.feedStore,
281
- );
282
- await dataSpaceManager.open();
283
- afterTest(() => dataSpaceManager.close());
284
-
285
- const space = dataSpaceManager.spaces.get(spaceKey)!;
213
+ await openAndClose(peer.dataSpaceManager);
214
+
215
+ const space = peer.dataSpaceManager.spaces.get(spaceKey)!;
286
216
 
287
217
  const epochs: number[] = [];
288
218
  space.dataPipeline.onNewEpoch.on((epoch: SpecificCredential<Epoch>) => {
@@ -297,4 +227,28 @@ describe('DataSpaceManager', () => {
297
227
  }
298
228
  });
299
229
  });
230
+
231
+ describe('activation', () => {
232
+ test('can activate and deactivate a space', async () => {
233
+ const builder = new TestBuilder();
234
+
235
+ const peer = builder.createPeer();
236
+ await peer.createIdentity();
237
+ await openAndClose(peer.dataSpaceManager);
238
+
239
+ const space = await peer.dataSpaceManager.createSpace();
240
+ await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
241
+ expect(space.state).to.equal(SpaceState.READY);
242
+
243
+ await space.deactivate();
244
+ expect(space.state).to.equal(SpaceState.INACTIVE);
245
+
246
+ await space.activate();
247
+ await asyncTimeout(
248
+ space.stateUpdate.waitForCondition(() => space.state === SpaceState.READY),
249
+ 500,
250
+ );
251
+ await testLocalDatabase(space.dataPipeline);
252
+ });
253
+ });
300
254
  });
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { Event, synchronized, trackLeaks } from '@dxos/async';
8
8
  import { cancelWithContext, Context } from '@dxos/context';
@@ -86,7 +86,9 @@ export class DataSpaceManager {
86
86
  try {
87
87
  log('load space', { spaceMetadata });
88
88
  const space = await this._constructSpace(spaceMetadata);
89
- space.initializeDataPipelineAsync();
89
+ if (spaceMetadata.state !== SpaceState.INACTIVE) {
90
+ space.initializeDataPipelineAsync();
91
+ }
90
92
  } catch (err) {
91
93
  log.error('Error loading space', { spaceMetadata, err });
92
94
  }
@@ -112,7 +114,7 @@ export class DataSpaceManager {
112
114
  */
113
115
  @synchronized
114
116
  async createSpace() {
115
- assert(this._isOpen, 'Not open.');
117
+ invariant(this._isOpen, 'Not open.');
116
118
  const spaceKey = await this._keyring.createKey();
117
119
  const controlFeedKey = await this._keyring.createKey();
118
120
  const dataFeedKey = await this._keyring.createKey();
@@ -121,6 +123,7 @@ export class DataSpaceManager {
121
123
  genesisFeedKey: controlFeedKey,
122
124
  controlFeedKey,
123
125
  dataFeedKey,
126
+ state: SpaceState.ACTIVE,
124
127
  };
125
128
 
126
129
  log('creating space...', { spaceKey });
@@ -130,7 +133,7 @@ export class DataSpaceManager {
130
133
  await this._metadataStore.addSpace(metadata);
131
134
 
132
135
  const memberCredential = credentials[1];
133
- assert(getCredentialAssertion(memberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
136
+ invariant(getCredentialAssertion(memberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
134
137
  await this._signingContext.recordCredential(memberCredential);
135
138
 
136
139
  await space.initializeDataPipeline();
@@ -143,8 +146,8 @@ export class DataSpaceManager {
143
146
  @synchronized
144
147
  async acceptSpace(opts: AcceptSpaceOptions): Promise<DataSpace> {
145
148
  log('accept space', { opts });
146
- assert(this._isOpen, 'Not open.');
147
- assert(!this._spaces.has(opts.spaceKey), 'Space already exists.');
149
+ invariant(this._isOpen, 'Not open.');
150
+ invariant(!this._spaces.has(opts.spaceKey), 'Space already exists.');
148
151
 
149
152
  const metadata: SpaceMetadata = {
150
153
  key: opts.spaceKey,
@@ -155,7 +158,6 @@ export class DataSpaceManager {
155
158
 
156
159
  const space = await this._constructSpace(metadata);
157
160
  await this._metadataStore.addSpace(metadata);
158
-
159
161
  space.initializeDataPipelineAsync();
160
162
 
161
163
  this.updated.emit();
@@ -218,6 +220,7 @@ export class DataSpaceManager {
218
220
 
219
221
  const dataSpace = new DataSpace({
220
222
  inner: space,
223
+ initialState: metadata.state === SpaceState.INACTIVE ? SpaceState.INACTIVE : SpaceState.CLOSED,
221
224
  metadataStore: this._metadataStore,
222
225
  gossip,
223
226
  presence,
@@ -227,7 +230,7 @@ export class DataSpaceManager {
227
230
  callbacks: {
228
231
  beforeReady: async () => {
229
232
  log('before space ready', { space: space.key });
230
- this._dataServiceSubscriptions.registerSpace(
233
+ await this._dataServiceSubscriptions.registerSpace(
231
234
  space.key,
232
235
  dataSpace.dataPipeline.databaseHost!.createDataServiceHost(),
233
236
  );
@@ -238,11 +241,17 @@ export class DataSpaceManager {
238
241
  this.updated.emit();
239
242
  }
240
243
  },
244
+ beforeClose: async () => {
245
+ log('before space close', { space: space.key });
246
+ await this._dataServiceSubscriptions.unregisterSpace(space.key);
247
+ },
241
248
  },
242
249
  cache: metadata.cache,
243
250
  });
244
251
 
245
- await dataSpace.open();
252
+ if (metadata.state !== SpaceState.INACTIVE) {
253
+ await dataSpace.open();
254
+ }
246
255
 
247
256
  if (metadata.controlTimeframe) {
248
257
  dataSpace.inner.controlPipeline.state.setTargetTimeframe(metadata.controlTimeframe);
@@ -2,10 +2,9 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { Event, scheduleTask, trackLeaks } from '@dxos/async';
5
+ import { Event, scheduleTask, synchronized, trackLeaks } from '@dxos/async';
6
6
  import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
7
  import { cancelWithContext, Context } from '@dxos/context';
8
- import { CredentialConsumer } from '@dxos/credentials';
9
8
  import { timed } from '@dxos/debug';
10
9
  import { MetadataStore, Space, createMappedFeedWriter, DataPipeline } from '@dxos/echo-pipeline';
11
10
  import { CancelledError, SystemError } from '@dxos/errors';
@@ -36,9 +35,15 @@ export type DataSpaceCallbacks = {
36
35
  * Called after transitioning to the ready state.
37
36
  */
38
37
  afterReady?: () => Promise<void>;
38
+
39
+ /**
40
+ * Called before space gets closed.
41
+ */
42
+ beforeClose?: () => Promise<void>;
39
43
  };
40
44
 
41
45
  export type DataSpaceParams = {
46
+ initialState: SpaceState;
42
47
  inner: Space;
43
48
  metadataStore: MetadataStore;
44
49
  gossip: Gossip;
@@ -52,7 +57,7 @@ export type DataSpaceParams = {
52
57
 
53
58
  @trackLeaks('open', 'close')
54
59
  export class DataSpace {
55
- private readonly _ctx = new Context();
60
+ private _ctx = new Context();
56
61
  private readonly _inner: Space;
57
62
  private readonly _gossip: Gossip;
58
63
  private readonly _presence: Presence;
@@ -60,7 +65,7 @@ export class DataSpace {
60
65
  private readonly _feedStore: FeedStore<FeedMessage>;
61
66
  private readonly _metadataStore: MetadataStore;
62
67
  private readonly _signingContext: SigningContext;
63
- private readonly _notarizationPluginConsumer: CredentialConsumer<NotarizationPlugin>;
68
+ private readonly _notarizationPlugin = new NotarizationPlugin();
64
69
  private readonly _callbacks: DataSpaceCallbacks;
65
70
  private readonly _cache?: SpaceCache;
66
71
 
@@ -94,8 +99,9 @@ export class DataSpace {
94
99
  authTimeout: AUTH_TIMEOUT,
95
100
  });
96
101
 
97
- this._notarizationPluginConsumer = this._inner.spaceState.registerProcessor(new NotarizationPlugin());
98
102
  this._cache = params.cache;
103
+
104
+ this._state = params.initialState;
99
105
  }
100
106
 
101
107
  get key() {
@@ -124,30 +130,44 @@ export class DataSpace {
124
130
  }
125
131
 
126
132
  get notarizationPlugin() {
127
- return this._notarizationPluginConsumer.processor;
133
+ return this._notarizationPlugin;
128
134
  }
129
135
 
130
136
  get cache() {
131
137
  return this._cache;
132
138
  }
133
139
 
140
+ @synchronized
134
141
  async open() {
135
- await this.notarizationPlugin.open();
136
- await this._notarizationPluginConsumer.open();
142
+ await this._open();
143
+ }
144
+
145
+ private async _open() {
146
+ await this._notarizationPlugin.open();
147
+ await this._inner.spaceState.addCredentialProcessor(this._notarizationPlugin);
137
148
  await this._inner.open();
138
- this._state = SpaceState.INACTIVE;
149
+ this._state = SpaceState.CONTROL_ONLY;
150
+ this.stateUpdate.emit();
151
+ this.metrics = {};
139
152
  this.metrics.open = new Date();
140
153
  }
141
154
 
155
+ @synchronized
142
156
  async close() {
157
+ await this._close();
158
+ }
159
+
160
+ private async _close() {
161
+ await this._callbacks.beforeClose?.();
143
162
  this._state = SpaceState.CLOSED;
144
163
  await this._ctx.dispose();
164
+ this._ctx = new Context();
145
165
 
146
166
  await this.authVerifier.close();
147
167
 
148
168
  await this._inner.close();
149
- await this._notarizationPluginConsumer.close();
150
- await this.notarizationPlugin.close();
169
+ await this._inner.spaceState.removeCredentialProcessor(this._notarizationPlugin);
170
+ await this._notarizationPlugin.close();
151
171
 
152
172
  await this._presence.destroy();
153
173
  }
@@ -185,7 +205,7 @@ export class DataSpace {
185
205
  }
186
206
 
187
207
  async initializeDataPipeline() {
188
- if (this._state !== SpaceState.INACTIVE) {
208
+ if (this._state !== SpaceState.CONTROL_ONLY) {
189
209
  throw new SystemError('Invalid operation');
190
210
  }
191
211
  this._state = SpaceState.INITIALIZING;
@@ -201,14 +221,16 @@ export class DataSpace {
201
221
  log('Writable feeds created');
202
222
  this.stateUpdate.emit();
203
223
 
204
- this.notarizationPlugin.setWriter(
205
- createMappedFeedWriter<Credential, FeedMessage.Payload>(
206
- (credential) => ({
207
- credential: { credential },
208
- }),
209
- this._inner.controlPipeline.writer,
210
- ),
211
- );
224
+ if (!this.notarizationPlugin.hasWriter) {
225
+ this.notarizationPlugin.setWriter(
226
+ createMappedFeedWriter<Credential, FeedMessage.Payload>(
227
+ (credential) => ({
228
+ credential: { credential },
229
+ }),
230
+ this._inner.controlPipeline.writer,
231
+ ),
232
+ );
233
+ }
212
234
 
213
235
  await this._inner.initializeDataPipeline();
214
236
 
@@ -309,4 +331,28 @@ export class DataSpace {
309
331
  }
310
332
  }
311
333
  }
334
+
335
+ @synchronized
336
+ async activate() {
337
+ if (this._state !== SpaceState.INACTIVE) {
338
+ throw new SystemError('Invalid operation');
339
+ }
340
+
341
+ await this._metadataStore.setSpaceState(this.key, SpaceState.ACTIVE);
342
+ await this._open();
343
+ this.initializeDataPipelineAsync();
344
+ }
345
+
346
+ @synchronized
347
+ async deactivate() {
348
+ if (this._state === SpaceState.INACTIVE) {
349
+ throw new SystemError('Invalid operation');
350
+ }
351
+
352
+ // de-register from data service
353
+ await this._metadataStore.setSpaceState(this.key, SpaceState.INACTIVE);
354
+ await this._close();
355
+ this._state = SpaceState.INACTIVE;
356
+ this.stateUpdate.emit();
357
+ }
312
358
  }
@@ -25,7 +25,7 @@ class TestAgent extends TestPeer {
25
25
  super();
26
26
  this.feed.written.on(this._ctx, async ([credential]) => {
27
27
  log('written to feed', { credential });
28
- await this.notarizationPlugin.process(credential);
28
+ await this.notarizationPlugin.processCredential(credential);
29
29
  });
30
30
  }
31
31
 
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { DeferredTask, Event, scheduleTask, sleep, TimeoutError, Trigger } from '@dxos/async';
8
8
  import { Context, rejectOnDispose } from '@dxos/context';
@@ -68,6 +68,10 @@ export class NotarizationPlugin implements CredentialProcessor {
68
68
  private readonly _processedCredentials = new ComplexSet<PublicKey>(PublicKey.hash);
69
69
  private readonly _processCredentialsTriggers = new ComplexMap<PublicKey, Trigger>(PublicKey.hash);
70
70
 
71
+ get hasWriter() {
72
+ return !!this._writer;
73
+ }
74
+
71
75
  async open() {}
72
76
 
73
77
  async close() {
@@ -85,7 +89,7 @@ export class NotarizationPlugin implements CredentialProcessor {
85
89
  successDelay = DEFAULT_SUCCESS_DELAY,
86
90
  }: NotarizeParams) {
87
91
  log('notarize', { credentials });
88
- assert(
92
+ invariant(
89
93
  credentials.every((credential) => credential.id),
90
94
  'Credentials must have an id',
91
95
  );
@@ -165,7 +169,7 @@ export class NotarizationPlugin implements CredentialProcessor {
165
169
  /**
166
170
  * Called with credentials arriving from the control pipeline.
167
171
  */
168
- async process(credential: Credential) {
172
+ async processCredential(credential: Credential) {
169
173
  if (!credential.id) {
170
174
  return;
171
175
  }
@@ -175,7 +179,7 @@ export class NotarizationPlugin implements CredentialProcessor {
175
179
  }
176
180
 
177
181
  setWriter(writer: FeedWriter<Credential>) {
178
- assert(!this._writer, 'Writer already set.');
182
+ invariant(!this._writer, 'Writer already set.');
179
183
  this._writer = writer;
180
184
  }
181
185
 
@@ -194,7 +198,7 @@ export class NotarizationPlugin implements CredentialProcessor {
194
198
  throw new Error(WRITER_NOT_SET_ERROR_CODE);
195
199
  }
196
200
  for (const credential of request.credentials ?? []) {
197
- assert(credential.id, 'Credential must have an id');
201
+ invariant(credential.id, 'Credential must have an id');
198
202
  if (this._processedCredentials.has(credential.id)) {
199
203
  continue;
200
204
  }