@dxos/client-services 0.1.53 → 0.1.54-main.270ec56

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 (69) hide show
  1. package/dist/lib/browser/{chunk-6ITWCQIT.mjs → chunk-F4BHRFH4.mjs} +861 -702
  2. package/dist/lib/browser/chunk-F4BHRFH4.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +115 -173
  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 +24 -7
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +951 -853
  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 +832 -708
  12. package/dist/lib/node/packlets/testing/index.cjs.map +4 -4
  13. package/dist/types/src/packlets/identity/authenticator.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/index.d.ts +1 -0
  16. package/dist/types/src/packlets/services/index.d.ts.map +1 -1
  17. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  18. package/dist/types/src/packlets/services/service-host.d.ts +7 -1
  19. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  20. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  21. package/dist/types/src/packlets/spaces/data-space.d.ts +11 -2
  22. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  23. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +2 -1
  24. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  25. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  26. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  27. package/dist/types/src/packlets/testing/test-builder.d.ts +7 -1
  28. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  29. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +1 -1
  30. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -1
  31. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts +1 -1
  32. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -1
  33. package/dist/types/src/packlets/vault/shell-runtime.d.ts +1 -21
  34. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  35. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  36. package/package.json +33 -32
  37. package/src/packlets/identity/authenticator.ts +4 -2
  38. package/src/packlets/identity/identity-manager.test.ts +1 -1
  39. package/src/packlets/identity/identity-manager.ts +8 -8
  40. package/src/packlets/identity/identity-service.ts +2 -2
  41. package/src/packlets/identity/identity.test.ts +1 -1
  42. package/src/packlets/identity/identity.ts +22 -27
  43. package/src/packlets/invitations/device-invitation-protocol.ts +4 -4
  44. package/src/packlets/invitations/invitation-extension.ts +5 -5
  45. package/src/packlets/invitations/invitations-handler.ts +6 -6
  46. package/src/packlets/invitations/invitations-service.ts +3 -3
  47. package/src/packlets/invitations/space-invitation-protocol.ts +9 -9
  48. package/src/packlets/locks/node.ts +2 -2
  49. package/src/packlets/logging/logging-service.ts +4 -4
  50. package/src/packlets/services/index.ts +1 -0
  51. package/src/packlets/services/service-context.test.ts +1 -1
  52. package/src/packlets/services/service-context.ts +12 -10
  53. package/src/packlets/services/service-host.test.ts +50 -2
  54. package/src/packlets/services/service-host.ts +25 -9
  55. package/src/packlets/spaces/data-space-manager.test.ts +75 -119
  56. package/src/packlets/spaces/data-space-manager.ts +18 -9
  57. package/src/packlets/spaces/data-space.ts +66 -20
  58. package/src/packlets/spaces/notarization-plugin.test.ts +1 -1
  59. package/src/packlets/spaces/notarization-plugin.ts +9 -5
  60. package/src/packlets/spaces/spaces-service.test.ts +5 -3
  61. package/src/packlets/spaces/spaces-service.ts +26 -8
  62. package/src/packlets/testing/invitation-utils.ts +2 -2
  63. package/src/packlets/testing/test-builder.ts +36 -4
  64. package/src/packlets/vault/iframe-host-runtime.ts +2 -2
  65. package/src/packlets/vault/iframe-proxy-runtime.ts +2 -2
  66. package/src/packlets/vault/shell-runtime.ts +3 -49
  67. package/src/packlets/vault/worker-runtime.ts +1 -1
  68. package/src/packlets/vault/worker-session.ts +7 -6
  69. package/dist/lib/browser/chunk-6ITWCQIT.mjs.map +0 -7
@@ -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 } from '@dxos/async';
8
8
  import { AUTH_TIMEOUT, LOAD_CONTROL_FEEDS_TIMEOUT } from '@dxos/client-protocol';
@@ -12,7 +12,6 @@ import {
12
12
  createCredentialSignerWithKey,
13
13
  createCredentialSignerWithChain,
14
14
  ProfileStateMachine,
15
- CredentialConsumer,
16
15
  } from '@dxos/credentials';
17
16
  import { Signer } from '@dxos/crypto';
18
17
  import { failUndefined } from '@dxos/debug';
