@dxos/client-services 0.6.12-staging.e11e696 → 0.6.12

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 (101) hide show
  1. package/dist/lib/browser/{chunk-67FEJJ6J.mjs → chunk-TOAILL4T.mjs} +5127 -5565
  2. package/dist/lib/browser/chunk-TOAILL4T.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 +6 -5
  7. package/dist/lib/browser/testing/index.mjs.map +2 -2
  8. package/dist/lib/node/{chunk-2KIDYJ7O.cjs → chunk-H6C4XY6B.cjs} +4908 -5346
  9. package/dist/lib/node/chunk-H6C4XY6B.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 +12 -11
  14. package/dist/lib/node/testing/index.cjs.map +2 -2
  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-host-extension.d.ts.map +1 -1
  26. package/dist/types/src/packlets/services/automerge-host.test.d.ts +2 -0
  27. package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +1 -0
  28. package/dist/types/src/packlets/services/service-context.d.ts +6 -7
  29. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  30. package/dist/types/src/packlets/services/service-host.d.ts +0 -1
  31. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  32. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +3 -4
  33. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  34. package/dist/types/src/packlets/spaces/data-space.d.ts +3 -3
  35. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  36. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +0 -3
  37. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  38. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
  39. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  40. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -31
  41. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  42. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  43. package/dist/types/src/packlets/testing/test-builder.d.ts +2 -1
  44. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  45. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  46. package/dist/types/src/version.d.ts +1 -1
  47. package/dist/types/src/version.d.ts.map +1 -1
  48. package/package.json +39 -43
  49. package/src/packlets/devices/devices-service.test.ts +5 -4
  50. package/src/packlets/diagnostics/diagnostics-broadcast.ts +0 -1
  51. package/src/packlets/identity/{authenticator.node.test.ts → authenticator.test.ts} +3 -2
  52. package/src/packlets/identity/authenticator.ts +2 -5
  53. package/src/packlets/identity/contacts-service.ts +1 -1
  54. package/src/packlets/identity/identity-manager.test.ts +6 -5
  55. package/src/packlets/identity/identity-manager.ts +19 -35
  56. package/src/packlets/identity/identity-service.test.ts +8 -4
  57. package/src/packlets/identity/identity.test.ts +239 -128
  58. package/src/packlets/identity/identity.ts +8 -42
  59. package/src/packlets/invitations/device-invitation-protocol.test.ts +4 -7
  60. package/src/packlets/invitations/invitation-host-extension.ts +3 -0
  61. package/src/packlets/invitations/invitations-handler.test.ts +7 -14
  62. package/src/packlets/invitations/invitations-handler.ts +1 -1
  63. package/src/packlets/invitations/space-invitation-protocol.test.ts +3 -4
  64. package/src/packlets/logging/logging.test.ts +2 -1
  65. package/src/packlets/network/network-service.test.ts +3 -2
  66. package/src/packlets/services/automerge-host.test.ts +60 -0
  67. package/src/packlets/services/service-context.test.ts +1 -3
  68. package/src/packlets/services/service-context.ts +28 -64
  69. package/src/packlets/services/service-host.test.ts +12 -8
  70. package/src/packlets/services/service-host.ts +6 -8
  71. package/src/packlets/services/service-registry.test.ts +2 -1
  72. package/src/packlets/spaces/data-space-manager.test.ts +2 -2
  73. package/src/packlets/spaces/data-space-manager.ts +2 -9
  74. package/src/packlets/spaces/data-space.ts +6 -30
  75. package/src/packlets/spaces/edge-feed-replicator.ts +22 -80
  76. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  77. package/src/packlets/spaces/notarization-plugin.test.ts +7 -10
  78. package/src/packlets/spaces/notarization-plugin.ts +29 -169
  79. package/src/packlets/spaces/spaces-service.test.ts +9 -5
  80. package/src/packlets/storage/storage.ts +1 -0
  81. package/src/packlets/system/system-service.test.ts +2 -1
  82. package/src/packlets/testing/test-builder.ts +3 -2
  83. package/src/packlets/worker/worker-runtime.ts +2 -2
  84. package/src/version.ts +5 -1
  85. package/dist/lib/browser/chunk-67FEJJ6J.mjs.map +0 -7
  86. package/dist/lib/node/chunk-2KIDYJ7O.cjs.map +0 -7
  87. package/dist/lib/node-esm/chunk-36ZRRDQI.mjs +0 -8154
  88. package/dist/lib/node-esm/chunk-36ZRRDQI.mjs.map +0 -7
  89. package/dist/lib/node-esm/index.mjs +0 -416
  90. package/dist/lib/node-esm/index.mjs.map +0 -7
  91. package/dist/lib/node-esm/meta.json +0 -1
  92. package/dist/lib/node-esm/testing/index.mjs +0 -418
  93. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  94. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +0 -2
  95. package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +0 -1
  96. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +0 -2
  97. package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +0 -1
  98. package/dist/types/src/testing/setup.d.ts +0 -3
  99. package/dist/types/src/testing/setup.d.ts.map +0 -1
  100. package/src/packlets/spaces/edge-feed-replicator.test.ts +0 -251
  101. package/src/testing/setup.ts +0 -11
