@dxos/client-services 0.5.0 → 0.5.1-main.0ec204c
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-5I77NB7R.mjs} +1437 -1119
- package/dist/lib/browser/chunk-5I77NB7R.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +36 -5
- 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-W24O4MIS.cjs} +1552 -1238
- package/dist/lib/node/chunk-W24O4MIS.cjs.map +7 -0
- package/dist/lib/node/index.cjs +78 -47
- 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/diagnostics/diagnostics-collector.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
- 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/spaces/spaces-service.d.ts +2 -1
- package/dist/types/src/packlets/spaces/spaces-service.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/shell-runtime.d.ts +10 -2
- package/dist/types/src/packlets/vault/shell-runtime.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/diagnostics/diagnostics-collector.ts +14 -9
- package/src/packlets/diagnostics/diagnostics.ts +78 -68
- 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 +45 -3
- package/src/packlets/invitations/space-invitation-protocol.ts +23 -3
- 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 +11 -28
- 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 +16 -19
- package/src/packlets/spaces/genesis.ts +1 -1
- package/src/packlets/spaces/spaces-service.ts +12 -6
- 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/shell-runtime.ts +40 -2
- 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
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event, PushStream } from '@dxos/async';
|
|
5
|
+
import { Event, PushStream, TimeoutError, Trigger } from '@dxos/async';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
AuthenticatingInvitation,
|
|
8
8
|
AUTHENTICATION_CODE_LENGTH,
|
|
9
9
|
CancellableInvitation,
|
|
10
10
|
INVITATION_TIMEOUT,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type AuthenticationRequest,
|
|
21
21
|
Invitation,
|
|
22
22
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
23
|
+
import { SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
24
|
|
|
24
25
|
import type { InvitationProtocol } from './invitation-protocol';
|
|
25
26
|
import { createAdmissionKeypair, type InvitationsHandler } from './invitations-handler';
|
|
@@ -117,7 +118,8 @@ export class InvitationsManager {
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
const handler = this._getHandler(options);
|
|
120
|
-
const invitation = this.
|
|
121
|
+
const { ctx, invitation, stream, otpEnteredTrigger } = this._createObservableAcceptingInvitation(handler, options);
|
|
122
|
+
this._invitationsHandler.acceptInvitation(ctx, stream, handler, options, otpEnteredTrigger, request.deviceProfile);
|
|
121
123
|
this._acceptInvitations.set(invitation.get().invitationId, invitation);
|
|
122
124
|
this.invitationAccepted.emit(invitation.get());
|
|
123
125
|
|
|
@@ -149,6 +151,10 @@ export class InvitationsManager {
|
|
|
149
151
|
if (created.get().persistent) {
|
|
150
152
|
await this._metadataStore.removeInvitation(invitationId);
|
|
151
153
|
}
|
|
154
|
+
if (created.get().type === Invitation.Type.DELEGATED) {
|
|
155
|
+
const handler = this._getHandler(created.get());
|
|
156
|
+
await handler.cancelDelegation(created.get());
|
|
157
|
+
}
|
|
152
158
|
await created.cancel();
|
|
153
159
|
this._createInvitations.delete(invitationId);
|
|
154
160
|
this.removedCreated.emit(created.get());
|
|
@@ -190,6 +196,7 @@ export class InvitationsManager {
|
|
|
190
196
|
persistent = options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
|
|
191
197
|
created = new Date(),
|
|
192
198
|
guestKeypair = undefined,
|
|
199
|
+
role = SpaceMember.Role.ADMIN,
|
|
193
200
|
lifetime = 86400, // 1 day,
|
|
194
201
|
multiUse = false,
|
|
195
202
|
} = options ?? {};
|
|
@@ -210,6 +217,7 @@ export class InvitationsManager {
|
|
|
210
217
|
guestKeypair ?? (authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? createAdmissionKeypair() : undefined),
|
|
211
218
|
created,
|
|
212
219
|
lifetime,
|
|
220
|
+
role,
|
|
213
221
|
multiUse,
|
|
214
222
|
delegationCredentialId: options?.delegationCredentialId,
|
|
215
223
|
...protocol.getInvitationContext(),
|
|
@@ -239,6 +247,40 @@ export class InvitationsManager {
|
|
|
239
247
|
return { ctx, stream, observableInvitation };
|
|
240
248
|
}
|
|
241
249
|
|
|
250
|
+
private _createObservableAcceptingInvitation(handler: InvitationProtocol, initialState: Invitation) {
|
|
251
|
+
const otpEnteredTrigger = new Trigger<string>();
|
|
252
|
+
const stream = new PushStream<Invitation>();
|
|
253
|
+
const ctx = new Context({
|
|
254
|
+
onError: (err) => {
|
|
255
|
+
if (err instanceof TimeoutError) {
|
|
256
|
+
log('timeout', { ...handler.toJSON() });
|
|
257
|
+
stream.next({ ...initialState, state: Invitation.State.TIMEOUT });
|
|
258
|
+
} else {
|
|
259
|
+
log.warn('auth failed', err);
|
|
260
|
+
stream.next({ ...initialState, state: Invitation.State.ERROR });
|
|
261
|
+
}
|
|
262
|
+
void ctx.dispose();
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
ctx.onDispose(() => {
|
|
266
|
+
log('complete', { ...handler.toJSON() });
|
|
267
|
+
stream.complete();
|
|
268
|
+
});
|
|
269
|
+
const invitation = new AuthenticatingInvitation({
|
|
270
|
+
initialInvitation: initialState,
|
|
271
|
+
subscriber: stream.observable,
|
|
272
|
+
onCancel: async () => {
|
|
273
|
+
stream.next({ ...initialState, state: Invitation.State.CANCELLED });
|
|
274
|
+
await ctx.dispose();
|
|
275
|
+
},
|
|
276
|
+
onAuthenticate: async (code: string) => {
|
|
277
|
+
// TODO(burdon): Reset creates a race condition? Event?
|
|
278
|
+
otpEnteredTrigger.wake(code);
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return { ctx, invitation, stream, otpEnteredTrigger };
|
|
282
|
+
}
|
|
283
|
+
|
|
242
284
|
private async _persistIfRequired(
|
|
243
285
|
handler: InvitationProtocol,
|
|
244
286
|
changeStream: PushStream<Invitation>,
|
|
@@ -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';
|
|
@@ -53,7 +54,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
53
54
|
guestProfile?: ProfileDocument | undefined,
|
|
54
55
|
): Promise<AdmissionResponse> {
|
|
55
56
|
invariant(this._spaceKey);
|
|
56
|
-
const space =
|
|
57
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
57
58
|
invariant(space);
|
|
58
59
|
|
|
59
60
|
invariant(request.space);
|
|
@@ -66,6 +67,8 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
66
67
|
identityKey,
|
|
67
68
|
space.key,
|
|
68
69
|
space.inner.genesisFeedKey,
|
|
70
|
+
invitation.role ?? SpaceMember.Role.ADMIN,
|
|
71
|
+
space.inner.spaceState.membershipChainHeads,
|
|
69
72
|
guestProfile,
|
|
70
73
|
invitation.delegationCredentialId,
|
|
71
74
|
);
|
|
@@ -87,7 +90,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
87
90
|
|
|
88
91
|
async delegate(invitation: Invitation): Promise<PublicKey> {
|
|
89
92
|
invariant(this._spaceKey);
|
|
90
|
-
const space =
|
|
93
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
91
94
|
invariant(space);
|
|
92
95
|
if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
|
|
93
96
|
invariant(invitation.guestKeypair?.publicKey);
|
|
@@ -101,7 +104,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
101
104
|
invitationId: invitation.invitationId,
|
|
102
105
|
authMethod: invitation.authMethod,
|
|
103
106
|
swarmKey: invitation.swarmKey,
|
|
104
|
-
role: SpaceMember.Role.ADMIN,
|
|
107
|
+
role: invitation.role ?? SpaceMember.Role.ADMIN,
|
|
105
108
|
expiresOn: invitation.lifetime
|
|
106
109
|
? new Date((invitation.created?.getTime() ?? Date.now()) + invitation.lifetime)
|
|
107
110
|
: undefined,
|
|
@@ -118,6 +121,23 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
118
121
|
return credential.credential.credential.id!;
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
async cancelDelegation(invitation: Invitation): Promise<void> {
|
|
125
|
+
invariant(this._spaceKey);
|
|
126
|
+
invariant(invitation.type === Invitation.Type.DELEGATED && invitation.delegationCredentialId);
|
|
127
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
128
|
+
invariant(space);
|
|
129
|
+
|
|
130
|
+
log('cancelling delegated space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
|
|
131
|
+
const credential = await createCancelDelegatedSpaceInvitationCredential(
|
|
132
|
+
this._signingContext.credentialSigner,
|
|
133
|
+
space.key,
|
|
134
|
+
invitation.delegationCredentialId,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
invariant(credential.credential);
|
|
138
|
+
await writeMessages(space.inner.controlPipeline.writer, [credential]);
|
|
139
|
+
}
|
|
140
|
+
|
|
121
141
|
checkInvitation(invitation: Partial<Invitation>) {
|
|
122
142
|
if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
|
|
123
143
|
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;
|
|
@@ -356,10 +339,10 @@ export class ClientServicesHost {
|
|
|
356
339
|
const traceId = PublicKey.random().toHex();
|
|
357
340
|
log.trace('dxos.sdk.client-services-host.reset', trace.begin({ id: traceId }));
|
|
358
341
|
|
|
359
|
-
log('resetting...');
|
|
342
|
+
log.info('resetting...');
|
|
360
343
|
await this._serviceContext?.close();
|
|
361
344
|
await this._storage!.reset();
|
|
362
|
-
log('reset');
|
|
345
|
+
log.info('reset');
|
|
363
346
|
log.trace('dxos.sdk.client-services-host.reset', trace.end({ id: traceId }));
|
|
364
347
|
await this._callbacks?.onReset?.();
|
|
365
348
|
}
|
|
@@ -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) {
|