@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.
- package/dist/lib/browser/{chunk-67FEJJ6J.mjs → chunk-TOAILL4T.mjs} +5127 -5565
- package/dist/lib/browser/chunk-TOAILL4T.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +3 -3
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +6 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/{chunk-2KIDYJ7O.cjs → chunk-H6C4XY6B.cjs} +4908 -5346
- package/dist/lib/node/chunk-H6C4XY6B.cjs.map +7 -0
- package/dist/lib/node/index.cjs +46 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +12 -11
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.test.d.ts +2 -0
- package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -19
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +1 -8
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/services/automerge-host.test.d.ts +2 -0
- package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-context.d.ts +6 -7
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +0 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +3 -4
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +3 -3
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +0 -3
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -31
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +2 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +39 -43
- package/src/packlets/devices/devices-service.test.ts +5 -4
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +0 -1
- package/src/packlets/identity/{authenticator.node.test.ts → authenticator.test.ts} +3 -2
- package/src/packlets/identity/authenticator.ts +2 -5
- package/src/packlets/identity/contacts-service.ts +1 -1
- package/src/packlets/identity/identity-manager.test.ts +6 -5
- package/src/packlets/identity/identity-manager.ts +19 -35
- package/src/packlets/identity/identity-service.test.ts +8 -4
- package/src/packlets/identity/identity.test.ts +239 -128
- package/src/packlets/identity/identity.ts +8 -42
- package/src/packlets/invitations/device-invitation-protocol.test.ts +4 -7
- package/src/packlets/invitations/invitation-host-extension.ts +3 -0
- package/src/packlets/invitations/invitations-handler.test.ts +7 -14
- package/src/packlets/invitations/invitations-handler.ts +1 -1
- package/src/packlets/invitations/space-invitation-protocol.test.ts +3 -4
- package/src/packlets/logging/logging.test.ts +2 -1
- package/src/packlets/network/network-service.test.ts +3 -2
- package/src/packlets/services/automerge-host.test.ts +60 -0
- package/src/packlets/services/service-context.test.ts +1 -3
- package/src/packlets/services/service-context.ts +28 -64
- package/src/packlets/services/service-host.test.ts +12 -8
- package/src/packlets/services/service-host.ts +6 -8
- package/src/packlets/services/service-registry.test.ts +2 -1
- package/src/packlets/spaces/data-space-manager.test.ts +2 -2
- package/src/packlets/spaces/data-space-manager.ts +2 -9
- package/src/packlets/spaces/data-space.ts +6 -30
- package/src/packlets/spaces/edge-feed-replicator.ts +22 -80
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +7 -10
- package/src/packlets/spaces/notarization-plugin.ts +29 -169
- package/src/packlets/spaces/spaces-service.test.ts +9 -5
- package/src/packlets/storage/storage.ts +1 -0
- package/src/packlets/system/system-service.test.ts +2 -1
- package/src/packlets/testing/test-builder.ts +3 -2
- package/src/packlets/worker/worker-runtime.ts +2 -2
- package/src/version.ts +5 -1
- package/dist/lib/browser/chunk-67FEJJ6J.mjs.map +0 -7
- package/dist/lib/node/chunk-2KIDYJ7O.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-36ZRRDQI.mjs +0 -8154
- package/dist/lib/node-esm/chunk-36ZRRDQI.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -416
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -418
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +0 -2
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +0 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +0 -2
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +0 -1
- package/dist/types/src/testing/setup.d.ts +0 -3
- package/dist/types/src/testing/setup.d.ts.map +0 -1
- package/src/packlets/spaces/edge-feed-replicator.test.ts +0 -251
- 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 {
|
|
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
|
|
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
|
|
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('
|
|
67
|
+
log.info('recv', { from: message.source, payload });
|
|
76
68
|
this._onMessage(payload);
|
|
77
69
|
}),
|
|
78
70
|
);
|
|
79
71
|
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
89
|
+
log.info('addFeed', { key: feed.key });
|
|
117
90
|
this._feeds.set(feed.key, feed);
|
|
118
91
|
|
|
119
|
-
if (this._connected
|
|
120
|
-
await this._replicateFeed(
|
|
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(
|
|
129
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
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-
|
|
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 {
|
|
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
|
|
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
|
|
22
|
+
notarizationPlugin = new NotarizationPlugin();
|
|
23
23
|
|
|
24
|
-
constructor(
|
|
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
|
-
|
|
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(
|
|
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
|
|
6
|
-
import {
|
|
7
|
-
import { type CredentialProcessor
|
|
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 {
|
|
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
|
|
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
|
-
|
|
106
|
-
if (this._edgeClient && this._writer) {
|
|
107
|
-
this._notarizePendingEdgeCredentials(this._edgeClient, this._writer);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
74
|
+
async open() {}
|
|
110
75
|
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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()).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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,
|
|
57
|
+
return new ServiceContext(storage, level, networkManager, signalManager, undefined, {
|
|
57
58
|
invitationConnectionDefaultParams: { controlHeartbeatInterval: 200 },
|
|
58
59
|
...runtimeParams,
|
|
59
60
|
});
|