@@ -5,19 +5,15 @@
5
5
  import { decode as decodeCbor, encode as encodeCbor } from 'cbor-x';
6
6
 
7
7
  import { Event, Mutex, scheduleMicroTask } from '@dxos/async';
8
- import { Context, Resource } from '@dxos/context';
8
+ import { Resource, type Context } from '@dxos/context';
9
9
  import { type EdgeConnection } from '@dxos/edge-client';
10
- import { EdgeConnectionClosedError, EdgeIdentityChangedError } from '@dxos/edge-client';
11
10
  import { type FeedWrapper } from '@dxos/feed-store';
12
11
  import { invariant } from '@dxos/invariant';
13
12
  import { PublicKey, type SpaceId } from '@dxos/keys';
14
- import { log, logInfo } from '@dxos/log';
13
+ import { log } from '@dxos/log';
15
14
  import { EdgeService } from '@dxos/protocols';
16
15
  import { buf } from '@dxos/protocols/buf';
17
- import {
18
- MessageSchema as RouterMessageSchema,
19
- type Message as RouterMessage,
20
- } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
16
+ import { MessageSchema as RouterMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
21
17
  import type { FeedBlock, ProtocolMessage } from '@dxos/protocols/feed-replication';
22
18
  import { ComplexMap, arrayToBuffer, bufferToArray, defaultMap, rangeFromTo } from '@dxos/util';
23
19
 
@@ -28,10 +24,7 @@ export type EdgeFeedReplicatorParams = {
28
24
 
29
25
  export class EdgeFeedReplicator extends Resource {
30
26
  private readonly _messenger: EdgeConnection;
31
-
32
- @logInfo
33
27
  private readonly _spaceId: SpaceId;
34
-
35
28
  private readonly _feeds = new ComplexMap<PublicKey, FeedWrapper<any>>(PublicKey.hash);
36
29
 
37
30
  private _connectionCtx?: Context = undefined;
@@ -53,10 +46,9 @@ export class EdgeFeedReplicator extends Resource {
53
46
  }
54
47
 
55
48
  protected override async _open(): Promise<void> {
56
- log('open');
57
49
  // TODO: handle reconnects
58
50
  this._ctx.onDispose(
59
- this._messenger.addListener((message: RouterMessage) => {
51
+ this._messenger.addListener(async (message) => {
60
52
  if (!message.serviceId) {
61
53
  return;
62
54
  }
@@ -72,40 +64,21 @@ export class EdgeFeedReplicator extends Resource {
72
64
  }
73
65
 
74
66
  const payload = decodeCbor(message.payload!.value) as ProtocolMessage;
75
- log('receive', { from: message.source, feedKey: payload.feedKey, type: payload.type });
67
+ log.info('recv', { from: message.source, payload });
76
68
  this._onMessage(payload);
77
69
  }),
78
70
  );
79
71
 
80
- this._messenger.connected.on(this._ctx, async () => {
81
- await this._resetConnection();
82
- this._startReplication();
83
- });
84
-
85
- if (this._messenger.isConnected) {
86
- this._startReplication();
72
+ this._connected = true;
73
+ this._connectionCtx = this._ctx.derive();
74
+ for (const feed of this._feeds.values()) {
75
+ await this._replicateFeed(feed);
87
76
  }
88
77
  }
89
78
 
90
79
  protected override async _close(): Promise<void> {
91
- log('close');
92
- await this._resetConnection();
93
- }
94
-
95
- private _startReplication() {
96
- this._connected = true;
97
- const connectionCtx = this._createConnectionContext();
98
- this._connectionCtx = connectionCtx;
99
- log('connection context created');
100
- scheduleMicroTask(connectionCtx, async () => {
101
- for (const feed of this._feeds.values()) {
102
- await this._replicateFeed(connectionCtx, feed);
103
- }
104
- });
105
- }
80
+ this._connected = false;
106
81
 
107
- private async _resetConnection() {
108
- log('resetConnection');
109
82
  this._connected = false;
110
83
  await this._connectionCtx?.dispose();
111
84
  this._connectionCtx = undefined;
@@ -113,11 +86,11 @@ export class EdgeFeedReplicator extends Resource {
113
86
  }
114
87
 
115
88
  async addFeed(feed: FeedWrapper<any>) {
116
- log.info('addFeed', { key: feed.key, connected: this._connected, hasConnectionCtx: !!this._connectionCtx });
89
+ log.info('addFeed', { key: feed.key });
117
90
  this._feeds.set(feed.key, feed);
118
91
 
119
- if (this._connected && this._connectionCtx) {
120
- await this._replicateFeed(this._connectionCtx, feed);
92
+ if (this._connected) {
93
+ await this._replicateFeed(feed);
121
94
  }
122
95
  }
123
96
 
@@ -125,32 +98,25 @@ export class EdgeFeedReplicator extends Resource {
125
98
  return defaultMap(this._pushMutex, key, () => new Mutex());
126
99
  }
127
100
 
128
- private async _replicateFeed(ctx: Context, feed: FeedWrapper<any>) {
129
- log('replicateFeed', { key: feed.key });
101
+ private async _replicateFeed(feed: FeedWrapper<any>) {
102
+ invariant(this._connectionCtx);
103
+
130
104
  await this._sendMessage({
131
105
  type: 'get-metadata',
132
106
  feedKey: feed.key.toHex(),
133
107
  });
134
108
 
135
- Event.wrap(feed.core as any, 'append').on(ctx, async () => {
109
+ Event.wrap(feed.core as any, 'append').on(this._connectionCtx, async () => {
136
110
  await this._pushBlocksIfNeeded(feed);
137
111
  });
138
112
  }
139
113
 
140
114
  private async _sendMessage(message: ProtocolMessage) {
141
- if (!this._connectionCtx) {
142
- log.info('message dropped because connection was disposed');
143
- return;
144
- }
145
-
146
- const logPayload =
147
- message.type === 'data' ? { feedKey: message.feedKey, blocks: message.blocks.map((b) => b.index) } : { message };
148
- log.info('sending message', logPayload);
115
+ log.info('sending message', { message });
149
116
 
150
117
  invariant(message.feedKey);
151
118
  const payloadValue = bufferToArray(encodeCbor(message));
152
119
 
153
- log('send', { type: message.type });
154
120
  await this._messenger.send(
155
121
  buf.create(RouterMessageSchema, {
156
122
  source: {
@@ -164,15 +130,11 @@ export class EdgeFeedReplicator extends Resource {
164
130
  }
165
131
 
166
132
  private _onMessage(message: ProtocolMessage) {
167
- if (!this._connectionCtx) {
168
- log.warn('received message after connection context was disposed');
169
- return;
170
- }
171
- scheduleMicroTask(this._connectionCtx, async () => {
133
+ log.info('received message', { message });
134
+
135
+ scheduleMicroTask(this._ctx, async () => {
172
136
  switch (message.type) {
173
137
  case 'metadata': {
174
- log.info('received metadata', { message });
175
-
176
138
  const feedKey = PublicKey.fromHex(message.feedKey);
177
139
  const feed = this._feeds.get(feedKey);
178
140
  if (!feed) {
@@ -198,8 +160,6 @@ export class EdgeFeedReplicator extends Resource {
198
160
  }
199
161
 
200
162
  case 'data': {
201
- log.info('received data', { feed: message.feedKey, blocks: message.blocks.map((b) => b.index) });
202
-
203
163
  const feedKey = PublicKey.fromHex(message.feedKey);
204
164
  const feed = this._feeds.get(feedKey);
205
165
  if (!feed) {
@@ -263,10 +223,9 @@ export class EdgeFeedReplicator extends Resource {
263
223
  }
264
224
 
265
225
  private async _pushBlocksIfNeeded(feed: FeedWrapper<any>) {
266
- using _ = await this._getPushMutex(feed.key).acquire();
226
+ using _guard = await this._getPushMutex(feed.key).acquire();
267
227
 
268
228
  if (!this._remoteLength.has(feed.key)) {
269
- log('blocks not pushed because remote length is unknown');
270
229
  return;
271
230
  }
272
231
 
@@ -275,23 +234,6 @@ export class EdgeFeedReplicator extends Resource {
275
234
  await this._pushBlocks(feed, remoteLength, feed.length);
276
235
  }
277
236
  }
278
-
279
- private _createConnectionContext() {
280
- const connectionCtx = new Context({
281
- onError: async (err: any) => {
282
- if (connectionCtx !== this._connectionCtx) {
283
- return;
284
- }
285
- if (err instanceof EdgeIdentityChangedError || err instanceof EdgeConnectionClosedError) {
286
- log('resetting on reconnect');
287
- await this._resetConnection();
288
- } else {
289
- this._ctx.raise(err);
290
- }
291
- },
292
- });
293
- return connectionCtx;
294
- }
295
237
  }
296
238
 
297
239
  // hypercore requires buffers
@@ -4,13 +4,13 @@
4
4
 
5
5
  import type { AutomergeUrl } from '@dxos/automerge/automerge-repo';
6
6
  import { type Context } from '@dxos/context';
7
- import { migrateDocument } from '@dxos/echo-db';
8
7
  import {
9
8
  convertLegacyReferences,
10
9
  convertLegacySpaceRootDoc,
11
10
  findInlineObjectOfType,
11
+ migrateDocument,
12
12
  type EchoHost,
13
- } from '@dxos/echo-pipeline';
13
+ } from '@dxos/echo-db';
14
14
  import { SpaceDocVersion, type SpaceDoc } from '@dxos/echo-protocol';
15
15
  import { TYPE_PROPERTIES } from '@dxos/echo-schema';
16
16
  import { invariant } from '@dxos/invariant';
@@ -2,28 +2,27 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { onTestFinished, describe, expect, test } from 'vitest';
5
+ import { expect } from 'chai';
6
6
 
7
7
  import { Context } from '@dxos/context';
8
8
  import { CredentialGenerator } from '@dxos/credentials';
9
9
  import { MockFeedWriter } from '@dxos/feed-store/testing';
10
10
  import { Keyring } from '@dxos/keyring';
11
- import { SpaceId } from '@dxos/keys';
12
11
  import { log } from '@dxos/log';
13
12
  import { AdmittedFeed, type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
14
13
  import { TestBuilder, type TestConnection, TestPeer } from '@dxos/teleport/testing';
14
+ import { afterTest, describe, test } from '@dxos/test';
15
15
 
16
- import { NotarizationPlugin, type NotarizationPluginParams } from './notarization-plugin';
16
+ import { NotarizationPlugin } from './notarization-plugin';
17
17
 
18
18
  class TestAgent extends TestPeer {
19
19
  private readonly _ctx = new Context();
20
20
 
21
21
  feed = new MockFeedWriter<Credential>();
22
- notarizationPlugin: NotarizationPlugin;
22
+ notarizationPlugin = new NotarizationPlugin();
23
23
 
24
- constructor(params: NotarizationPluginParams) {
24
+ constructor() {
25
25
  super();
26
- this.notarizationPlugin = new NotarizationPlugin(params);
27
26
  this.feed.written.on(this._ctx, async ([credential]) => {
28
27
  log('written to feed', { credential });
29
28
  await this.notarizationPlugin.processCredential(credential);
@@ -50,12 +49,10 @@ class TestAgent extends TestPeer {
50
49
  describe('NotarizationPlugin', () => {
51
50
  test('notarize single credential', async () => {
52
51
  const testBuilder = new TestBuilder();
53
- onTestFinished(() => testBuilder.destroy());
54
-
55
- const params = { spaceId: SpaceId.random() };
52
+ afterTest(() => testBuilder.destroy());
56
53
 
57
54
  // peer0 is there to test retries.
58
- const [_peer0, peer1, peer2] = await testBuilder.createPeers({ factory: () => new TestAgent(params) });
55
+ const [_peer0, peer1, peer2] = await testBuilder.createPeers({ factory: () => new TestAgent() });
59
56
  peer1.enableWriting();
60
57
 
61
58
  peer1.feed.written.on(async ([credential]) => {
@@ -2,18 +2,14 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { DeferredTask, Event, scheduleTask, sleep, TimeoutError, Trigger, scheduleMicroTask } from '@dxos/async';
6
- import { type Context, rejectOnDispose, Resource } from '@dxos/context';
7
- import { type CredentialProcessor, verifyCredential } from '@dxos/credentials';
8
- import { type EdgeHttpClient } from '@dxos/edge-client';
5
+ import { DeferredTask, Event, scheduleTask, sleep, TimeoutError, Trigger } from '@dxos/async';
6
+ import { Context, rejectOnDispose } from '@dxos/context';
7
+ import { type CredentialProcessor } from '@dxos/credentials';
9
8
  import { type FeedWriter } from '@dxos/feed-store';
10
9
  import { invariant } from '@dxos/invariant';
11
10
  import { PublicKey } from '@dxos/keys';
12
- import { type SpaceId } from '@dxos/keys';
13
- import { logInfo, log } from '@dxos/log';
14
- import { EdgeCallFailedError } from '@dxos/protocols';
11
+ import { log } from '@dxos/log';
15
12
  import { schema } from '@dxos/protocols/proto';
16
- import { type Runtime } from '@dxos/protocols/proto/dxos/config';
17
13
  import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
18
14
  import { type NotarizationService, type NotarizeRequest } from '@dxos/protocols/proto/dxos/mesh/teleport/notarization';
19
15
  import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
@@ -25,18 +21,8 @@ const DEFAULT_SUCCESS_DELAY = 1_000;
25
21
 
26
22
  const DEFAULT_NOTARIZE_TIMEOUT = 10_000;
27
23
 
28
- const MAX_EDGE_RETRIES = 2;
29
-
30
24
  const WRITER_NOT_SET_ERROR_CODE = 'WRITER_NOT_SET';
31
25
 
32
- const credentialCodec = schema.getCodecForType('dxos.halo.credentials.Credential');
33
-
34
- export type NotarizationPluginParams = {
35
- spaceId: SpaceId;
36
- edgeClient?: EdgeHttpClient;
37
- edgeFeatures?: Runtime.Client.EdgeFeatures;
38
- };
39
-
40
26
  export type NotarizeParams = {
41
27
  /**
42
28
  * For cancellation.
@@ -67,17 +53,13 @@ export type NotarizeParams = {
67
53
  * @default {@link DEFAULT_SUCCESS_DELAY}
68
54
  */
69
55
  successDelay?: number;
70
-
71
- /**
72
- * A random amount of time before making or retrying an edge request to help prevent large bursts of requests.
73
- */
74
- edgeRetryJitter?: number;
75
56
  };
76
57
 
77
58
  /**
78
59
  * See NotarizationService proto.
79
60
  */
80
- export class NotarizationPlugin extends Resource implements CredentialProcessor {
61
+ export class NotarizationPlugin implements CredentialProcessor {
62
+ private readonly _ctx = new Context();
81
63
  private readonly _extensionOpened = new Event();
82
64
 
83
65
  private _writer: FeedWriter<Credential> | undefined;
@@ -85,30 +67,13 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
85
67
  private readonly _processedCredentials = new ComplexSet<PublicKey>(PublicKey.hash);
86
68
  private readonly _processCredentialsTriggers = new ComplexMap<PublicKey, Trigger>(PublicKey.hash);
87
69
 
88
- @logInfo
89
- private readonly _spaceId: SpaceId;
90
-
91
- private readonly _edgeClient: EdgeHttpClient | undefined;
92
-
93
- constructor(params: NotarizationPluginParams) {
94
- super();
95
- this._spaceId = params.spaceId;
96
- if (params.edgeClient && params.edgeFeatures?.feedReplicator) {
97
- this._edgeClient = params.edgeClient;
98
- }
99
- }
100
-
101
70
  get hasWriter() {
102
71
  return !!this._writer;
103
72
  }
104
73
 
105
- protected override async _open() {
106
- if (this._edgeClient && this._writer) {
107
- this._notarizePendingEdgeCredentials(this._edgeClient, this._writer);
108
- }
109
- }
74
+ async open() {}
110
75
 
111
- protected override async _close() {
76
+ async close() {
112
77
  await this._ctx.dispose();
113
78
  }
114
79
 
@@ -121,7 +86,6 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
121
86
  timeout = DEFAULT_NOTARIZE_TIMEOUT,
122
87
  retryTimeout = DEFAULT_RETRY_TIMEOUT,
123
88
  successDelay = DEFAULT_SUCCESS_DELAY,
124
- edgeRetryJitter,
125
89
  }: NotarizeParams) {
126
90
  log('notarize', { credentials });
127
91
  invariant(
@@ -139,35 +103,24 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
139
103
  });
140
104
  opCtx?.onDispose(() => ctx.dispose());
141
105
 
106
+ // Timeout/
142
107
  if (timeout !== 0) {
143
- this._scheduleTimeout(ctx, errors, timeout);
108
+ scheduleTask(
109
+ ctx,
110
+ () => {
111
+ log.warn('Notarization timeout', {
112
+ timeout,
113
+ peers: Array.from(this._extensions).map((extension) => extension.remotePeerId),
114
+ });
115
+ void ctx.dispose();
116
+ errors.throw(new TimeoutError(timeout, 'Notarization timed out'));
117
+ },
118
+ timeout,
119
+ );
144
120
  }
145
121
 
146
122
  const allNotarized = Promise.all(credentials.map((credential) => this._waitUntilProcessed(credential.id!)));
147
123
 
148
- this._tryNotarizeCredentialsWithPeers(ctx, credentials, { retryTimeout, successDelay });
149
-
150
- if (this._edgeClient) {
151
- this._tryNotarizeCredentialsWithEdge(ctx, this._edgeClient, credentials, {
152
- retryTimeout,
153
- successDelay,
154
- jitter: edgeRetryJitter,
155
- });
156
- }
157
-
158
- try {
159
- await Promise.race([rejectOnDispose(ctx), allNotarized, errors.wait()]);
160
- log('done');
161
- } finally {
162
- await ctx.dispose();
163
- }
164
- }
165
-
166
- private _tryNotarizeCredentialsWithPeers(
167
- ctx: Context,
168
- credentials: Credential[],
169
- { retryTimeout, successDelay }: NotarizationTimeouts,
170
- ) {
171
124
  const peersTried = new Set<NotarizationTeleportExtension>();
172
125
 
173
126
  // Repeatable task that tries to notarize credentials with one of the available peers.
@@ -192,7 +145,6 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
192
145
  credentials: credentials.filter((credential) => !this._processedCredentials.has(credential.id!)),
193
146
  });
194
147
  log('success');
195
-
196
148
  await sleep(successDelay); // wait before trying with a new peer
197
149
  } catch (err: any) {
198
150
  if (!ctx.disposed && !err.message.includes(WRITER_NOT_SET_ERROR_CODE)) {
@@ -204,31 +156,13 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
204
156
 
205
157
  notarizeTask.schedule();
206
158
  this._extensionOpened.on(ctx, () => notarizeTask.schedule());
207
- }
208
159
 
209
- private _tryNotarizeCredentialsWithEdge(
210
- ctx: Context,
211
- client: EdgeHttpClient,
212
- credentials: Credential[],
213
- timeouts: NotarizationTimeouts & { jitter?: number },
214
- ) {
215
- const encodedCredentials = credentials.map((credential) => {
216
- const binary = credentialCodec.encode(credential);
217
- return Buffer.from(binary).toString('base64');
218
- });
219
- scheduleTask(ctx, async () => {
220
- try {
221
- await client.notarizeCredentials(
222
- this._spaceId,
223
- { credentials: encodedCredentials },
224
- { retry: { count: MAX_EDGE_RETRIES, timeout: timeouts.retryTimeout, jitter: timeouts.jitter } },
225
- );
226
-
227
- log('edge notarization success');
228
- } catch (error: any) {
229
- handleEdgeError(error);
230
- }
231
- });
160
+ try {
161
+ await Promise.race([rejectOnDispose(ctx), allNotarized, errors.wait()]);
162
+ log('done');
163
+ } finally {
164
+ await ctx.dispose();
165
+ }
232
166
  }
233
167
 
234
168
  /**
@@ -246,44 +180,6 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
246
180
  setWriter(writer: FeedWriter<Credential>) {
247
181
  invariant(!this._writer, 'Writer already set.');
248
182
  this._writer = writer;
249
- if (this._edgeClient) {
250
- this._notarizePendingEdgeCredentials(this._edgeClient, writer);
251
- }
252
- }
253
-
254
- /**
255
- * The method is used only for adding agent feeds to spaces.
256
- * When an agent is created we can admit them into all the existing spaces. In case the operation fails
257
- * this method will fix it on the next space open.
258
- * Given how rarely this happens there's no need to poll the endpoint.
259
- */
260
- private _notarizePendingEdgeCredentials(client: EdgeHttpClient, writer: FeedWriter<Credential>) {
261
- scheduleMicroTask(this._ctx, async () => {
262
- try {
263
- const response = await client.getCredentialsForNotarization(this._spaceId, {
264
- retry: { count: MAX_EDGE_RETRIES },
265
- });
266
-
267
- const credentials = response.awaitingNotarization.credentials;
268
- if (!credentials.length) {
269
- log('edge did not return credentials for notarization');
270
- return;
271
- }
272
-
273
- log('got edge credentials for notarization', { count: credentials.length });
274
-
275
- const decodedCredentials = credentials.map((credential) => {
276
- const binary = Buffer.from(credential, 'base64');
277
- return credentialCodec.decode(binary);
278
- });
279
-
280
- await this._notarizeCredentials(writer, decodedCredentials);
281
-
282
- log.info('notarized edge credentials', { count: decodedCredentials.length });
283
- } catch (error: any) {
284
- handleEdgeError(error);
285
- }
286
- });
287
183
  }
288
184
 
289
185
  private async _waitUntilProcessed(id: PublicKey) {
@@ -300,20 +196,12 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
300
196
  if (!this._writer) {
301
197
  throw new Error(WRITER_NOT_SET_ERROR_CODE);
302
198
  }
303
- await this._notarizeCredentials(this._writer, request.credentials ?? []);
304
- }
305
-
306
- private async _notarizeCredentials(writer: FeedWriter<Credential>, credentials: Credential[]) {
307
- for (const credential of credentials) {
199
+ for (const credential of request.credentials ?? []) {
308
200
  invariant(credential.id, 'Credential must have an id');
309
201
  if (this._processedCredentials.has(credential.id)) {
310
202
  continue;
311
203
  }
312
- const verificationResult = await verifyCredential(credential);
313
- if (verificationResult.kind === 'fail') {
314
- throw new Error(`Credential verification failed: ${verificationResult.errors.join('\n')}.`);
315
- }
316
- await writer.write(credential);
204
+ await this._writer.write(credential);
317
205
  }
318
206
  }
319
207
 
@@ -332,31 +220,8 @@ export class NotarizationPlugin extends Resource implements CredentialProcessor
332
220
  });
333
221
  return extension;
334
222
  }
335
-
336
- private _scheduleTimeout(ctx: Context, errors: Trigger, timeout: number) {
337
- scheduleTask(
338
- ctx,
339
- () => {
340
- log.warn('Notarization timeout', {
341
- timeout,
342
- peers: Array.from(this._extensions).map((extension) => extension.remotePeerId),
343
- });
344
- void ctx.dispose();
345
- errors.throw(new TimeoutError(timeout, 'Notarization timed out'));
346
- },
347
- timeout,
348
- );
349
- }
350
223
  }
351
224
 
352
- const handleEdgeError = (error: any) => {
353
- if (!(error instanceof EdgeCallFailedError) || error.errorData) {
354
- log.catch(error);
355
- } else {
356
- log.info('Edge notarization failure', { reason: error.reason });
357
- }
358
- };
359
-
360
225
  export type NotarizationTeleportExtensionParams = {
361
226
  onOpen: () => Promise<void>;
362
227
  onClose: () => Promise<void>;
@@ -396,11 +261,6 @@ export class NotarizationTeleportExtension extends RpcExtension<Services, Servic
396
261
  }
397
262
  }
398
263
 
399
- type NotarizationTimeouts = {
400
- retryTimeout: number;
401
- successDelay: number;
402
- };
403
-
404
264
  type Services = {
405
265
  NotarizationService: NotarizationService;
406
266
  };
@@ -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 Space, type SpacesService } from '@dxos/protocols/proto/dxos/client/services';
12
+ import { afterEach, afterTest, beforeEach, describe, test } from '@dxos/test';
11
13
 
12
14
  import { SpacesServiceImpl } from './spaces-service';
13
15
  import { type ServiceContext } from '../services';
14
16
  import { createServiceContext } from '../testing';
15
17
 
18
+ chai.use(chaiAsPromised);
19
+
16
20
  describe('SpacesService', () => {
17
21
  let serviceContext: ServiceContext;
18
22
  let spacesService: SpacesService;
@@ -32,7 +36,7 @@ describe('SpacesService', () => {
32
36
 
33
37
  describe('createSpace', () => {
34
38
  test('fails if no identity is available', async () => {
35
- await expect(spacesService.createSpace()).rejects.toBeInstanceOf(Error);
39
+ await expect(spacesService.createSpace()).to.be.rejectedWith();
36
40
  });
37
41
 
38
42
  test('creates a new space', async () => {
@@ -52,7 +56,7 @@ describe('SpacesService', () => {
52
56
  query.subscribe(({ spaces }) => {
53
57
  result.wake(spaces);
54
58
  });
55
- onTestFinished(() => query.close());
59
+ afterTest(() => query.close());
56
60
  expect(await result.wait()).to.be.length(0);
57
61
  });
58
62
 
@@ -69,7 +73,7 @@ describe('SpacesService', () => {
69
73
  query.subscribe(({ spaces }) => {
70
74
  result.wake(spaces);
71
75
  });
72
- onTestFinished(() => query.close());
76
+ afterTest(() => query.close());
73
77
 
74
78
  const spaces = await result.wait();
75
79
  expect(spaces).to.be.length(3);
@@ -83,7 +87,7 @@ describe('SpacesService', () => {
83
87
  query.subscribe(({ spaces }) => {
84
88
  result.wake(spaces);
85
89
  });
86
- onTestFinished(() => query.close());
90
+ afterTest(() => query.close());
87
91
  expect(await result.wait()).to.be.length(0);
88
92
 
89
93
  result.reset();
@@ -14,6 +14,7 @@ import { getRootPath } from './util';
14
14
  // TODO(burdon): Factor out.
15
15
  export const createStorageObjects = (config: Runtime.Client.Storage) => {
16
16
  const { persistent = false, keyStore, dataStore } = config ?? {};
17
+
17
18
  if (persistent && dataStore === StorageDriver.RAM) {
18
19
  throw new InvalidConfigError('RAM storage cannot be used in persistent mode.');
19
20
  }
@@ -2,11 +2,12 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { beforeEach, describe, expect, test } from 'vitest';
5
+ import { expect } from 'chai';
6
6
 
7
7
  import { Event, Trigger } from '@dxos/async';
8
8
  import { Config } from '@dxos/config';
9
9
  import { type SystemService, SystemStatus, type QueryStatusResponse } from '@dxos/protocols/proto/dxos/client/services';
10
+ import { beforeEach, describe, test } from '@dxos/test';
10
11
 
11
12
  import { SystemServiceImpl } from './system-service';
12
13
 
@@ -6,7 +6,8 @@ import { type Config } from '@dxos/config';
6
6
  import { Context } from '@dxos/context';
7
7
  import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
8
8
  import { failUndefined } from '@dxos/debug';
9
- import { EchoHost, MetadataStore, SpaceManager, valueEncoding, MeshEchoReplicator } from '@dxos/echo-pipeline';
9
+ import { EchoHost } from '@dxos/echo-db';
10
+ import { MetadataStore, SpaceManager, valueEncoding, MeshEchoReplicator } from '@dxos/echo-pipeline';
10
11
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
11
12
  import { Keyring } from '@dxos/keyring';
12
13
  import { type LevelDB } from '@dxos/kv-store';
@@ -53,7 +54,7 @@ export const createServiceContext = async ({
53
54
  const level = createTestLevel();
54
55
  await level.open();
55
56
 
56
- return new ServiceContext(storage, level, networkManager, signalManager, undefined, undefined, {
57
+ return new ServiceContext(storage, level, networkManager, signalManager, undefined, {
57
58
  invitationConnectionDefaultParams: { controlHeartbeatInterval: 200 },
58
59
  ...runtimeParams,
59
60
  });