@dxos/client-services 0.6.13-main.548ca8d → 0.6.13-staging.1e988a3

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 (118) hide show
  1. package/dist/lib/browser/{chunk-UEQIHAL2.mjs → chunk-4XKSDWEO.mjs} +5218 -5905
  2. package/dist/lib/browser/chunk-4XKSDWEO.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3 -3
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +7 -8
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/{chunk-MA5EWTRH.cjs → chunk-HNOOWB6F.cjs} +5060 -5743
  9. package/dist/lib/node/chunk-HNOOWB6F.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +46 -46
  11. package/dist/lib/node/index.cjs.map +3 -3
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +13 -14
  14. package/dist/lib/node/testing/index.cjs.map +3 -3
  15. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  16. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  17. package/dist/types/src/packlets/identity/authenticator.test.d.ts +2 -0
  18. package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +1 -0
  19. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  20. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  21. package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -19
  22. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  23. package/dist/types/src/packlets/identity/identity.d.ts +1 -8
  24. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  25. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +1 -2
  26. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  27. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts +1 -2
  28. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  29. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -8
  30. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  31. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  32. package/dist/types/src/packlets/services/automerge-host.test.d.ts +2 -0
  33. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +1 -0
  34. package/dist/types/src/packlets/services/service-context.d.ts +9 -9
  35. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-host.d.ts +0 -1
  37. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  38. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +3 -6
  39. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  40. package/dist/types/src/packlets/spaces/data-space.d.ts +3 -4
  41. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  42. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +0 -3
  43. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  44. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  45. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  46. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -31
  47. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  48. package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
  49. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  50. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  51. package/dist/types/src/packlets/testing/test-builder.d.ts +2 -1
  52. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  53. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  54. package/dist/types/src/version.d.ts +1 -1
  55. package/dist/types/src/version.d.ts.map +1 -1
  56. package/package.json +39 -43
  57. package/src/packlets/devices/devices-service.test.ts +5 -4
  58. package/src/packlets/diagnostics/diagnostics-broadcast.ts +0 -1
  59. package/src/packlets/identity/{authenticator.node.test.ts → authenticator.test.ts} +3 -2
  60. package/src/packlets/identity/authenticator.ts +2 -5
  61. package/src/packlets/identity/contacts-service.ts +1 -1
  62. package/src/packlets/identity/identity-manager.test.ts +6 -5
  63. package/src/packlets/identity/identity-manager.ts +19 -35
  64. package/src/packlets/identity/identity-service.test.ts +8 -4
  65. package/src/packlets/identity/identity.test.ts +239 -128
  66. package/src/packlets/identity/identity.ts +8 -42
  67. package/src/packlets/invitations/device-invitation-protocol.test.ts +4 -7
  68. package/src/packlets/invitations/invitation-guest-extenstion.ts +4 -8
  69. package/src/packlets/invitations/invitation-host-extension.ts +7 -8
  70. package/src/packlets/invitations/invitations-handler.test.ts +9 -16
  71. package/src/packlets/invitations/invitations-handler.ts +92 -23
  72. package/src/packlets/invitations/space-invitation-protocol.test.ts +3 -4
  73. package/src/packlets/invitations/space-invitation-protocol.ts +0 -4
  74. package/src/packlets/logging/logging.test.ts +2 -1
  75. package/src/packlets/network/network-service.test.ts +3 -2
  76. package/src/packlets/services/automerge-host.test.ts +60 -0
  77. package/src/packlets/services/service-context.test.ts +1 -3
  78. package/src/packlets/services/service-context.ts +31 -68
  79. package/src/packlets/services/service-host.test.ts +12 -8
  80. package/src/packlets/services/service-host.ts +6 -8
  81. package/src/packlets/services/service-registry.test.ts +2 -1
  82. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  83. package/src/packlets/spaces/data-space-manager.ts +5 -40
  84. package/src/packlets/spaces/data-space.ts +6 -34
  85. package/src/packlets/spaces/edge-feed-replicator.ts +22 -80
  86. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  87. package/src/packlets/spaces/notarization-plugin.test.ts +7 -10
  88. package/src/packlets/spaces/notarization-plugin.ts +29 -169
  89. package/src/packlets/spaces/spaces-service.test.ts +9 -5
  90. package/src/packlets/spaces/spaces-service.ts +1 -6
  91. package/src/packlets/storage/storage.ts +1 -0
  92. package/src/packlets/system/system-service.test.ts +2 -1
  93. package/src/packlets/testing/test-builder.ts +4 -3
  94. package/src/packlets/worker/worker-runtime.ts +2 -2
  95. package/src/version.ts +1 -1
  96. package/dist/lib/browser/chunk-UEQIHAL2.mjs.map +0 -7
  97. package/dist/lib/node/chunk-MA5EWTRH.cjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-AIBLDI4U.mjs +0 -8403
  99. package/dist/lib/node-esm/chunk-AIBLDI4U.mjs.map +0 -7
  100. package/dist/lib/node-esm/index.mjs +0 -416
  101. package/dist/lib/node-esm/index.mjs.map +0 -7
  102. package/dist/lib/node-esm/meta.json +0 -1
  103. package/dist/lib/node-esm/testing/index.mjs +0 -420
  104. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  105. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +0 -2
  106. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +0 -1
  107. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +0 -30
  108. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +0 -1
  109. package/dist/types/src/packlets/invitations/invitation-state.d.ts +0 -19
  110. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +0 -1
  111. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +0 -2
  112. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +0 -1
  113. package/dist/types/src/testing/setup.d.ts +0 -3
  114. package/dist/types/src/testing/setup.d.ts.map +0 -1
  115. package/src/packlets/invitations/edge-invitation-handler.ts +0 -184
  116. package/src/packlets/invitations/invitation-state.ts +0 -111
  117. package/src/packlets/spaces/edge-feed-replicator.test.ts +0 -253
  118. package/src/testing/setup.ts +0 -11
