@dxos/echo-pipeline 0.5.9-next.a50ff17 → 0.6.0
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-I2J5TTHJ.mjs → chunk-HS77A4I4.mjs} +174 -23
- package/dist/lib/browser/{chunk-I2J5TTHJ.mjs.map → chunk-HS77A4I4.mjs.map} +4 -4
- package/dist/lib/browser/index.mjs +182 -126
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +11 -7
- package/dist/lib/browser/testing/index.mjs.map +1 -1
- package/dist/lib/node/{chunk-QPCNQ4ZK.cjs → chunk-Y5U7UXEL.cjs} +185 -34
- package/dist/lib/node/{chunk-QPCNQ4ZK.cjs.map → chunk-Y5U7UXEL.cjs.map} +4 -4
- package/dist/lib/node/index.cjs +230 -174
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +21 -17
- package/dist/lib/node/testing/index.cjs.map +1 -1
- package/dist/types/src/automerge/automerge-doc-loader.d.ts +2 -1
- package/dist/types/src/automerge/automerge-doc-loader.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-host.d.ts +40 -3
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +2 -0
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts +30 -0
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -0
- package/dist/types/src/space/index.d.ts +1 -0
- package/dist/types/src/space/index.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +8 -0
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/automerge/automerge-doc-loader.test.ts +8 -5
- package/src/automerge/automerge-doc-loader.ts +29 -20
- package/src/automerge/automerge-host.ts +96 -32
- package/src/automerge/echo-network-adapter.ts +10 -4
- package/src/automerge/mesh-echo-replicator.ts +7 -2
- package/src/space/admission-discovery-extension.ts +90 -0
- package/src/space/index.ts +1 -0
- package/src/space/space-manager.ts +46 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Trigger } from '@dxos/async';
|
|
2
|
+
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
3
|
+
import { type AdmissionDiscoveryService, type GetAdmissionCredentialRequest } from '@dxos/protocols/proto/dxos/mesh/teleport';
|
|
4
|
+
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
5
|
+
import { type Space } from './space';
|
|
6
|
+
/**
|
|
7
|
+
* Guest's side for a connection to a concrete peer in p2p network during invitation.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CredentialRetrieverExtension extends RpcExtension<{
|
|
10
|
+
AdmissionDiscoveryService: AdmissionDiscoveryService;
|
|
11
|
+
}, {}> {
|
|
12
|
+
private readonly _request;
|
|
13
|
+
private readonly _onResult;
|
|
14
|
+
private _ctx;
|
|
15
|
+
constructor(_request: GetAdmissionCredentialRequest, _onResult: Trigger<Credential>);
|
|
16
|
+
protected getHandlers(): Promise<{}>;
|
|
17
|
+
onOpen(context: ExtensionContext): Promise<void>;
|
|
18
|
+
onClose(): Promise<void>;
|
|
19
|
+
onAbort(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare class CredentialServerExtension extends RpcExtension<{}, {
|
|
22
|
+
AdmissionDiscoveryService: AdmissionDiscoveryService;
|
|
23
|
+
}> {
|
|
24
|
+
private readonly _space;
|
|
25
|
+
constructor(_space: Space);
|
|
26
|
+
protected getHandlers(): Promise<{
|
|
27
|
+
AdmissionDiscoveryService: AdmissionDiscoveryService;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=admission-discovery-extension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admission-discovery-extension.d.ts","sourceRoot":"","sources":["../../../../src/space/admission-discovery-extension.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,6BAA6B,EACnC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,KAAK,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;GAEG;AACH,qBAAa,4BAA6B,SAAQ,YAAY,CAC5D;IAAE,yBAAyB,EAAE,yBAAyB,CAAA;CAAE,EACxD,EAAE,CACH;IAIG,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ5B,OAAO,CAAC,IAAI,CAAiB;gBAGV,QAAQ,EAAE,6BAA6B,EACvC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC;cASxB,WAAW,IAAI,OAAO,CAAC,EAAE,CAAC;IAIpC,MAAM,CAAC,OAAO,EAAE,gBAAgB;IAYhC,OAAO;IAIP,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAGxC;AAED,qBAAa,yBAA0B,SAAQ,YAAY,CACzD,EAAE,EACF;IAAE,yBAAyB,EAAE,yBAAyB,CAAA;CAAE,CACzD;IACa,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;cAQjB,WAAW,IAAI,OAAO,CAAC;QAAE,yBAAyB,EAAE,yBAAyB,CAAA;KAAE,CAAC;CAe1G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/space/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/space/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iCAAiC,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { PublicKey } from '@dxos/keys';
|
|
|
4
4
|
import { type SwarmNetworkManager } from '@dxos/network-manager';
|
|
5
5
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
6
6
|
import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
7
|
+
import type { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
7
8
|
import { type Teleport } from '@dxos/teleport';
|
|
8
9
|
import { type BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
9
10
|
import { ComplexMap } from '@dxos/util';
|
|
@@ -33,6 +34,12 @@ export type ConstructSpaceParams = {
|
|
|
33
34
|
onDelegatedInvitationStatusChange: (invitation: DelegateInvitationCredential, isActive: boolean) => Promise<void>;
|
|
34
35
|
onMemberRolesChanged: (member: MemberInfo[]) => Promise<void>;
|
|
35
36
|
};
|
|
37
|
+
export type RequestSpaceAdmissionCredentialParams = {
|
|
38
|
+
spaceKey: PublicKey;
|
|
39
|
+
identityKey: PublicKey;
|
|
40
|
+
swarmIdentity: SwarmIdentity;
|
|
41
|
+
timeout: number;
|
|
42
|
+
};
|
|
36
43
|
/**
|
|
37
44
|
* Manages a collection of ECHO (Data) Spaces.
|
|
38
45
|
*/
|
|
@@ -49,5 +56,6 @@ export declare class SpaceManager {
|
|
|
49
56
|
open(): Promise<void>;
|
|
50
57
|
close(): Promise<void>;
|
|
51
58
|
constructSpace({ metadata, swarmIdentity, onAuthorizedConnection, onAuthFailure, onDelegatedInvitationStatusChange, onMemberRolesChanged, memberKey, }: ConstructSpaceParams): Promise<Space>;
|
|
59
|
+
requestSpaceAdmissionCredential(params: RequestSpaceAdmissionCredentialParams): Promise<Credential>;
|
|
52
60
|
}
|
|
53
61
|
//# sourceMappingURL=space-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space-manager.d.ts","sourceRoot":"","sources":["../../../../src/space/space-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,4BAA4B,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"space-manager.d.ts","sourceRoot":"","sources":["../../../../src/space/space-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,4BAA4B,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,OAAO,EAAE,KAAK,EAAwB,MAAM,SAAS,CAAC;AACtD,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAClC,cAAc,EAAE,mBAAmB,CAAC;IACpC,aAAa,EAAE,aAAa,CAAC;IAE7B;;OAEG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB;;OAEG;IACH,sBAAsB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC5C,iCAAiC,EAAE,CAAC,UAAU,EAAE,4BAA4B,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClH,oBAAoB,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG;IAClD,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,EAAE,SAAS,CAAC;IACvB,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,qBACa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoD;IAC5E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;gBAE9C,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,kBAAkB;IAUtG,IAAI,MAAM,iCAET;IAGK,IAAI;IAGJ,KAAK;IAIL,cAAc,CAAC,EACnB,QAAQ,EACR,aAAa,EACb,sBAAsB,EACtB,aAAa,EACb,iCAAiC,EACjC,oBAAoB,EACpB,SAAS,GACV,EAAE,oBAAoB;IAqCV,+BAA+B,CAAC,MAAM,EAAE,qCAAqC,GAAG,OAAO,CAAC,UAAU,CAAC;CAmCjH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "ECHO database.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -39,38 +39,38 @@
|
|
|
39
39
|
"crc-32": "^1.2.2",
|
|
40
40
|
"level": "^8.0.1",
|
|
41
41
|
"level-transcoder": "^1.0.1",
|
|
42
|
-
"@dxos/async": "0.
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/context": "0.
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/credentials": "0.
|
|
47
|
-
"@dxos/crypto": "0.
|
|
48
|
-
"@dxos/debug": "0.
|
|
49
|
-
"@dxos/echo-
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/invariant": "0.
|
|
55
|
-
"@dxos/keyring": "0.
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/teleport-extension-automerge-replicator": "0.
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/teleport-extension-object-sync": "0.
|
|
68
|
-
"@dxos/teleport": "0.
|
|
69
|
-
"@dxos/teleport-extension-replicator": "0.
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/util": "0.
|
|
73
|
-
"@dxos/
|
|
42
|
+
"@dxos/async": "0.6.0",
|
|
43
|
+
"@dxos/automerge": "0.6.0",
|
|
44
|
+
"@dxos/context": "0.6.0",
|
|
45
|
+
"@dxos/codec-protobuf": "0.6.0",
|
|
46
|
+
"@dxos/credentials": "0.6.0",
|
|
47
|
+
"@dxos/crypto": "0.6.0",
|
|
48
|
+
"@dxos/debug": "0.6.0",
|
|
49
|
+
"@dxos/echo-schema": "0.6.0",
|
|
50
|
+
"@dxos/echo-protocol": "0.6.0",
|
|
51
|
+
"@dxos/indexing": "0.6.0",
|
|
52
|
+
"@dxos/feed-store": "0.6.0",
|
|
53
|
+
"@dxos/hypercore": "0.6.0",
|
|
54
|
+
"@dxos/invariant": "0.6.0",
|
|
55
|
+
"@dxos/keyring": "0.6.0",
|
|
56
|
+
"@dxos/keys": "0.6.0",
|
|
57
|
+
"@dxos/kv-store": "0.6.0",
|
|
58
|
+
"@dxos/log": "0.6.0",
|
|
59
|
+
"@dxos/node-std": "0.6.0",
|
|
60
|
+
"@dxos/network-manager": "0.6.0",
|
|
61
|
+
"@dxos/messaging": "0.6.0",
|
|
62
|
+
"@dxos/protocols": "0.6.0",
|
|
63
|
+
"@dxos/random-access-storage": "0.6.0",
|
|
64
|
+
"@dxos/teleport": "0.6.0",
|
|
65
|
+
"@dxos/teleport-extension-automerge-replicator": "0.6.0",
|
|
66
|
+
"@dxos/rpc": "0.6.0",
|
|
67
|
+
"@dxos/teleport-extension-object-sync": "0.6.0",
|
|
68
|
+
"@dxos/teleport-extension-gossip": "0.6.0",
|
|
69
|
+
"@dxos/teleport-extension-replicator": "0.6.0",
|
|
70
|
+
"@dxos/timeframe": "0.6.0",
|
|
71
|
+
"@dxos/tracing": "0.6.0",
|
|
72
|
+
"@dxos/util": "0.6.0",
|
|
73
|
+
"@dxos/typings": "0.6.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"fast-check": "^3.19.0",
|
|
@@ -7,7 +7,7 @@ import { expect } from 'chai';
|
|
|
7
7
|
import { sleep } from '@dxos/async';
|
|
8
8
|
import { Repo } from '@dxos/automerge/automerge-repo';
|
|
9
9
|
import { Context } from '@dxos/context';
|
|
10
|
-
import { type SpaceDoc } from '@dxos/echo-protocol';
|
|
10
|
+
import { type SpaceDoc, SpaceDocVersion } from '@dxos/echo-protocol';
|
|
11
11
|
import { generateEchoId } from '@dxos/echo-schema';
|
|
12
12
|
import { PublicKey } from '@dxos/keys';
|
|
13
13
|
import { describe, test } from '@dxos/test';
|
|
@@ -22,6 +22,7 @@ import { createIdFromSpaceKey } from '../space';
|
|
|
22
22
|
const ctx = new Context();
|
|
23
23
|
const SPACE_KEY = PublicKey.random();
|
|
24
24
|
const randomId = () => generateEchoId();
|
|
25
|
+
|
|
25
26
|
describe('AutomergeDocumentLoader', () => {
|
|
26
27
|
test('space access is set on root doc handle and it is accessible', async () => {
|
|
27
28
|
const { loader, spaceRootDocHandle } = await setupTest();
|
|
@@ -76,13 +77,15 @@ describe('AutomergeDocumentLoader', () => {
|
|
|
76
77
|
const spaceId = await createIdFromSpaceKey(SPACE_KEY);
|
|
77
78
|
const repo = new Repo({ network: [] });
|
|
78
79
|
const loader = new AutomergeDocumentLoaderImpl(spaceId, repo, SPACE_KEY);
|
|
79
|
-
const spaceRootDocHandle = repo
|
|
80
|
-
await loader.loadSpaceRootDocHandle(ctx, {
|
|
81
|
-
rootUrl: spaceRootDocHandle.url,
|
|
82
|
-
});
|
|
80
|
+
const spaceRootDocHandle = createRootDoc(repo);
|
|
81
|
+
await loader.loadSpaceRootDocHandle(ctx, { rootUrl: spaceRootDocHandle.url });
|
|
83
82
|
return { loader, spaceRootDocHandle, repo };
|
|
84
83
|
};
|
|
85
84
|
|
|
85
|
+
const createRootDoc = (repo: Repo) => {
|
|
86
|
+
return repo.create<SpaceDoc>({ version: SpaceDocVersion.CURRENT });
|
|
87
|
+
};
|
|
88
|
+
|
|
86
89
|
const loadLinkedObjects = (loader: AutomergeDocumentLoader, links: SpaceDoc['links']) => {
|
|
87
90
|
Object.keys(links ?? {}).forEach((objectId) => loader.loadObjectDocument(objectId));
|
|
88
91
|
loader.onObjectLinksUpdated(links);
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Event } from '@dxos/async';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type DocHandle,
|
|
8
|
+
type AutomergeUrl,
|
|
9
|
+
type DocumentId,
|
|
10
|
+
type Repo,
|
|
11
|
+
interpretAsDocumentId,
|
|
12
|
+
} from '@dxos/automerge/automerge-repo';
|
|
7
13
|
import { cancelWithContext, type Context } from '@dxos/context';
|
|
8
14
|
import { warnAfterTimeout } from '@dxos/debug';
|
|
9
15
|
import { type SpaceState, type SpaceDoc, SpaceDocVersion } from '@dxos/echo-protocol';
|
|
@@ -21,6 +27,7 @@ export interface AutomergeDocumentLoader {
|
|
|
21
27
|
|
|
22
28
|
loadSpaceRootDocHandle(ctx: Context, spaceState: SpaceState): Promise<void>;
|
|
23
29
|
loadObjectDocument(objectId: string | string[]): void;
|
|
30
|
+
getObjectDocumentId(objectId: string): string | undefined;
|
|
24
31
|
getSpaceRootDocHandle(): DocHandle<SpaceDoc>;
|
|
25
32
|
createDocumentForObject(objectId: string): DocHandle<SpaceDoc>;
|
|
26
33
|
onObjectLinksUpdated(links: SpaceDocumentLinks): void;
|
|
@@ -69,17 +76,17 @@ export class AutomergeDocumentLoaderImpl implements AutomergeDocumentLoader {
|
|
|
69
76
|
return;
|
|
70
77
|
}
|
|
71
78
|
if (!spaceState.rootUrl) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this._spaceRootDocHandle = existingDocHandle;
|
|
79
|
+
throw new Error('Database opened with no rootUrl');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const existingDocHandle = await this._initDocHandle(ctx, spaceState.rootUrl);
|
|
83
|
+
const doc = existingDocHandle.docSync();
|
|
84
|
+
invariant(doc);
|
|
85
|
+
invariant(doc.version === SpaceDocVersion.CURRENT);
|
|
86
|
+
if (doc.access == null) {
|
|
87
|
+
this._initDocAccess(existingDocHandle);
|
|
82
88
|
}
|
|
89
|
+
this._spaceRootDocHandle = existingDocHandle;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
public loadObjectDocument(objectIdOrMany: string | string[]) {
|
|
@@ -107,6 +114,17 @@ export class AutomergeDocumentLoaderImpl implements AutomergeDocumentLoader {
|
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
|
|
117
|
+
public getObjectDocumentId(objectId: string): string | undefined {
|
|
118
|
+
invariant(this._spaceRootDocHandle);
|
|
119
|
+
const spaceRootDoc = this._spaceRootDocHandle.docSync();
|
|
120
|
+
invariant(spaceRootDoc);
|
|
121
|
+
if (spaceRootDoc.objects?.[objectId]) {
|
|
122
|
+
return this._spaceRootDocHandle.documentId;
|
|
123
|
+
}
|
|
124
|
+
const documentUrl = (spaceRootDoc.links ?? {})[objectId];
|
|
125
|
+
return documentUrl && interpretAsDocumentId(documentUrl);
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
public onObjectLinksUpdated(links: SpaceDocumentLinks) {
|
|
111
129
|
if (!links) {
|
|
112
130
|
return;
|
|
@@ -198,15 +216,6 @@ export class AutomergeDocumentLoaderImpl implements AutomergeDocumentLoader {
|
|
|
198
216
|
return docHandle;
|
|
199
217
|
}
|
|
200
218
|
|
|
201
|
-
private _createContextBoundSpaceRootDocument(ctx: Context) {
|
|
202
|
-
const docHandle = this._repo.create<SpaceDoc>();
|
|
203
|
-
this._spaceRootDocHandle = docHandle;
|
|
204
|
-
ctx.onDispose(() => {
|
|
205
|
-
docHandle.delete();
|
|
206
|
-
this._spaceRootDocHandle = null;
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
219
|
private _initDocAccess(handle: DocHandle<SpaceDoc>) {
|
|
211
220
|
handle.change((newDoc: SpaceDoc) => {
|
|
212
221
|
newDoc.access ??= { spaceKey: this._spaceKey.toHex() };
|
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event } from '@dxos/async';
|
|
6
|
-
import {
|
|
5
|
+
import { Event, asyncTimeout } from '@dxos/async';
|
|
6
|
+
import {
|
|
7
|
+
next as automerge,
|
|
8
|
+
getBackend,
|
|
9
|
+
getHeads,
|
|
10
|
+
isAutomerge,
|
|
11
|
+
save,
|
|
12
|
+
type Doc,
|
|
13
|
+
type Heads,
|
|
14
|
+
} from '@dxos/automerge/automerge';
|
|
7
15
|
import {
|
|
8
16
|
Repo,
|
|
17
|
+
type AnyDocumentId,
|
|
9
18
|
type DocHandle,
|
|
10
19
|
type DocHandleChangePayload,
|
|
11
20
|
type DocumentId,
|
|
@@ -13,13 +22,11 @@ import {
|
|
|
13
22
|
type StorageAdapterInterface,
|
|
14
23
|
} from '@dxos/automerge/automerge-repo';
|
|
15
24
|
import { type Stream } from '@dxos/codec-protobuf';
|
|
16
|
-
import { Context, type Lifecycle } from '@dxos/context';
|
|
25
|
+
import { Context, cancelWithContext, type Lifecycle } from '@dxos/context';
|
|
17
26
|
import { type SpaceDoc } from '@dxos/echo-protocol';
|
|
18
27
|
import { type IndexMetadataStore } from '@dxos/indexing';
|
|
19
|
-
import { invariant } from '@dxos/invariant';
|
|
20
28
|
import { PublicKey } from '@dxos/keys';
|
|
21
29
|
import { type SublevelDB } from '@dxos/kv-store';
|
|
22
|
-
import { log } from '@dxos/log';
|
|
23
30
|
import { objectPointerCodec } from '@dxos/protocols';
|
|
24
31
|
import {
|
|
25
32
|
type FlushRequest,
|
|
@@ -30,7 +37,7 @@ import {
|
|
|
30
37
|
import { trace } from '@dxos/tracing';
|
|
31
38
|
import { mapValues } from '@dxos/util';
|
|
32
39
|
|
|
33
|
-
import { EchoNetworkAdapter } from './echo-network-adapter';
|
|
40
|
+
import { EchoNetworkAdapter, isEchoPeerMetadata } from './echo-network-adapter';
|
|
34
41
|
import { type EchoReplicator } from './echo-replicator';
|
|
35
42
|
import { LevelDBStorageAdapter, type BeforeSaveParams } from './leveldb-storage-adapter';
|
|
36
43
|
import { LocalHostNetworkAdapter } from './local-host-network-adapter';
|
|
@@ -44,6 +51,20 @@ export type AutomergeHostParams = {
|
|
|
44
51
|
indexMetadataStore: IndexMetadataStore;
|
|
45
52
|
};
|
|
46
53
|
|
|
54
|
+
export type LoadDocOptions = {
|
|
55
|
+
timeout?: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type CreateDocOptions = {
|
|
59
|
+
/**
|
|
60
|
+
* Import the document together with its history.
|
|
61
|
+
*/
|
|
62
|
+
preserveHistory?: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Abstracts over the AutomergeRepo.
|
|
67
|
+
*/
|
|
47
68
|
@trace.resource()
|
|
48
69
|
export class AutomergeHost {
|
|
49
70
|
private readonly _indexMetadataStore: IndexMetadataStore;
|
|
@@ -59,8 +80,6 @@ export class AutomergeHost {
|
|
|
59
80
|
@trace.info()
|
|
60
81
|
private _peerId!: string;
|
|
61
82
|
|
|
62
|
-
public _requestedDocs = new Set<string>();
|
|
63
|
-
|
|
64
83
|
constructor({ db, indexMetadataStore }: AutomergeHostParams) {
|
|
65
84
|
this._storage = new LevelDBStorageAdapter({
|
|
66
85
|
db,
|
|
@@ -105,6 +124,9 @@ export class AutomergeHost {
|
|
|
105
124
|
await this._ctx.dispose();
|
|
106
125
|
}
|
|
107
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated To be abstracted away.
|
|
129
|
+
*/
|
|
108
130
|
get repo(): Repo {
|
|
109
131
|
return this._repo;
|
|
110
132
|
}
|
|
@@ -117,6 +139,46 @@ export class AutomergeHost {
|
|
|
117
139
|
await this._echoNetworkAdapter.removeReplicator(replicator);
|
|
118
140
|
}
|
|
119
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Loads the document handle from the repo and waits for it to be ready.
|
|
144
|
+
*/
|
|
145
|
+
async loadDoc<T>(ctx: Context, documentId: AnyDocumentId, opts?: LoadDocOptions): Promise<DocHandle<T>> {
|
|
146
|
+
let handle: DocHandle<T> | undefined;
|
|
147
|
+
if (typeof documentId === 'string') {
|
|
148
|
+
// NOTE: documentId might also be a URL, in which case this lookup will fail.
|
|
149
|
+
handle = this._repo.handles[documentId as DocumentId];
|
|
150
|
+
}
|
|
151
|
+
if (!handle) {
|
|
152
|
+
handle = this._repo.find(documentId as DocumentId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// `whenReady` creates a timeout so we guard it with an if to skip it if the handle is already ready.
|
|
156
|
+
if (!handle.isReady()) {
|
|
157
|
+
if (!opts?.timeout) {
|
|
158
|
+
await cancelWithContext(ctx, handle.whenReady());
|
|
159
|
+
} else {
|
|
160
|
+
await cancelWithContext(ctx, asyncTimeout(handle.whenReady(), opts.timeout));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return handle;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create new persisted document.
|
|
169
|
+
*/
|
|
170
|
+
createDoc<T>(initialValue?: T | Doc<T>, opts?: CreateDocOptions): DocHandle<T> {
|
|
171
|
+
if (opts?.preserveHistory) {
|
|
172
|
+
if (!isAutomerge(initialValue)) {
|
|
173
|
+
throw new TypeError('Initial value must be an Automerge document');
|
|
174
|
+
}
|
|
175
|
+
// TODO(dmaretskyi): There's a more efficient way.
|
|
176
|
+
return this._repo.import(save(initialValue as Doc<T>));
|
|
177
|
+
} else {
|
|
178
|
+
return this._repo.create(initialValue);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
120
182
|
// TODO(dmaretskyi): Share based on HALO permissions and space affinity.
|
|
121
183
|
// Hosts, running in the worker, don't share documents unless requested by other peers.
|
|
122
184
|
// NOTE: If both peers return sharePolicy=false the replication will not happen
|
|
@@ -133,19 +195,8 @@ export class AutomergeHost {
|
|
|
133
195
|
return false;
|
|
134
196
|
}
|
|
135
197
|
|
|
136
|
-
// Workaround for https://github.com/automerge/automerge-repo/pull/292
|
|
137
|
-
// NOTE: This must override the per-connection policy.
|
|
138
|
-
const doc = this._repo.handles[documentId]?.docSync();
|
|
139
|
-
if (!doc) {
|
|
140
|
-
// TODO(dmaretskyi): Verify that this works as intended.
|
|
141
|
-
// TODO(dmaretskyi): Move to MESH replicator?
|
|
142
|
-
const isRequested = this._requestedDocs.has(`automerge:${documentId}`);
|
|
143
|
-
log('doc share policy check', { peerId, documentId, isRequested });
|
|
144
|
-
return isRequested;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
198
|
const peerMetadata = this.repo.peerMetadataByPeerId[peerId];
|
|
148
|
-
if ((peerMetadata
|
|
199
|
+
if (isEchoPeerMetadata(peerMetadata)) {
|
|
149
200
|
return this._echoNetworkAdapter.shouldAdvertize(peerId, { documentId });
|
|
150
201
|
}
|
|
151
202
|
|
|
@@ -226,39 +277,52 @@ export class AutomergeHost {
|
|
|
226
277
|
return PublicKey.from(spaceKeyHex);
|
|
227
278
|
}
|
|
228
279
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Flush documents to disk.
|
|
282
|
+
*/
|
|
232
283
|
@trace.span({ showInBrowserTimeline: true })
|
|
233
284
|
async flush({ states }: FlushRequest): Promise<void> {
|
|
234
285
|
// Note: Wait for all requested documents to be loaded/synced from thin-client.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
286
|
+
if (states) {
|
|
287
|
+
await Promise.all(
|
|
288
|
+
states.map(async ({ heads, documentId }) => {
|
|
289
|
+
if (!heads) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const handle = this.repo.handles[documentId as DocumentId] ?? this._repo.find(documentId as DocumentId);
|
|
293
|
+
await waitForHeads(handle, heads);
|
|
294
|
+
}) ?? [],
|
|
295
|
+
);
|
|
296
|
+
}
|
|
242
297
|
|
|
243
298
|
await this._repo.flush(states?.map(({ documentId }) => documentId as DocumentId));
|
|
244
299
|
}
|
|
245
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Host <-> Client sync.
|
|
303
|
+
*/
|
|
246
304
|
syncRepo(request: SyncRepoRequest): Stream<SyncRepoResponse> {
|
|
247
305
|
return this._clientNetwork.syncRepo(request);
|
|
248
306
|
}
|
|
249
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Host <-> Client sync.
|
|
310
|
+
*/
|
|
250
311
|
sendSyncMessage(request: SyncRepoRequest): Promise<void> {
|
|
251
312
|
return this._clientNetwork.sendSyncMessage(request);
|
|
252
313
|
}
|
|
253
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Host <-> Client sync.
|
|
317
|
+
*/
|
|
254
318
|
async getHostInfo(): Promise<HostInfo> {
|
|
255
319
|
return this._clientNetwork.getHostInfo();
|
|
256
320
|
}
|
|
257
321
|
}
|
|
258
322
|
|
|
259
|
-
export const getSpaceKeyFromDoc = (doc:
|
|
323
|
+
export const getSpaceKeyFromDoc = (doc: Doc<SpaceDoc>): string | null => {
|
|
260
324
|
// experimental_spaceKey is set on old documents, new ones are created with doc.access.spaceKey
|
|
261
|
-
const rawSpaceKey = doc.access?.spaceKey ?? doc.experimental_spaceKey;
|
|
325
|
+
const rawSpaceKey = doc.access?.spaceKey ?? (doc as any).experimental_spaceKey;
|
|
262
326
|
if (rawSpaceKey == null) {
|
|
263
327
|
return null;
|
|
264
328
|
}
|
|
@@ -174,10 +174,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
174
174
|
private _emitPeerCandidate(connection: ReplicatorConnection) {
|
|
175
175
|
this.emit('peer-candidate', {
|
|
176
176
|
peerId: connection.peerId as PeerId,
|
|
177
|
-
peerMetadata:
|
|
178
|
-
// TODO(dmaretskyi): Refactor this.
|
|
179
|
-
dxos_peerSource: 'EchoNetworkAdapter',
|
|
180
|
-
} as any,
|
|
177
|
+
peerMetadata: createEchoPeerMetadata(),
|
|
181
178
|
});
|
|
182
179
|
}
|
|
183
180
|
}
|
|
@@ -188,3 +185,12 @@ type ConnectionEntry = {
|
|
|
188
185
|
writer: WritableStreamDefaultWriter<Message>;
|
|
189
186
|
isOpen: boolean;
|
|
190
187
|
};
|
|
188
|
+
|
|
189
|
+
export const createEchoPeerMetadata = (): PeerMetadata =>
|
|
190
|
+
({
|
|
191
|
+
// TODO(dmaretskyi): Refactor this.
|
|
192
|
+
dxos_peerSource: 'EchoNetworkAdapter',
|
|
193
|
+
}) as any;
|
|
194
|
+
|
|
195
|
+
export const isEchoPeerMetadata = (metadata: PeerMetadata): boolean =>
|
|
196
|
+
(metadata as any)?.dxos_peerSource === 'EchoNetworkAdapter';
|
|
@@ -63,15 +63,15 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
63
63
|
this._context.onConnectionAuthScopeChanged(connection);
|
|
64
64
|
} else {
|
|
65
65
|
this._connectionsPerPeer.set(connection.peerId, connection);
|
|
66
|
-
await connection.enable();
|
|
67
66
|
this._context.onConnectionOpen(connection);
|
|
67
|
+
await connection.enable();
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
onRemoteDisconnected: async () => {
|
|
71
71
|
log('onRemoteDisconnected', { peerId: connection.peerId });
|
|
72
72
|
this._context?.onConnectionClosed(connection);
|
|
73
|
-
await connection.disable();
|
|
74
73
|
this._connectionsPerPeer.delete(connection.peerId);
|
|
74
|
+
await connection.disable();
|
|
75
75
|
this._connections.delete(connection);
|
|
76
76
|
},
|
|
77
77
|
shouldAdvertize: async (params: ShouldAdvertizeParams) => {
|
|
@@ -121,6 +121,11 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
121
121
|
authorizeDevice(spaceKey: PublicKey, deviceKey: PublicKey) {
|
|
122
122
|
log('authorizeDevice', { spaceKey, deviceKey });
|
|
123
123
|
defaultMap(this._authorizedDevices, spaceKey, () => new ComplexSet(PublicKey.hash)).add(deviceKey);
|
|
124
|
+
for (const connection of this._connections) {
|
|
125
|
+
if (connection.remoteDeviceKey && connection.remoteDeviceKey.equals(deviceKey)) {
|
|
126
|
+
this._context?.onConnectionAuthScopeChanged(connection);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { scheduleTask, type Trigger } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
7
|
+
import { ProtocolError, schema } from '@dxos/protocols';
|
|
8
|
+
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
9
|
+
import {
|
|
10
|
+
type AdmissionDiscoveryService,
|
|
11
|
+
type GetAdmissionCredentialResponse,
|
|
12
|
+
type GetAdmissionCredentialRequest,
|
|
13
|
+
} from '@dxos/protocols/proto/dxos/mesh/teleport';
|
|
14
|
+
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
15
|
+
|
|
16
|
+
import { type Space } from './space';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Guest's side for a connection to a concrete peer in p2p network during invitation.
|
|
20
|
+
*/
|
|
21
|
+
export class CredentialRetrieverExtension extends RpcExtension<
|
|
22
|
+
{ AdmissionDiscoveryService: AdmissionDiscoveryService },
|
|
23
|
+
{}
|
|
24
|
+
> {
|
|
25
|
+
private _ctx = new Context();
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly _request: GetAdmissionCredentialRequest,
|
|
29
|
+
private readonly _onResult: Trigger<Credential>,
|
|
30
|
+
) {
|
|
31
|
+
super({
|
|
32
|
+
requested: {
|
|
33
|
+
AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected override async getHandlers(): Promise<{}> {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override async onOpen(context: ExtensionContext) {
|
|
43
|
+
await super.onOpen(context);
|
|
44
|
+
scheduleTask(this._ctx, async () => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await this.rpc.AdmissionDiscoveryService.getAdmissionCredential(this._request);
|
|
47
|
+
this._onResult.wake(result.admissionCredential);
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
context.close(err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override async onClose() {
|
|
55
|
+
await this._ctx.dispose();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override async onAbort(): Promise<void> {
|
|
59
|
+
await this._ctx.dispose();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class CredentialServerExtension extends RpcExtension<
|
|
64
|
+
{},
|
|
65
|
+
{ AdmissionDiscoveryService: AdmissionDiscoveryService }
|
|
66
|
+
> {
|
|
67
|
+
constructor(private readonly _space: Space) {
|
|
68
|
+
super({
|
|
69
|
+
exposed: {
|
|
70
|
+
AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected override async getHandlers(): Promise<{ AdmissionDiscoveryService: AdmissionDiscoveryService }> {
|
|
76
|
+
return {
|
|
77
|
+
AdmissionDiscoveryService: {
|
|
78
|
+
getAdmissionCredential: async (
|
|
79
|
+
request: GetAdmissionCredentialRequest,
|
|
80
|
+
): Promise<GetAdmissionCredentialResponse> => {
|
|
81
|
+
const memberInfo = this._space.spaceState.members.get(request.memberKey);
|
|
82
|
+
if (!memberInfo?.credential) {
|
|
83
|
+
throw new ProtocolError('Space member not found.', request);
|
|
84
|
+
}
|
|
85
|
+
return { admissionCredential: memberInfo.credential };
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/space/index.ts
CHANGED