@dxos/client-services 0.5.0 → 0.5.1-main.1670c35
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-ESEYLOPB.mjs → chunk-JXX5ZSCO.mjs} +1337 -1040
- package/dist/lib/browser/chunk-JXX5ZSCO.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +6 -4
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +36 -14
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-GA7JFIXK.cjs → chunk-EHOZ2U3P.cjs} +1463 -1170
- package/dist/lib/node/chunk-EHOZ2U3P.cjs.map +7 -0
- package/dist/lib/node/index.cjs +48 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +40 -18
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +39 -0
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/{invitation-extension.d.ts → invitation-host-extension.d.ts} +17 -31
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +6 -1
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-topology.d.ts +37 -0
- package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +19 -10
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts +2 -0
- package/dist/types/src/packlets/invitations/invitations-handler.test.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +2 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +1 -0
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/utils.d.ts +6 -0
- package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-context.d.ts +12 -11
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -2
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +4 -3
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +4 -3
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/level.d.ts +1 -2
- package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +2 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +5 -3
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +36 -35
- package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
- package/src/packlets/invitations/invitation-guest-extenstion.ts +126 -0
- package/src/packlets/invitations/{invitation-extension.ts → invitation-host-extension.ts} +99 -105
- package/src/packlets/invitations/invitation-protocol.ts +7 -1
- package/src/packlets/invitations/invitation-topology.ts +87 -0
- package/src/packlets/invitations/invitations-handler.test.ts +361 -0
- package/src/packlets/invitations/invitations-handler.ts +246 -149
- package/src/packlets/invitations/invitations-manager.ts +42 -3
- package/src/packlets/invitations/space-invitation-protocol.ts +19 -1
- package/src/packlets/invitations/utils.ts +27 -0
- package/src/packlets/services/automerge-host.test.ts +6 -4
- package/src/packlets/services/service-context.test.ts +3 -3
- package/src/packlets/services/service-context.ts +18 -31
- package/src/packlets/services/service-host.test.ts +6 -0
- package/src/packlets/services/service-host.ts +9 -26
- package/src/packlets/spaces/data-space-manager.test.ts +4 -4
- package/src/packlets/spaces/data-space-manager.ts +8 -11
- package/src/packlets/spaces/data-space.ts +14 -18
- package/src/packlets/storage/level.ts +3 -2
- package/src/packlets/testing/invitation-utils.ts +23 -3
- package/src/packlets/testing/test-builder.ts +13 -15
- package/src/packlets/vault/worker-runtime.ts +3 -1
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-ESEYLOPB.mjs.map +0 -7
- package/dist/lib/node/chunk-GA7JFIXK.cjs.map +0 -7
- package/dist/types/src/packlets/indexing/index.d.ts +0 -2
- package/dist/types/src/packlets/indexing/index.d.ts.map +0 -1
- package/dist/types/src/packlets/indexing/util.d.ts +0 -11
- package/dist/types/src/packlets/indexing/util.d.ts.map +0 -1
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +0 -1
- package/src/packlets/indexing/index.ts +0 -5
- package/src/packlets/indexing/util.ts +0 -32
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
createAdmissionCredentials,
|
|
7
|
+
createCancelDelegatedSpaceInvitationCredential,
|
|
7
8
|
createDelegatedSpaceInvitationCredential,
|
|
8
9
|
getCredentialAssertion,
|
|
9
10
|
} from '@dxos/credentials';
|
|
@@ -87,7 +88,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
87
88
|
|
|
88
89
|
async delegate(invitation: Invitation): Promise<PublicKey> {
|
|
89
90
|
invariant(this._spaceKey);
|
|
90
|
-
const space =
|
|
91
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
91
92
|
invariant(space);
|
|
92
93
|
if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
|
|
93
94
|
invariant(invitation.guestKeypair?.publicKey);
|
|
@@ -118,6 +119,23 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
118
119
|
return credential.credential.credential.id!;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
async cancelDelegation(invitation: Invitation): Promise<void> {
|
|
123
|
+
invariant(this._spaceKey);
|
|
124
|
+
invariant(invitation.type === Invitation.Type.DELEGATED && invitation.delegationCredentialId);
|
|
125
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
126
|
+
invariant(space);
|
|
127
|
+
|
|
128
|
+
log('cancelling delegated space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
|
|
129
|
+
const credential = await createCancelDelegatedSpaceInvitationCredential(
|
|
130
|
+
this._signingContext.credentialSigner,
|
|
131
|
+
space.key,
|
|
132
|
+
invitation.delegationCredentialId,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
invariant(credential.credential);
|
|
136
|
+
await writeMessages(space.inner.controlPipeline.writer, [credential]);
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
checkInvitation(invitation: Partial<Invitation>) {
|
|
122
140
|
if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
|
|
123
141
|
return new AlreadyJoinedError('Already joined space.');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Mutex, type MutexGuard } from '@dxos/async';
|
|
6
|
+
import { cancelWithContext, type Context, ContextDisposedError } from '@dxos/context';
|
|
7
|
+
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
8
|
+
|
|
9
|
+
export const stateToString = (state: Invitation.State): string => {
|
|
10
|
+
return Object.entries(Invitation.State).find(([key, val]) => val === state)?.[0] ?? 'unknown';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const tryAcquireBeforeContextDisposed = async (ctx: Context, mutex: Mutex): Promise<MutexGuard> => {
|
|
14
|
+
let guard: MutexGuard | undefined;
|
|
15
|
+
return cancelWithContext(
|
|
16
|
+
ctx,
|
|
17
|
+
(async () => {
|
|
18
|
+
guard = await mutex.acquire();
|
|
19
|
+
if (ctx.disposed) {
|
|
20
|
+
guard.release();
|
|
21
|
+
guard = undefined;
|
|
22
|
+
throw new ContextDisposedError();
|
|
23
|
+
}
|
|
24
|
+
return guard;
|
|
25
|
+
})(),
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
|
-
import { asyncTimeout
|
|
7
|
+
import { asyncTimeout } from '@dxos/async';
|
|
8
|
+
import { getHeads } from '@dxos/automerge/automerge';
|
|
8
9
|
import { AutomergeContext } from '@dxos/echo-db';
|
|
9
10
|
import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
|
|
10
|
-
import {
|
|
11
|
+
import { IndexMetadataStore } from '@dxos/indexing';
|
|
12
|
+
import { createTestLevel } from '@dxos/kv-store/testing';
|
|
11
13
|
import { afterTest, describe, test } from '@dxos/test';
|
|
12
14
|
|
|
13
15
|
describe('AutomergeHost', () => {
|
|
@@ -25,6 +27,7 @@ describe('AutomergeHost', () => {
|
|
|
25
27
|
|
|
26
28
|
const host = new AutomergeHost({
|
|
27
29
|
db: level.sublevel('automerge'),
|
|
30
|
+
indexMetadataStore: new IndexMetadataStore({ db: level.sublevel('index-metadata') }),
|
|
28
31
|
});
|
|
29
32
|
await host.open();
|
|
30
33
|
afterTest(() => host.close());
|
|
@@ -50,9 +53,8 @@ describe('AutomergeHost', () => {
|
|
|
50
53
|
doc.change((doc: any) => {
|
|
51
54
|
doc.text = newText;
|
|
52
55
|
});
|
|
56
|
+
await client.flush({ states: [{ documentId: doc.documentId, heads: getHeads(doc.docSync()) }] });
|
|
53
57
|
|
|
54
|
-
// TODO(mykola): Is there a way to know when automerge has started replication?
|
|
55
|
-
await sleep(100);
|
|
56
58
|
await asyncTimeout(handle.whenReady(), 1_000);
|
|
57
59
|
expect(handle.docSync().text).to.equal(newText);
|
|
58
60
|
});
|
|
@@ -27,15 +27,15 @@ describe('services/ServiceContext', () => {
|
|
|
27
27
|
test('joined space is synchronized on device invitations', async () => {
|
|
28
28
|
const networkContext = new MemorySignalManagerContext();
|
|
29
29
|
const device1 = await createServiceContext({ signalContext: networkContext });
|
|
30
|
-
await openAndClose(device1.
|
|
30
|
+
await openAndClose(device1.echoHost);
|
|
31
31
|
await device1.createIdentity();
|
|
32
32
|
|
|
33
33
|
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
34
|
-
await openAndClose(device2.
|
|
34
|
+
await openAndClose(device2.echoHost);
|
|
35
35
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
36
36
|
|
|
37
37
|
const identity2 = await createServiceContext({ signalContext: networkContext });
|
|
38
|
-
await openAndClose(identity2.
|
|
38
|
+
await openAndClose(identity2.echoHost);
|
|
39
39
|
await identity2.createIdentity();
|
|
40
40
|
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
41
41
|
await Promise.all(
|
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Level } from 'level';
|
|
6
|
-
|
|
7
5
|
import { Trigger } from '@dxos/async';
|
|
8
6
|
import { Context, Resource } from '@dxos/context';
|
|
9
7
|
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
10
8
|
import { failUndefined } from '@dxos/debug';
|
|
11
|
-
import {
|
|
9
|
+
import { EchoHost } from '@dxos/echo-db';
|
|
10
|
+
import { MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
12
11
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
13
|
-
import { IndexMetadataStore, IndexStore, Indexer, createStorageCallbacks } from '@dxos/indexing';
|
|
14
12
|
import { invariant } from '@dxos/invariant';
|
|
15
13
|
import { Keyring } from '@dxos/keyring';
|
|
16
14
|
import { PublicKey } from '@dxos/keys';
|
|
15
|
+
import { type LevelDB } from '@dxos/kv-store';
|
|
17
16
|
import { log } from '@dxos/log';
|
|
18
17
|
import { type SignalManager } from '@dxos/messaging';
|
|
19
18
|
import { type NetworkManager } from '@dxos/network-manager';
|
|
@@ -22,6 +21,7 @@ import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
|
22
21
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
23
22
|
import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
24
23
|
import { type Storage } from '@dxos/random-access-storage';
|
|
24
|
+
import type { TeleportParams } from '@dxos/teleport';
|
|
25
25
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
26
26
|
import { trace as Trace } from '@dxos/tracing';
|
|
27
27
|
import { safeInstanceof } from '@dxos/util';
|
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
type IdentityManagerRuntimeParams,
|
|
33
33
|
type JoinIdentityParams,
|
|
34
34
|
} from '../identity';
|
|
35
|
-
import { createSelectedDocumentsIterator } from '../indexing';
|
|
36
35
|
import {
|
|
37
36
|
DeviceInvitationProtocol,
|
|
38
37
|
InvitationsHandler,
|
|
@@ -42,7 +41,8 @@ import {
|
|
|
42
41
|
import { InvitationsManager } from '../invitations/invitations-manager';
|
|
43
42
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
44
43
|
|
|
45
|
-
export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
|
|
44
|
+
export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams &
|
|
45
|
+
DataSpaceManagerRuntimeParams & { invitationConnectionDefaultParams?: Partial<TeleportParams> };
|
|
46
46
|
/**
|
|
47
47
|
* Shared backend for all client services.
|
|
48
48
|
*/
|
|
@@ -64,9 +64,7 @@ export class ServiceContext extends Resource {
|
|
|
64
64
|
public readonly identityManager: IdentityManager;
|
|
65
65
|
public readonly invitations: InvitationsHandler;
|
|
66
66
|
public readonly invitationsManager: InvitationsManager;
|
|
67
|
-
public readonly
|
|
68
|
-
public readonly indexMetadata: IndexMetadataStore;
|
|
69
|
-
public readonly indexer: Indexer;
|
|
67
|
+
public readonly echoHost: EchoHost;
|
|
70
68
|
|
|
71
69
|
// Initialized after identity is initialized.
|
|
72
70
|
public dataSpaceManager?: DataSpaceManager;
|
|
@@ -82,10 +80,10 @@ export class ServiceContext extends Resource {
|
|
|
82
80
|
|
|
83
81
|
constructor(
|
|
84
82
|
public readonly storage: Storage,
|
|
85
|
-
public readonly level:
|
|
83
|
+
public readonly level: LevelDB,
|
|
86
84
|
public readonly networkManager: NetworkManager,
|
|
87
85
|
public readonly signalManager: SignalManager,
|
|
88
|
-
public readonly _runtimeParams?:
|
|
86
|
+
public readonly _runtimeParams?: ServiceContextRuntimeParams,
|
|
89
87
|
) {
|
|
90
88
|
super();
|
|
91
89
|
|
|
@@ -122,22 +120,12 @@ export class ServiceContext extends Resource {
|
|
|
122
120
|
this._runtimeParams as IdentityManagerRuntimeParams,
|
|
123
121
|
);
|
|
124
122
|
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
directory: storage.createDirectory('automerge'),
|
|
129
|
-
db: level.sublevel('automerge'),
|
|
130
|
-
storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
this.indexer = new Indexer({
|
|
134
|
-
db: this.level,
|
|
135
|
-
indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
|
|
136
|
-
metadataStore: this.indexMetadata,
|
|
137
|
-
loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
|
|
123
|
+
this.echoHost = new EchoHost({
|
|
124
|
+
kv: this.level,
|
|
125
|
+
storage: this.storage,
|
|
138
126
|
});
|
|
139
127
|
|
|
140
|
-
this.invitations = new InvitationsHandler(this.networkManager);
|
|
128
|
+
this.invitations = new InvitationsHandler(this.networkManager, _runtimeParams?.invitationConnectionDefaultParams);
|
|
141
129
|
this.invitationsManager = new InvitationsManager(
|
|
142
130
|
this.invitations,
|
|
143
131
|
(invitation) => this.getInvitationHandler(invitation),
|
|
@@ -166,7 +154,7 @@ export class ServiceContext extends Resource {
|
|
|
166
154
|
await this.signalManager.open();
|
|
167
155
|
await this.networkManager.open();
|
|
168
156
|
|
|
169
|
-
await this.
|
|
157
|
+
await this.echoHost.open(ctx);
|
|
170
158
|
await this.metadataStore.load();
|
|
171
159
|
await this.spaceManager.open();
|
|
172
160
|
await this.identityManager.open(ctx);
|
|
@@ -181,20 +169,19 @@ export class ServiceContext extends Resource {
|
|
|
181
169
|
log('opened');
|
|
182
170
|
}
|
|
183
171
|
|
|
184
|
-
protected override async _close() {
|
|
172
|
+
protected override async _close(ctx: Context) {
|
|
185
173
|
log('closing...');
|
|
186
174
|
if (this._deviceSpaceSync && this.identityManager.identity) {
|
|
187
175
|
await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
|
|
188
176
|
}
|
|
189
|
-
await this.automergeHost.close();
|
|
190
177
|
await this.dataSpaceManager?.close();
|
|
191
178
|
await this.identityManager.close();
|
|
192
179
|
await this.spaceManager.close();
|
|
193
180
|
await this.feedStore.close();
|
|
181
|
+
await this.metadataStore.close();
|
|
182
|
+
await this.echoHost.close(ctx);
|
|
194
183
|
await this.networkManager.close();
|
|
195
184
|
await this.signalManager.close();
|
|
196
|
-
await this.metadataStore.close();
|
|
197
|
-
await this.indexer.destroy();
|
|
198
185
|
log('closed');
|
|
199
186
|
}
|
|
200
187
|
|
|
@@ -255,7 +242,7 @@ export class ServiceContext extends Resource {
|
|
|
255
242
|
this.keyring,
|
|
256
243
|
signingContext,
|
|
257
244
|
this.feedStore,
|
|
258
|
-
this.
|
|
245
|
+
this.echoHost,
|
|
259
246
|
this.invitationsManager,
|
|
260
247
|
this._runtimeParams as DataSpaceManagerRuntimeParams,
|
|
261
248
|
);
|
|
@@ -29,6 +29,12 @@ describe('ClientServicesHost', () => {
|
|
|
29
29
|
isNode() && rmSync(dataRoot, { recursive: true, force: true });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
test('open and close', async () => {
|
|
33
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
34
|
+
await host.open(new Context());
|
|
35
|
+
await host.close();
|
|
36
|
+
});
|
|
37
|
+
|
|
32
38
|
test('queryCredentials', async () => {
|
|
33
39
|
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
34
40
|
await host.open(new Context());
|
|
@@ -2,23 +2,15 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Level } from 'level';
|
|
6
|
-
|
|
7
5
|
import { Event, synchronized } from '@dxos/async';
|
|
8
6
|
import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
|
|
9
7
|
import { type Config } from '@dxos/config';
|
|
10
8
|
import { Context } from '@dxos/context';
|
|
11
|
-
import {
|
|
12
|
-
DataServiceImpl,
|
|
13
|
-
type ObjectStructure,
|
|
14
|
-
encodeReference,
|
|
15
|
-
type SpaceDoc,
|
|
16
|
-
type LevelDB,
|
|
17
|
-
} from '@dxos/echo-pipeline';
|
|
9
|
+
import { type ObjectStructure, encodeReference, type SpaceDoc } from '@dxos/echo-protocol';
|
|
18
10
|
import { getTypeReference } from '@dxos/echo-schema';
|
|
19
|
-
import { QueryServiceImpl } from '@dxos/indexing';
|
|
20
11
|
import { invariant } from '@dxos/invariant';
|
|
21
12
|
import { PublicKey } from '@dxos/keys';
|
|
13
|
+
import { type LevelDB } from '@dxos/kv-store';
|
|
22
14
|
import { log } from '@dxos/log';
|
|
23
15
|
import { WebsocketSignalManager, type SignalManager } from '@dxos/messaging';
|
|
24
16
|
import { NetworkManager, createSimplePeerTransportFactory, type TransportFactory } from '@dxos/network-manager';
|
|
@@ -83,14 +75,13 @@ export class ClientServicesHost {
|
|
|
83
75
|
private readonly _systemService: SystemServiceImpl;
|
|
84
76
|
private readonly _loggingService: LoggingServiceImpl;
|
|
85
77
|
private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
|
|
86
|
-
private _queryService!: QueryServiceImpl;
|
|
87
78
|
|
|
88
79
|
private _config?: Config;
|
|
89
80
|
private readonly _statusUpdate = new Event<void>();
|
|
90
81
|
private _signalManager?: SignalManager;
|
|
91
82
|
private _networkManager?: NetworkManager;
|
|
92
83
|
private _storage?: Storage;
|
|
93
|
-
private _level?:
|
|
84
|
+
private _level?: LevelDB;
|
|
94
85
|
private _callbacks?: ClientServicesHostCallbacks;
|
|
95
86
|
private _devtoolsProxy?: WebsocketRpcClient<{}, ClientServices>;
|
|
96
87
|
|
|
@@ -246,13 +237,13 @@ export class ClientServicesHost {
|
|
|
246
237
|
this._opening = true;
|
|
247
238
|
log('opening...', { lockKey: this._resourceLock?.lockKey });
|
|
248
239
|
|
|
240
|
+
await this._resourceLock?.acquire();
|
|
241
|
+
|
|
249
242
|
if (!this._level) {
|
|
250
243
|
this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
|
|
251
244
|
}
|
|
252
245
|
await this._level.open();
|
|
253
246
|
|
|
254
|
-
await this._resourceLock?.acquire();
|
|
255
|
-
|
|
256
247
|
await this._loggingService.open();
|
|
257
248
|
|
|
258
249
|
this._serviceContext = new ServiceContext(
|
|
@@ -263,12 +254,6 @@ export class ClientServicesHost {
|
|
|
263
254
|
this._runtimeParams,
|
|
264
255
|
);
|
|
265
256
|
|
|
266
|
-
this._queryService = new QueryServiceImpl({
|
|
267
|
-
indexer: this._serviceContext.indexer,
|
|
268
|
-
automergeHost: this._serviceContext.automergeHost,
|
|
269
|
-
});
|
|
270
|
-
await this._queryService.open(ctx);
|
|
271
|
-
|
|
272
257
|
this._serviceRegistry.setServices({
|
|
273
258
|
SystemService: this._systemService,
|
|
274
259
|
|
|
@@ -292,9 +277,8 @@ export class ClientServicesHost {
|
|
|
292
277
|
},
|
|
293
278
|
),
|
|
294
279
|
|
|
295
|
-
DataService:
|
|
296
|
-
|
|
297
|
-
QueryService: this._queryService,
|
|
280
|
+
DataService: this._serviceContext.echoHost.dataService,
|
|
281
|
+
QueryService: this._serviceContext.echoHost.queryService,
|
|
298
282
|
|
|
299
283
|
NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
|
|
300
284
|
|
|
@@ -344,7 +328,6 @@ export class ClientServicesHost {
|
|
|
344
328
|
await this._devtoolsProxy?.close();
|
|
345
329
|
this._serviceRegistry.setServices({ SystemService: this._systemService });
|
|
346
330
|
await this._loggingService.close();
|
|
347
|
-
await this._queryService.close();
|
|
348
331
|
await this._serviceContext.close();
|
|
349
332
|
await this._level?.close();
|
|
350
333
|
this._open = false;
|
|
@@ -373,7 +356,7 @@ export class ClientServicesHost {
|
|
|
373
356
|
|
|
374
357
|
const automergeIndex = space.automergeSpaceState.rootUrl;
|
|
375
358
|
invariant(automergeIndex);
|
|
376
|
-
const document = await this._serviceContext.
|
|
359
|
+
const document = await this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
|
|
377
360
|
await document.whenReady();
|
|
378
361
|
|
|
379
362
|
// TODO(dmaretskyi): Better API for low-level data access.
|
|
@@ -393,7 +376,7 @@ export class ClientServicesHost {
|
|
|
393
376
|
assignDeep(doc, ['objects', propertiesId], properties);
|
|
394
377
|
});
|
|
395
378
|
|
|
396
|
-
await this._serviceContext.
|
|
379
|
+
await this._serviceContext.echoHost.flush();
|
|
397
380
|
|
|
398
381
|
return identity;
|
|
399
382
|
}
|
|
@@ -20,7 +20,7 @@ describe('DataSpaceManager', () => {
|
|
|
20
20
|
|
|
21
21
|
const peer = builder.createPeer();
|
|
22
22
|
await peer.createIdentity();
|
|
23
|
-
await openAndClose(peer.
|
|
23
|
+
await openAndClose(peer.echoHost, peer.dataSpaceManager);
|
|
24
24
|
|
|
25
25
|
const space = await peer.dataSpaceManager.createSpace();
|
|
26
26
|
|
|
@@ -42,7 +42,7 @@ describe('DataSpaceManager', () => {
|
|
|
42
42
|
const peer2 = builder.createPeer();
|
|
43
43
|
await peer2.createIdentity();
|
|
44
44
|
|
|
45
|
-
await openAndClose(peer1.
|
|
45
|
+
await openAndClose(peer1.echoHost, peer1.dataSpaceManager, peer2.echoHost, peer2.dataSpaceManager);
|
|
46
46
|
|
|
47
47
|
const space1 = await peer1.dataSpaceManager.createSpace();
|
|
48
48
|
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
@@ -111,7 +111,7 @@ describe('DataSpaceManager', () => {
|
|
|
111
111
|
await peer2.createIdentity();
|
|
112
112
|
await peer2.dataSpaceManager.open();
|
|
113
113
|
|
|
114
|
-
await openAndClose(peer1.
|
|
114
|
+
await openAndClose(peer1.echoHost, peer1.dataSpaceManager, peer2.echoHost, peer2.dataSpaceManager);
|
|
115
115
|
|
|
116
116
|
const space1 = await peer1.dataSpaceManager.createSpace();
|
|
117
117
|
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
@@ -153,7 +153,7 @@ describe('DataSpaceManager', () => {
|
|
|
153
153
|
|
|
154
154
|
const peer = builder.createPeer();
|
|
155
155
|
await peer.createIdentity();
|
|
156
|
-
await openAndClose(peer.
|
|
156
|
+
await openAndClose(peer.echoHost, peer.dataSpaceManager);
|
|
157
157
|
|
|
158
158
|
const space = await peer.dataSpaceManager.createSpace();
|
|
159
159
|
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { Context, cancelWithContext } from '@dxos/context';
|
|
7
7
|
import { getCredentialAssertion, type CredentialSigner, type DelegateInvitationCredential } from '@dxos/credentials';
|
|
8
|
-
import { type
|
|
8
|
+
import { type EchoHost } from '@dxos/echo-db';
|
|
9
|
+
import { type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
|
|
9
10
|
import { type FeedStore } from '@dxos/feed-store';
|
|
10
11
|
import { invariant } from '@dxos/invariant';
|
|
11
12
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -79,7 +80,7 @@ export class DataSpaceManager {
|
|
|
79
80
|
private readonly _keyring: Keyring,
|
|
80
81
|
private readonly _signingContext: SigningContext,
|
|
81
82
|
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
82
|
-
private readonly
|
|
83
|
+
private readonly _echoHost: EchoHost,
|
|
83
84
|
private readonly _invitationsManager: InvitationsManager,
|
|
84
85
|
params?: DataSpaceManagerRuntimeParams,
|
|
85
86
|
) {
|
|
@@ -152,14 +153,10 @@ export class DataSpaceManager {
|
|
|
152
153
|
|
|
153
154
|
log('creating space...', { spaceKey });
|
|
154
155
|
|
|
155
|
-
const
|
|
156
|
-
automergeRoot.change((doc: any) => {
|
|
157
|
-
doc.access = { spaceKey: spaceKey.toHex() };
|
|
158
|
-
});
|
|
159
|
-
|
|
156
|
+
const automergeRootUrl = await this._echoHost.createSpaceRoot(spaceKey);
|
|
160
157
|
const space = await this._constructSpace(metadata);
|
|
161
158
|
|
|
162
|
-
const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner,
|
|
159
|
+
const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, automergeRootUrl);
|
|
163
160
|
await this._metadataStore.addSpace(metadata);
|
|
164
161
|
|
|
165
162
|
const memberCredential = credentials[1];
|
|
@@ -243,8 +240,8 @@ export class DataSpaceManager {
|
|
|
243
240
|
gossip.createExtension({ remotePeerId: session.remotePeerId }),
|
|
244
241
|
);
|
|
245
242
|
session.addExtension('dxos.mesh.teleport.notarization', dataSpace.notarizationPlugin.createExtension());
|
|
246
|
-
this.
|
|
247
|
-
session.addExtension('dxos.mesh.teleport.automerge', this.
|
|
243
|
+
this._echoHost.authorizeDevice(space.key, session.remotePeerId);
|
|
244
|
+
session.addExtension('dxos.mesh.teleport.automerge', this._echoHost.createReplicationExtension());
|
|
248
245
|
},
|
|
249
246
|
onAuthFailure: () => {
|
|
250
247
|
log.warn('auth failure');
|
|
@@ -265,6 +262,7 @@ export class DataSpaceManager {
|
|
|
265
262
|
presence,
|
|
266
263
|
keyring: this._keyring,
|
|
267
264
|
feedStore: this._feedStore,
|
|
265
|
+
echoHost: this._echoHost,
|
|
268
266
|
signingContext: this._signingContext,
|
|
269
267
|
callbacks: {
|
|
270
268
|
beforeReady: async () => {
|
|
@@ -282,7 +280,6 @@ export class DataSpaceManager {
|
|
|
282
280
|
},
|
|
283
281
|
},
|
|
284
282
|
cache: metadata.cache,
|
|
285
|
-
automergeHost: this._automergeHost,
|
|
286
283
|
});
|
|
287
284
|
|
|
288
285
|
if (metadata.state !== SpaceState.INACTIVE) {
|
|
@@ -6,14 +6,10 @@ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } fr
|
|
|
6
6
|
import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
7
7
|
import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
|
|
8
8
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
type Space,
|
|
12
|
-
createMappedFeedWriter,
|
|
13
|
-
type AutomergeHost,
|
|
14
|
-
type SpaceDoc,
|
|
15
|
-
} from '@dxos/echo-pipeline';
|
|
9
|
+
import { type EchoHost } from '@dxos/echo-db';
|
|
10
|
+
import { type MetadataStore, type Space, createMappedFeedWriter } from '@dxos/echo-pipeline';
|
|
16
11
|
import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
|
|
12
|
+
import { type SpaceDoc } from '@dxos/echo-protocol';
|
|
17
13
|
import { TYPE_PROPERTIES } from '@dxos/echo-schema';
|
|
18
14
|
import { type FeedStore } from '@dxos/feed-store';
|
|
19
15
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
@@ -66,10 +62,10 @@ export type DataSpaceParams = {
|
|
|
66
62
|
presence: Presence;
|
|
67
63
|
keyring: Keyring;
|
|
68
64
|
feedStore: FeedStore<FeedMessage>;
|
|
65
|
+
echoHost: EchoHost;
|
|
69
66
|
signingContext: SigningContext;
|
|
70
67
|
callbacks?: DataSpaceCallbacks;
|
|
71
68
|
cache?: SpaceCache;
|
|
72
|
-
automergeHost: AutomergeHost;
|
|
73
69
|
};
|
|
74
70
|
|
|
75
71
|
export type CreateEpochOptions = {
|
|
@@ -92,7 +88,7 @@ export class DataSpace {
|
|
|
92
88
|
private readonly _notarizationPlugin = new NotarizationPlugin();
|
|
93
89
|
private readonly _callbacks: DataSpaceCallbacks;
|
|
94
90
|
private readonly _cache?: SpaceCache = undefined;
|
|
95
|
-
private readonly
|
|
91
|
+
private readonly _echoHost: EchoHost;
|
|
96
92
|
|
|
97
93
|
// TODO(dmaretskyi): Move into Space?
|
|
98
94
|
private readonly _automergeSpaceState = new AutomergeSpaceState((rootUrl) => this._onNewAutomergeRoot(rootUrl));
|
|
@@ -120,7 +116,7 @@ export class DataSpace {
|
|
|
120
116
|
this._metadataStore = params.metadataStore;
|
|
121
117
|
this._signingContext = params.signingContext;
|
|
122
118
|
this._callbacks = params.callbacks ?? {};
|
|
123
|
-
this.
|
|
119
|
+
this._echoHost = params.echoHost;
|
|
124
120
|
|
|
125
121
|
this.authVerifier = new TrustedKeySetAuthVerifier({
|
|
126
122
|
trustedKeysProvider: () =>
|
|
@@ -363,8 +359,8 @@ export class DataSpace {
|
|
|
363
359
|
|
|
364
360
|
private _onNewAutomergeRoot(rootUrl: string) {
|
|
365
361
|
log('loading automerge root doc for space', { space: this.key, rootUrl });
|
|
366
|
-
this.
|
|
367
|
-
const handle = this.
|
|
362
|
+
this._echoHost.replicateDocument(rootUrl);
|
|
363
|
+
const handle = this._echoHost.automergeRepo.find(rootUrl as any);
|
|
368
364
|
|
|
369
365
|
queueMicrotask(async () => {
|
|
370
366
|
try {
|
|
@@ -419,7 +415,7 @@ export class DataSpace {
|
|
|
419
415
|
break;
|
|
420
416
|
case CreateEpochRequest.Migration.INIT_AUTOMERGE:
|
|
421
417
|
{
|
|
422
|
-
const document = this.
|
|
418
|
+
const document = this._echoHost.automergeRepo.create();
|
|
423
419
|
// TODO(dmaretskyi): Unify epoch construction.
|
|
424
420
|
epoch = {
|
|
425
421
|
previousId: this._automergeSpaceState.lastEpoch?.id,
|
|
@@ -432,9 +428,9 @@ export class DataSpace {
|
|
|
432
428
|
case CreateEpochRequest.Migration.PRUNE_AUTOMERGE_ROOT_HISTORY:
|
|
433
429
|
{
|
|
434
430
|
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
435
|
-
const rootHandle = this.
|
|
431
|
+
const rootHandle = this._echoHost.automergeRepo.find(currentRootUrl as any);
|
|
436
432
|
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
437
|
-
const newRoot = this.
|
|
433
|
+
const newRoot = this._echoHost.automergeRepo.create(rootHandle.docSync());
|
|
438
434
|
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
439
435
|
// TODO(dmaretskyi): Unify epoch construction.
|
|
440
436
|
epoch = {
|
|
@@ -450,7 +446,7 @@ export class DataSpace {
|
|
|
450
446
|
log.info('Fragmenting');
|
|
451
447
|
|
|
452
448
|
const currentRootUrl = this._automergeSpaceState.rootUrl;
|
|
453
|
-
const rootHandle = this.
|
|
449
|
+
const rootHandle = this._echoHost.automergeRepo.find<SpaceDoc>(currentRootUrl as any);
|
|
454
450
|
await cancelWithContext(this._ctx, asyncTimeout(rootHandle.whenReady(), 10_000));
|
|
455
451
|
|
|
456
452
|
// Find properties object.
|
|
@@ -461,11 +457,11 @@ export class DataSpace {
|
|
|
461
457
|
|
|
462
458
|
// Create a new space doc with the properties object.
|
|
463
459
|
const newSpaceDoc: SpaceDoc = { ...rootHandle.docSync(), objects: Object.fromEntries([properties]) };
|
|
464
|
-
const newRoot = this.
|
|
460
|
+
const newRoot = this._echoHost.automergeRepo.create(newSpaceDoc);
|
|
465
461
|
invariant(typeof newRoot.url === 'string' && newRoot.url.length > 0);
|
|
466
462
|
|
|
467
463
|
// Create new automerge documents for all objects.
|
|
468
|
-
const docLoader = new AutomergeDocumentLoaderImpl(this.key, this.
|
|
464
|
+
const docLoader = new AutomergeDocumentLoaderImpl(this.key, this._echoHost.automergeRepo);
|
|
469
465
|
await docLoader.loadSpaceRootDocHandle(this._ctx, { rootUrl: newRoot.url });
|
|
470
466
|
|
|
471
467
|
otherObjects.forEach(([key, value]) => {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Level } from 'level';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
|
|
8
7
|
import { PublicKey } from '@dxos/keys';
|
|
8
|
+
import { createLevel as createKV } from '@dxos/kv-store';
|
|
9
9
|
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
10
10
|
|
|
11
11
|
import { getRootPath, isPersistent } from './util';
|
|
@@ -13,7 +13,8 @@ import { getRootPath, isPersistent } from './util';
|
|
|
13
13
|
export const createLevel = async (config: Runtime.Client.Storage) => {
|
|
14
14
|
const persistent = isPersistent(config);
|
|
15
15
|
const storagePath = persistent ? path.join(getRootPath(config), 'level') : `/tmp/dxos-${PublicKey.random().toHex()}`;
|
|
16
|
-
const level =
|
|
16
|
+
const level = createKV(storagePath);
|
|
17
|
+
// TODO(dmaretskyi): This function shouldn't call open - .
|
|
17
18
|
await level.open();
|
|
18
19
|
return level;
|
|
19
20
|
};
|
|
@@ -45,6 +45,7 @@ export type PerformInvitationParams = {
|
|
|
45
45
|
guest?: PerformInvitationCallbacks<AuthenticatingInvitation>;
|
|
46
46
|
};
|
|
47
47
|
guestDeviceProfile?: DeviceProfileDocument;
|
|
48
|
+
codeInputDelay?: number;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
export type Result = { invitation?: Invitation; error?: Error };
|
|
@@ -55,7 +56,10 @@ export const performInvitation = ({
|
|
|
55
56
|
options,
|
|
56
57
|
hooks,
|
|
57
58
|
guestDeviceProfile,
|
|
59
|
+
codeInputDelay,
|
|
58
60
|
}: PerformInvitationParams): [Promise<Result>, Promise<Result>] => {
|
|
61
|
+
let guestError = false;
|
|
62
|
+
let guestConnected = false;
|
|
59
63
|
const hostComplete = new Trigger<Result>();
|
|
60
64
|
const guestComplete = new Trigger<Result>();
|
|
61
65
|
const authCode = new Trigger<string>();
|
|
@@ -65,6 +69,10 @@ export const performInvitation = ({
|
|
|
65
69
|
async (hostInvitation: Invitation) => {
|
|
66
70
|
switch (hostInvitation.state) {
|
|
67
71
|
case Invitation.State.CONNECTING: {
|
|
72
|
+
if (guestConnected) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
guestConnected = true;
|
|
68
76
|
if (hooks?.host?.onConnecting?.(hostObservable)) {
|
|
69
77
|
break;
|
|
70
78
|
}
|
|
@@ -89,7 +97,16 @@ export const performInvitation = ({
|
|
|
89
97
|
if (hooks?.guest?.onReady?.(guestObservable)) {
|
|
90
98
|
break;
|
|
91
99
|
}
|
|
92
|
-
|
|
100
|
+
const code = await authCode.wait();
|
|
101
|
+
if (codeInputDelay == null) {
|
|
102
|
+
await guestObservable.authenticate(code);
|
|
103
|
+
} else {
|
|
104
|
+
setTimeout(async () => {
|
|
105
|
+
if (!guestError) {
|
|
106
|
+
await guestObservable.authenticate(code);
|
|
107
|
+
}
|
|
108
|
+
}, codeInputDelay);
|
|
109
|
+
}
|
|
93
110
|
break;
|
|
94
111
|
}
|
|
95
112
|
|
|
@@ -123,6 +140,7 @@ export const performInvitation = ({
|
|
|
123
140
|
}
|
|
124
141
|
},
|
|
125
142
|
(error: Error) => {
|
|
143
|
+
guestError = true;
|
|
126
144
|
if (hooks?.guest?.onError?.(guestObservable)) {
|
|
127
145
|
return;
|
|
128
146
|
}
|
|
@@ -216,8 +234,10 @@ const acceptInvitation = (
|
|
|
216
234
|
invitation = sanitizeInvitation(invitation);
|
|
217
235
|
|
|
218
236
|
if (guest instanceof ServiceContext) {
|
|
219
|
-
|
|
220
|
-
|
|
237
|
+
return guest.invitationsManager.acceptInvitation({
|
|
238
|
+
invitation,
|
|
239
|
+
deviceProfile: guestDeviceProfile,
|
|
240
|
+
});
|
|
221
241
|
}
|
|
222
242
|
|
|
223
243
|
return guest.join(invitation, guestDeviceProfile);
|