@@ -7,7 +7,6 @@ import { Event } from '@dxos/async';
7
7
  import { Context } from '@dxos/context';
8
8
  import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
9
9
  import { type MetadataStore, type SpaceManager, type SwarmIdentity } from '@dxos/echo-pipeline';
10
- import { type EdgeConnection } from '@dxos/edge-client';
11
10
  import { type FeedStore } from '@dxos/feed-store';
12
11
  import { invariant } from '@dxos/invariant';
13
12
  import { type Keyring } from '@dxos/keyring';
@@ -15,7 +14,6 @@ import { PublicKey } from '@dxos/keys';
15
14
  import { log } from '@dxos/log';
16
15
  import { trace } from '@dxos/protocols';
17
16
  import { Device, DeviceKind } from '@dxos/protocols/proto/dxos/client/services';
18
- import { type Runtime } from '@dxos/protocols/proto/dxos/config';
19
17
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
20
18
  import { type IdentityRecord, type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
21
19
  import {
@@ -65,20 +63,9 @@ export type CreateIdentityOptions = {
65
63
  deviceProfile?: DeviceProfileDocument;
66
64
  };
67
65
 
68
- export type IdentityManagerCallbacks = {
69
- onIdentityConstruction?: (identity: Identity) => void;
70
- };
71
-
72
- export type IdentityManagerParams = {
73
- metadataStore: MetadataStore;
74
- keyring: Keyring;
75
- feedStore: FeedStore<FeedMessage>;
76
- spaceManager: SpaceManager;
77
- edgeConnection?: EdgeConnection;
78
- edgeFeatures?: Runtime.Client.EdgeFeatures;
66
+ export type IdentityManagerRuntimeParams = {
79
67
  devicePresenceAnnounceInterval?: number;
80
68
  devicePresenceOfflineTimeout?: number;
81
- callbacks?: IdentityManagerCallbacks;
82
69
  };
83
70
 
84
71
  // TODO(dmaretskyi): Rename: represents the peer's state machine.
@@ -86,29 +73,28 @@ export type IdentityManagerParams = {
86
73
  export class IdentityManager {
87
74
  readonly stateUpdate = new Event();
88
75
 
89
- private readonly _metadataStore: MetadataStore;
90
- private readonly _keyring: Keyring;
91
- private readonly _feedStore: FeedStore<FeedMessage>;
92
- private readonly _spaceManager: SpaceManager;
76
+ private _identity?: Identity;
93
77
  private readonly _devicePresenceAnnounceInterval: number;
94
78
  private readonly _devicePresenceOfflineTimeout: number;
95
- private readonly _edgeConnection: EdgeConnection | undefined;
96
- private readonly _edgeFeatures: Runtime.Client.EdgeFeatures | undefined;
97
- private readonly _callbacks: IdentityManagerCallbacks | undefined;
98
-
99
- private _identity?: Identity;
100
79
 
80
+ // TODO(burdon): IdentityManagerParams.
101
81
  // TODO(dmaretskyi): Perhaps this should take/generate the peerKey outside of an initialized identity.
102
- constructor(params: IdentityManagerParams) {
103
- this._metadataStore = params.metadataStore;
104
- this._keyring = params.keyring;
105
- this._feedStore = params.feedStore;
106
- this._spaceManager = params.spaceManager;
107
- this._edgeConnection = params.edgeConnection;
108
- this._edgeFeatures = params.edgeFeatures;
109
- this._devicePresenceAnnounceInterval = params.devicePresenceAnnounceInterval ?? DEVICE_PRESENCE_ANNOUNCE_INTERVAL;
110
- this._devicePresenceOfflineTimeout = params.devicePresenceOfflineTimeout ?? DEVICE_PRESENCE_OFFLINE_TIMEOUT;
111
- this._callbacks = params.callbacks;
82
+ constructor(
83
+ private readonly _metadataStore: MetadataStore,
84
+ private readonly _keyring: Keyring,
85
+ private readonly _feedStore: FeedStore<FeedMessage>,
86
+ private readonly _spaceManager: SpaceManager,
87
+ params?: IdentityManagerRuntimeParams,
88
+ private readonly _callbacks?: {
89
+ onIdentityConstruction?: (identity: Identity) => void;
90
+ },
91
+ ) {
92
+ const {
93
+ devicePresenceAnnounceInterval = DEVICE_PRESENCE_ANNOUNCE_INTERVAL,
94
+ devicePresenceOfflineTimeout = DEVICE_PRESENCE_OFFLINE_TIMEOUT,
95
+ } = params ?? {};
96
+ this._devicePresenceAnnounceInterval = devicePresenceAnnounceInterval;
97
+ this._devicePresenceOfflineTimeout = devicePresenceOfflineTimeout;
112
98
  }
113
99
 
114
100
  get identity() {
@@ -374,8 +360,6 @@ export class IdentityManager {
374
360
  signer: this._keyring,
375
361
  identityKey: identityRecord.identityKey,
376
362
  deviceKey: identityRecord.deviceKey,
377
- edgeConnection: this._edgeConnection,
378
- edgeFeatures: this._edgeFeatures,
379
363
  });
380
364
  log('done', { identityKey: identityRecord.identityKey });
381
365
  this._callbacks?.onIdentityConstruction?.(identity);
@@ -2,17 +2,21 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { afterEach, onTestFinished, beforeEach, describe, expect, test } from 'vitest';
5
+ import chai, { expect } from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
6
7
 
7
8
  import { Trigger } from '@dxos/async';
8
9
  import { Context } from '@dxos/context';
9
10
  import { PublicKey } from '@dxos/keys';
10
11
  import { type Identity, type IdentityService } from '@dxos/protocols/proto/dxos/client/services';
12
+ import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
11
13
 
12
14
  import { IdentityServiceImpl } from './identity-service';
13
15
  import { type ServiceContext } from '../services';
14
16
  import { createServiceContext } from '../testing';
15
17
 
18
+ chai.use(chaiAsPromised);
19
+
16
20
  describe('IdentityService', () => {
17
21
  let serviceContext: ServiceContext;
18
22
  let identityService: IdentityService;
@@ -45,7 +49,7 @@ describe('IdentityService', () => {
45
49
 
46
50
  test('fails to create identity if one already exists', async () => {
47
51
  await identityService.createIdentity({});
48
- await expect(identityService.createIdentity({})).rejects.toThrowError('Identity already exists');
52
+ await expect(identityService.createIdentity({})).to.be.rejectedWith('Identity already exists');
49
53
  });
50
54
  });
51
55
 
@@ -68,7 +72,7 @@ describe('IdentityService', () => {
68
72
  query.subscribe(({ identity }) => {
69
73
  result.wake(identity);
70
74
  });
71
- onTestFinished(() => query.close());
75
+ afterTest(() => query.close());
72
76
  expect(await result.wait()).to.be.undefined;
73
77
  });
74
78
 
@@ -78,7 +82,7 @@ describe('IdentityService', () => {
78
82
  query.subscribe(({ identity }) => {
79
83
  result.wake(identity);
80
84
  });
81
- onTestFinished(() => query.close());
85
+ afterTest(() => query.close());
82
86
  expect(await result.wait()).to.be.undefined;
83
87
 
84
88
  result = new Trigger<Identity | undefined>();
@@ -2,9 +2,8 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { onTestFinished, describe, expect, test } from 'vitest';
5
+ import expect from 'expect';
6
6
 
7
- import { Event } from '@dxos/async';
8
7
  import { Context } from '@dxos/context';
9
8
  import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
10
9
  import {
@@ -16,9 +15,7 @@ import {
16
15
  SpaceProtocol,
17
16
  valueEncoding,
18
17
  } from '@dxos/echo-pipeline';
19
- import { type EdgeConnection, type MessageListener } from '@dxos/edge-client';
20
18
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
21
- import { type FeedWrapper } from '@dxos/feed-store';
22
19
  import { Keyring } from '@dxos/keyring';
23
20
  import { type PublicKey } from '@dxos/keys';
24
21
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
@@ -27,6 +24,7 @@ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
27
24
  import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
28
25
  import { createStorage, StorageType } from '@dxos/random-access-storage';
29
26
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
27
+ import { afterTest, describe, test } from '@dxos/test';
30
28
 
31
29
  import { Identity } from './identity';
32
30
 
@@ -44,108 +42,16 @@ const createStores = () => {
44
42
 
45
43
  describe('identity/identity', () => {
46
44
  test('create', async () => {
47
- const setup = await setupIdentity();
48
-
49
- await writeGenesisCredential(setup);
50
-
51
- // Wait for identity to be ready.
52
- await setup.identity.ready();
53
- const identitySigner = setup.identity.getIdentityCredentialSigner();
54
- const credential = await identitySigner.createCredential({
55
- subject: setup.identityKey,
56
- assertion: {
57
- '@type': 'dxos.halo.credentials.IdentityProfile',
58
- profile: {
59
- displayName: 'Alice',
60
- },
61
- },
62
- });
63
-
64
- expect(credential.issuer).toEqual(setup.identityKey);
65
- expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
66
- });
67
-
68
- test('two devices', async () => {
69
- const signalContext = new MemorySignalManagerContext();
70
-
71
- const owner = await setupIdentity({ signalContext });
72
- await writeGenesisCredential(owner);
73
- await owner.identity.ready();
74
-
75
- const secondDevice = (
76
- await setupIdentity({
77
- signalContext,
78
- spaceKey: owner.spaceKey,
79
- identityKey: owner.identityKey,
80
- genesisFeedKey: owner.controlFeed.key,
81
- })
82
- ).identity;
83
-
84
- //
85
- // Second device admission
86
- //
87
- {
88
- const signer = owner.identity.getIdentityCredentialSigner();
89
- void owner.identity.controlPipeline.writer.write({
90
- credential: {
91
- credential: await signer.createCredential({
92
- subject: secondDevice.deviceKey,
93
- assertion: {
94
- '@type': 'dxos.halo.credentials.AuthorizedDevice',
95
- identityKey: owner.identityKey,
96
- deviceKey: secondDevice.deviceKey,
97
- },
98
- }),
99
- },
100
- });
101
-
102
- await secondDevice.ready();
103
- }
104
-
105
- expect(Array.from(owner.identity.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
106
- expect(Array.from(secondDevice.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
107
- });
108
-
109
- test('edge feed replicator', async () => {
110
- let replicationStarted = false;
111
- const connectedEvent = new Event();
112
- const setup = await setupIdentity({
113
- edgeConnection: {
114
- connected: connectedEvent,
115
- open: async () => {},
116
- close: async () => {},
117
- addListener: (_: MessageListener): (() => void) => {
118
- return () => {};
119
- },
120
- send: async (_) => {
121
- replicationStarted = true;
122
- },
123
- } as EdgeConnection,
124
- });
125
-
126
- await writeGenesisCredential(setup);
127
- connectedEvent.emit();
128
-
129
- await expect.poll(() => replicationStarted).toBeTruthy();
130
- });
131
-
132
- const setupIdentity = async (args?: {
133
- signalContext?: MemorySignalManagerContext;
134
- spaceKey?: PublicKey;
135
- identityKey?: PublicKey;
136
- genesisFeedKey?: PublicKey;
137
- edgeConnection?: EdgeConnection;
138
- }): Promise<TestIdentitySetup> => {
139
45
  const { storage, metadataStore, blobStore } = createStores();
140
46
 
141
47
  const keyring = new Keyring();
48
+ const identityKey = await keyring.createKey();
142
49
  const deviceKey = await keyring.createKey();
143
- const identityKey = args?.identityKey ?? (await keyring.createKey());
144
- const spaceKey = args?.spaceKey ?? (await keyring.createKey());
50
+ const spaceKey = await keyring.createKey();
145
51
 
146
52
  const feedStore = new FeedStore<FeedMessage>({
147
53
  factory: new FeedFactory<FeedMessage>({
148
- root: storage.createDirectory(),
54
+ root: storage.createDirectory('feeds'),
149
55
  signer: keyring,
150
56
  hypercore: {
151
57
  valueEncoding,
@@ -171,7 +77,7 @@ describe('identity/identity', () => {
171
77
  },
172
78
  blobStore,
173
79
  networkManager: new SwarmNetworkManager({
174
- signalManager: new MemorySignalManager(args?.signalContext ?? new MemorySignalManagerContext()),
80
+ signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
175
81
  transportFactory: MemoryTransportFactory,
176
82
  }),
177
83
  });
@@ -181,7 +87,7 @@ describe('identity/identity', () => {
181
87
  id: await createIdFromSpaceKey(spaceKey),
182
88
  spaceKey,
183
89
  protocol,
184
- genesisFeed: args?.genesisFeedKey ? await feedStore.openFeed(args.genesisFeedKey) : controlFeed,
90
+ genesisFeed: controlFeed,
185
91
  feedProvider: (feedKey) => feedStore.openFeed(feedKey),
186
92
  memberKey: identityKey,
187
93
  metadataStore,
@@ -197,37 +103,242 @@ describe('identity/identity', () => {
197
103
  identityKey,
198
104
  deviceKey,
199
105
  space,
200
- edgeFeatures: args?.edgeConnection && { feedReplicator: true },
201
- edgeConnection: args?.edgeConnection,
202
106
  });
203
107
 
204
108
  await identity.open(new Context());
205
- onTestFinished(() => identity.close(new Context()));
206
- return { identity, identityKey, keyring, deviceKey, controlFeed, spaceKey, dataFeed };
207
- };
109
+ afterTest(() => identity.close(new Context()));
110
+
111
+ //
112
+ // Identity genesis
113
+ //
114
+ {
115
+ const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
116
+ const credentials = [
117
+ ...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
118
+ await generator.createDeviceAuthorization(deviceKey),
119
+ await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
120
+ ];
121
+
122
+ for (const credential of credentials) {
123
+ await identity.controlPipeline.writer.write({
124
+ credential: { credential },
125
+ });
126
+ }
127
+ }
128
+
129
+ // Wait for identity to be ready.
130
+ await identity.ready();
131
+ const identitySigner = identity.getIdentityCredentialSigner();
132
+ const credential = await identitySigner.createCredential({
133
+ subject: identityKey,
134
+ assertion: {
135
+ '@type': 'dxos.halo.credentials.IdentityProfile',
136
+ profile: {
137
+ displayName: 'Alice',
138
+ },
139
+ },
140
+ });
141
+
142
+ expect(credential.issuer).toEqual(identityKey);
143
+ expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
144
+ });
208
145
 
209
- const writeGenesisCredential = async (setup: TestIdentitySetup) => {
210
- const generator = new CredentialGenerator(setup.keyring, setup.identityKey, setup.deviceKey);
211
- const credentials = [
212
- ...(await generator.createSpaceGenesis(setup.spaceKey, setup.controlFeed.key)),
213
- await generator.createDeviceAuthorization(setup.deviceKey),
214
- await generator.createFeedAdmission(setup.spaceKey, setup.dataFeed.key, AdmittedFeed.Designation.DATA),
215
- ];
216
-
217
- for (const credential of credentials) {
218
- await setup.identity.controlPipeline.writer.write({
219
- credential: { credential },
146
+ test('two devices', async () => {
147
+ const signalContext = new MemorySignalManagerContext();
148
+
149
+ let spaceKey: PublicKey;
150
+ let identityKey: PublicKey;
151
+ let genesisFeedKey: PublicKey;
152
+ let identity1: Identity;
153
+ let identity2: Identity;
154
+
155
+ //
156
+ // First device
157
+ //
158
+ {
159
+ const { storage, metadataStore, blobStore } = createStores();
160
+
161
+ const keyring = new Keyring();
162
+ identityKey = await keyring.createKey();
163
+ const deviceKey = await keyring.createKey();
164
+ spaceKey = await keyring.createKey();
165
+
166
+ const feedStore = new FeedStore<FeedMessage>({
167
+ factory: new FeedFactory<FeedMessage>({
168
+ root: storage.createDirectory(),
169
+ signer: keyring,
170
+ hypercore: {
171
+ valueEncoding,
172
+ },
173
+ }),
174
+ });
175
+
176
+ const createFeed = async () => {
177
+ const feedKey = await keyring.createKey();
178
+ return feedStore.openFeed(feedKey, { writable: true });
179
+ };
180
+
181
+ const controlFeed = await createFeed();
182
+ genesisFeedKey = controlFeed.key;
183
+
184
+ const dataFeed = await createFeed();
185
+
186
+ const protocol = new SpaceProtocol({
187
+ topic: spaceKey,
188
+ swarmIdentity: {
189
+ peerKey: deviceKey,
190
+ identityKey,
191
+ credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
192
+ credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
193
+ },
194
+ blobStore,
195
+ networkManager: new SwarmNetworkManager({
196
+ signalManager: new MemorySignalManager(signalContext),
197
+ transportFactory: MemoryTransportFactory,
198
+ }),
199
+ });
200
+
201
+ await metadataStore.setIdentityRecord({ haloSpace: { key: spaceKey }, identityKey, deviceKey });
202
+ const space = new Space({
203
+ id: await createIdFromSpaceKey(spaceKey),
204
+ spaceKey,
205
+ protocol,
206
+ genesisFeed: controlFeed,
207
+ feedProvider: (feedKey) => feedStore.openFeed(feedKey),
208
+ memberKey: identityKey,
209
+ metadataStore,
210
+ onDelegatedInvitationStatusChange: async () => {},
211
+ onMemberRolesChanged: async () => {},
220
212
  });
213
+ await space.setControlFeed(controlFeed);
214
+ await space.setDataFeed(dataFeed);
215
+
216
+ const identity = (identity1 = new Identity({
217
+ signer: keyring,
218
+ identityKey,
219
+ deviceKey,
220
+ space,
221
+ }));
222
+
223
+ await identity.open(new Context());
224
+ afterTest(() => identity.close(new Context()));
225
+
226
+ //
227
+ // Identity genesis
228
+ //
229
+ {
230
+ const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
231
+ const credentials = [
232
+ ...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
233
+ await generator.createDeviceAuthorization(deviceKey),
234
+ await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
235
+ ];
236
+
237
+ for (const credential of credentials) {
238
+ await identity.controlPipeline.writer.write({
239
+ credential: { credential },
240
+ });
241
+ }
242
+ }
243
+
244
+ // Wait for identity to be ready.
245
+ await identity.ready();
221
246
  }
222
- };
223
- });
224
247
 
225
- type TestIdentitySetup = {
226
- identity: Identity;
227
- keyring: Keyring;
228
- identityKey: PublicKey;
229
- deviceKey: PublicKey;
230
- spaceKey: PublicKey;
231
- controlFeed: FeedWrapper<FeedMessage>;
232
- dataFeed: FeedWrapper<FeedMessage>;
233
- };
248
+ //
249
+ // Second device
250
+ //
251
+ {
252
+ const { storage, metadataStore, blobStore } = createStores();
253
+
254
+ const keyring = new Keyring();
255
+ const deviceKey = await keyring.createKey();
256
+
257
+ const feedStore = new FeedStore<FeedMessage>({
258
+ factory: new FeedFactory<FeedMessage>({
259
+ root: storage.createDirectory(),
260
+ signer: keyring,
261
+ hypercore: {
262
+ valueEncoding,
263
+ },
264
+ }),
265
+ });
266
+
267
+ const createFeed = async () => {
268
+ const feedKey = await keyring.createKey();
269
+ return feedStore.openFeed(feedKey, { writable: true });
270
+ };
271
+
272
+ const controlFeed = await createFeed();
273
+ const dataFeed = await createFeed();
274
+
275
+ const protocol = new SpaceProtocol({
276
+ topic: spaceKey,
277
+ swarmIdentity: {
278
+ peerKey: deviceKey,
279
+ identityKey,
280
+ credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
281
+ credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
282
+ },
283
+ blobStore,
284
+ networkManager: new SwarmNetworkManager({
285
+ signalManager: new MemorySignalManager(signalContext),
286
+ transportFactory: MemoryTransportFactory,
287
+ }),
288
+ });
289
+
290
+ await metadataStore.setIdentityRecord({
291
+ haloSpace: { key: spaceKey },
292
+ identityKey: identity1.identityKey,
293
+ deviceKey,
294
+ });
295
+ const space = new Space({
296
+ id: await createIdFromSpaceKey(spaceKey),
297
+ spaceKey,
298
+ protocol,
299
+ genesisFeed: await feedStore.openFeed(genesisFeedKey),
300
+ feedProvider: (feedKey) => feedStore.openFeed(feedKey),
301
+ memberKey: identityKey,
302
+ metadataStore,
303
+ onDelegatedInvitationStatusChange: async () => {},
304
+ onMemberRolesChanged: async () => {},
305
+ });
306
+ await space.setControlFeed(controlFeed);
307
+ await space.setDataFeed(dataFeed);
308
+
309
+ const identity = (identity2 = new Identity({
310
+ signer: keyring,
311
+ identityKey: identity1.identityKey,
312
+ deviceKey,
313
+ space,
314
+ }));
315
+
316
+ await identity.open(new Context());
317
+ afterTest(() => identity.close(new Context()));
318
+ }
319
+
320
+ //
321
+ // Second device admission
322
+ //
323
+ {
324
+ const signer = identity1.getIdentityCredentialSigner();
325
+ void identity1.controlPipeline.writer.write({
326
+ credential: {
327
+ credential: await signer.createCredential({
328
+ subject: identity2.deviceKey,
329
+ assertion: {
330
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
331
+ identityKey: identity1.identityKey,
332
+ deviceKey: identity2.deviceKey,
333
+ },
334
+ }),
335
+ },
336
+ });
337
+
338
+ await identity2.ready();
339
+ }
340
+
341
+ expect(Array.from(identity1.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
342
+ expect(Array.from(identity2.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
343
+ });
344
+ });
@@ -14,12 +14,10 @@ import {
14
14
  } from '@dxos/credentials';
15
15
  import { type Signer } from '@dxos/crypto';
16
16
  import { type Space } from '@dxos/echo-pipeline';
17
- import { type EdgeConnection } from '@dxos/edge-client';
18
- import { writeMessages, type FeedWrapper } from '@dxos/feed-store';
17
+ import { writeMessages } from '@dxos/feed-store';
19
18
  import { invariant } from '@dxos/invariant';
20
19
  import { PublicKey, type SpaceId } from '@dxos/keys';
21
20
  import { log } from '@dxos/log';
22
- import { type Runtime } from '@dxos/protocols/proto/dxos/config';
23
21
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
24
22
  import {
25
23
  AdmittedFeed,
@@ -34,7 +32,6 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
34
32
 
35
33
  import { TrustedKeySetAuthVerifier } from './authenticator';
36
34
  import { DefaultSpaceStateMachine } from './default-space-state-machine';
37
- import { EdgeFeedReplicator } from '../spaces';
38
35
 
39
36
  export type IdentityParams = {
40
37
  identityKey: PublicKey;
@@ -42,9 +39,6 @@ export type IdentityParams = {
42
39
  signer: Signer;
43
40
  space: Space;
44
41
  presence?: Presence;
45
-
46
- edgeConnection?: EdgeConnection;
47
- edgeFeatures?: Runtime.Client.EdgeFeatures;
48
42
  };
49
43
 
50
44
  /**
@@ -58,8 +52,6 @@ export class Identity {
58
52
  private readonly _deviceStateMachine: DeviceStateMachine;
59
53
  private readonly _profileStateMachine: ProfileStateMachine;
60
54
  private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
61
- private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
62
-
63
55
  public readonly authVerifier: TrustedKeySetAuthVerifier;
64
56
 
65
57
  public readonly identityKey: PublicKey;
@@ -67,15 +59,15 @@ export class Identity {
67
59
 
68
60
  public readonly stateUpdate = new Event();
69
61
 
70
- constructor(params: IdentityParams) {
71
- this.space = params.space;
72
- this._signer = params.signer;
73
- this._presence = params.presence;
62
+ constructor({ space, signer, identityKey, deviceKey, presence }: IdentityParams) {
63
+ this.space = space;
64
+ this._signer = signer;
65
+ this._presence = presence;
74
66
 
75
- this.identityKey = params.identityKey;
76
- this.deviceKey = params.deviceKey;
67
+ this.identityKey = identityKey;
68
+ this.deviceKey = deviceKey;
77
69
 
78
- log.trace('dxos.halo.device', { deviceKey: params.deviceKey });
70
+ log.trace('dxos.halo.device', { deviceKey });
79
71
 
80
72
  this._deviceStateMachine = new DeviceStateMachine({
81
73
  identityKey: this.identityKey,
@@ -96,10 +88,6 @@ export class Identity {
96
88
  update: this.stateUpdate,
97
89
  authTimeout: AUTH_TIMEOUT,
98
90
  });
99
-
100
- if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
101
- this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
102
- }
103
91
  }
104
92
 
105
93
  // TODO(burdon): Expose state object?
@@ -117,14 +105,7 @@ export class Identity {
117
105
  await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
118
106
  await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
119
107
  await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
120
-
121
- if (this._edgeFeedReplicator) {
122
- this.space.protocol.feedAdded.append(this._onFeedAdded);
123
- }
124
-
125
108
  await this.space.open(ctx);
126
-
127
- await this._edgeFeedReplicator?.open();
128
109
  }
129
110
 
130
111
  @trace.span()
@@ -134,13 +115,6 @@ export class Identity {
134
115
  await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
135
116
  await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
136
117
  await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
137
-
138
- if (this._edgeFeedReplicator) {
139
- this.space.protocol.feedAdded.remove(this._onFeedAdded);
140
- }
141
-
142
- await this._edgeFeedReplicator?.close();
143
-
144
118
  await this.space.close();
145
119
  }
146
120
 
@@ -177,10 +151,6 @@ export class Identity {
177
151
  return this._presence;
178
152
  }
179
153
 
180
- get signer() {
181
- return this._signer;
182
- }
183
-
184
154
  /**
185
155
  * Issues credentials as identity.
186
156
  * Requires identity to be ready.
@@ -253,8 +223,4 @@ export class Identity {
253
223
  ].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
254
224
  );
255
225
  }
256
-
257
- private _onFeedAdded = async (feed: FeedWrapper<any>) => {
258
- await this._edgeFeedReplicator!.addFeed(feed);
259
- };
260
226
  }