@dxos/client-services 0.5.1-main.f02b2c7 → 0.5.1-main.f2d5836

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 (79) hide show
  1. package/dist/lib/browser/{chunk-T3ZIANWR.mjs → chunk-CZKKHM32.mjs} +1381 -1004
  2. package/dist/lib/browser/chunk-CZKKHM32.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +31 -2
  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 +29 -9
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-425TJE5X.cjs → chunk-T34IF5GF.cjs} +1576 -1207
  9. package/dist/lib/node/chunk-T34IF5GF.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +73 -44
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/packlets/testing/index.cjs +35 -15
  14. package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  16. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  17. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  18. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  19. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +39 -0
  20. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -0
  21. package/dist/types/src/packlets/invitations/{invitation-extension.d.ts → invitation-host-extension.d.ts} +17 -31
  22. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -0
  23. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +6 -1
  24. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  25. package/dist/types/src/packlets/invitations/invitation-topology.d.ts +37 -0
  26. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -0
  27. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +19 -10
  28. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts +2 -0
  30. package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts.map +1 -0
  31. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +2 -1
  32. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  33. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +1 -0
  34. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  35. package/dist/types/src/packlets/invitations/utils.d.ts +6 -0
  36. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -0
  37. package/dist/types/src/packlets/services/service-context.d.ts +8 -5
  38. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  39. package/dist/types/src/packlets/services/service-host.d.ts +1 -1
  40. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  41. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  42. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
  43. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  44. package/dist/types/src/packlets/storage/level.d.ts +1 -2
  45. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  46. package/dist/types/src/packlets/testing/invitation-utils.d.ts +2 -1
  47. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  48. package/dist/types/src/packlets/testing/test-builder.d.ts +2 -1
  49. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  50. package/dist/types/src/packlets/vault/shell-runtime.d.ts +10 -2
  51. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -1
  52. package/dist/types/src/version.d.ts +1 -1
  53. package/package.json +36 -35
  54. package/src/packlets/diagnostics/diagnostics-collector.ts +14 -9
  55. package/src/packlets/diagnostics/diagnostics.ts +78 -68
  56. package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
  57. package/src/packlets/invitations/invitation-guest-extenstion.ts +126 -0
  58. package/src/packlets/invitations/{invitation-extension.ts → invitation-host-extension.ts} +99 -105
  59. package/src/packlets/invitations/invitation-protocol.ts +7 -1
  60. package/src/packlets/invitations/invitation-topology.ts +87 -0
  61. package/src/packlets/invitations/invitations-handler.test.ts +361 -0
  62. package/src/packlets/invitations/invitations-handler.ts +246 -149
  63. package/src/packlets/invitations/invitations-manager.ts +45 -3
  64. package/src/packlets/invitations/space-invitation-protocol.ts +23 -3
  65. package/src/packlets/invitations/utils.ts +27 -0
  66. package/src/packlets/services/automerge-host.test.ts +3 -1
  67. package/src/packlets/services/service-context.ts +7 -6
  68. package/src/packlets/services/service-host.ts +5 -6
  69. package/src/packlets/spaces/data-space.ts +4 -2
  70. package/src/packlets/spaces/genesis.ts +1 -1
  71. package/src/packlets/spaces/spaces-service.ts +12 -6
  72. package/src/packlets/storage/level.ts +2 -2
  73. package/src/packlets/testing/invitation-utils.ts +23 -3
  74. package/src/packlets/testing/test-builder.ts +6 -3
  75. package/src/packlets/vault/shell-runtime.ts +40 -2
  76. package/src/version.ts +1 -1
  77. package/dist/lib/browser/chunk-T3ZIANWR.mjs.map +0 -7
  78. package/dist/lib/node/chunk-425TJE5X.cjs.map +0 -7
  79. package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +0 -1
