@dxos/echo-pipeline 0.7.4 → 0.7.5-main.9cb18ac
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-LZK5YFYE.mjs → chunk-KY5AZOEF.mjs} +26 -8
- package/dist/lib/browser/{chunk-LZK5YFYE.mjs.map → chunk-KY5AZOEF.mjs.map} +3 -3
- package/dist/lib/browser/index.mjs +113 -64
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-MACQJ2EP.cjs → chunk-SXCQEIFA.cjs} +29 -11
- package/dist/lib/node/{chunk-MACQJ2EP.cjs.map → chunk-SXCQEIFA.cjs.map} +3 -3
- package/dist/lib/node/index.cjs +129 -87
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +10 -10
- package/dist/lib/node-esm/{chunk-JIZPSASG.mjs → chunk-ZIPZXXS7.mjs} +26 -8
- package/dist/lib/node-esm/{chunk-JIZPSASG.mjs.map → chunk-ZIPZXXS7.mjs.map} +3 -3
- package/dist/lib/node-esm/index.mjs +113 -64
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +1 -1
- package/dist/types/src/automerge/automerge-host.d.ts +5 -1
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/collection-synchronizer.d.ts +1 -0
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +1 -0
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +3 -2
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +1 -0
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +34 -34
- package/src/automerge/automerge-host.ts +49 -14
- package/src/automerge/collection-synchronizer.ts +8 -4
- package/src/automerge/echo-network-adapter.ts +7 -0
- package/src/automerge/mesh-echo-replicator.ts +2 -2
- package/src/db-host/echo-host.ts +4 -1
- package/src/edge/echo-edge-replicator.ts +47 -19
- package/src/space/space-manager.ts +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-pipeline",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5-main.9cb18ac",
|
|
4
4
|
"description": "ECHO database.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -37,44 +37,44 @@
|
|
|
37
37
|
"crc-32": "^1.2.2",
|
|
38
38
|
"level-transcoder": "^1.0.1",
|
|
39
39
|
"lodash.isequal": "^4.5.0",
|
|
40
|
-
"@dxos/async": "0.7.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/credentials": "0.7.
|
|
45
|
-
"@dxos/crypto": "0.7.
|
|
46
|
-
"@dxos/debug": "0.7.
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/invariant": "0.7.
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/keyring": "0.7.
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/log": "0.7.
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/teleport": "0.7.
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/teleport
|
|
66
|
-
"@dxos/teleport-extension-
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/typings": "0.7.
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/util": "0.7.
|
|
40
|
+
"@dxos/async": "0.7.5-main.9cb18ac",
|
|
41
|
+
"@dxos/codec-protobuf": "0.7.5-main.9cb18ac",
|
|
42
|
+
"@dxos/automerge": "0.7.5-main.9cb18ac",
|
|
43
|
+
"@dxos/context": "0.7.5-main.9cb18ac",
|
|
44
|
+
"@dxos/credentials": "0.7.5-main.9cb18ac",
|
|
45
|
+
"@dxos/crypto": "0.7.5-main.9cb18ac",
|
|
46
|
+
"@dxos/debug": "0.7.5-main.9cb18ac",
|
|
47
|
+
"@dxos/echo-protocol": "0.7.5-main.9cb18ac",
|
|
48
|
+
"@dxos/echo-schema": "0.7.5-main.9cb18ac",
|
|
49
|
+
"@dxos/edge-client": "0.7.5-main.9cb18ac",
|
|
50
|
+
"@dxos/feed-store": "0.7.5-main.9cb18ac",
|
|
51
|
+
"@dxos/indexing": "0.7.5-main.9cb18ac",
|
|
52
|
+
"@dxos/invariant": "0.7.5-main.9cb18ac",
|
|
53
|
+
"@dxos/hypercore": "0.7.5-main.9cb18ac",
|
|
54
|
+
"@dxos/keyring": "0.7.5-main.9cb18ac",
|
|
55
|
+
"@dxos/keys": "0.7.5-main.9cb18ac",
|
|
56
|
+
"@dxos/log": "0.7.5-main.9cb18ac",
|
|
57
|
+
"@dxos/messaging": "0.7.5-main.9cb18ac",
|
|
58
|
+
"@dxos/node-std": "0.7.5-main.9cb18ac",
|
|
59
|
+
"@dxos/network-manager": "0.7.5-main.9cb18ac",
|
|
60
|
+
"@dxos/kv-store": "0.7.5-main.9cb18ac",
|
|
61
|
+
"@dxos/protocols": "0.7.5-main.9cb18ac",
|
|
62
|
+
"@dxos/teleport-extension-automerge-replicator": "0.7.5-main.9cb18ac",
|
|
63
|
+
"@dxos/teleport-extension-gossip": "0.7.5-main.9cb18ac",
|
|
64
|
+
"@dxos/random-access-storage": "0.7.5-main.9cb18ac",
|
|
65
|
+
"@dxos/teleport": "0.7.5-main.9cb18ac",
|
|
66
|
+
"@dxos/teleport-extension-object-sync": "0.7.5-main.9cb18ac",
|
|
67
|
+
"@dxos/teleport-extension-replicator": "0.7.5-main.9cb18ac",
|
|
68
|
+
"@dxos/tracing": "0.7.5-main.9cb18ac",
|
|
69
|
+
"@dxos/typings": "0.7.5-main.9cb18ac",
|
|
70
|
+
"@dxos/timeframe": "0.7.5-main.9cb18ac",
|
|
71
|
+
"@dxos/util": "0.7.5-main.9cb18ac"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/lodash.isequal": "^4.5.0",
|
|
75
75
|
"fast-check": "^3.19.0",
|
|
76
76
|
"get-port-please": "^3.1.1",
|
|
77
|
-
"@dxos/test-utils": "0.7.
|
|
77
|
+
"@dxos/test-utils": "0.7.5-main.9cb18ac"
|
|
78
78
|
},
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
|
@@ -44,6 +44,8 @@ import { LevelDBStorageAdapter, type BeforeSaveParams } from './leveldb-storage-
|
|
|
44
44
|
|
|
45
45
|
export type PeerIdProvider = () => string | undefined;
|
|
46
46
|
|
|
47
|
+
export type RootDocumentSpaceKeyProvider = (documentId: string) => PublicKey | undefined;
|
|
48
|
+
|
|
47
49
|
export type AutomergeHostParams = {
|
|
48
50
|
db: LevelDB;
|
|
49
51
|
|
|
@@ -54,6 +56,7 @@ export type AutomergeHostParams = {
|
|
|
54
56
|
* Used for creating stable ids. A random key is generated on open, if no value is provided.
|
|
55
57
|
*/
|
|
56
58
|
peerIdProvider?: PeerIdProvider;
|
|
59
|
+
getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export type LoadDocOptions = {
|
|
@@ -90,10 +93,17 @@ export class AutomergeHost extends Resource {
|
|
|
90
93
|
private _peerId!: PeerId;
|
|
91
94
|
|
|
92
95
|
private readonly _peerIdProvider?: PeerIdProvider;
|
|
96
|
+
private readonly _getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
|
|
93
97
|
|
|
94
98
|
public readonly collectionStateUpdated = new Event<{ collectionId: CollectionId }>();
|
|
95
99
|
|
|
96
|
-
constructor({
|
|
100
|
+
constructor({
|
|
101
|
+
db,
|
|
102
|
+
indexMetadataStore,
|
|
103
|
+
dataMonitor,
|
|
104
|
+
peerIdProvider,
|
|
105
|
+
getSpaceKeyByRootDocumentId,
|
|
106
|
+
}: AutomergeHostParams) {
|
|
97
107
|
super();
|
|
98
108
|
this._db = db;
|
|
99
109
|
this._storage = new LevelDBStorageAdapter({
|
|
@@ -114,6 +124,7 @@ export class AutomergeHost extends Resource {
|
|
|
114
124
|
this._headsStore = new HeadsStore({ db: db.sublevel('heads') });
|
|
115
125
|
this._indexMetadataStore = indexMetadataStore;
|
|
116
126
|
this._peerIdProvider = peerIdProvider;
|
|
127
|
+
this._getSpaceKeyByRootDocumentId = getSpaceKeyByRootDocumentId;
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
protected override async _open() {
|
|
@@ -132,14 +143,28 @@ export class AutomergeHost extends Resource {
|
|
|
132
143
|
],
|
|
133
144
|
});
|
|
134
145
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.
|
|
146
|
+
let updatingAuthScope = false;
|
|
147
|
+
Event.wrap(this._echoNetworkAdapter, 'peer-candidate').on(
|
|
148
|
+
this._ctx,
|
|
149
|
+
((e: PeerCandidatePayload) => !updatingAuthScope && this._onPeerConnected(e.peerId)) as any,
|
|
150
|
+
);
|
|
151
|
+
Event.wrap(this._echoNetworkAdapter, 'peer-disconnected').on(
|
|
152
|
+
this._ctx,
|
|
153
|
+
((e: PeerDisconnectedPayload) => !updatingAuthScope && this._onPeerDisconnected(e.peerId)) as any,
|
|
154
|
+
);
|
|
139
155
|
|
|
140
|
-
this._collectionSynchronizer.remoteStateUpdated.on(this._ctx, ({ collectionId, peerId }) => {
|
|
156
|
+
this._collectionSynchronizer.remoteStateUpdated.on(this._ctx, ({ collectionId, peerId, newDocsAppeared }) => {
|
|
141
157
|
this._onRemoteCollectionStateUpdated(collectionId, peerId);
|
|
142
158
|
this.collectionStateUpdated.emit({ collectionId: collectionId as CollectionId });
|
|
159
|
+
// We use collection lookups during share policy check, so we might need to update share policy for the new doc
|
|
160
|
+
if (newDocsAppeared) {
|
|
161
|
+
updatingAuthScope = true;
|
|
162
|
+
try {
|
|
163
|
+
this._echoNetworkAdapter.onConnectionAuthScopeChanged(peerId);
|
|
164
|
+
} finally {
|
|
165
|
+
updatingAuthScope = false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
143
168
|
});
|
|
144
169
|
|
|
145
170
|
await this._echoNetworkAdapter.open();
|
|
@@ -351,16 +376,23 @@ export class AutomergeHost extends Resource {
|
|
|
351
376
|
|
|
352
377
|
private async _getContainingSpaceForDocument(documentId: string): Promise<PublicKey | null> {
|
|
353
378
|
const doc = this._repo.handles[documentId as any]?.docSync();
|
|
354
|
-
if (
|
|
355
|
-
|
|
379
|
+
if (doc) {
|
|
380
|
+
const spaceKeyHex = getSpaceKeyFromDoc(doc);
|
|
381
|
+
if (spaceKeyHex) {
|
|
382
|
+
return PublicKey.from(spaceKeyHex);
|
|
383
|
+
}
|
|
356
384
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Edge case on the initial space setup.
|
|
387
|
+
* A peer is maybe trying to share space root document with us after a successful invitation.
|
|
388
|
+
* We don't have a document to check access block locally, so we need to rely on external sources (space metada).
|
|
389
|
+
*/
|
|
390
|
+
const rootDocSpaceKey = this._getSpaceKeyByRootDocumentId?.(documentId);
|
|
391
|
+
if (rootDocSpaceKey) {
|
|
392
|
+
return rootDocSpaceKey;
|
|
361
393
|
}
|
|
362
394
|
|
|
363
|
-
return
|
|
395
|
+
return null;
|
|
364
396
|
}
|
|
365
397
|
|
|
366
398
|
/**
|
|
@@ -496,7 +528,10 @@ export class AutomergeHost extends Resource {
|
|
|
496
528
|
return;
|
|
497
529
|
}
|
|
498
530
|
|
|
499
|
-
log.info('
|
|
531
|
+
log.info('replicating documents after collection sync', {
|
|
532
|
+
collectionId,
|
|
533
|
+
peerId,
|
|
534
|
+
toReplicate,
|
|
500
535
|
count: toReplicate.length,
|
|
501
536
|
});
|
|
502
537
|
|
|
@@ -35,7 +35,7 @@ export class CollectionSynchronizer extends Resource {
|
|
|
35
35
|
|
|
36
36
|
private readonly _connectedPeers = new Set<PeerId>();
|
|
37
37
|
|
|
38
|
-
public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId }>();
|
|
38
|
+
public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId; newDocsAppeared: boolean }>();
|
|
39
39
|
|
|
40
40
|
constructor(params: CollectionSynchronizerParams) {
|
|
41
41
|
super();
|
|
@@ -121,7 +121,7 @@ export class CollectionSynchronizer extends Resource {
|
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
for (const [collectionId, state] of this._perCollectionStates.entries()) {
|
|
124
|
-
if (this._shouldSyncCollection(collectionId, peerId)) {
|
|
124
|
+
if (this._activeCollections.has(collectionId) && this._shouldSyncCollection(collectionId, peerId)) {
|
|
125
125
|
state.interestedPeers.add(peerId);
|
|
126
126
|
state.lastQueried.set(peerId, Date.now());
|
|
127
127
|
this._queryCollectionState(collectionId, peerId);
|
|
@@ -159,8 +159,12 @@ export class CollectionSynchronizer extends Resource {
|
|
|
159
159
|
log('onRemoteStateReceived', { collectionId, peerId, state });
|
|
160
160
|
validateCollectionState(state);
|
|
161
161
|
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
162
|
-
perCollectionState.remoteStates.
|
|
163
|
-
|
|
162
|
+
const existingState = perCollectionState.remoteStates.get(peerId) ?? { documents: {} };
|
|
163
|
+
const diff = diffCollectionState(existingState, state);
|
|
164
|
+
if (diff.missingOnLocal.length > 0 || diff.different.length > 0) {
|
|
165
|
+
perCollectionState.remoteStates.set(peerId, state);
|
|
166
|
+
this.remoteStateUpdated.emit({ peerId, collectionId, newDocsAppeared: diff.missingOnLocal.length > 0 });
|
|
167
|
+
}
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
private _getOrCreatePerCollectionState(collectionId: string): PerCollectionState {
|
|
@@ -246,6 +246,13 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
246
246
|
this._params.monitor?.recordMessageReceived(message);
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
public onConnectionAuthScopeChanged(peer: PeerId) {
|
|
250
|
+
const entry = this._connections.get(peer);
|
|
251
|
+
if (entry) {
|
|
252
|
+
this._onConnectionAuthScopeChanged(entry.connection);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
249
256
|
/**
|
|
250
257
|
* Trigger doc-synchronizer shared documents set recalculation. Happens on peer-candidate.
|
|
251
258
|
* TODO(y): replace with a proper API call when sharePolicy update becomes supported by automerge-repo
|
|
@@ -88,10 +88,10 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
88
88
|
documentId: params.documentId,
|
|
89
89
|
peerId: connection.peerId,
|
|
90
90
|
});
|
|
91
|
-
log('document not found locally for share policy check
|
|
91
|
+
log('document not found locally for share policy check', {
|
|
92
92
|
peerId: connection.peerId,
|
|
93
93
|
documentId: params.documentId,
|
|
94
|
-
remoteDocumentExists,
|
|
94
|
+
acceptDocument: remoteDocumentExists,
|
|
95
95
|
});
|
|
96
96
|
// If a document is not present locally return true if another peer claims to have it.
|
|
97
97
|
// Simply returning true will add the peer to "generous peers list" for this document which will
|
package/src/db-host/echo-host.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type EchoReplicator,
|
|
34
34
|
type EchoDataStats,
|
|
35
35
|
type PeerIdProvider,
|
|
36
|
+
type RootDocumentSpaceKeyProvider,
|
|
36
37
|
} from '../automerge';
|
|
37
38
|
|
|
38
39
|
const INDEXER_CONFIG: IndexConfig = {
|
|
@@ -43,6 +44,7 @@ const INDEXER_CONFIG: IndexConfig = {
|
|
|
43
44
|
export type EchoHostParams = {
|
|
44
45
|
kv: LevelDB;
|
|
45
46
|
peerIdProvider?: PeerIdProvider;
|
|
47
|
+
getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
/**
|
|
@@ -60,7 +62,7 @@ export class EchoHost extends Resource {
|
|
|
60
62
|
private readonly _spaceStateManager = new SpaceStateManager();
|
|
61
63
|
private readonly _echoDataMonitor: EchoDataMonitor;
|
|
62
64
|
|
|
63
|
-
constructor({ kv, peerIdProvider }: EchoHostParams) {
|
|
65
|
+
constructor({ kv, peerIdProvider, getSpaceKeyByRootDocumentId }: EchoHostParams) {
|
|
64
66
|
super();
|
|
65
67
|
|
|
66
68
|
this._indexMetadataStore = new IndexMetadataStore({ db: kv.sublevel('index-metadata') });
|
|
@@ -72,6 +74,7 @@ export class EchoHost extends Resource {
|
|
|
72
74
|
dataMonitor: this._echoDataMonitor,
|
|
73
75
|
indexMetadataStore: this._indexMetadataStore,
|
|
74
76
|
peerIdProvider,
|
|
77
|
+
getSpaceKeyByRootDocumentId,
|
|
75
78
|
});
|
|
76
79
|
|
|
77
80
|
this._indexer = new Indexer({
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Mutex, scheduleTask, scheduleMicroTask } from '@dxos/async';
|
|
6
|
-
import * as A from '@dxos/automerge/automerge';
|
|
5
|
+
import { Mutex, scheduleTask, scheduleMicroTask, Trigger } from '@dxos/async';
|
|
7
6
|
import { cbor } from '@dxos/automerge/automerge-repo';
|
|
8
7
|
import { Context, Resource } from '@dxos/context';
|
|
9
8
|
import { randomUUID } from '@dxos/crypto';
|
|
@@ -184,9 +183,11 @@ type EdgeReplicatorConnectionsParams = {
|
|
|
184
183
|
onRestartRequested: () => Promise<void>;
|
|
185
184
|
};
|
|
186
185
|
|
|
186
|
+
const MAX_INFLIGHT_REQUESTS = 5;
|
|
187
|
+
|
|
187
188
|
class EdgeReplicatorConnection extends Resource implements ReplicatorConnection {
|
|
188
189
|
private readonly _edgeConnection: EdgeConnection;
|
|
189
|
-
private _remotePeerId: string | null = null;
|
|
190
|
+
private readonly _remotePeerId: string | null = null;
|
|
190
191
|
private readonly _targetServiceId: string;
|
|
191
192
|
private readonly _spaceId: SpaceId;
|
|
192
193
|
private readonly _context: EchoReplicatorContext;
|
|
@@ -195,6 +196,16 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
195
196
|
private readonly _onRemoteDisconnected: () => Promise<void>;
|
|
196
197
|
private readonly _onRestartRequested: () => void;
|
|
197
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Prevents sending too many messages to edge over this connection so that we don't overwhelm
|
|
201
|
+
* a replicator durable object.
|
|
202
|
+
* inflightRequests counter is incremented on outgoing sync messages and decremented on incoming messages.
|
|
203
|
+
* The trigger is waiting while the counter is above MAX_INFLIGHT_REQUESTS.
|
|
204
|
+
* The counter can go negative because we receive edge-initiated sync messages on doc change broadcasts.
|
|
205
|
+
*/
|
|
206
|
+
private _outgoingRequestsBarrier = new Trigger();
|
|
207
|
+
private _inflightRequests = 0;
|
|
208
|
+
|
|
198
209
|
private _readableStreamController!: ReadableStreamDefaultController<AutomergeProtocolMessage>;
|
|
199
210
|
|
|
200
211
|
public readable: ReadableStream<AutomergeProtocolMessage>;
|
|
@@ -213,7 +224,6 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
213
224
|
this._edgeConnection = edgeConnection;
|
|
214
225
|
this._spaceId = spaceId;
|
|
215
226
|
this._context = context;
|
|
216
|
-
|
|
217
227
|
// Generate a unique peer id for every connection.
|
|
218
228
|
// This way automerge-repo will have separate sync states for every connection.
|
|
219
229
|
// This is important because the previous connection might have had some messages that failed to deliver
|
|
@@ -225,6 +235,8 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
225
235
|
this._onRemoteDisconnected = onRemoteDisconnected;
|
|
226
236
|
this._onRestartRequested = onRestartRequested;
|
|
227
237
|
|
|
238
|
+
this._outgoingRequestsBarrier.wake();
|
|
239
|
+
|
|
228
240
|
this.readable = new ReadableStream<AutomergeProtocolMessage>({
|
|
229
241
|
start: (controller) => {
|
|
230
242
|
this._readableStreamController = controller;
|
|
@@ -233,6 +245,13 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
233
245
|
|
|
234
246
|
this.writable = new WritableStream<AutomergeProtocolMessage>({
|
|
235
247
|
write: async (message: AutomergeProtocolMessage, controller) => {
|
|
248
|
+
await this._outgoingRequestsBarrier.wait();
|
|
249
|
+
|
|
250
|
+
this._inflightRequests++;
|
|
251
|
+
if (this._inflightRequests === MAX_INFLIGHT_REQUESTS) {
|
|
252
|
+
this._outgoingRequestsBarrier.reset();
|
|
253
|
+
}
|
|
254
|
+
|
|
236
255
|
await this._sendMessage(message);
|
|
237
256
|
},
|
|
238
257
|
});
|
|
@@ -253,6 +272,9 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
253
272
|
protected override async _close(): Promise<void> {
|
|
254
273
|
log('close');
|
|
255
274
|
this._readableStreamController.close();
|
|
275
|
+
|
|
276
|
+
this._outgoingRequestsBarrier.throw(new Error('Connection closed.'));
|
|
277
|
+
|
|
256
278
|
await this._onRemoteDisconnected();
|
|
257
279
|
}
|
|
258
280
|
|
|
@@ -272,9 +294,10 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
272
294
|
peerId: this._remotePeerId as PeerId,
|
|
273
295
|
});
|
|
274
296
|
|
|
275
|
-
log.
|
|
297
|
+
log.verbose('edge-replicator document not found locally for share policy check', {
|
|
276
298
|
documentId: params.documentId,
|
|
277
|
-
remoteDocumentExists,
|
|
299
|
+
acceptDocument: remoteDocumentExists,
|
|
300
|
+
remoteId: this._remotePeerId,
|
|
278
301
|
});
|
|
279
302
|
|
|
280
303
|
// If a document is not present locally return true only if it already exists on edge.
|
|
@@ -290,7 +313,8 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
290
313
|
return true;
|
|
291
314
|
}
|
|
292
315
|
const spaceId = getSpaceIdFromCollectionId(params.collectionId as CollectionId);
|
|
293
|
-
|
|
316
|
+
// Only sync collections of form space:id:rootDoc, edge ignores legacy space:id collections
|
|
317
|
+
return spaceId === this._spaceId && params.collectionId.split(':').length === 3;
|
|
294
318
|
}
|
|
295
319
|
|
|
296
320
|
private _onMessage(message: RouterMessage) {
|
|
@@ -299,15 +323,12 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
299
323
|
}
|
|
300
324
|
|
|
301
325
|
const payload = cbor.decode(message.payload!.value) as AutomergeProtocolMessage;
|
|
302
|
-
log('
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
: payload.type === 'collection-state'
|
|
307
|
-
? (payload as any).state
|
|
308
|
-
: payload;
|
|
309
|
-
return { from: message.serviceId, type: payload.type, decodedData };
|
|
326
|
+
log.verbose('edge replicator receive', {
|
|
327
|
+
type: payload.type,
|
|
328
|
+
documentId: payload.type === 'sync' && payload.documentId,
|
|
329
|
+
remoteId: this._remotePeerId,
|
|
310
330
|
});
|
|
331
|
+
|
|
311
332
|
// Fix the peer id.
|
|
312
333
|
payload.senderId = this._remotePeerId! as PeerId;
|
|
313
334
|
this._processMessage(payload);
|
|
@@ -322,6 +343,13 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
322
343
|
return;
|
|
323
344
|
}
|
|
324
345
|
|
|
346
|
+
if (message.type === 'sync') {
|
|
347
|
+
this._inflightRequests--;
|
|
348
|
+
if (this._inflightRequests === MAX_INFLIGHT_REQUESTS - 1) {
|
|
349
|
+
this._outgoingRequestsBarrier.wake();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
325
353
|
this._readableStreamController.enqueue(message);
|
|
326
354
|
}
|
|
327
355
|
|
|
@@ -329,12 +357,12 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
329
357
|
// Fix the peer id.
|
|
330
358
|
(message as any).targetId = this._targetServiceId as PeerId;
|
|
331
359
|
|
|
332
|
-
log('send', {
|
|
360
|
+
log.verbose('edge replicator send', {
|
|
333
361
|
type: message.type,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
documentId: (message as any).documentId,
|
|
362
|
+
documentId: message.type === 'sync' && message.documentId,
|
|
363
|
+
remoteId: this._remotePeerId,
|
|
337
364
|
});
|
|
365
|
+
|
|
338
366
|
const encoded = cbor.encode(message);
|
|
339
367
|
|
|
340
368
|
await this._edgeConnection.send(
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { synchronized, trackLeaks, Trigger } from '@dxos/async';
|
|
6
|
-
import { type
|
|
6
|
+
import { type AutomergeUrl, parseAutomergeUrl } from '@dxos/automerge/automerge-repo';
|
|
7
|
+
import { getCredentialAssertion, type DelegateInvitationCredential, type MemberInfo } from '@dxos/credentials';
|
|
7
8
|
import { failUndefined } from '@dxos/debug';
|
|
8
9
|
import { type FeedStore } from '@dxos/feed-store';
|
|
9
10
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -168,4 +169,19 @@ export class SpaceManager {
|
|
|
168
169
|
await protocol.stop();
|
|
169
170
|
}
|
|
170
171
|
}
|
|
172
|
+
|
|
173
|
+
public findSpaceByRootDocumentId(documentId: string): Space | undefined {
|
|
174
|
+
return [...this._spaces.values()].find((space) => {
|
|
175
|
+
return space.spaceState.credentials.some((credential) => {
|
|
176
|
+
const assertion = getCredentialAssertion(credential);
|
|
177
|
+
if (assertion['@type'] !== 'dxos.halo.credentials.Epoch') {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (!assertion?.automergeRoot) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return parseAutomergeUrl(assertion.automergeRoot as AutomergeUrl).documentId === documentId;
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
171
187
|
}
|