@dxos/client-services 0.6.12-main.78ddbdf → 0.6.12-main.7907542
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-XSFLJVDP.mjs → chunk-6UTFANWL.mjs} +686 -304
- package/dist/lib/browser/chunk-6UTFANWL.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +2 -2
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node/{chunk-F3WGFGEN.cjs → chunk-2UUETFZ2.cjs} +776 -394
- package/dist/lib/node/chunk-2UUETFZ2.cjs.map +7 -0
- package/dist/lib/node/index.cjs +45 -45
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +8 -8
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/lib/node-esm/{chunk-3HDLTAT2.mjs → chunk-EQAI46LQ.mjs} +686 -304
- package/dist/lib/node-esm/chunk-EQAI46LQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +2 -2
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- 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.d.ts +1 -0
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +4 -2
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -0
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +5 -1
- 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 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +31 -6
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +38 -38
- package/src/packlets/identity/authenticator.ts +5 -2
- package/src/packlets/identity/contacts-service.ts +1 -1
- package/src/packlets/identity/identity.ts +4 -0
- package/src/packlets/services/service-context.ts +41 -17
- package/src/packlets/services/service-host.ts +7 -5
- package/src/packlets/spaces/data-space-manager.ts +32 -4
- package/src/packlets/spaces/data-space.ts +27 -4
- package/src/packlets/spaces/edge-feed-replicator.test.ts +22 -15
- package/src/packlets/spaces/edge-feed-replicator.ts +45 -25
- package/src/packlets/spaces/notarization-plugin.test.ts +8 -4
- package/src/packlets/spaces/notarization-plugin.ts +169 -29
- package/src/packlets/spaces/spaces-service.ts +6 -1
- package/src/packlets/testing/test-builder.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-XSFLJVDP.mjs.map +0 -7
- package/dist/lib/node/chunk-F3WGFGEN.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-3HDLTAT2.mjs.map +0 -7
|
@@ -11,7 +11,7 @@ import { EdgeConnectionClosedError, EdgeIdentityChangedError } from '@dxos/edge-
|
|
|
11
11
|
import { type FeedWrapper } from '@dxos/feed-store';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
14
|
-
import { log } from '@dxos/log';
|
|
14
|
+
import { log, logInfo } from '@dxos/log';
|
|
15
15
|
import { EdgeService } from '@dxos/protocols';
|
|
16
16
|
import { buf } from '@dxos/protocols/buf';
|
|
17
17
|
import {
|
|
@@ -28,7 +28,10 @@ export type EdgeFeedReplicatorParams = {
|
|
|
28
28
|
|
|
29
29
|
export class EdgeFeedReplicator extends Resource {
|
|
30
30
|
private readonly _messenger: EdgeConnection;
|
|
31
|
+
|
|
32
|
+
@logInfo
|
|
31
33
|
private readonly _spaceId: SpaceId;
|
|
34
|
+
|
|
32
35
|
private readonly _feeds = new ComplexMap<PublicKey, FeedWrapper<any>>(PublicKey.hash);
|
|
33
36
|
|
|
34
37
|
private _connectionCtx?: Context = undefined;
|
|
@@ -50,6 +53,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
protected override async _open(): Promise<void> {
|
|
56
|
+
log('open');
|
|
53
57
|
// TODO: handle reconnects
|
|
54
58
|
this._ctx.onDispose(
|
|
55
59
|
this._messenger.addListener((message: RouterMessage) => {
|
|
@@ -68,43 +72,40 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
const payload = decodeCbor(message.payload!.value) as ProtocolMessage;
|
|
71
|
-
log
|
|
75
|
+
log('receive', { from: message.source, feedKey: payload.feedKey, type: payload.type });
|
|
72
76
|
this._onMessage(payload);
|
|
73
77
|
}),
|
|
74
78
|
);
|
|
75
79
|
|
|
76
80
|
this._messenger.connected.on(this._ctx, async () => {
|
|
77
81
|
await this._resetConnection();
|
|
78
|
-
|
|
79
|
-
this._connected = true;
|
|
80
|
-
const connectionCtx = new Context({
|
|
81
|
-
onError: async (err: any) => {
|
|
82
|
-
if (connectionCtx !== this._connectionCtx) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (err instanceof EdgeIdentityChangedError || err instanceof EdgeConnectionClosedError) {
|
|
86
|
-
log('resetting on reconnect');
|
|
87
|
-
await this._resetConnection();
|
|
88
|
-
} else {
|
|
89
|
-
this._ctx.raise(err);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
this._connectionCtx = connectionCtx;
|
|
94
|
-
log('connection context created');
|
|
95
|
-
scheduleMicroTask(connectionCtx, async () => {
|
|
96
|
-
for (const feed of this._feeds.values()) {
|
|
97
|
-
await this._replicateFeed(connectionCtx, feed);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
82
|
+
this._startReplication();
|
|
100
83
|
});
|
|
84
|
+
|
|
85
|
+
if (this._messenger.isConnected) {
|
|
86
|
+
this._startReplication();
|
|
87
|
+
}
|
|
101
88
|
}
|
|
102
89
|
|
|
103
90
|
protected override async _close(): Promise<void> {
|
|
91
|
+
log('close');
|
|
104
92
|
await this._resetConnection();
|
|
105
93
|
}
|
|
106
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
|
+
}
|
|
106
|
+
|
|
107
107
|
private async _resetConnection() {
|
|
108
|
+
log('resetConnection');
|
|
108
109
|
this._connected = false;
|
|
109
110
|
await this._connectionCtx?.dispose();
|
|
110
111
|
this._connectionCtx = undefined;
|
|
@@ -112,7 +113,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
async addFeed(feed: FeedWrapper<any>) {
|
|
115
|
-
log.info('addFeed', { key: feed.key });
|
|
116
|
+
log.info('addFeed', { key: feed.key, connected: this._connected, hasConnectionCtx: !!this._connectionCtx });
|
|
116
117
|
this._feeds.set(feed.key, feed);
|
|
117
118
|
|
|
118
119
|
if (this._connected && this._connectionCtx) {
|
|
@@ -125,6 +126,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
private async _replicateFeed(ctx: Context, feed: FeedWrapper<any>) {
|
|
129
|
+
log('replicateFeed', { key: feed.key });
|
|
128
130
|
await this._sendMessage({
|
|
129
131
|
type: 'get-metadata',
|
|
130
132
|
feedKey: feed.key.toHex(),
|
|
@@ -148,6 +150,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
148
150
|
invariant(message.feedKey);
|
|
149
151
|
const payloadValue = bufferToArray(encodeCbor(message));
|
|
150
152
|
|
|
153
|
+
log('send', { type: message.type });
|
|
151
154
|
await this._messenger.send(
|
|
152
155
|
buf.create(RouterMessageSchema, {
|
|
153
156
|
source: {
|
|
@@ -272,6 +275,23 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
272
275
|
await this._pushBlocks(feed, remoteLength, feed.length);
|
|
273
276
|
}
|
|
274
277
|
}
|
|
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
|
+
}
|
|
275
295
|
}
|
|
276
296
|
|
|
277
297
|
// hypercore requires buffers
|
|
@@ -8,20 +8,22 @@ 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';
|
|
11
12
|
import { log } from '@dxos/log';
|
|
12
13
|
import { AdmittedFeed, type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
13
14
|
import { TestBuilder, type TestConnection, TestPeer } from '@dxos/teleport/testing';
|
|
14
15
|
|
|
15
|
-
import { NotarizationPlugin } from './notarization-plugin';
|
|
16
|
+
import { NotarizationPlugin, type NotarizationPluginParams } from './notarization-plugin';
|
|
16
17
|
|
|
17
18
|
class TestAgent extends TestPeer {
|
|
18
19
|
private readonly _ctx = new Context();
|
|
19
20
|
|
|
20
21
|
feed = new MockFeedWriter<Credential>();
|
|
21
|
-
notarizationPlugin
|
|
22
|
+
notarizationPlugin: NotarizationPlugin;
|
|
22
23
|
|
|
23
|
-
constructor() {
|
|
24
|
+
constructor(params: NotarizationPluginParams) {
|
|
24
25
|
super();
|
|
26
|
+
this.notarizationPlugin = new NotarizationPlugin(params);
|
|
25
27
|
this.feed.written.on(this._ctx, async ([credential]) => {
|
|
26
28
|
log('written to feed', { credential });
|
|
27
29
|
await this.notarizationPlugin.processCredential(credential);
|
|
@@ -50,8 +52,10 @@ describe('NotarizationPlugin', () => {
|
|
|
50
52
|
const testBuilder = new TestBuilder();
|
|
51
53
|
onTestFinished(() => testBuilder.destroy());
|
|
52
54
|
|
|
55
|
+
const params = { spaceId: SpaceId.random() };
|
|
56
|
+
|
|
53
57
|
// peer0 is there to test retries.
|
|
54
|
-
const [_peer0, peer1, peer2] = await testBuilder.createPeers({ factory: () => new TestAgent() });
|
|
58
|
+
const [_peer0, peer1, peer2] = await testBuilder.createPeers({ factory: () => new TestAgent(params) });
|
|
55
59
|
peer1.enableWriting();
|
|
56
60
|
|
|
57
61
|
peer1.feed.written.on(async ([credential]) => {
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
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';
|
|
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';
|
|
8
9
|
import { type FeedWriter } from '@dxos/feed-store';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import {
|
|
12
|
+
import { type SpaceId } from '@dxos/keys';
|
|
13
|
+
import { logInfo, log } from '@dxos/log';
|
|
14
|
+
import { EdgeCallFailedError } from '@dxos/protocols';
|
|
12
15
|
import { schema } from '@dxos/protocols/proto';
|
|
16
|
+
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
13
17
|
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
18
|
import { type NotarizationService, type NotarizeRequest } from '@dxos/protocols/proto/dxos/mesh/teleport/notarization';
|
|
15
19
|
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
@@ -21,8 +25,18 @@ const DEFAULT_SUCCESS_DELAY = 1_000;
|
|
|
21
25
|
|
|
22
26
|
const DEFAULT_NOTARIZE_TIMEOUT = 10_000;
|
|
23
27
|
|
|
28
|
+
const MAX_EDGE_RETRIES = 2;
|
|
29
|
+
|
|
24
30
|
const WRITER_NOT_SET_ERROR_CODE = 'WRITER_NOT_SET';
|
|
25
31
|
|
|
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
|
+
|
|
26
40
|
export type NotarizeParams = {
|
|
27
41
|
/**
|
|
28
42
|
* For cancellation.
|
|
@@ -53,13 +67,17 @@ export type NotarizeParams = {
|
|
|
53
67
|
* @default {@link DEFAULT_SUCCESS_DELAY}
|
|
54
68
|
*/
|
|
55
69
|
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;
|
|
56
75
|
};
|
|
57
76
|
|
|
58
77
|
/**
|
|
59
78
|
* See NotarizationService proto.
|
|
60
79
|
*/
|
|
61
|
-
export class NotarizationPlugin implements CredentialProcessor {
|
|
62
|
-
private readonly _ctx = new Context();
|
|
80
|
+
export class NotarizationPlugin extends Resource implements CredentialProcessor {
|
|
63
81
|
private readonly _extensionOpened = new Event();
|
|
64
82
|
|
|
65
83
|
private _writer: FeedWriter<Credential> | undefined;
|
|
@@ -67,13 +85,30 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
67
85
|
private readonly _processedCredentials = new ComplexSet<PublicKey>(PublicKey.hash);
|
|
68
86
|
private readonly _processCredentialsTriggers = new ComplexMap<PublicKey, Trigger>(PublicKey.hash);
|
|
69
87
|
|
|
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
|
+
|
|
70
101
|
get hasWriter() {
|
|
71
102
|
return !!this._writer;
|
|
72
103
|
}
|
|
73
104
|
|
|
74
|
-
async
|
|
105
|
+
protected override async _open() {
|
|
106
|
+
if (this._edgeClient && this._writer) {
|
|
107
|
+
this._notarizePendingEdgeCredentials(this._edgeClient, this._writer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
75
110
|
|
|
76
|
-
async
|
|
111
|
+
protected override async _close() {
|
|
77
112
|
await this._ctx.dispose();
|
|
78
113
|
}
|
|
79
114
|
|
|
@@ -86,6 +121,7 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
86
121
|
timeout = DEFAULT_NOTARIZE_TIMEOUT,
|
|
87
122
|
retryTimeout = DEFAULT_RETRY_TIMEOUT,
|
|
88
123
|
successDelay = DEFAULT_SUCCESS_DELAY,
|
|
124
|
+
edgeRetryJitter,
|
|
89
125
|
}: NotarizeParams) {
|
|
90
126
|
log('notarize', { credentials });
|
|
91
127
|
invariant(
|
|
@@ -103,24 +139,35 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
103
139
|
});
|
|
104
140
|
opCtx?.onDispose(() => ctx.dispose());
|
|
105
141
|
|
|
106
|
-
// Timeout/
|
|
107
142
|
if (timeout !== 0) {
|
|
108
|
-
|
|
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
|
-
);
|
|
143
|
+
this._scheduleTimeout(ctx, errors, timeout);
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
const allNotarized = Promise.all(credentials.map((credential) => this._waitUntilProcessed(credential.id!)));
|
|
123
147
|
|
|
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
|
+
) {
|
|
124
171
|
const peersTried = new Set<NotarizationTeleportExtension>();
|
|
125
172
|
|
|
126
173
|
// Repeatable task that tries to notarize credentials with one of the available peers.
|
|
@@ -145,6 +192,7 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
145
192
|
credentials: credentials.filter((credential) => !this._processedCredentials.has(credential.id!)),
|
|
146
193
|
});
|
|
147
194
|
log('success');
|
|
195
|
+
|
|
148
196
|
await sleep(successDelay); // wait before trying with a new peer
|
|
149
197
|
} catch (err: any) {
|
|
150
198
|
if (!ctx.disposed && !err.message.includes(WRITER_NOT_SET_ERROR_CODE)) {
|
|
@@ -156,13 +204,31 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
156
204
|
|
|
157
205
|
notarizeTask.schedule();
|
|
158
206
|
this._extensionOpened.on(ctx, () => notarizeTask.schedule());
|
|
207
|
+
}
|
|
159
208
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
});
|
|
166
232
|
}
|
|
167
233
|
|
|
168
234
|
/**
|
|
@@ -180,6 +246,44 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
180
246
|
setWriter(writer: FeedWriter<Credential>) {
|
|
181
247
|
invariant(!this._writer, 'Writer already set.');
|
|
182
248
|
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
|
+
});
|
|
183
287
|
}
|
|
184
288
|
|
|
185
289
|
private async _waitUntilProcessed(id: PublicKey) {
|
|
@@ -196,12 +300,20 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
196
300
|
if (!this._writer) {
|
|
197
301
|
throw new Error(WRITER_NOT_SET_ERROR_CODE);
|
|
198
302
|
}
|
|
199
|
-
|
|
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) {
|
|
200
308
|
invariant(credential.id, 'Credential must have an id');
|
|
201
309
|
if (this._processedCredentials.has(credential.id)) {
|
|
202
310
|
continue;
|
|
203
311
|
}
|
|
204
|
-
await
|
|
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);
|
|
205
317
|
}
|
|
206
318
|
}
|
|
207
319
|
|
|
@@ -220,8 +332,31 @@ export class NotarizationPlugin implements CredentialProcessor {
|
|
|
220
332
|
});
|
|
221
333
|
return extension;
|
|
222
334
|
}
|
|
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
|
+
}
|
|
223
350
|
}
|
|
224
351
|
|
|
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
|
+
|
|
225
360
|
export type NotarizationTeleportExtensionParams = {
|
|
226
361
|
onOpen: () => Promise<void>;
|
|
227
362
|
onClose: () => Promise<void>;
|
|
@@ -261,6 +396,11 @@ export class NotarizationTeleportExtension extends RpcExtension<Services, Servic
|
|
|
261
396
|
}
|
|
262
397
|
}
|
|
263
398
|
|
|
399
|
+
type NotarizationTimeouts = {
|
|
400
|
+
retryTimeout: number;
|
|
401
|
+
successDelay: number;
|
|
402
|
+
};
|
|
403
|
+
|
|
264
404
|
type Services = {
|
|
265
405
|
NotarizationService: NotarizationService;
|
|
266
406
|
};
|
|
@@ -60,7 +60,7 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
60
60
|
return this._serializeSpace(space);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
async updateSpace({ spaceKey, state }: UpdateSpaceRequest) {
|
|
63
|
+
async updateSpace({ spaceKey, state, edgeReplication }: UpdateSpaceRequest) {
|
|
64
64
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
65
65
|
const space = dataSpaceManager.spaces.get(spaceKey) ?? raise(new SpaceNotFoundError(spaceKey));
|
|
66
66
|
|
|
@@ -77,6 +77,10 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
77
77
|
throw new ApiError('Invalid space state');
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
if (edgeReplication !== undefined) {
|
|
82
|
+
await dataSpaceManager.setSpaceEdgeReplicationSetting(spaceKey, edgeReplication);
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
async updateMemberRole(request: UpdateMemberRoleRequest): Promise<void> {
|
|
@@ -308,6 +312,7 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
308
312
|
creator: space.inner.spaceState.creator?.key,
|
|
309
313
|
cache: space.cache,
|
|
310
314
|
metrics: space.metrics,
|
|
315
|
+
edgeReplication: space.getEdgeReplicationSetting(),
|
|
311
316
|
};
|
|
312
317
|
}
|
|
313
318
|
|
|
@@ -53,7 +53,7 @@ export const createServiceContext = async ({
|
|
|
53
53
|
const level = createTestLevel();
|
|
54
54
|
await level.open();
|
|
55
55
|
|
|
56
|
-
return new ServiceContext(storage, level, networkManager, signalManager, undefined, {
|
|
56
|
+
return new ServiceContext(storage, level, networkManager, signalManager, undefined, undefined, {
|
|
57
57
|
invitationConnectionDefaultParams: { controlHeartbeatInterval: 200 },
|
|
58
58
|
...runtimeParams,
|
|
59
59
|
});
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DXOS_VERSION = "0.6.12-main.
|
|
1
|
+
export const DXOS_VERSION = "0.6.12-main.7907542";
|