@@ -2,9 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Event, PushStream } from '@dxos/async';
5
+ import { Event, PushStream, TimeoutError, Trigger } from '@dxos/async';
6
6
  import {
7
- type AuthenticatingInvitation,
7
+ AuthenticatingInvitation,
8
8
  AUTHENTICATION_CODE_LENGTH,
9
9
  CancellableInvitation,
10
10
  INVITATION_TIMEOUT,
@@ -20,6 +20,7 @@ import {
20
20
  type AuthenticationRequest,
21
21
  Invitation,
22
22
  } from '@dxos/protocols/proto/dxos/client/services';
23
+ import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
23
24
 
24
25
  import type { InvitationProtocol } from './invitation-protocol';
25
26
  import { createAdmissionKeypair, type InvitationsHandler } from './invitations-handler';
@@ -117,7 +118,8 @@ export class InvitationsManager {
117
118
  }
118
119
 
119
120
  const handler = this._getHandler(options);
120
- const invitation = this._invitationsHandler.acceptInvitation(handler, options, request.deviceProfile);
121
+ const { ctx, invitation, stream, otpEnteredTrigger } = this._createObservableAcceptingInvitation(handler, options);
122
+ this._invitationsHandler.acceptInvitation(ctx, stream, handler, options, otpEnteredTrigger, request.deviceProfile);
121
123
  this._acceptInvitations.set(invitation.get().invitationId, invitation);
122
124
  this.invitationAccepted.emit(invitation.get());
123
125
 
@@ -149,6 +151,10 @@ export class InvitationsManager {
149
151
  if (created.get().persistent) {
150
152
  await this._metadataStore.removeInvitation(invitationId);
151
153
  }
154
+ if (created.get().type === Invitation.Type.DELEGATED) {
155
+ const handler = this._getHandler(created.get());
156
+ await handler.cancelDelegation(created.get());
157
+ }
152
158
  await created.cancel();
153
159
  this._createInvitations.delete(invitationId);
154
160
  this.removedCreated.emit(created.get());
@@ -190,6 +196,7 @@ export class InvitationsManager {
190
196
  persistent = options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
191
197
  created = new Date(),
192
198
  guestKeypair = undefined,
199
+ role = SpaceMember.Role.ADMIN,
193
200
  lifetime = 86400, // 1 day,
194
201
  multiUse = false,
195
202
  } = options ?? {};
@@ -210,6 +217,7 @@ export class InvitationsManager {
210
217
  guestKeypair ?? (authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? createAdmissionKeypair() : undefined),
211
218
  created,
212
219
  lifetime,
220
+ role,
213
221
  multiUse,
214
222
  delegationCredentialId: options?.delegationCredentialId,
215
223
  ...protocol.getInvitationContext(),
@@ -239,6 +247,40 @@ export class InvitationsManager {
239
247
  return { ctx, stream, observableInvitation };
240
248
  }
241
249
 
250
+ private _createObservableAcceptingInvitation(handler: InvitationProtocol, initialState: Invitation) {
251
+ const otpEnteredTrigger = new Trigger<string>();
252
+ const stream = new PushStream<Invitation>();
253
+ const ctx = new Context({
254
+ onError: (err) => {
255
+ if (err instanceof TimeoutError) {
256
+ log('timeout', { ...handler.toJSON() });
257
+ stream.next({ ...initialState, state: Invitation.State.TIMEOUT });
258
+ } else {
259
+ log.warn('auth failed', err);
260
+ stream.next({ ...initialState, state: Invitation.State.ERROR });
261
+ }
262
+ void ctx.dispose();
263
+ },
264
+ });
265
+ ctx.onDispose(() => {
266
+ log('complete', { ...handler.toJSON() });
267
+ stream.complete();
268
+ });
269
+ const invitation = new AuthenticatingInvitation({
270
+ initialInvitation: initialState,
271
+ subscriber: stream.observable,
272
+ onCancel: async () => {
273
+ stream.next({ ...initialState, state: Invitation.State.CANCELLED });
274
+ await ctx.dispose();
275
+ },
276
+ onAuthenticate: async (code: string) => {
277
+ // TODO(burdon): Reset creates a race condition? Event?
278
+ otpEnteredTrigger.wake(code);
279
+ },
280
+ });
281
+ return { ctx, invitation, stream, otpEnteredTrigger };
282
+ }
283
+
242
284
  private async _persistIfRequired(
243
285
  handler: InvitationProtocol,
244
286
  changeStream: PushStream<Invitation>,
@@ -4,6 +4,7 @@
4
4
 
5
5
  import {
6
6
  createAdmissionCredentials,
7
+ createCancelDelegatedSpaceInvitationCredential,
7
8
  createDelegatedSpaceInvitationCredential,
8
9
  getCredentialAssertion,
9
10
  } from '@dxos/credentials';
@@ -53,7 +54,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
53
54
  guestProfile?: ProfileDocument | undefined,
54
55
  ): Promise<AdmissionResponse> {
55
56
  invariant(this._spaceKey);
56
- const space = await this._spaceManager.spaces.get(this._spaceKey);
57
+ const space = this._spaceManager.spaces.get(this._spaceKey);
57
58
  invariant(space);
58
59
 
59
60
  invariant(request.space);
@@ -66,6 +67,8 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
66
67
  identityKey,
67
68
  space.key,
68
69
  space.inner.genesisFeedKey,
70
+ invitation.role ?? SpaceMember.Role.ADMIN,
71
+ space.inner.spaceState.membershipChainHeads,
69
72
  guestProfile,
70
73
  invitation.delegationCredentialId,
71
74
  );
@@ -87,7 +90,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
87
90
 
88
91
  async delegate(invitation: Invitation): Promise<PublicKey> {
89
92
  invariant(this._spaceKey);
90
- const space = await this._spaceManager.spaces.get(this._spaceKey);
93
+ const space = this._spaceManager.spaces.get(this._spaceKey);
91
94
  invariant(space);
92
95
  if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
93
96
  invariant(invitation.guestKeypair?.publicKey);
@@ -101,7 +104,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
101
104
  invitationId: invitation.invitationId,
102
105
  authMethod: invitation.authMethod,
103
106
  swarmKey: invitation.swarmKey,
104
- role: SpaceMember.Role.ADMIN,
107
+ role: invitation.role ?? SpaceMember.Role.ADMIN,
105
108
  expiresOn: invitation.lifetime
106
109
  ? new Date((invitation.created?.getTime() ?? Date.now()) + invitation.lifetime)
107
110
  : undefined,
@@ -118,6 +121,23 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
118
121
  return credential.credential.credential.id!;
119
122
  }
120
123
 
124
+ async cancelDelegation(invitation: Invitation): Promise<void> {
125
+ invariant(this._spaceKey);
126
+ invariant(invitation.type === Invitation.Type.DELEGATED && invitation.delegationCredentialId);
127
+ const space = this._spaceManager.spaces.get(this._spaceKey);
128
+ invariant(space);
129
+
130
+ log('cancelling delegated space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
131
+ const credential = await createCancelDelegatedSpaceInvitationCredential(
132
+ this._signingContext.credentialSigner,
133
+ space.key,
134
+ invitation.delegationCredentialId,
135
+ );
136
+
137
+ invariant(credential.credential);
138
+ await writeMessages(space.inner.controlPipeline.writer, [credential]);
139
+ }
140
+
121
141
  checkInvitation(invitation: Partial<Invitation>) {
122
142
  if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
123
143
  return new AlreadyJoinedError('Already joined space.');
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Mutex, type MutexGuard } from '@dxos/async';
6
+ import { cancelWithContext, type Context, ContextDisposedError } from '@dxos/context';
7
+ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
8
+
9
+ export const stateToString = (state: Invitation.State): string => {
10
+ return Object.entries(Invitation.State).find(([key, val]) => val === state)?.[0] ?? 'unknown';
11
+ };
12
+
13
+ export const tryAcquireBeforeContextDisposed = async (ctx: Context, mutex: Mutex): Promise<MutexGuard> => {
14
+ let guard: MutexGuard | undefined;
15
+ return cancelWithContext(
16
+ ctx,
17
+ (async () => {
18
+ guard = await mutex.acquire();
19
+ if (ctx.disposed) {
20
+ guard.release();
21
+ guard = undefined;
22
+ throw new ContextDisposedError();
23
+ }
24
+ return guard;
25
+ })(),
26
+ );
27
+ };
@@ -8,7 +8,8 @@ import { asyncTimeout } from '@dxos/async';
8
8
  import { getHeads } from '@dxos/automerge/automerge';
9
9
  import { AutomergeContext } from '@dxos/echo-db';
10
10
  import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
11
- import { createTestLevel } from '@dxos/echo-pipeline/testing';
11
+ import { IndexMetadataStore } from '@dxos/indexing';
12
+ import { createTestLevel } from '@dxos/kv-store/testing';
12
13
  import { afterTest, describe, test } from '@dxos/test';
13
14
 
14
15
  describe('AutomergeHost', () => {
@@ -26,6 +27,7 @@ describe('AutomergeHost', () => {
26
27
 
27
28
  const host = new AutomergeHost({
28
29
  db: level.sublevel('automerge'),
30
+ indexMetadataStore: new IndexMetadataStore({ db: level.sublevel('index-metadata') }),
29
31
  });
30
32
  await host.open();
31
33
  afterTest(() => host.close());
@@ -2,8 +2,6 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type Level } from 'level';
6
-
7
5
  import { Trigger } from '@dxos/async';
8
6
  import { Context, Resource } from '@dxos/context';
9
7
  import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
@@ -14,6 +12,7 @@ import { FeedFactory, FeedStore } from '@dxos/feed-store';
14
12
  import { invariant } from '@dxos/invariant';
15
13
  import { Keyring } from '@dxos/keyring';
16
14
  import { PublicKey } from '@dxos/keys';
15
+ import { type LevelDB } from '@dxos/kv-store';
17
16
  import { log } from '@dxos/log';
18
17
  import { type SignalManager } from '@dxos/messaging';
19
18
  import { type NetworkManager } from '@dxos/network-manager';
@@ -22,6 +21,7 @@ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
22
21
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
23
22
  import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
24
23
  import { type Storage } from '@dxos/random-access-storage';
24
+ import type { TeleportParams } from '@dxos/teleport';
25
25
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
26
26
  import { trace as Trace } from '@dxos/tracing';
27
27
  import { safeInstanceof } from '@dxos/util';
@@ -41,7 +41,8 @@ import {
41
41
  import { InvitationsManager } from '../invitations/invitations-manager';
42
42
  import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
43
43
 
44
- export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
44
+ export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
45
+ DataSpaceManagerRuntimeParams & { invitationConnectionDefaultParams?: Partial<TeleportParams> };
45
46
  /**
46
47
  * Shared backend for all client services.
47
48
  */
@@ -79,10 +80,10 @@ export class ServiceContext extends Resource {
79
80
 
80
81
  constructor(
81
82
  public readonly storage: Storage,
82
- public readonly level: Level<string, string>,
83
+ public readonly level: LevelDB,
83
84
  public readonly networkManager: NetworkManager,
84
85
  public readonly signalManager: SignalManager,
85
- public readonly _runtimeParams?: IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams,
86
+ public readonly _runtimeParams?: ServiceContextRuntimeParams,
86
87
  ) {
87
88
  super();
88
89
 
@@ -124,7 +125,7 @@ export class ServiceContext extends Resource {
124
125
  storage: this.storage,
125
126
  });
126
127
 
127
- this.invitations = new InvitationsHandler(this.networkManager);
128
+ this.invitations = new InvitationsHandler(this.networkManager, _runtimeParams?.invitationConnectionDefaultParams);
128
129
  this.invitationsManager = new InvitationsManager(
129
130
  this.invitations,
130
131
  (invitation) => this.getInvitationHandler(invitation),
@@ -2,16 +2,15 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { type Level } from 'level';
6
-
7
5
  import { Event, synchronized } from '@dxos/async';
8
6
  import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
9
7
  import { type Config } from '@dxos/config';
10
8
  import { Context } from '@dxos/context';
11
- import { type ObjectStructure, encodeReference, type SpaceDoc, type LevelDB } from '@dxos/echo-pipeline';
9
+ import { type ObjectStructure, encodeReference, type SpaceDoc } from '@dxos/echo-protocol';
12
10
  import { getTypeReference } from '@dxos/echo-schema';
13
11
  import { invariant } from '@dxos/invariant';
14
12
  import { PublicKey } from '@dxos/keys';
13
+ import { type LevelDB } from '@dxos/kv-store';
15
14
  import { log } from '@dxos/log';
16
15
  import { WebsocketSignalManager, type SignalManager } from '@dxos/messaging';
17
16
  import { NetworkManager, createSimplePeerTransportFactory, type TransportFactory } from '@dxos/network-manager';
@@ -82,7 +81,7 @@ export class ClientServicesHost {
82
81
  private _signalManager?: SignalManager;
83
82
  private _networkManager?: NetworkManager;
84
83
  private _storage?: Storage;
85
- private _level?: Level<string, string>;
84
+ private _level?: LevelDB;
86
85
  private _callbacks?: ClientServicesHostCallbacks;
87
86
  private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
88
87
 
@@ -340,10 +339,10 @@ export class ClientServicesHost {
340
339
  const traceId = PublicKey.random().toHex();
341
340
  log.trace('dxos.sdk.client-services-host.reset', trace.begin({ id: traceId }));
342
341
 
343
- log('resetting...');
342
+ log.info('resetting...');
344
343
  await this._serviceContext?.close();
345
344
  await this._storage!.reset();
346
- log('reset');
345
+ log.info('reset');
347
346
  log.trace('dxos.sdk.client-services-host.reset', trace.end({ id: traceId }));
348
347
  await this._callbacks?.onReset?.();
349
348
  }
@@ -7,8 +7,9 @@ import { AUTH_TIMEOUT } from '@dxos/client-protocol';
7
7
  import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
8
8
  import { timed, warnAfterTimeout } from '@dxos/debug';
9
9
  import { type EchoHost } from '@dxos/echo-db';
10
- import { type MetadataStore, type Space, createMappedFeedWriter, type SpaceDoc } from '@dxos/echo-pipeline';
10
+ import { type MetadataStore, type Space, createMappedFeedWriter } from '@dxos/echo-pipeline';
11
11
  import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
12
+ import { type SpaceDoc } from '@dxos/echo-protocol';
12
13
  import { TYPE_PROPERTIES } from '@dxos/echo-schema';
13
14
  import { type FeedStore } from '@dxos/feed-store';
14
15
  import { failedInvariant, invariant } from '@dxos/invariant';
@@ -19,6 +20,7 @@ import { CancelledError, SystemError } from '@dxos/protocols';
19
20
  import { SpaceState, type Space as SpaceProto, CreateEpochRequest } from '@dxos/protocols/proto/dxos/client/services';
20
21
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
21
22
  import { type SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
23
+ import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
22
24
  import {
23
25
  AdmittedFeed,
24
26
  type ProfileDocument,
@@ -122,7 +124,7 @@ export class DataSpace {
122
124
  new ComplexSet(
123
125
  PublicKey.hash,
124
126
  Array.from(this._inner.spaceState.members.values())
125
- .filter((member) => !member.removed)
127
+ .filter((member) => member.role !== SpaceMember.Role.REMOVED)
126
128
  .map((member) => member.key),
127
129
  ),
128
130
  update: this._inner.stateUpdate,
@@ -36,7 +36,7 @@ export const spaceGenesis = async (
36
36
  assertion: {
37
37
  '@type': 'dxos.halo.credentials.SpaceMember',
38
38
  spaceKey: space.key,
39
- role: SpaceMember.Role.ADMIN,
39
+ role: SpaceMember.Role.OWNER,
40
40
  profile: signingContext.getProfile(),
41
41
  genesisFeedKey: space.controlFeedKey ?? failUndefined(),
42
42
  },
@@ -22,8 +22,9 @@ import {
22
22
  type SubscribeMessagesRequest,
23
23
  type UpdateSpaceRequest,
24
24
  type WriteCredentialsRequest,
25
+ type UpdateMemberRoleRequest,
25
26
  } from '@dxos/protocols/proto/dxos/client/services';
26
- import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
27
+ import { type Credential, SpaceMember as HaloSpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
27
28
  import { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';
28
29
  import { type Provider } from '@dxos/util';
29
30
 
@@ -67,6 +68,10 @@ export class SpacesServiceImpl implements SpacesService {
67
68
  }
68
69
  }
69
70
 
71
+ async updateMemberRole(_: UpdateMemberRoleRequest): Promise<void> {
72
+ throw new Error('not implemented');
73
+ }
74
+
70
75
  querySpaces(): Stream<QuerySpacesResponse> {
71
76
  return new Stream<QuerySpacesResponse>(({ next, ctx }) => {
72
77
  const scheduler = new UpdateScheduler(
@@ -213,11 +218,12 @@ export class SpacesServiceImpl implements SpacesService {
213
218
  identityKey: member.key,
214
219
  profile: member.profile ?? {},
215
220
  },
216
- presence: member.removed
217
- ? SpaceMember.PresenceState.REMOVED
218
- : isMe || peers.length > 0
219
- ? SpaceMember.PresenceState.ONLINE
220
- : SpaceMember.PresenceState.OFFLINE,
221
+ presence:
222
+ member.role === HaloSpaceMember.Role.REMOVED
223
+ ? SpaceMember.PresenceState.REMOVED
224
+ : isMe || peers.length > 0
225
+ ? SpaceMember.PresenceState.ONLINE
226
+ : SpaceMember.PresenceState.OFFLINE,
221
227
  peerStates: peers,
222
228
  };
223
229
  }),
@@ -2,10 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Level } from 'level';
6
5
  import path from 'node:path';
7
6
 
8
7
  import { PublicKey } from '@dxos/keys';
8
+ import { createLevel as createKV } from '@dxos/kv-store';
9
9
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
10
10
 
11
11
  import { getRootPath, isPersistent } from './util';
@@ -13,7 +13,7 @@ import { getRootPath, isPersistent } from './util';
13
13
  export const createLevel = async (config: Runtime.Client.Storage) => {
14
14
  const persistent = isPersistent(config);
15
15
  const storagePath = persistent ? path.join(getRootPath(config), 'level') : `/tmp/dxos-${PublicKey.random().toHex()}`;
16
- const level = new Level<string, string>(storagePath);
16
+ const level = createKV(storagePath);
17
17
  // TODO(dmaretskyi): This function shouldn't call open - .
18
18
  await level.open();
19
19
  return level;
@@ -45,6 +45,7 @@ export type PerformInvitationParams = {
45
45
  guest?: PerformInvitationCallbacks<AuthenticatingInvitation>;
46
46
  };
47
47
  guestDeviceProfile?: DeviceProfileDocument;
48
+ codeInputDelay?: number;
48
49
  };
49
50
 
50
51
  export type Result = { invitation?: Invitation; error?: Error };
@@ -55,7 +56,10 @@ export const performInvitation = ({
55
56
  options,
56
57
  hooks,
57
58
  guestDeviceProfile,
59
+ codeInputDelay,
58
60
  }: PerformInvitationParams): [Promise<Result>, Promise<Result>] => {
61
+ let guestError = false;
62
+ let guestConnected = false;
59
63
  const hostComplete = new Trigger<Result>();
60
64
  const guestComplete = new Trigger<Result>();
61
65
  const authCode = new Trigger<string>();
@@ -65,6 +69,10 @@ export const performInvitation = ({
65
69
  async (hostInvitation: Invitation) => {
66
70
  switch (hostInvitation.state) {
67
71
  case Invitation.State.CONNECTING: {
72
+ if (guestConnected) {
73
+ break;
74
+ }
75
+ guestConnected = true;
68
76
  if (hooks?.host?.onConnecting?.(hostObservable)) {
69
77
  break;
70
78
  }
@@ -89,7 +97,16 @@ export const performInvitation = ({
89
97
  if (hooks?.guest?.onReady?.(guestObservable)) {
90
98
  break;
91
99
  }
92
- await guestObservable.authenticate(await authCode.wait());
100
+ const code = await authCode.wait();
101
+ if (codeInputDelay == null) {
102
+ await guestObservable.authenticate(code);
103
+ } else {
104
+ setTimeout(async () => {
105
+ if (!guestError) {
106
+ await guestObservable.authenticate(code);
107
+ }
108
+ }, codeInputDelay);
109
+ }
93
110
  break;
94
111
  }
95
112
 
@@ -123,6 +140,7 @@ export const performInvitation = ({
123
140
  }
124
141
  },
125
142
  (error: Error) => {
143
+ guestError = true;
126
144
  if (hooks?.guest?.onError?.(guestObservable)) {
127
145
  return;
128
146
  }
@@ -216,8 +234,10 @@ const acceptInvitation = (
216
234
  invitation = sanitizeInvitation(invitation);
217
235
 
218
236
  if (guest instanceof ServiceContext) {
219
- const guestHandler = guest.getInvitationHandler({ kind: invitation.kind });
220
- return guest.invitations.acceptInvitation(guestHandler, invitation, guestDeviceProfile);
237
+ return guest.invitationsManager.acceptInvitation({
238
+ invitation,
239
+ deviceProfile: guestDeviceProfile,
240
+ });
221
241
  }
222
242
 
223
243
  return guest.join(invitation, guestDeviceProfile);
@@ -7,10 +7,11 @@ import { Context } from '@dxos/context';
7
7
  import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
8
8
  import { failUndefined } from '@dxos/debug';
9
9
  import { EchoHost } from '@dxos/echo-db';
10
- import { MetadataStore, type LevelDB, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
11
- import { createTestLevel } from '@dxos/echo-pipeline/testing';
10
+ import { MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
12
11
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
13
12
  import { Keyring } from '@dxos/keyring';
13
+ import { type LevelDB } from '@dxos/kv-store';
14
+ import { createTestLevel } from '@dxos/kv-store/testing';
14
15
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
15
16
  import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
16
17
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
@@ -48,7 +49,9 @@ export const createServiceContext = async ({
48
49
  const level = createTestLevel();
49
50
  await level.open();
50
51
 
51
- return new ServiceContext(storage, level, networkManager, signalManager);
52
+ return new ServiceContext(storage, level, networkManager, signalManager, {
53
+ invitationConnectionDefaultParams: { controlHeartbeatInterval: 200 },
54
+ });
52
55
  };
53
56
 
54
57
  export const createPeers = async (numPeers: number) => {
@@ -6,7 +6,12 @@ import { Event } from '@dxos/async';
6
6
  import { appServiceBundle, type AppServiceBundle, type ShellRuntime, shellServiceBundle } from '@dxos/client-protocol';
7
7
  import { invariant } from '@dxos/invariant';
8
8
  import { type PublicKey } from '@dxos/keys';
9
- import { type AppContextRequest, type LayoutRequest, ShellLayout } from '@dxos/protocols/proto/dxos/iframe';
9
+ import {
10
+ type AppContextRequest,
11
+ type LayoutRequest,
12
+ ShellLayout,
13
+ type InvitationUrlRequest,
14
+ } from '@dxos/protocols/proto/dxos/iframe';
10
15
  import { createProtoRpcPeer, type ProtoRpcPeer, type RpcPort } from '@dxos/rpc';
11
16
 
12
17
  /**
@@ -14,11 +19,19 @@ import { createProtoRpcPeer, type ProtoRpcPeer, type RpcPort } from '@dxos/rpc';
14
19
  */
15
20
  export class ShellRuntimeImpl implements ShellRuntime {
16
21
  readonly layoutUpdate = new Event<LayoutRequest>();
22
+ readonly invitationUrlUpdate = new Event<InvitationUrlRequest>();
23
+
17
24
  private _appRpc?: ProtoRpcPeer<AppServiceBundle>;
18
25
  private _layout = ShellLayout.DEFAULT;
19
- private _invitationCode?: string;
20
26
  private _spaceKey?: PublicKey;
21
27
 
28
+ private _invitationCode?: string;
29
+ private _invitationUrl? = typeof window !== 'undefined' ? window.location.origin : undefined;
30
+
31
+ // TODO(burdon): Change to using underscores (coordinate with @dxos/web-auth).
32
+ private _deviceInvitationParam = 'deviceInvitationCode'; // TODO(burdon): device_invitation_code
33
+ private _spaceInvitationParam = 'spaceInvitationCode'; // TODO(burdon): space_invitation_code
34
+
22
35
  constructor(private readonly _port: RpcPort) {}
23
36
 
24
37
  get layout() {
@@ -33,6 +46,18 @@ export class ShellRuntimeImpl implements ShellRuntime {
33
46
  return this._spaceKey;
34
47
  }
35
48
 
49
+ get invitationUrl() {
50
+ return this._invitationUrl!;
51
+ }
52
+
53
+ get deviceInvitationParam() {
54
+ return this._deviceInvitationParam;
55
+ }
56
+
57
+ get spaceInvitationParam() {
58
+ return this._spaceInvitationParam;
59
+ }
60
+
36
61
  setLayout({ layout, invitationCode, spaceKey }: LayoutRequest) {
37
62
  this._layout = layout;
38
63
  this._invitationCode = invitationCode;
@@ -40,6 +65,13 @@ export class ShellRuntimeImpl implements ShellRuntime {
40
65
  this.layoutUpdate.emit({ layout, invitationCode, spaceKey });
41
66
  }
42
67
 
68
+ setInvitationUrl({ invitationUrl, deviceInvitationParam, spaceInvitationParam }: InvitationUrlRequest) {
69
+ this._invitationUrl = invitationUrl;
70
+ this._deviceInvitationParam = deviceInvitationParam;
71
+ this._spaceInvitationParam = spaceInvitationParam;
72
+ this.invitationUrlUpdate.emit({ invitationUrl, deviceInvitationParam, spaceInvitationParam });
73
+ }
74
+
43
75
  async setAppContext(context: AppContextRequest) {
44
76
  invariant(this._appRpc, 'runtime not open');
45
77
 
@@ -58,6 +90,12 @@ export class ShellRuntimeImpl implements ShellRuntime {
58
90
  this._spaceKey = request.spaceKey;
59
91
  this.layoutUpdate.emit(request);
60
92
  },
93
+ setInvitationUrl: async (request) => {
94
+ this._invitationUrl = request.invitationUrl;
95
+ this._deviceInvitationParam = request.deviceInvitationParam;
96
+ this._spaceInvitationParam = request.spaceInvitationParam;
97
+ this.invitationUrlUpdate.emit(request);
98
+ },
61
99
  },
62
100
  },
63
101
  port: this._port,
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.5.1-main.f02b2c7";
1
+ export const DXOS_VERSION = "0.5.1-main.f2d5836";