@dxos/echo-pipeline 0.4.8-main.c67d72c → 0.4.8-main.cd60bd2
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-XR2636AC.mjs → chunk-WAN2XUWE.mjs} +38 -700
- package/dist/lib/browser/chunk-WAN2XUWE.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +633 -6
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +2 -274
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/{chunk-LD4R726W.cjs → chunk-U6J2HC4T.cjs} +39 -691
- package/dist/lib/node/chunk-U6J2HC4T.cjs.map +7 -0
- package/dist/lib/node/index.cjs +647 -30
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +12 -282
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/data-pipeline.d.ts +0 -1
- package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +0 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +2 -6
- package/dist/types/src/testing/util.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/automerge/automerge-host.ts +2 -5
- package/src/space/control-pipeline.ts +3 -1
- package/src/space/data-pipeline.ts +1 -44
- package/src/testing/index.ts +0 -1
- package/src/testing/util.ts +2 -26
- package/dist/lib/browser/chunk-XR2636AC.mjs.map +0 -7
- package/dist/lib/node/chunk-LD4R726W.cjs.map +0 -7
- package/dist/types/src/testing/database-test-rig.d.ts +0 -67
- package/dist/types/src/testing/database-test-rig.d.ts.map +0 -1
- package/dist/types/src/tests/database.test.d.ts +0 -2
- package/dist/types/src/tests/database.test.d.ts.map +0 -1
- package/src/testing/database-test-rig.ts +0 -289
- package/src/tests/database.test.ts +0 -100
|
@@ -2,17 +2,13 @@ import "@dxos/node-std/globals";
|
|
|
2
2
|
import {
|
|
3
3
|
AuthExtension,
|
|
4
4
|
AuthStatus,
|
|
5
|
-
AutomergeHost,
|
|
6
|
-
AutomergeStorageAdapter,
|
|
7
5
|
DataPipeline,
|
|
8
6
|
DataServiceHost,
|
|
9
7
|
DataServiceImpl,
|
|
10
8
|
DataServiceSubscriptions,
|
|
11
9
|
DatabaseHost,
|
|
12
|
-
LocalHostNetworkAdapter,
|
|
13
10
|
MOCK_AUTH_PROVIDER,
|
|
14
11
|
MOCK_AUTH_VERIFIER,
|
|
15
|
-
MeshNetworkAdapter,
|
|
16
12
|
MetadataStore,
|
|
17
13
|
Pipeline,
|
|
18
14
|
SnapshotManager,
|
|
@@ -24,12 +20,643 @@ import {
|
|
|
24
20
|
TimeframeClock,
|
|
25
21
|
codec,
|
|
26
22
|
createMappedFeedWriter,
|
|
27
|
-
getSpaceKeyFromDoc,
|
|
28
23
|
mapFeedIndexesToTimeframe,
|
|
29
24
|
mapTimeframeToFeedIndexes,
|
|
30
25
|
startAfter,
|
|
31
26
|
valueEncoding
|
|
32
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-WAN2XUWE.mjs";
|
|
28
|
+
|
|
29
|
+
// packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
|
|
30
|
+
import { next as automerge, getHeads } from "@dxos/automerge/automerge";
|
|
31
|
+
import { Repo } from "@dxos/automerge/automerge-repo";
|
|
32
|
+
import { IndexedDBStorageAdapter } from "@dxos/automerge/automerge-repo-storage-indexeddb";
|
|
33
|
+
import { Context } from "@dxos/context";
|
|
34
|
+
import { PublicKey } from "@dxos/keys";
|
|
35
|
+
import { log as log3 } from "@dxos/log";
|
|
36
|
+
import { idCodec } from "@dxos/protocols";
|
|
37
|
+
import { StorageType } from "@dxos/random-access-storage";
|
|
38
|
+
import { trace } from "@dxos/tracing";
|
|
39
|
+
import { ComplexMap, ComplexSet, defaultMap, mapValues } from "@dxos/util";
|
|
40
|
+
|
|
41
|
+
// packages/core/echo/echo-pipeline/src/automerge/automerge-storage-adapter.ts
|
|
42
|
+
import { arrayToBuffer, bufferToArray } from "@dxos/util";
|
|
43
|
+
var AutomergeStorageAdapter = class {
|
|
44
|
+
constructor(_directory) {
|
|
45
|
+
this._directory = _directory;
|
|
46
|
+
this._state = "opened";
|
|
47
|
+
}
|
|
48
|
+
async load(key) {
|
|
49
|
+
if (this._state !== "opened") {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
const filename = this._getFilename(key);
|
|
53
|
+
const file = this._directory.getOrCreateFile(filename);
|
|
54
|
+
const { size } = await file.stat();
|
|
55
|
+
if (!size || size === 0) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const buffer = await file.read(0, size);
|
|
59
|
+
return bufferToArray(buffer);
|
|
60
|
+
}
|
|
61
|
+
async save(key, data) {
|
|
62
|
+
if (this._state !== "opened") {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
const filename = this._getFilename(key);
|
|
66
|
+
const file = this._directory.getOrCreateFile(filename);
|
|
67
|
+
await file.write(0, arrayToBuffer(data));
|
|
68
|
+
await file.truncate?.(data.length);
|
|
69
|
+
await file.flush?.();
|
|
70
|
+
}
|
|
71
|
+
async remove(key) {
|
|
72
|
+
if (this._state !== "opened") {
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
const filename = this._getFilename(key);
|
|
76
|
+
const file = this._directory.getOrCreateFile(filename);
|
|
77
|
+
await file.destroy();
|
|
78
|
+
}
|
|
79
|
+
async loadRange(keyPrefix) {
|
|
80
|
+
if (this._state !== "opened") {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const filename = this._getFilename(keyPrefix);
|
|
84
|
+
const entries = await this._directory.list();
|
|
85
|
+
return Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
|
|
86
|
+
const file = this._directory.getOrCreateFile(entry);
|
|
87
|
+
const { size } = await file.stat();
|
|
88
|
+
const buffer = await file.read(0, size);
|
|
89
|
+
return {
|
|
90
|
+
key: this._getKeyFromFilename(entry),
|
|
91
|
+
data: bufferToArray(buffer)
|
|
92
|
+
};
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
async removeRange(keyPrefix) {
|
|
96
|
+
if (this._state !== "opened") {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
const filename = this._getFilename(keyPrefix);
|
|
100
|
+
const entries = await this._directory.list();
|
|
101
|
+
await Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
|
|
102
|
+
const file = this._directory.getOrCreateFile(entry);
|
|
103
|
+
await file.destroy();
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
async close() {
|
|
107
|
+
this._state = "closed";
|
|
108
|
+
}
|
|
109
|
+
_getFilename(key) {
|
|
110
|
+
return key.map((k) => k.replaceAll("%", "%25").replaceAll("-", "%2D")).join("-");
|
|
111
|
+
}
|
|
112
|
+
_getKeyFromFilename(filename) {
|
|
113
|
+
return filename.split("-").map((k) => k.replaceAll("%2D", "-").replaceAll("%25", "%"));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// packages/core/echo/echo-pipeline/src/automerge/automerge-storage–wrapper.ts
|
|
118
|
+
var AutomergeStorageWrapper = class {
|
|
119
|
+
constructor({ storage, callbacks }) {
|
|
120
|
+
this._storage = storage;
|
|
121
|
+
this._callbacks = callbacks;
|
|
122
|
+
}
|
|
123
|
+
async load(key) {
|
|
124
|
+
return this._storage.load(key);
|
|
125
|
+
}
|
|
126
|
+
async save(key, value) {
|
|
127
|
+
await this._callbacks.beforeSave?.(key);
|
|
128
|
+
await this._storage.save(key, value);
|
|
129
|
+
await this._callbacks.afterSave?.(key);
|
|
130
|
+
}
|
|
131
|
+
async remove(key) {
|
|
132
|
+
return this._storage.remove(key);
|
|
133
|
+
}
|
|
134
|
+
async loadRange(keyPrefix) {
|
|
135
|
+
return this._storage.loadRange(keyPrefix);
|
|
136
|
+
}
|
|
137
|
+
async removeRange(keyPrefix) {
|
|
138
|
+
return this._storage.removeRange(keyPrefix);
|
|
139
|
+
}
|
|
140
|
+
async close() {
|
|
141
|
+
if (this._storage instanceof AutomergeStorageAdapter) {
|
|
142
|
+
return this._storage.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// packages/core/echo/echo-pipeline/src/automerge/local-host-network-adapter.ts
|
|
148
|
+
import { Trigger } from "@dxos/async";
|
|
149
|
+
import { NetworkAdapter, cbor } from "@dxos/automerge/automerge-repo";
|
|
150
|
+
import { Stream } from "@dxos/codec-protobuf";
|
|
151
|
+
import { invariant } from "@dxos/invariant";
|
|
152
|
+
import { log } from "@dxos/log";
|
|
153
|
+
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/local-host-network-adapter.ts";
|
|
154
|
+
var LocalHostNetworkAdapter = class extends NetworkAdapter {
|
|
155
|
+
constructor() {
|
|
156
|
+
super(...arguments);
|
|
157
|
+
this._peers = /* @__PURE__ */ new Map();
|
|
158
|
+
this._connected = new Trigger();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Emits `ready` event. That signals to `Repo` that it can start using the adapter.
|
|
162
|
+
*/
|
|
163
|
+
ready() {
|
|
164
|
+
this.emit("ready", {
|
|
165
|
+
network: this
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
connect(peerId) {
|
|
169
|
+
this.peerId = peerId;
|
|
170
|
+
this._connected.wake();
|
|
171
|
+
}
|
|
172
|
+
send(message) {
|
|
173
|
+
const peer = this._peers.get(message.targetId);
|
|
174
|
+
invariant(peer, "Peer not found.", {
|
|
175
|
+
F: __dxlog_file,
|
|
176
|
+
L: 45,
|
|
177
|
+
S: this,
|
|
178
|
+
A: [
|
|
179
|
+
"peer",
|
|
180
|
+
"'Peer not found.'"
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
peer.send(message);
|
|
184
|
+
}
|
|
185
|
+
async close() {
|
|
186
|
+
this._peers.forEach((peer) => peer.disconnect());
|
|
187
|
+
this.emit("close");
|
|
188
|
+
}
|
|
189
|
+
disconnect() {
|
|
190
|
+
}
|
|
191
|
+
syncRepo({ id, syncMessage }) {
|
|
192
|
+
const peerId = this._getPeerId(id);
|
|
193
|
+
return new Stream(({ next, close }) => {
|
|
194
|
+
invariant(!this._peers.has(peerId), "Peer already connected.", {
|
|
195
|
+
F: __dxlog_file,
|
|
196
|
+
L: 63,
|
|
197
|
+
S: this,
|
|
198
|
+
A: [
|
|
199
|
+
"!this._peers.has(peerId)",
|
|
200
|
+
"'Peer already connected.'"
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
this._peers.set(peerId, {
|
|
204
|
+
connected: true,
|
|
205
|
+
send: (message) => {
|
|
206
|
+
next({
|
|
207
|
+
syncMessage: cbor.encode(message)
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
disconnect: () => {
|
|
211
|
+
this._peers.delete(peerId);
|
|
212
|
+
close();
|
|
213
|
+
this.emit("peer-disconnected", {
|
|
214
|
+
peerId
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
this._connected.wait({
|
|
219
|
+
timeout: 1e3
|
|
220
|
+
}).then(() => {
|
|
221
|
+
this.emit("peer-candidate", {
|
|
222
|
+
peerMetadata: {},
|
|
223
|
+
peerId
|
|
224
|
+
});
|
|
225
|
+
}).catch((err) => log.catch(err, void 0, {
|
|
226
|
+
F: __dxlog_file,
|
|
227
|
+
L: 88,
|
|
228
|
+
S: this,
|
|
229
|
+
C: (f, a) => f(...a)
|
|
230
|
+
}));
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async sendSyncMessage({ id, syncMessage }) {
|
|
234
|
+
await this._connected.wait({
|
|
235
|
+
timeout: 1e3
|
|
236
|
+
});
|
|
237
|
+
const message = cbor.decode(syncMessage);
|
|
238
|
+
this.emit("message", message);
|
|
239
|
+
}
|
|
240
|
+
async getHostInfo() {
|
|
241
|
+
await this._connected.wait({
|
|
242
|
+
timeout: 1e3
|
|
243
|
+
});
|
|
244
|
+
invariant(this.peerId, "Peer id not set.", {
|
|
245
|
+
F: __dxlog_file,
|
|
246
|
+
L: 100,
|
|
247
|
+
S: this,
|
|
248
|
+
A: [
|
|
249
|
+
"this.peerId",
|
|
250
|
+
"'Peer id not set.'"
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
peerId: this.peerId
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
_getPeerId(id) {
|
|
258
|
+
return id;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// packages/core/echo/echo-pipeline/src/automerge/mesh-network-adapter.ts
|
|
263
|
+
import { Trigger as Trigger2 } from "@dxos/async";
|
|
264
|
+
import { NetworkAdapter as NetworkAdapter2, cbor as cbor2 } from "@dxos/automerge/automerge-repo";
|
|
265
|
+
import { invariant as invariant2 } from "@dxos/invariant";
|
|
266
|
+
import { log as log2 } from "@dxos/log";
|
|
267
|
+
import { AutomergeReplicator } from "@dxos/teleport-extension-automerge-replicator";
|
|
268
|
+
var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/mesh-network-adapter.ts";
|
|
269
|
+
var MeshNetworkAdapter = class extends NetworkAdapter2 {
|
|
270
|
+
constructor() {
|
|
271
|
+
super(...arguments);
|
|
272
|
+
this._extensions = /* @__PURE__ */ new Map();
|
|
273
|
+
this._connected = new Trigger2();
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Emits `ready` event. That signals to `Repo` that it can start using the adapter.
|
|
277
|
+
*/
|
|
278
|
+
ready() {
|
|
279
|
+
this.emit("ready", {
|
|
280
|
+
network: this
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
connect(peerId) {
|
|
284
|
+
this.peerId = peerId;
|
|
285
|
+
this._connected.wake();
|
|
286
|
+
}
|
|
287
|
+
send(message) {
|
|
288
|
+
const receiverId = message.targetId;
|
|
289
|
+
const extension = this._extensions.get(receiverId);
|
|
290
|
+
invariant2(extension, "Extension not found.", {
|
|
291
|
+
F: __dxlog_file2,
|
|
292
|
+
L: 38,
|
|
293
|
+
S: this,
|
|
294
|
+
A: [
|
|
295
|
+
"extension",
|
|
296
|
+
"'Extension not found.'"
|
|
297
|
+
]
|
|
298
|
+
});
|
|
299
|
+
extension.sendSyncMessage({
|
|
300
|
+
payload: cbor2.encode(message)
|
|
301
|
+
}).catch((err) => log2.catch(err, void 0, {
|
|
302
|
+
F: __dxlog_file2,
|
|
303
|
+
L: 39,
|
|
304
|
+
S: this,
|
|
305
|
+
C: (f, a) => f(...a)
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
disconnect() {
|
|
309
|
+
}
|
|
310
|
+
createExtension() {
|
|
311
|
+
invariant2(this.peerId, "Peer id not set.", {
|
|
312
|
+
F: __dxlog_file2,
|
|
313
|
+
L: 47,
|
|
314
|
+
S: this,
|
|
315
|
+
A: [
|
|
316
|
+
"this.peerId",
|
|
317
|
+
"'Peer id not set.'"
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
let peerInfo;
|
|
321
|
+
const extension = new AutomergeReplicator({
|
|
322
|
+
peerId: this.peerId
|
|
323
|
+
}, {
|
|
324
|
+
onStartReplication: async (info, remotePeerId) => {
|
|
325
|
+
await this._connected.wait();
|
|
326
|
+
log2("onStartReplication", {
|
|
327
|
+
id: info.id,
|
|
328
|
+
thisPeerId: this.peerId,
|
|
329
|
+
remotePeerId: remotePeerId.toHex()
|
|
330
|
+
}, {
|
|
331
|
+
F: __dxlog_file2,
|
|
332
|
+
L: 70,
|
|
333
|
+
S: this,
|
|
334
|
+
C: (f, a) => f(...a)
|
|
335
|
+
});
|
|
336
|
+
if (!this._extensions.has(info.id)) {
|
|
337
|
+
peerInfo = info;
|
|
338
|
+
this._extensions.set(info.id, extension);
|
|
339
|
+
log2("peer-candidate", {
|
|
340
|
+
id: info.id,
|
|
341
|
+
thisPeerId: this.peerId,
|
|
342
|
+
remotePeerId: remotePeerId.toHex()
|
|
343
|
+
}, {
|
|
344
|
+
F: __dxlog_file2,
|
|
345
|
+
L: 76,
|
|
346
|
+
S: this,
|
|
347
|
+
C: (f, a) => f(...a)
|
|
348
|
+
});
|
|
349
|
+
this.emit("peer-candidate", {
|
|
350
|
+
// TODO(mykola): Hack, stop abusing `peerMetadata` field.
|
|
351
|
+
peerMetadata: {
|
|
352
|
+
dxos_deviceKey: remotePeerId.toHex()
|
|
353
|
+
},
|
|
354
|
+
peerId: info.id
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
onSyncMessage: async ({ payload }) => {
|
|
359
|
+
if (!peerInfo) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const message = cbor2.decode(payload);
|
|
363
|
+
this.emit("message", message);
|
|
364
|
+
},
|
|
365
|
+
onClose: async () => {
|
|
366
|
+
if (!peerInfo) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.emit("peer-disconnected", {
|
|
370
|
+
peerId: peerInfo.id
|
|
371
|
+
});
|
|
372
|
+
this._extensions.delete(peerInfo.id);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return extension;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
|
|
380
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
381
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
382
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
383
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
384
|
+
else
|
|
385
|
+
for (var i = decorators.length - 1; i >= 0; i--)
|
|
386
|
+
if (d = decorators[i])
|
|
387
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
388
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
389
|
+
}
|
|
390
|
+
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts";
|
|
391
|
+
var AutomergeHost = class {
|
|
392
|
+
constructor({ directory, metadata }) {
|
|
393
|
+
this._ctx = new Context();
|
|
394
|
+
/**
|
|
395
|
+
* spaceKey -> deviceKey[]
|
|
396
|
+
*/
|
|
397
|
+
this._authorizedDevices = new ComplexMap(PublicKey.hash);
|
|
398
|
+
this._updatingMetadata = /* @__PURE__ */ new Map();
|
|
399
|
+
this._requestedDocs = /* @__PURE__ */ new Set();
|
|
400
|
+
this._metadata = metadata;
|
|
401
|
+
this._meshNetwork = new MeshNetworkAdapter();
|
|
402
|
+
this._clientNetwork = new LocalHostNetworkAdapter();
|
|
403
|
+
this._storage = new AutomergeStorageWrapper({
|
|
404
|
+
storage: (
|
|
405
|
+
// TODO(mykola): Delete specific handling of IDB storage.
|
|
406
|
+
directory.type === StorageType.IDB ? new IndexedDBStorageAdapter(directory.path, "data") : new AutomergeStorageAdapter(directory)
|
|
407
|
+
),
|
|
408
|
+
callbacks: {
|
|
409
|
+
beforeSave: (params) => this._beforeSave(params)
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
this._peerId = `host-${PublicKey.random().toHex()}`;
|
|
413
|
+
this._repo = new Repo({
|
|
414
|
+
peerId: this._peerId,
|
|
415
|
+
network: [
|
|
416
|
+
this._clientNetwork,
|
|
417
|
+
this._meshNetwork
|
|
418
|
+
],
|
|
419
|
+
storage: this._storage,
|
|
420
|
+
// TODO(dmaretskyi): Share based on HALO permissions and space affinity.
|
|
421
|
+
// Hosts, running in the worker, don't share documents unless requested by other peers.
|
|
422
|
+
sharePolicy: async (peerId, documentId) => {
|
|
423
|
+
if (peerId.startsWith("client-")) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (!documentId) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const doc = this._repo.handles[documentId]?.docSync();
|
|
430
|
+
if (!doc) {
|
|
431
|
+
const isRequested = this._requestedDocs.has(`automerge:${documentId}`);
|
|
432
|
+
log3("doc share policy check", {
|
|
433
|
+
peerId,
|
|
434
|
+
documentId,
|
|
435
|
+
isRequested
|
|
436
|
+
}, {
|
|
437
|
+
F: __dxlog_file3,
|
|
438
|
+
L: 96,
|
|
439
|
+
S: this,
|
|
440
|
+
C: (f, a) => f(...a)
|
|
441
|
+
});
|
|
442
|
+
return isRequested;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const spaceKey = getSpaceKeyFromDoc(doc);
|
|
446
|
+
if (!spaceKey) {
|
|
447
|
+
log3("space key not found for share policy check", {
|
|
448
|
+
peerId,
|
|
449
|
+
documentId
|
|
450
|
+
}, {
|
|
451
|
+
F: __dxlog_file3,
|
|
452
|
+
L: 103,
|
|
453
|
+
S: this,
|
|
454
|
+
C: (f, a) => f(...a)
|
|
455
|
+
});
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const authorizedDevices = this._authorizedDevices.get(PublicKey.from(spaceKey));
|
|
459
|
+
const deviceKeyHex = this.repo.peerMetadataByPeerId[peerId]?.dxos_deviceKey;
|
|
460
|
+
if (!deviceKeyHex) {
|
|
461
|
+
log3("device key not found for share policy check", {
|
|
462
|
+
peerId,
|
|
463
|
+
documentId
|
|
464
|
+
}, {
|
|
465
|
+
F: __dxlog_file3,
|
|
466
|
+
L: 112,
|
|
467
|
+
S: this,
|
|
468
|
+
C: (f, a) => f(...a)
|
|
469
|
+
});
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const deviceKey = PublicKey.from(deviceKeyHex);
|
|
473
|
+
const isAuthorized = authorizedDevices?.has(deviceKey) ?? false;
|
|
474
|
+
log3("share policy check", {
|
|
475
|
+
localPeer: this._peerId,
|
|
476
|
+
remotePeer: peerId,
|
|
477
|
+
documentId,
|
|
478
|
+
deviceKey,
|
|
479
|
+
spaceKey,
|
|
480
|
+
isAuthorized
|
|
481
|
+
}, {
|
|
482
|
+
F: __dxlog_file3,
|
|
483
|
+
L: 118,
|
|
484
|
+
S: this,
|
|
485
|
+
C: (f, a) => f(...a)
|
|
486
|
+
});
|
|
487
|
+
return isAuthorized;
|
|
488
|
+
} catch (err) {
|
|
489
|
+
log3.catch(err, void 0, {
|
|
490
|
+
F: __dxlog_file3,
|
|
491
|
+
L: 128,
|
|
492
|
+
S: this,
|
|
493
|
+
C: (f, a) => f(...a)
|
|
494
|
+
});
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
this._clientNetwork.ready();
|
|
500
|
+
this._meshNetwork.ready();
|
|
501
|
+
{
|
|
502
|
+
const listener = ({ handle }) => this._onDocument(handle);
|
|
503
|
+
this._repo.on("document", listener);
|
|
504
|
+
this._ctx.onDispose(() => {
|
|
505
|
+
this._repo.off("document", listener);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
get repo() {
|
|
510
|
+
return this._repo;
|
|
511
|
+
}
|
|
512
|
+
async _beforeSave(path) {
|
|
513
|
+
const id = path[0];
|
|
514
|
+
if (this._updatingMetadata.has(id)) {
|
|
515
|
+
return this._updatingMetadata.get(id);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
_onDocument(handle) {
|
|
519
|
+
const listener = (event) => this._onUpdate(event);
|
|
520
|
+
handle.on("change", listener);
|
|
521
|
+
this._ctx.onDispose(() => {
|
|
522
|
+
handle.off("change", listener);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
_onUpdate(event) {
|
|
526
|
+
if (this._metadata == null) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const objectIds = getInlineChanges(event);
|
|
530
|
+
if (objectIds.length === 0) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const heads = getHeads(event.doc);
|
|
534
|
+
const lastAvailableHash = heads.at(-1);
|
|
535
|
+
if (!lastAvailableHash) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const encodedIds = objectIds.map((objectId) => idCodec.encode({
|
|
539
|
+
documentId: event.handle.documentId,
|
|
540
|
+
objectId
|
|
541
|
+
}));
|
|
542
|
+
const idToLastHash = new Map(encodedIds.map((id) => [
|
|
543
|
+
id,
|
|
544
|
+
lastAvailableHash
|
|
545
|
+
]));
|
|
546
|
+
const markingDirtyPromise = this._metadata.markDirty(idToLastHash).then(() => {
|
|
547
|
+
this._updatingMetadata.delete(event.handle.documentId);
|
|
548
|
+
}).catch((err) => {
|
|
549
|
+
this._ctx.disposed && log3.catch(err, void 0, {
|
|
550
|
+
F: __dxlog_file3,
|
|
551
|
+
L: 188,
|
|
552
|
+
S: this,
|
|
553
|
+
C: (f, a) => f(...a)
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
this._updatingMetadata.set(event.handle.documentId, markingDirtyPromise);
|
|
557
|
+
}
|
|
558
|
+
_automergeDocs() {
|
|
559
|
+
return mapValues(this._repo.handles, (handle) => ({
|
|
560
|
+
state: handle.state,
|
|
561
|
+
hasDoc: !!handle.docSync(),
|
|
562
|
+
heads: handle.docSync() ? automerge.getHeads(handle.docSync()) : null,
|
|
563
|
+
data: handle.docSync()?.doc && mapValues(handle.docSync()?.doc, (value, key) => {
|
|
564
|
+
try {
|
|
565
|
+
switch (key) {
|
|
566
|
+
case "access":
|
|
567
|
+
case "links":
|
|
568
|
+
return value;
|
|
569
|
+
case "objects":
|
|
570
|
+
return Object.keys(value);
|
|
571
|
+
default:
|
|
572
|
+
return `${value}`;
|
|
573
|
+
}
|
|
574
|
+
} catch (err) {
|
|
575
|
+
return `${err}`;
|
|
576
|
+
}
|
|
577
|
+
})
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
_automergePeers() {
|
|
581
|
+
return this._repo.peers;
|
|
582
|
+
}
|
|
583
|
+
async close() {
|
|
584
|
+
await this._storage.close();
|
|
585
|
+
await this._clientNetwork.close();
|
|
586
|
+
await this._ctx.dispose();
|
|
587
|
+
}
|
|
588
|
+
//
|
|
589
|
+
// Methods for client-services.
|
|
590
|
+
//
|
|
591
|
+
syncRepo(request) {
|
|
592
|
+
return this._clientNetwork.syncRepo(request);
|
|
593
|
+
}
|
|
594
|
+
sendSyncMessage(request) {
|
|
595
|
+
return this._clientNetwork.sendSyncMessage(request);
|
|
596
|
+
}
|
|
597
|
+
async getHostInfo() {
|
|
598
|
+
return this._clientNetwork.getHostInfo();
|
|
599
|
+
}
|
|
600
|
+
//
|
|
601
|
+
// Mesh replication.
|
|
602
|
+
//
|
|
603
|
+
createExtension() {
|
|
604
|
+
return this._meshNetwork.createExtension();
|
|
605
|
+
}
|
|
606
|
+
authorizeDevice(spaceKey, deviceKey) {
|
|
607
|
+
log3("authorizeDevice", {
|
|
608
|
+
spaceKey,
|
|
609
|
+
deviceKey
|
|
610
|
+
}, {
|
|
611
|
+
F: __dxlog_file3,
|
|
612
|
+
L: 255,
|
|
613
|
+
S: this,
|
|
614
|
+
C: (f, a) => f(...a)
|
|
615
|
+
});
|
|
616
|
+
defaultMap(this._authorizedDevices, spaceKey, () => new ComplexSet(PublicKey.hash)).add(deviceKey);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
_ts_decorate([
|
|
620
|
+
trace.info()
|
|
621
|
+
], AutomergeHost.prototype, "_peerId", void 0);
|
|
622
|
+
_ts_decorate([
|
|
623
|
+
trace.info({
|
|
624
|
+
depth: null
|
|
625
|
+
})
|
|
626
|
+
], AutomergeHost.prototype, "_automergeDocs", null);
|
|
627
|
+
_ts_decorate([
|
|
628
|
+
trace.info({
|
|
629
|
+
depth: null
|
|
630
|
+
})
|
|
631
|
+
], AutomergeHost.prototype, "_automergePeers", null);
|
|
632
|
+
AutomergeHost = _ts_decorate([
|
|
633
|
+
trace.resource()
|
|
634
|
+
], AutomergeHost);
|
|
635
|
+
var getInlineChanges = (event) => {
|
|
636
|
+
const inlineChangedObjectIds = /* @__PURE__ */ new Set();
|
|
637
|
+
for (const { path } of event.patches) {
|
|
638
|
+
if (path.length < 2) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
switch (path[0]) {
|
|
642
|
+
case "objects":
|
|
643
|
+
if (path.length >= 2) {
|
|
644
|
+
inlineChangedObjectIds.add(path[1]);
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return [
|
|
650
|
+
...inlineChangedObjectIds
|
|
651
|
+
];
|
|
652
|
+
};
|
|
653
|
+
var getSpaceKeyFromDoc = (doc) => {
|
|
654
|
+
const rawSpaceKey = doc.access?.spaceKey ?? doc.experimental_spaceKey;
|
|
655
|
+
if (rawSpaceKey == null) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
return String(rawSpaceKey);
|
|
659
|
+
};
|
|
33
660
|
export {
|
|
34
661
|
AuthExtension,
|
|
35
662
|
AuthStatus,
|