@@ -40,8 +39,8 @@ export type IdentityParams = {
40
39
  export class Identity {
41
40
  public readonly space: Space;
42
41
  private readonly _signer: Signer;
43
- private readonly _deviceStateMachine: CredentialConsumer<DeviceStateMachine>;
44
- private readonly _profileStateMachine: CredentialConsumer<ProfileStateMachine>;
42
+ private readonly _deviceStateMachine: DeviceStateMachine;
43
+ private readonly _profileStateMachine: ProfileStateMachine;
45
44
  public readonly authVerifier: TrustedKeySetAuthVerifier;
46
45
 
47
46
  public readonly identityKey: PublicKey;
@@ -56,19 +55,15 @@ export class Identity {
56
55
  this.identityKey = identityKey;
57
56
  this.deviceKey = deviceKey;
58
57
 
59
- this._deviceStateMachine = this.space.spaceState.registerProcessor(
60
- new DeviceStateMachine({
61
- identityKey: this.identityKey,
62
- deviceKey: this.deviceKey,
63
- onUpdate: () => this.stateUpdate.emit(),
64
- }),
65
- );
66
- this._profileStateMachine = this.space.spaceState.registerProcessor(
67
- new ProfileStateMachine({
68
- identityKey: this.identityKey,
69
- onUpdate: () => this.stateUpdate.emit(),
70
- }),
71
- );
58
+ this._deviceStateMachine = new DeviceStateMachine({
59
+ identityKey: this.identityKey,
60
+ deviceKey: this.deviceKey,
61
+ onUpdate: () => this.stateUpdate.emit(),
62
+ });
63
+ this._profileStateMachine = new ProfileStateMachine({
64
+ identityKey: this.identityKey,
65
+ onUpdate: () => this.stateUpdate.emit(),
66
+ });
72
67
 
73
68
  this.authVerifier = new TrustedKeySetAuthVerifier({
74
69
  trustedKeysProvider: () => this.authorizedDeviceKeys,
@@ -79,30 +74,30 @@ export class Identity {
79
74
 
80
75
  // TODO(burdon): Expose state object?
81
76
  get authorizedDeviceKeys(): ComplexSet<PublicKey> {
82
- return this._deviceStateMachine.processor.authorizedDeviceKeys;
77
+ return this._deviceStateMachine.authorizedDeviceKeys;
83
78
  }
84
79
 
85
80
  async open() {
86
- await this._deviceStateMachine.open();
87
- await this._profileStateMachine.open();
81
+ await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
82
+ await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
88
83
  await this.space.open();
89
84
  }
90
85
 
91
86
  async close() {
92
87
  await this.authVerifier.close();
93
- await this._deviceStateMachine.close();
94
- await this._profileStateMachine.close();
88
+ await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
89
+ await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
95
90
  await this.space.close();
96
91
  }
97
92
 
98
93
  async ready() {
99
- await this._deviceStateMachine.processor.deviceChainReady.wait();
94
+ await this._deviceStateMachine.deviceChainReady.wait();
100
95
 
101
96
  await this.controlPipeline.state.waitUntilReachedTargetTimeframe({ timeout: LOAD_CONTROL_FEEDS_TIMEOUT });
102
97
  }
103
98
 
104
99
  get profileDocument(): ProfileDocument | undefined {
105
- return this._profileStateMachine.processor.profile;
100
+ return this._profileStateMachine.profile;
106
101
  }
107
102
 
108
103
  /**
@@ -121,7 +116,7 @@ export class Identity {
121
116
  }
122
117
 
123
118
  get deviceCredentialChain() {
124
- return this._deviceStateMachine.processor.deviceCredentialChain;
119
+ return this._deviceStateMachine.deviceCredentialChain;
125
120
  }
126
121
 
127
122
  getAdmissionCredentials(): DeviceAdmissionRequest {
@@ -137,10 +132,10 @@ export class Identity {
137
132
  * Requires identity to be ready.
138
133
  */
139
134
  getIdentityCredentialSigner(): CredentialSigner {
140
- assert(this._deviceStateMachine.processor.deviceCredentialChain, 'Device credential chain is not ready.');
135
+ invariant(this._deviceStateMachine.deviceCredentialChain, 'Device credential chain is not ready.');
141
136
  return createCredentialSignerWithChain(
142
137
  this._signer,
143
- this._deviceStateMachine.processor.deviceCredentialChain,
138
+ this._deviceStateMachine.deviceCredentialChain,
144
139
  this.deviceKey,
145
140
  );
146
141
  }
@@ -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 { Keyring } from '@dxos/keyring';
8
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
@@ -29,7 +29,7 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
29
29
  }
30
30
 
31
31
  async admit(request: AdmissionRequest): Promise<AdmissionResponse> {
32
- assert(request.device);
32
+ invariant(request.device);
33
33
  const identity = this._getIdentity();
34
34
  await identity.admitDevice(request.device);
35
35
 
@@ -62,10 +62,10 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
62
62
  }
63
63
 
64
64
  async accept(response: AdmissionResponse, request: AdmissionRequest): Promise<Partial<Invitation>> {
65
- assert(response.device);
65
+ invariant(response.device);
66
66
  const { identityKey, haloSpaceKey, genesisFeedKey, controlTimeframe } = response.device;
67
67
 
68
- assert(request.device);
68
+ invariant(request.device);
69
69
  const { deviceKey, controlFeedKey, dataFeedKey } = request.device;
70
70
 
71
71
  await this._acceptIdentity({
@@ -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 { Trigger } from '@dxos/async';
8
8
  import { cancelWithContext, Context } from '@dxos/context';
@@ -86,7 +86,7 @@ export class InvitationHostExtension extends RpcExtension<
86
86
  // Perhaps in the future we will have more complex logic here.
87
87
  InvitationHostService: {
88
88
  options: async (options) => {
89
- assert(!this._remoteOptions, 'Remote options already set.');
89
+ invariant(!this._remoteOptions, 'Remote options already set.');
90
90
  this._remoteOptions = options;
91
91
  this._remoteOptionsTrigger.wake();
92
92
  },
@@ -128,7 +128,7 @@ export class InvitationHostExtension extends RpcExtension<
128
128
  log('received authentication request', { authCode: code });
129
129
  let status = AuthenticationResponse.Status.OK;
130
130
 
131
- assert(this.invitation, 'Invitation is not set.');
131
+ invariant(this.invitation, 'Invitation is not set.');
132
132
  switch (this.invitation.authMethod) {
133
133
  case Invitation.AuthMethod.NONE: {
134
134
  log('authentication not required');
@@ -164,7 +164,7 @@ export class InvitationHostExtension extends RpcExtension<
164
164
  log.trace('dxos.sdk.invitation-handler.host.admit', trace.begin({ id: traceId }));
165
165
 
166
166
  try {
167
- assert(this.invitation, 'Invitation is not set.');
167
+ invariant(this.invitation, 'Invitation is not set.');
168
168
  // Check authenticated.
169
169
  if (isAuthenticationRequired(this.invitation) && !this.authenticationPassed) {
170
170
  throw new Error('Not authenticated');
@@ -239,7 +239,7 @@ export class InvitationGuestExtension extends RpcExtension<
239
239
  return {
240
240
  InvitationHostService: {
241
241
  options: async (options) => {
242
- assert(!this._remoteOptions, 'Remote options already set.');
242
+ invariant(!this._remoteOptions, 'Remote options already set.');
243
243
  this._remoteOptions = options;
244
244
  this._remoteOptionsTrigger.wake();
245
245
  },
@@ -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 { PushStream, scheduleTask, TimeoutError, Trigger } from '@dxos/async';
8
8
  import {
@@ -74,7 +74,7 @@ export class InvitationsHandler {
74
74
  const authCode =
75
75
  options?.authCode ??
76
76
  (authMethod === Invitation.AuthMethod.SHARED_SECRET ? generatePasscode(AUTHENTICATION_CODE_LENGTH) : undefined);
77
- assert(protocol);
77
+ invariant(protocol);
78
78
 
79
79
  const invitation: Invitation = {
80
80
  invitationId,
@@ -117,7 +117,7 @@ export class InvitationsHandler {
117
117
  admit: async (admissionRequest) => {
118
118
  try {
119
119
  const deviceKey = admissionRequest.device?.deviceKey ?? admissionRequest.space?.deviceKey;
120
- assert(deviceKey);
120
+ invariant(deviceKey);
121
121
  const admissionResponse = await protocol.admit(admissionRequest, extension.guestProfile);
122
122
 
123
123
  // Updating credentials complete.
@@ -208,7 +208,7 @@ export class InvitationsHandler {
208
208
 
209
209
  acceptInvitation(protocol: InvitationProtocol, invitation: Invitation): AuthenticatingInvitationObservable {
210
210
  const { timeout = INVITATION_TIMEOUT } = invitation;
211
- assert(protocol);
211
+ invariant(protocol);
212
212
 
213
213
  const authenticated = new Trigger<string>();
214
214
 
@@ -219,7 +219,7 @@ export class InvitationsHandler {
219
219
  let currentState: Invitation.State;
220
220
  const stream = new PushStream<Invitation>();
221
221
  const setState = (newData: Partial<Invitation>) => {
222
- assert(newData.state !== undefined);
222
+ invariant(newData.state !== undefined);
223
223
  currentState = newData.state;
224
224
  stream.next({ ...invitation, ...newData });
225
225
  };
@@ -354,7 +354,7 @@ export class InvitationsHandler {
354
354
  };
355
355
 
356
356
  scheduleTask(ctx, async () => {
357
- assert(invitation.swarmKey);
357
+ invariant(invitation.swarmKey);
358
358
  const topic = invitation.swarmKey;
359
359
  const swarmConnection = await this._networkManager.joinSwarm({
360
360
  topic,
@@ -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 } from '@dxos/async';
8
8
  import { AuthenticatingInvitationObservable, CancellableInvitationObservable } from '@dxos/client-protocol';
@@ -101,7 +101,7 @@ export class InvitationsServiceImpl implements InvitationsService {
101
101
 
102
102
  async authenticate({ invitationId, authCode }: AuthenticationRequest): Promise<void> {
103
103
  log('authenticating...');
104
- assert(invitationId);
104
+ invariant(invitationId);
105
105
  const observable = this._acceptInvitations.get(invitationId);
106
106
  if (!observable) {
107
107
  log.warn('invalid invitation', { invitationId });
@@ -112,7 +112,7 @@ export class InvitationsServiceImpl implements InvitationsService {
112
112
 
113
113
  async cancelInvitation({ invitationId }: { invitationId: string }): Promise<void> {
114
114
  log('deleting...');
115
- assert(invitationId);
115
+ invariant(invitationId);
116
116
  const created = this._createInvitations.get(invitationId);
117
117
  const accepted = this._acceptInvitations.get(invitationId);
118
118
  if (created) {
@@ -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 { createAdmissionCredentials, getCredentialAssertion } from '@dxos/credentials';
8
8
  import { writeMessages } from '@dxos/feed-store';
@@ -40,11 +40,11 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
40
40
  }
41
41
 
42
42
  async admit(request: AdmissionRequest, guestProfile?: ProfileDocument | undefined): Promise<AdmissionResponse> {
43
- assert(this._spaceKey);
43
+ invariant(this._spaceKey);
44
44
  const space = await this._spaceManager.spaces.get(this._spaceKey);
45
- assert(space);
45
+ invariant(space);
46
46
 
47
- assert(request.space);
47
+ invariant(request.space);
48
48
  const { identityKey, deviceKey } = request.space;
49
49
 
50
50
  log('writing guest credentials', { host: this._signingContext.deviceKey, guest: deviceKey });
@@ -58,9 +58,9 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
58
58
  );
59
59
 
60
60
  // TODO(dmaretskyi): Refactor.
61
- assert(credentials[0].credential);
61
+ invariant(credentials[0].credential);
62
62
  const spaceMemberCredential = credentials[0].credential.credential;
63
- assert(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
63
+ invariant(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
64
64
 
65
65
  await writeMessages(space.inner.controlPipeline.writer, credentials);
66
66
 
@@ -95,11 +95,11 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
95
95
  }
96
96
 
97
97
  async accept(response: AdmissionResponse): Promise<Partial<Invitation>> {
98
- assert(response.space);
98
+ invariant(response.space);
99
99
  const { credential, controlTimeframe, dataTimeframe } = response.space;
100
100
  const assertion = getCredentialAssertion(credential);
101
- assert(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
102
- assert(credential.subject.id.equals(this._signingContext.identityKey));
101
+ invariant(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
102
+ invariant(credential.subject.id.equals(this._signingContext.identityKey));
103
103
 
104
104
  // Create local space.
105
105
  await this._spaceManager.acceptSpace({
@@ -2,8 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
6
5
  import { FileHandle } from 'node:fs/promises';
6
+ import invariant from 'tiny-invariant';
7
7
 
8
8
  import { LockFile } from '@dxos/lock-file';
9
9
  import { log, logInfo } from '@dxos/log';
@@ -39,7 +39,7 @@ export class Lock implements ResourceLock {
39
39
 
40
40
  async release() {
41
41
  await this._onRelease?.();
42
- assert(this._fileHandle, 'Lock is not acquired');
42
+ invariant(this._fileHandle, 'Lock is not acquired');
43
43
  await LockFile.release(this._fileHandle);
44
44
  }
45
45
  }
@@ -33,7 +33,7 @@ export class LoggingServiceImpl implements LoggingService {
33
33
 
34
34
  // Prevent logging feedback loop from logging service.
35
35
  if (
36
- entry.meta?.file.includes('logging-service') ||
36
+ entry.meta?.F.includes('logging-service') ||
37
37
  (entry.context &&
38
38
  Object.values(entry.context).some((value) => typeof value === 'string' && value.includes('LoggingService')))
39
39
  ) {
@@ -50,8 +50,8 @@ export class LoggingServiceImpl implements LoggingService {
50
50
  timestamp: new Date(),
51
51
  meta: {
52
52
  // TODO(dmaretskyi): Fix proto.
53
- file: entry.meta?.file ?? '',
54
- line: entry.meta?.line ?? 0,
53
+ file: entry.meta?.F ?? '',
54
+ line: entry.meta?.L ?? 0,
55
55
  },
56
56
  };
57
57
 
@@ -94,7 +94,7 @@ const shouldLog = (entry: NaturalLogEntry, request: QueryLogsRequest): boolean =
94
94
  if (request.filters === undefined) {
95
95
  return options === QueryLogsRequest.MatchingOptions.INCLUSIVE;
96
96
  } else {
97
- return request.filters.some((filter) => matchFilter(filter, entry.level, entry.meta?.file ?? '', options));
97
+ return request.filters.some((filter) => matchFilter(filter, entry.level, entry.meta?.F ?? '', options));
98
98
  }
99
99
  };
100
100
 
@@ -2,6 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ export * from './client-rpc-server';
5
6
  export * from './service-context';
6
7
  export * from './service-host';
7
8
  export * from './service-registry';
@@ -36,7 +36,7 @@ describe('services/ServiceContext', () => {
36
36
  await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
37
37
  const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
38
38
  await syncItemsLocal(space1.dataPipeline, space2!.dataPipeline);
39
- });
39
+ }).tag('flaky');
40
40
 
41
41
  test('joined space is synchronized on device invitations', async () => {
42
42
  const networkContext = new MemorySignalManagerContext();
@@ -2,10 +2,10 @@
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 { Trigger } from '@dxos/async';
8
- import { CredentialConsumer, getCredentialAssertion } from '@dxos/credentials';
8
+ import { CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
9
9
  import { failUndefined } from '@dxos/debug';
10
10
  import {
11
11
  valueEncoding,
@@ -64,7 +64,7 @@ export class ServiceContext {
64
64
  (invitation: Partial<Invitation>) => InvitationProtocol
65
65
  >();
66
66
 
67
- private _deviceSpaceSync?: CredentialConsumer<any>;
67
+ private _deviceSpaceSync?: CredentialProcessor;
68
68
 
69
69
  private readonly _instanceId = PublicKey.random().toHex();
70
70
 
@@ -141,7 +141,9 @@ export class ServiceContext {
141
141
 
142
142
  async close() {
143
143
  log('closing...');
144
- await this._deviceSpaceSync?.close();
144
+ if (this._deviceSpaceSync && this.identityManager.identity) {
145
+ await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
146
+ }
145
147
  await this.dataSpaceManager?.close();
146
148
  await this.identityManager.close();
147
149
  await this.spaceManager.close();
@@ -161,7 +163,7 @@ export class ServiceContext {
161
163
 
162
164
  getInvitationHandler(invitation: Partial<Invitation> & Pick<Invitation, 'kind'>): InvitationProtocol {
163
165
  const factory = this._handlerFactories.get(invitation.kind);
164
- assert(factory, `Unknown invitation kind: ${invitation.kind}`);
166
+ invariant(factory, `Unknown invitation kind: ${invitation.kind}`);
165
167
  return factory(invitation);
166
168
  }
167
169
 
@@ -205,13 +207,13 @@ export class ServiceContext {
205
207
  await this.dataSpaceManager.open();
206
208
 
207
209
  this._handlerFactories.set(Invitation.Kind.SPACE, (invitation) => {
208
- assert(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
210
+ invariant(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
209
211
  return new SpaceInvitationProtocol(this.dataSpaceManager, signingContext, this.keyring, invitation.spaceKey);
210
212
  });
211
213
  this.initialized.wake();
212
214
 
213
- this._deviceSpaceSync = identity.space.spaceState.registerProcessor({
214
- process: async (credential: Credential) => {
215
+ this._deviceSpaceSync = {
216
+ processCredential: async (credential: Credential) => {
215
217
  const assertion = getCredentialAssertion(credential);
216
218
  if (assertion['@type'] !== 'dxos.halo.credentials.SpaceMember') {
217
219
  return;
@@ -239,7 +241,7 @@ export class ServiceContext {
239
241
  log.catch(err);
240
242
  }
241
243
  },
242
- });
243
- await this._deviceSpaceSync.open();
244
+ };
245
+ await identity.space.spaceState.addCredentialProcessor(this._deviceSpaceSync);
244
246
  }
245
247
  }
@@ -2,19 +2,32 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
5
+ import chai, { expect } from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
7
+ import { rmSync } from 'node:fs';
6
8
 
7
- import { latch, Trigger } from '@dxos/async';
9
+ import { asyncTimeout, latch, Trigger } from '@dxos/async';
8
10
  import { Config } from '@dxos/config';
9
11
  import { verifyPresentation } from '@dxos/credentials';
10
12
  import { PublicKey } from '@dxos/keys';
11
13
  import { MemorySignalManagerContext } from '@dxos/messaging';
14
+ import { Identity } from '@dxos/protocols/proto/dxos/client/services';
12
15
  import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
13
16
  import { afterTest, describe, test } from '@dxos/test';
17
+ import { isNode } from '@dxos/util';
14
18
 
15
19
  import { createMockCredential, createServiceHost } from '../testing';
16
20
 
21
+ chai.use(chaiAsPromised);
22
+
17
23
  describe('ClientServicesHost', () => {
24
+ const persistentStoragePath = '/tmp/dxos/client-services/service-host/storage';
25
+
26
+ afterEach(async () => {
27
+ // Clean up.
28
+ isNode() && rmSync(persistentStoragePath, { recursive: true, force: true });
29
+ });
30
+
18
31
  test('queryCredentials', async () => {
19
32
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
20
33
  await host.open();
@@ -97,4 +110,39 @@ describe('ClientServicesHost', () => {
97
110
  kind: 'pass',
98
111
  });
99
112
  });
113
+
114
+ test('storage reset', async () => {
115
+ const config = new Config({
116
+ runtime: { client: { storage: { persistent: true, path: persistentStoragePath } } },
117
+ });
118
+ {
119
+ const host = createServiceHost(config, new MemorySignalManagerContext());
120
+ await host.open();
121
+
122
+ await host.services.IdentityService?.createIdentity({});
123
+
124
+ expect(host._serviceContext.storage.size).to.exist;
125
+
126
+ await asyncTimeout(host.reset(), 1000);
127
+ await host.close();
128
+ }
129
+
130
+ {
131
+ const host = createServiceHost(config, new MemorySignalManagerContext());
132
+ await host.open();
133
+ const trigger = new Trigger<Identity>();
134
+
135
+ const stream = host.services.IdentityService?.queryIdentity();
136
+ await stream?.waitUntilReady();
137
+
138
+ stream?.subscribe((identity) => {
139
+ if (identity.identity) {
140
+ trigger.wake(identity.identity);
141
+ }
142
+ });
143
+ await expect(asyncTimeout(trigger.wait(), 200)).to.be.rejectedWith();
144
+ stream?.close();
145
+ await host.close();
146
+ }
147
+ }).onlyEnvironments('nodejs', 'chromium', 'firefox');
100
148
  });
@@ -2,11 +2,12 @@
2
2
  // Copyright 2021 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 } from '@dxos/async';
8
- import { clientServiceBundle, ClientServices, createDefaultModelFactory } from '@dxos/client-protocol';
8
+ import { clientServiceBundle, ClientServices } from '@dxos/client-protocol';
9
9
  import { Config } from '@dxos/config';
10
+ import { DocumentModel } from '@dxos/document-model';
10
11
  import { DataServiceImpl } from '@dxos/echo-pipeline';
11
12
  import { PublicKey } from '@dxos/keys';
12
13
  import { log } from '@dxos/log';
@@ -16,6 +17,7 @@ import { createWebRTCTransportFactory, NetworkManager, TransportFactory } from '
16
17
  import { trace } from '@dxos/protocols';
17
18
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
18
19
  import { Storage } from '@dxos/random-access-storage';
20
+ import { TextModel } from '@dxos/text-model';
19
21
 
20
22
  import { DevicesServiceImpl } from '../devices';
21
23
  import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
@@ -30,6 +32,11 @@ import { SystemServiceImpl } from '../system';
30
32
  import { ServiceContext } from './service-context';
31
33
  import { ServiceRegistry } from './service-registry';
32
34
 
35
+ // TODO(burdon): Factor out to spaces.
36
+ export const createDefaultModelFactory = () => {
37
+ return new ModelFactory().registerModel(DocumentModel).registerModel(TextModel);
38
+ };
39
+
33
40
  export type ClientServicesHostParams = {
34
41
  /**
35
42
  * Can be omitted if `initialize` is later called.
@@ -41,6 +48,11 @@ export type ClientServicesHostParams = {
41
48
  connectionLog?: boolean;
42
49
  storage?: Storage;
43
50
  lockKey?: string;
51
+ callbacks?: ClientServicesHostCallbacks;
52
+ };
53
+
54
+ export type ClientServicesHostCallbacks = {
55
+ onReset?: () => Promise<void>;
44
56
  };
45
57
 
46
58
  export type InitializeOptions = {
@@ -65,6 +77,7 @@ export class ClientServicesHost {
65
77
  private _signalManager?: SignalManager;
66
78
  private _networkManager?: NetworkManager;
67
79
  private _storage?: Storage;
80
+ private _callbacks?: ClientServicesHostCallbacks;
68
81
 
69
82
  _serviceContext!: ServiceContext;
70
83
  private _opening = false;
@@ -80,9 +93,11 @@ export class ClientServicesHost {
80
93
  storage,
81
94
  // TODO(wittjosiah): Turn this on by default.
82
95
  lockKey,
96
+ callbacks,
83
97
  }: ClientServicesHostParams = {}) {
84
98
  this._storage = storage;
85
99
  this._modelFactory = modelFactory;
100
+ this._callbacks = callbacks;
86
101
 
87
102
  if (config) {
88
103
  this.initialize({ config, transportFactory, signalManager });
@@ -150,10 +165,10 @@ export class ClientServicesHost {
150
165
  * Can only be called once.
151
166
  */
152
167
  initialize({ config, ...options }: InitializeOptions) {
153
- assert(!this._open, 'service host is open');
168
+ invariant(!this._open, 'service host is open');
154
169
 
155
170
  if (config) {
156
- assert(!this._config, 'config already set');
171
+ invariant(!this._config, 'config already set');
157
172
 
158
173
  this._config = config;
159
174
  if (!this._storage) {
@@ -170,7 +185,7 @@ export class ClientServicesHost {
170
185
  } = options;
171
186
  this._signalManager = signalManager;
172
187
 
173
- assert(!this._networkManager, 'network manager already set');
188
+ invariant(!this._networkManager, 'network manager already set');
174
189
  this._networkManager = new NetworkManager({
175
190
  log: connectionLog,
176
191
  transportFactory,
@@ -187,10 +202,10 @@ export class ClientServicesHost {
187
202
  const traceId = PublicKey.random().toHex();
188
203
  log.trace('dxos.sdk.client-services-host.open', trace.begin({ id: traceId }));
189
204
 
190
- assert(this._config, 'config not set');
191
- assert(this._storage, 'storage not set');
192
- assert(this._signalManager, 'signal manager not set');
193
- assert(this._networkManager, 'network manager not set');
205
+ invariant(this._config, 'config not set');
206
+ invariant(this._storage, 'storage not set');
207
+ invariant(this._signalManager, 'signal manager not set');
208
+ invariant(this._networkManager, 'network manager not set');
194
209
 
195
210
  this._opening = true;
196
211
  log('opening...', { lockKey: this._resourceLock?.lockKey });
@@ -277,5 +292,6 @@ export class ClientServicesHost {
277
292
  await this._storage!.reset();
278
293
  log('reset');
279
294
  log.trace('dxos.sdk.client-services-host.reset', trace.end({ id: traceId }));
295
+ await this._callbacks?.onReset?.();
280
296
  }
281
297
  }