@dxos/client-services 0.4.10-main.3e35a2f → 0.4.10-main.3f5e2d2
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-S3G2RM7S.mjs → chunk-HIQTBJPW.mjs} +837 -715
- package/dist/lib/browser/chunk-HIQTBJPW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +10 -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 +132 -116
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-3T6D6GIB.cjs → chunk-JGUWA36I.cjs} +859 -733
- package/dist/lib/node/chunk-JGUWA36I.cjs.map +7 -0
- package/dist/lib/node/index.cjs +50 -44
- 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 +131 -118
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +3 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/index.d.ts +1 -0
- package/dist/types/src/packlets/invitations/index.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts +1 -0
- package/dist/types/src/packlets/invitations/invitation-extension.d.ts.map +1 -1
- 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/invitations-handler.d.ts +8 -3
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +44 -0
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +7 -23
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +6 -6
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +2 -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 +8 -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/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +9 -6
- 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/package.json +34 -34
- package/src/packlets/identity/identity-manager.ts +1 -0
- package/src/packlets/identity/identity.test.ts +3 -0
- package/src/packlets/invitations/device-invitation-protocol.ts +6 -1
- package/src/packlets/invitations/index.ts +1 -0
- package/src/packlets/invitations/invitation-extension.ts +28 -1
- package/src/packlets/invitations/invitation-protocol.ts +7 -1
- package/src/packlets/invitations/invitations-handler.ts +77 -91
- package/src/packlets/invitations/invitations-manager.ts +271 -0
- package/src/packlets/invitations/invitations-service.ts +23 -168
- package/src/packlets/invitations/space-invitation-protocol.ts +45 -3
- package/src/packlets/services/automerge-host.test.ts +4 -4
- package/src/packlets/services/service-context.test.ts +3 -3
- package/src/packlets/services/service-context.ts +23 -25
- package/src/packlets/services/service-host.test.ts +6 -0
- package/src/packlets/services/service-host.ts +9 -29
- package/src/packlets/spaces/data-space-manager.test.ts +4 -4
- package/src/packlets/spaces/data-space-manager.ts +56 -13
- package/src/packlets/spaces/data-space.ts +14 -19
- package/src/packlets/testing/invitation-utils.ts +100 -97
- package/src/packlets/testing/test-builder.ts +29 -16
- package/src/packlets/vault/worker-runtime.ts +3 -1
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-S3G2RM7S.mjs.map +0 -7
- package/dist/lib/node/chunk-3T6D6GIB.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 -15
- package/dist/types/src/packlets/indexing/util.d.ts.map +0 -1
- package/src/packlets/indexing/index.ts +0 -5
- package/src/packlets/indexing/util.ts +0 -89
|
@@ -2,43 +2,22 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event, scheduleTask } from '@dxos/async';
|
|
6
|
-
import { type AuthenticatingInvitation, type CancellableInvitation } from '@dxos/client-protocol';
|
|
7
5
|
import { Stream } from '@dxos/codec-protobuf';
|
|
8
|
-
import { Context } from '@dxos/context';
|
|
9
|
-
import { hasInvitationExpired, type MetadataStore } from '@dxos/echo-pipeline';
|
|
10
|
-
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { log } from '@dxos/log';
|
|
12
6
|
import {
|
|
13
7
|
type AuthenticationRequest,
|
|
14
8
|
type AcceptInvitationRequest,
|
|
15
|
-
Invitation,
|
|
9
|
+
type Invitation,
|
|
16
10
|
type InvitationsService,
|
|
17
11
|
QueryInvitationsResponse,
|
|
18
12
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
19
13
|
|
|
20
|
-
import { type
|
|
21
|
-
import { type InvitationsHandler } from './invitations-handler';
|
|
14
|
+
import { type InvitationsManager } from './invitations-manager';
|
|
22
15
|
|
|
23
16
|
/**
|
|
24
17
|
* Adapts invitation service observable to client/service stream.
|
|
25
18
|
*/
|
|
26
19
|
export class InvitationsServiceImpl implements InvitationsService {
|
|
27
|
-
private readonly
|
|
28
|
-
private readonly _acceptInvitations = new Map<string, AuthenticatingInvitation>();
|
|
29
|
-
private readonly _invitationCreated = new Event<Invitation>();
|
|
30
|
-
private readonly _invitationAccepted = new Event<Invitation>();
|
|
31
|
-
private readonly _removedCreated = new Event<Invitation>();
|
|
32
|
-
private readonly _removedAccepted = new Event<Invitation>();
|
|
33
|
-
private readonly _saved = new Event<Invitation>();
|
|
34
|
-
private readonly _persistentInvitationsLoadedEvent = new Event();
|
|
35
|
-
private _persistentInvitationsLoaded = false;
|
|
36
|
-
|
|
37
|
-
constructor(
|
|
38
|
-
private readonly _invitationsHandler: InvitationsHandler,
|
|
39
|
-
private readonly _getHandler: (invitation: Invitation) => InvitationProtocol,
|
|
40
|
-
private readonly _metadataStore: MetadataStore,
|
|
41
|
-
) {}
|
|
20
|
+
constructor(private readonly _invitationsManager: InvitationsManager) {}
|
|
42
21
|
|
|
43
22
|
// TODO(burdon): Guest/host label.
|
|
44
23
|
getLoggingContext() {
|
|
@@ -48,148 +27,33 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
48
27
|
}
|
|
49
28
|
|
|
50
29
|
createInvitation(options: Invitation): Stream<Invitation> {
|
|
51
|
-
let invitation: CancellableInvitation;
|
|
52
|
-
|
|
53
|
-
const savePersistentInvitationCtx = new Context();
|
|
54
|
-
const existingInvitation = this._createInvitations.get(options.invitationId);
|
|
55
|
-
if (existingInvitation) {
|
|
56
|
-
invitation = existingInvitation;
|
|
57
|
-
} else {
|
|
58
|
-
const handler = this._getHandler(options);
|
|
59
|
-
invitation = this._invitationsHandler.createInvitation(handler, options);
|
|
60
|
-
this._createInvitations.set(invitation.get().invitationId, invitation);
|
|
61
|
-
this._invitationCreated.emit(invitation.get());
|
|
62
|
-
}
|
|
63
|
-
|
|
64
30
|
return new Stream<Invitation>(({ next, close }) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this._saved.emit(invitation.get());
|
|
70
|
-
} catch (err: any) {
|
|
71
|
-
close(err);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
invitation.subscribe(
|
|
76
|
-
(invitation) => {
|
|
77
|
-
next(invitation);
|
|
78
|
-
},
|
|
79
|
-
async (err: Error) => {
|
|
80
|
-
await savePersistentInvitationCtx.dispose();
|
|
81
|
-
|
|
82
|
-
// TODO(nf): also remove from storage?
|
|
83
|
-
close(err);
|
|
84
|
-
},
|
|
85
|
-
async () => {
|
|
86
|
-
close();
|
|
87
|
-
if (invitation.get().persistent) {
|
|
88
|
-
await savePersistentInvitationCtx.dispose();
|
|
89
|
-
// TODO(nf): remove on all complete conditions?
|
|
90
|
-
await this._metadataStore.removeInvitation(invitation.get().invitationId);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this._createInvitations.delete(invitation.get().invitationId);
|
|
94
|
-
if (!invitation.get().multiUse) {
|
|
95
|
-
this._removedCreated.emit(invitation.get());
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async loadPersistentInvitations() {
|
|
103
|
-
const persistentInvitations = this._metadataStore.getInvitations();
|
|
104
|
-
|
|
105
|
-
// get saved persistent invitations, filter and remove from storage those that have expired.
|
|
106
|
-
const freshInvitations = persistentInvitations.filter(async (invitation) => !hasInvitationExpired(invitation));
|
|
107
|
-
|
|
108
|
-
const cInvitations = freshInvitations.map((persistentInvitation) => {
|
|
109
|
-
invariant(!this._createInvitations.get(persistentInvitation.invitationId), 'invitation already exists');
|
|
110
|
-
|
|
111
|
-
const handler = this._getHandler(persistentInvitation);
|
|
112
|
-
const invitation = this._invitationsHandler.createInvitation(handler, persistentInvitation);
|
|
113
|
-
this._createInvitations.set(invitation.get().invitationId, invitation);
|
|
114
|
-
this._invitationCreated.emit(invitation.get());
|
|
115
|
-
return persistentInvitation;
|
|
31
|
+
void this._invitationsManager
|
|
32
|
+
.createInvitation(options)
|
|
33
|
+
.then((invitation) => invitation.subscribe(next, close, close))
|
|
34
|
+
.catch(close);
|
|
116
35
|
});
|
|
117
|
-
this._persistentInvitationsLoadedEvent.emit();
|
|
118
|
-
this._persistentInvitationsLoaded = true;
|
|
119
|
-
return { invitations: cInvitations };
|
|
120
36
|
}
|
|
121
37
|
|
|
122
|
-
acceptInvitation(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// TODO(nf): duplicate check in InvitationHandler
|
|
126
|
-
if (deviceProfile) {
|
|
127
|
-
invariant(options.kind === Invitation.Kind.DEVICE, 'deviceProfile provided for non-device invitation');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const existingInvitation = this._acceptInvitations.get(options.invitationId);
|
|
131
|
-
if (existingInvitation) {
|
|
132
|
-
invitation = existingInvitation;
|
|
133
|
-
} else {
|
|
134
|
-
const handler = this._getHandler(options);
|
|
135
|
-
invitation = this._invitationsHandler.acceptInvitation(handler, options, deviceProfile);
|
|
136
|
-
this._acceptInvitations.set(invitation.get().invitationId, invitation);
|
|
137
|
-
this._invitationAccepted.emit(invitation.get());
|
|
138
|
-
}
|
|
139
|
-
|
|
38
|
+
acceptInvitation(request: AcceptInvitationRequest): Stream<Invitation> {
|
|
39
|
+
const invitation = this._invitationsManager.acceptInvitation(request);
|
|
140
40
|
return new Stream<Invitation>(({ next, close }) => {
|
|
141
|
-
invitation.subscribe(
|
|
142
|
-
(invitation) => {
|
|
143
|
-
next(invitation);
|
|
144
|
-
},
|
|
145
|
-
(err: Error) => {
|
|
146
|
-
close(err);
|
|
147
|
-
},
|
|
148
|
-
() => {
|
|
149
|
-
close();
|
|
150
|
-
this._acceptInvitations.delete(invitation.get().invitationId);
|
|
151
|
-
if (!invitation.get().multiUse) {
|
|
152
|
-
this._removedAccepted.emit(invitation.get());
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
);
|
|
41
|
+
invitation.subscribe(next, close, close);
|
|
156
42
|
});
|
|
157
43
|
}
|
|
158
44
|
|
|
159
|
-
async authenticate(
|
|
160
|
-
|
|
161
|
-
invariant(invitationId);
|
|
162
|
-
const observable = this._acceptInvitations.get(invitationId);
|
|
163
|
-
if (!observable) {
|
|
164
|
-
log.warn('invalid invitation', { invitationId });
|
|
165
|
-
} else {
|
|
166
|
-
await observable.authenticate(authCode);
|
|
167
|
-
}
|
|
45
|
+
async authenticate(request: AuthenticationRequest): Promise<void> {
|
|
46
|
+
return this._invitationsManager.authenticate(request);
|
|
168
47
|
}
|
|
169
48
|
|
|
170
|
-
async cancelInvitation(
|
|
171
|
-
|
|
172
|
-
invariant(invitationId);
|
|
173
|
-
const created = this._createInvitations.get(invitationId);
|
|
174
|
-
const accepted = this._acceptInvitations.get(invitationId);
|
|
175
|
-
if (created) {
|
|
176
|
-
await created.cancel();
|
|
177
|
-
this._createInvitations.delete(invitationId);
|
|
178
|
-
this._removedCreated.emit(created.get());
|
|
179
|
-
if (created.get().persistent) {
|
|
180
|
-
await this._metadataStore.removeInvitation(created.get().invitationId);
|
|
181
|
-
}
|
|
182
|
-
} else if (accepted) {
|
|
183
|
-
await accepted.cancel();
|
|
184
|
-
this._acceptInvitations.delete(invitationId);
|
|
185
|
-
this._removedAccepted.emit(accepted.get());
|
|
186
|
-
}
|
|
49
|
+
async cancelInvitation(request: { invitationId: string }): Promise<void> {
|
|
50
|
+
return this._invitationsManager.cancelInvitation(request);
|
|
187
51
|
}
|
|
188
52
|
|
|
189
53
|
queryInvitations(): Stream<QueryInvitationsResponse> {
|
|
190
54
|
return new Stream<QueryInvitationsResponse>(({ next, ctx }) => {
|
|
191
55
|
// Push added invitations to the stream.
|
|
192
|
-
this.
|
|
56
|
+
this._invitationsManager.invitationCreated.on(ctx, (invitation) => {
|
|
193
57
|
next({
|
|
194
58
|
action: QueryInvitationsResponse.Action.ADDED,
|
|
195
59
|
type: QueryInvitationsResponse.Type.CREATED,
|
|
@@ -197,7 +61,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
197
61
|
});
|
|
198
62
|
});
|
|
199
63
|
|
|
200
|
-
this.
|
|
64
|
+
this._invitationsManager.invitationAccepted.on(ctx, (invitation) => {
|
|
201
65
|
next({
|
|
202
66
|
action: QueryInvitationsResponse.Action.ADDED,
|
|
203
67
|
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
@@ -206,7 +70,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
206
70
|
});
|
|
207
71
|
|
|
208
72
|
// Push removed invitations to the stream.
|
|
209
|
-
this.
|
|
73
|
+
this._invitationsManager.removedCreated.on(ctx, (invitation) => {
|
|
210
74
|
next({
|
|
211
75
|
action: QueryInvitationsResponse.Action.REMOVED,
|
|
212
76
|
type: QueryInvitationsResponse.Type.CREATED,
|
|
@@ -214,7 +78,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
214
78
|
});
|
|
215
79
|
});
|
|
216
80
|
|
|
217
|
-
this.
|
|
81
|
+
this._invitationsManager.removedAccepted.on(ctx, (invitation) => {
|
|
218
82
|
next({
|
|
219
83
|
action: QueryInvitationsResponse.Action.REMOVED,
|
|
220
84
|
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
@@ -223,7 +87,7 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
223
87
|
});
|
|
224
88
|
|
|
225
89
|
// used only for testing
|
|
226
|
-
this.
|
|
90
|
+
this._invitationsManager.saved.on(ctx, (invitation) => {
|
|
227
91
|
next({
|
|
228
92
|
action: QueryInvitationsResponse.Action.SAVED,
|
|
229
93
|
type: QueryInvitationsResponse.Type.CREATED,
|
|
@@ -235,33 +99,24 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
235
99
|
next({
|
|
236
100
|
action: QueryInvitationsResponse.Action.ADDED,
|
|
237
101
|
type: QueryInvitationsResponse.Type.CREATED,
|
|
238
|
-
invitations:
|
|
102
|
+
invitations: this._invitationsManager.getCreatedInvitations(),
|
|
239
103
|
existing: true,
|
|
240
104
|
});
|
|
241
105
|
|
|
242
106
|
next({
|
|
243
107
|
action: QueryInvitationsResponse.Action.ADDED,
|
|
244
108
|
type: QueryInvitationsResponse.Type.ACCEPTED,
|
|
245
|
-
invitations:
|
|
109
|
+
invitations: this._invitationsManager.getAcceptedInvitations(),
|
|
246
110
|
existing: true,
|
|
247
111
|
});
|
|
248
112
|
|
|
249
|
-
|
|
113
|
+
this._invitationsManager.onPersistentInvitationsLoaded(ctx, () => {
|
|
250
114
|
next({
|
|
251
115
|
action: QueryInvitationsResponse.Action.LOAD_COMPLETE,
|
|
252
116
|
type: QueryInvitationsResponse.Type.CREATED,
|
|
253
117
|
// TODO(nf): populate with invitations
|
|
254
118
|
});
|
|
255
|
-
}
|
|
256
|
-
this._persistentInvitationsLoadedEvent.on(ctx, () => {
|
|
257
|
-
next({
|
|
258
|
-
action: QueryInvitationsResponse.Action.LOAD_COMPLETE,
|
|
259
|
-
type: QueryInvitationsResponse.Type.CREATED,
|
|
260
|
-
// TODO(nf): populate with invitations
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
119
|
+
});
|
|
265
120
|
// TODO(nf): expired invitations?
|
|
266
121
|
});
|
|
267
122
|
}
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createAdmissionCredentials,
|
|
7
|
+
createDelegatedSpaceInvitationCredential,
|
|
8
|
+
getCredentialAssertion,
|
|
9
|
+
} from '@dxos/credentials';
|
|
6
10
|
import { writeMessages } from '@dxos/feed-store';
|
|
7
11
|
import { invariant } from '@dxos/invariant';
|
|
8
12
|
import { type Keyring } from '@dxos/keyring';
|
|
@@ -11,7 +15,7 @@ import { log } from '@dxos/log';
|
|
|
11
15
|
import { AlreadyJoinedError } from '@dxos/protocols';
|
|
12
16
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
17
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
14
|
-
import { type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
18
|
+
import { SpaceMember, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
15
19
|
import {
|
|
16
20
|
type AdmissionRequest,
|
|
17
21
|
type AdmissionResponse,
|
|
@@ -43,7 +47,11 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
async admit(
|
|
50
|
+
async admit(
|
|
51
|
+
invitation: Invitation,
|
|
52
|
+
request: AdmissionRequest,
|
|
53
|
+
guestProfile?: ProfileDocument | undefined,
|
|
54
|
+
): Promise<AdmissionResponse> {
|
|
47
55
|
invariant(this._spaceKey);
|
|
48
56
|
const space = await this._spaceManager.spaces.get(this._spaceKey);
|
|
49
57
|
invariant(space);
|
|
@@ -59,6 +67,7 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
59
67
|
space.key,
|
|
60
68
|
space.inner.genesisFeedKey,
|
|
61
69
|
guestProfile,
|
|
70
|
+
invitation.delegationCredentialId,
|
|
62
71
|
);
|
|
63
72
|
|
|
64
73
|
// TODO(dmaretskyi): Refactor.
|
|
@@ -76,6 +85,39 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
76
85
|
};
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
async delegate(invitation: Invitation): Promise<PublicKey> {
|
|
89
|
+
invariant(this._spaceKey);
|
|
90
|
+
const space = await this._spaceManager.spaces.get(this._spaceKey);
|
|
91
|
+
invariant(space);
|
|
92
|
+
if (invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY) {
|
|
93
|
+
invariant(invitation.guestKeypair?.publicKey);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
log('writing delegate space invitation', { host: this._signingContext.deviceKey, id: invitation.invitationId });
|
|
97
|
+
const credential = await createDelegatedSpaceInvitationCredential(
|
|
98
|
+
this._signingContext.credentialSigner,
|
|
99
|
+
space.key,
|
|
100
|
+
{
|
|
101
|
+
invitationId: invitation.invitationId,
|
|
102
|
+
authMethod: invitation.authMethod,
|
|
103
|
+
swarmKey: invitation.swarmKey,
|
|
104
|
+
role: SpaceMember.Role.ADMIN,
|
|
105
|
+
expiresOn: invitation.lifetime
|
|
106
|
+
? new Date((invitation.created?.getTime() ?? Date.now()) + invitation.lifetime)
|
|
107
|
+
: undefined,
|
|
108
|
+
multiUse: invitation.multiUse ?? false,
|
|
109
|
+
guestKey:
|
|
110
|
+
invitation.authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY
|
|
111
|
+
? invitation.guestKeypair!.publicKey
|
|
112
|
+
: undefined,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
invariant(credential.credential);
|
|
117
|
+
await writeMessages(space.inner.controlPipeline.writer, [credential]);
|
|
118
|
+
return credential.credential.credential.id!;
|
|
119
|
+
}
|
|
120
|
+
|
|
79
121
|
checkInvitation(invitation: Partial<Invitation>) {
|
|
80
122
|
if (invitation.spaceKey && this._spaceManager.spaces.has(invitation.spaceKey)) {
|
|
81
123
|
return new AlreadyJoinedError('Already joined space.');
|
|
@@ -4,10 +4,11 @@
|
|
|
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';
|
|
9
|
+
import { AutomergeContext } from '@dxos/echo-db';
|
|
8
10
|
import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
|
|
9
11
|
import { createTestLevel } from '@dxos/echo-pipeline/testing';
|
|
10
|
-
import { AutomergeContext } from '@dxos/echo-schema';
|
|
11
12
|
import { afterTest, describe, test } from '@dxos/test';
|
|
12
13
|
|
|
13
14
|
describe('AutomergeHost', () => {
|
|
@@ -50,9 +51,8 @@ describe('AutomergeHost', () => {
|
|
|
50
51
|
doc.change((doc: any) => {
|
|
51
52
|
doc.text = newText;
|
|
52
53
|
});
|
|
54
|
+
await client.flush({ states: [{ documentId: doc.documentId, heads: getHeads(doc.docSync()) }] });
|
|
53
55
|
|
|
54
|
-
// TODO(mykola): Is there a way to know when automerge has started replication?
|
|
55
|
-
await sleep(100);
|
|
56
56
|
await asyncTimeout(handle.whenReady(), 1_000);
|
|
57
57
|
expect(handle.docSync().text).to.equal(newText);
|
|
58
58
|
});
|
|
@@ -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(
|
|
@@ -8,9 +8,9 @@ import { Trigger } from '@dxos/async';
|
|
|
8
8
|
import { Context, Resource } from '@dxos/context';
|
|
9
9
|
import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
|
|
10
10
|
import { failUndefined } from '@dxos/debug';
|
|
11
|
-
import {
|
|
11
|
+
import { EchoHost } from '@dxos/echo-db';
|
|
12
|
+
import { MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
12
13
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
13
|
-
import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
|
|
14
14
|
import { invariant } from '@dxos/invariant';
|
|
15
15
|
import { Keyring } from '@dxos/keyring';
|
|
16
16
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -32,13 +32,13 @@ import {
|
|
|
32
32
|
type IdentityManagerRuntimeParams,
|
|
33
33
|
type JoinIdentityParams,
|
|
34
34
|
} from '../identity';
|
|
35
|
-
import { createDocumentsIterator, createSelectedDocumentsIterator } from '../indexing';
|
|
36
35
|
import {
|
|
37
36
|
DeviceInvitationProtocol,
|
|
38
37
|
InvitationsHandler,
|
|
39
38
|
SpaceInvitationProtocol,
|
|
40
39
|
type InvitationProtocol,
|
|
41
40
|
} from '../invitations';
|
|
41
|
+
import { InvitationsManager } from '../invitations/invitations-manager';
|
|
42
42
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
43
43
|
|
|
44
44
|
export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
|
|
@@ -62,9 +62,8 @@ export class ServiceContext extends Resource {
|
|
|
62
62
|
public readonly spaceManager: SpaceManager;
|
|
63
63
|
public readonly identityManager: IdentityManager;
|
|
64
64
|
public readonly invitations: InvitationsHandler;
|
|
65
|
-
public readonly
|
|
66
|
-
public readonly
|
|
67
|
-
public readonly indexer: Indexer;
|
|
65
|
+
public readonly invitationsManager: InvitationsManager;
|
|
66
|
+
public readonly echoHost: EchoHost;
|
|
68
67
|
|
|
69
68
|
// Initialized after identity is initialized.
|
|
70
69
|
public dataSpaceManager?: DataSpaceManager;
|
|
@@ -120,22 +119,17 @@ export class ServiceContext extends Resource {
|
|
|
120
119
|
this._runtimeParams as IdentityManagerRuntimeParams,
|
|
121
120
|
);
|
|
122
121
|
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
directory: storage.createDirectory('automerge'),
|
|
127
|
-
db: level.sublevel('automerge'),
|
|
128
|
-
metadata: this.indexMetadata,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
this.indexer = new Indexer({
|
|
132
|
-
indexStore: new IndexStore({ directory: storage.createDirectory('index-store') }),
|
|
133
|
-
metadataStore: this.indexMetadata,
|
|
134
|
-
loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
|
|
135
|
-
getAllDocuments: createDocumentsIterator(this.automergeHost),
|
|
122
|
+
this.echoHost = new EchoHost({
|
|
123
|
+
kv: this.level,
|
|
124
|
+
storage: this.storage,
|
|
136
125
|
});
|
|
137
126
|
|
|
138
127
|
this.invitations = new InvitationsHandler(this.networkManager);
|
|
128
|
+
this.invitationsManager = new InvitationsManager(
|
|
129
|
+
this.invitations,
|
|
130
|
+
(invitation) => this.getInvitationHandler(invitation),
|
|
131
|
+
this.metadataStore,
|
|
132
|
+
);
|
|
139
133
|
|
|
140
134
|
// TODO(burdon): _initialize called in multiple places.
|
|
141
135
|
// TODO(burdon): Call _initialize on success.
|
|
@@ -159,31 +153,34 @@ export class ServiceContext extends Resource {
|
|
|
159
153
|
await this.signalManager.open();
|
|
160
154
|
await this.networkManager.open();
|
|
161
155
|
|
|
162
|
-
await this.
|
|
156
|
+
await this.echoHost.open(ctx);
|
|
163
157
|
await this.metadataStore.load();
|
|
164
158
|
await this.spaceManager.open();
|
|
165
159
|
await this.identityManager.open(ctx);
|
|
166
160
|
if (this.identityManager.identity) {
|
|
167
161
|
await this._initialize(ctx);
|
|
168
162
|
}
|
|
163
|
+
|
|
164
|
+
const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
|
|
165
|
+
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
166
|
+
|
|
169
167
|
log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
|
|
170
168
|
log('opened');
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
protected override async _close() {
|
|
171
|
+
protected override async _close(ctx: Context) {
|
|
174
172
|
log('closing...');
|
|
175
173
|
if (this._deviceSpaceSync && this.identityManager.identity) {
|
|
176
174
|
await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
|
|
177
175
|
}
|
|
178
|
-
await this.automergeHost.close();
|
|
179
176
|
await this.dataSpaceManager?.close();
|
|
180
177
|
await this.identityManager.close();
|
|
181
178
|
await this.spaceManager.close();
|
|
182
179
|
await this.feedStore.close();
|
|
180
|
+
await this.metadataStore.close();
|
|
181
|
+
await this.echoHost.close(ctx);
|
|
183
182
|
await this.networkManager.close();
|
|
184
183
|
await this.signalManager.close();
|
|
185
|
-
await this.metadataStore.close();
|
|
186
|
-
await this.indexer.destroy();
|
|
187
184
|
log('closed');
|
|
188
185
|
}
|
|
189
186
|
|
|
@@ -244,7 +241,8 @@ export class ServiceContext extends Resource {
|
|
|
244
241
|
this.keyring,
|
|
245
242
|
signingContext,
|
|
246
243
|
this.feedStore,
|
|
247
|
-
this.
|
|
244
|
+
this.echoHost,
|
|
245
|
+
this.invitationsManager,
|
|
248
246
|
this._runtimeParams as DataSpaceManagerRuntimeParams,
|
|
249
247
|
);
|
|
250
248
|
await this.dataSpaceManager.open();
|
|
@@ -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());
|
|
@@ -8,15 +8,8 @@ import { Event, synchronized } from '@dxos/async';
|
|
|
8
8
|
import { clientServiceBundle, defaultKey, type ClientServices, Properties } from '@dxos/client-protocol';
|
|
9
9
|
import { type Config } from '@dxos/config';
|
|
10
10
|
import { Context } from '@dxos/context';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
type ObjectStructure,
|
|
14
|
-
encodeReference,
|
|
15
|
-
type SpaceDoc,
|
|
16
|
-
type MyLevel,
|
|
17
|
-
} from '@dxos/echo-pipeline';
|
|
18
|
-
import * as E from '@dxos/echo-schema';
|
|
19
|
-
import { IndexServiceImpl } from '@dxos/indexing';
|
|
11
|
+
import { type ObjectStructure, encodeReference, type SpaceDoc, type LevelDB } from '@dxos/echo-pipeline';
|
|
12
|
+
import { getTypeReference } from '@dxos/echo-schema';
|
|
20
13
|
import { invariant } from '@dxos/invariant';
|
|
21
14
|
import { PublicKey } from '@dxos/keys';
|
|
22
15
|
import { log } from '@dxos/log';
|
|
@@ -56,7 +49,7 @@ export type ClientServicesHostParams = {
|
|
|
56
49
|
signalManager?: SignalManager;
|
|
57
50
|
connectionLog?: boolean;
|
|
58
51
|
storage?: Storage;
|
|
59
|
-
level?:
|
|
52
|
+
level?: LevelDB;
|
|
60
53
|
lockKey?: string;
|
|
61
54
|
callbacks?: ClientServicesHostCallbacks;
|
|
62
55
|
runtimeParams?: ServiceContextRuntimeParams;
|
|
@@ -272,11 +265,7 @@ export class ClientServicesHost {
|
|
|
272
265
|
(profile) => this._serviceContext.broadcastProfileUpdate(profile),
|
|
273
266
|
),
|
|
274
267
|
|
|
275
|
-
InvitationsService: new InvitationsServiceImpl(
|
|
276
|
-
this._serviceContext.invitations,
|
|
277
|
-
(invitation) => this._serviceContext.getInvitationHandler(invitation),
|
|
278
|
-
this._serviceContext.metadataStore,
|
|
279
|
-
),
|
|
268
|
+
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
280
269
|
|
|
281
270
|
DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
|
|
282
271
|
|
|
@@ -289,12 +278,8 @@ export class ClientServicesHost {
|
|
|
289
278
|
},
|
|
290
279
|
),
|
|
291
280
|
|
|
292
|
-
DataService:
|
|
293
|
-
|
|
294
|
-
IndexService: new IndexServiceImpl({
|
|
295
|
-
indexer: this._serviceContext.indexer,
|
|
296
|
-
automergeHost: this._serviceContext.automergeHost,
|
|
297
|
-
}),
|
|
281
|
+
DataService: this._serviceContext.echoHost.dataService,
|
|
282
|
+
QueryService: this._serviceContext.echoHost.queryService,
|
|
298
283
|
|
|
299
284
|
NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
|
|
300
285
|
|
|
@@ -310,11 +295,6 @@ export class ClientServicesHost {
|
|
|
310
295
|
});
|
|
311
296
|
|
|
312
297
|
await this._serviceContext.open(ctx);
|
|
313
|
-
// TODO(nf): move to InvitationManager in ServiceContext?
|
|
314
|
-
invariant(this.serviceRegistry.services.InvitationsService);
|
|
315
|
-
const loadedInvitations = await this.serviceRegistry.services.InvitationsService.loadPersistentInvitations();
|
|
316
|
-
|
|
317
|
-
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
318
298
|
|
|
319
299
|
const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
|
|
320
300
|
if (devtoolsProxy) {
|
|
@@ -377,13 +357,13 @@ export class ClientServicesHost {
|
|
|
377
357
|
|
|
378
358
|
const automergeIndex = space.automergeSpaceState.rootUrl;
|
|
379
359
|
invariant(automergeIndex);
|
|
380
|
-
const document = await this._serviceContext.
|
|
360
|
+
const document = await this._serviceContext.echoHost.automergeRepo.find<SpaceDoc>(automergeIndex as any);
|
|
381
361
|
await document.whenReady();
|
|
382
362
|
|
|
383
363
|
// TODO(dmaretskyi): Better API for low-level data access.
|
|
384
364
|
const properties: ObjectStructure = {
|
|
385
365
|
system: {
|
|
386
|
-
type: encodeReference(
|
|
366
|
+
type: encodeReference(getTypeReference(Properties)!),
|
|
387
367
|
},
|
|
388
368
|
data: {
|
|
389
369
|
[defaultKey]: identity.identityKey.toHex(),
|
|
@@ -397,7 +377,7 @@ export class ClientServicesHost {
|
|
|
397
377
|
assignDeep(doc, ['objects', propertiesId], properties);
|
|
398
378
|
});
|
|
399
379
|
|
|
400
|
-
await this._serviceContext.
|
|
380
|
+
await this._serviceContext.echoHost.flush();
|
|
401
381
|
|
|
402
382
|
return identity;
|
|
403
383
|
}
|
|
@@ -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);
|