@dxos/client-services 0.4.10-main.fd4f2a3 → 0.4.10-main.fd8ea31
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-V2ALN47T.mjs → chunk-WLE7E36I.mjs} +1402 -1074
- package/dist/lib/browser/chunk-WLE7E36I.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +13 -3
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +133 -115
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-7WXQVUAE.cjs → chunk-YXZQQAQN.cjs} +1308 -1063
- package/dist/lib/node/chunk-YXZQQAQN.cjs.map +7 -0
- package/dist/lib/node/index.cjs +49 -39
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +133 -118
- package/dist/lib/node/packlets/testing/index.cjs.map +3 -3
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +5 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts +15 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -0
- package/dist/types/src/packlets/{services → diagnostics}/diagnostics.d.ts +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts +4 -0
- package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/indexing/util.d.ts +2 -6
- package/dist/types/src/packlets/indexing/util.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 -4
- 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/index.d.ts +1 -1
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +5 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/util.d.ts +1 -0
- package/dist/types/src/packlets/services/util.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +5 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts +1 -1
- package/dist/types/src/packlets/system/system-service.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 +6 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +35 -34
- package/src/index.ts +1 -0
- package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +94 -0
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +20 -0
- package/src/packlets/diagnostics/diagnostics-collector.ts +65 -0
- package/src/packlets/{services → diagnostics}/diagnostics.ts +2 -2
- package/src/packlets/diagnostics/index.ts +7 -0
- package/src/packlets/identity/identity-manager.ts +1 -0
- package/src/packlets/identity/identity.test.ts +3 -0
- package/src/packlets/indexing/util.ts +9 -66
- 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 +75 -96
- 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 +10 -4
- package/src/packlets/services/index.ts +1 -1
- package/src/packlets/services/service-context.test.ts +4 -1
- package/src/packlets/services/service-context.ts +19 -5
- package/src/packlets/services/service-host.ts +34 -19
- package/src/packlets/services/util.ts +2 -0
- package/src/packlets/spaces/data-space-manager.test.ts +4 -4
- package/src/packlets/spaces/data-space-manager.ts +48 -2
- package/src/packlets/spaces/data-space.ts +1 -1
- package/src/packlets/storage/level.ts +1 -1
- package/src/packlets/system/system-service.ts +1 -1
- package/src/packlets/testing/invitation-utils.ts +100 -97
- package/src/packlets/testing/test-builder.ts +39 -5
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-V2ALN47T.mjs.map +0 -7
- package/dist/lib/node/chunk-7WXQVUAE.cjs.map +0 -7
- package/dist/types/src/packlets/services/diagnostics.d.ts.map +0 -1
|
@@ -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 { 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 { invitationExpired, 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().type !== Invitation.Type.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) => !invitationExpired(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().type !== Invitation.Type.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.');
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
7
|
import { asyncTimeout, sleep } from '@dxos/async';
|
|
8
|
+
import { AutomergeContext } from '@dxos/echo-db';
|
|
8
9
|
import { AutomergeHost, DataServiceImpl } from '@dxos/echo-pipeline';
|
|
9
|
-
import {
|
|
10
|
-
import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
10
|
+
import { createTestLevel } from '@dxos/echo-pipeline/testing';
|
|
11
11
|
import { afterTest, describe, test } from '@dxos/test';
|
|
12
12
|
|
|
13
13
|
describe('AutomergeHost', () => {
|
|
@@ -19,10 +19,16 @@ describe('AutomergeHost', () => {
|
|
|
19
19
|
// creates repo and document | replicates repo | finds document in repo
|
|
20
20
|
//
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const level = createTestLevel();
|
|
23
|
+
await level.open();
|
|
24
|
+
afterTest(() => level.close());
|
|
23
25
|
|
|
24
|
-
const host = new AutomergeHost({
|
|
26
|
+
const host = new AutomergeHost({
|
|
27
|
+
db: level.sublevel('automerge'),
|
|
28
|
+
});
|
|
29
|
+
await host.open();
|
|
25
30
|
afterTest(() => host.close());
|
|
31
|
+
|
|
26
32
|
const dataService = new DataServiceImpl(host);
|
|
27
33
|
const client = new AutomergeContext(dataService);
|
|
28
34
|
afterTest(() => client.close());
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
6
6
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
-
import { describe, test } from '@dxos/test';
|
|
7
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
8
8
|
|
|
9
9
|
import { createServiceContext } from '../testing';
|
|
10
10
|
import { performInvitation } from '../testing/invitation-utils';
|
|
@@ -27,12 +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.automergeHost);
|
|
30
31
|
await device1.createIdentity();
|
|
31
32
|
|
|
32
33
|
const device2 = await createServiceContext({ signalContext: networkContext });
|
|
34
|
+
await openAndClose(device2.automergeHost);
|
|
33
35
|
await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
|
|
34
36
|
|
|
35
37
|
const identity2 = await createServiceContext({ signalContext: networkContext });
|
|
38
|
+
await openAndClose(identity2.automergeHost);
|
|
36
39
|
await identity2.createIdentity();
|
|
37
40
|
const space1 = await identity2.dataSpaceManager!.createSpace();
|
|
38
41
|
await Promise.all(
|
|
@@ -10,7 +10,7 @@ import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credenti
|
|
|
10
10
|
import { failUndefined } from '@dxos/debug';
|
|
11
11
|
import { AutomergeHost, MetadataStore, SnapshotStore, SpaceManager, valueEncoding } from '@dxos/echo-pipeline';
|
|
12
12
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
13
|
-
import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
|
|
13
|
+
import { IndexMetadataStore, IndexStore, Indexer, createStorageCallbacks } 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,14 @@ import {
|
|
|
32
32
|
type IdentityManagerRuntimeParams,
|
|
33
33
|
type JoinIdentityParams,
|
|
34
34
|
} from '../identity';
|
|
35
|
-
import {
|
|
35
|
+
import { createSelectedDocumentsIterator } from '../indexing';
|
|
36
36
|
import {
|
|
37
37
|
DeviceInvitationProtocol,
|
|
38
38
|
InvitationsHandler,
|
|
39
39
|
SpaceInvitationProtocol,
|
|
40
40
|
type InvitationProtocol,
|
|
41
41
|
} from '../invitations';
|
|
42
|
+
import { InvitationsManager } from '../invitations/invitations-manager';
|
|
42
43
|
import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
|
|
43
44
|
|
|
44
45
|
export type ServiceContextRuntimeParams = IdentityManagerRuntimeParams & DataSpaceManagerRuntimeParams;
|
|
@@ -62,6 +63,7 @@ export class ServiceContext extends Resource {
|
|
|
62
63
|
public readonly spaceManager: SpaceManager;
|
|
63
64
|
public readonly identityManager: IdentityManager;
|
|
64
65
|
public readonly invitations: InvitationsHandler;
|
|
66
|
+
public readonly invitationsManager: InvitationsManager;
|
|
65
67
|
public readonly automergeHost: AutomergeHost;
|
|
66
68
|
public readonly indexMetadata: IndexMetadataStore;
|
|
67
69
|
public readonly indexer: Indexer;
|
|
@@ -124,17 +126,23 @@ export class ServiceContext extends Resource {
|
|
|
124
126
|
|
|
125
127
|
this.automergeHost = new AutomergeHost({
|
|
126
128
|
directory: storage.createDirectory('automerge'),
|
|
127
|
-
|
|
129
|
+
db: level.sublevel('automerge'),
|
|
130
|
+
storageCallbacks: createStorageCallbacks({ host: () => this.automergeHost, metadata: this.indexMetadata }),
|
|
128
131
|
});
|
|
129
132
|
|
|
130
133
|
this.indexer = new Indexer({
|
|
131
|
-
|
|
134
|
+
db: this.level,
|
|
135
|
+
indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
|
|
132
136
|
metadataStore: this.indexMetadata,
|
|
133
137
|
loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
|
|
134
|
-
getAllDocuments: createDocumentsIterator(this.automergeHost),
|
|
135
138
|
});
|
|
136
139
|
|
|
137
140
|
this.invitations = new InvitationsHandler(this.networkManager);
|
|
141
|
+
this.invitationsManager = new InvitationsManager(
|
|
142
|
+
this.invitations,
|
|
143
|
+
(invitation) => this.getInvitationHandler(invitation),
|
|
144
|
+
this.metadataStore,
|
|
145
|
+
);
|
|
138
146
|
|
|
139
147
|
// TODO(burdon): _initialize called in multiple places.
|
|
140
148
|
// TODO(burdon): Call _initialize on success.
|
|
@@ -158,12 +166,17 @@ export class ServiceContext extends Resource {
|
|
|
158
166
|
await this.signalManager.open();
|
|
159
167
|
await this.networkManager.open();
|
|
160
168
|
|
|
169
|
+
await this.automergeHost.open();
|
|
161
170
|
await this.metadataStore.load();
|
|
162
171
|
await this.spaceManager.open();
|
|
163
172
|
await this.identityManager.open(ctx);
|
|
164
173
|
if (this.identityManager.identity) {
|
|
165
174
|
await this._initialize(ctx);
|
|
166
175
|
}
|
|
176
|
+
|
|
177
|
+
const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
|
|
178
|
+
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
179
|
+
|
|
167
180
|
log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
|
|
168
181
|
log('opened');
|
|
169
182
|
}
|
|
@@ -243,6 +256,7 @@ export class ServiceContext extends Resource {
|
|
|
243
256
|
signingContext,
|
|
244
257
|
this.feedStore,
|
|
245
258
|
this.automergeHost,
|
|
259
|
+
this.invitationsManager,
|
|
246
260
|
this._runtimeParams as DataSpaceManagerRuntimeParams,
|
|
247
261
|
);
|
|
248
262
|
await this.dataSpaceManager.open();
|
|
@@ -8,9 +8,15 @@ 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
|
-
|
|
11
|
+
import {
|
|
12
|
+
DataServiceImpl,
|
|
13
|
+
type ObjectStructure,
|
|
14
|
+
encodeReference,
|
|
15
|
+
type SpaceDoc,
|
|
16
|
+
type LevelDB,
|
|
17
|
+
} from '@dxos/echo-pipeline';
|
|
18
|
+
import { getTypeReference } from '@dxos/echo-schema';
|
|
19
|
+
import { QueryServiceImpl } from '@dxos/indexing';
|
|
14
20
|
import { invariant } from '@dxos/invariant';
|
|
15
21
|
import { PublicKey } from '@dxos/keys';
|
|
16
22
|
import { log } from '@dxos/log';
|
|
@@ -23,11 +29,15 @@ import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
|
23
29
|
import { assignDeep } from '@dxos/util';
|
|
24
30
|
import { WebsocketRpcClient } from '@dxos/websocket-rpc';
|
|
25
31
|
|
|
26
|
-
import { createDiagnostics } from './diagnostics';
|
|
27
32
|
import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
|
|
28
33
|
import { ServiceRegistry } from './service-registry';
|
|
29
34
|
import { DevicesServiceImpl } from '../devices';
|
|
30
35
|
import { DevtoolsHostEvents, DevtoolsServiceImpl } from '../devtools';
|
|
36
|
+
import {
|
|
37
|
+
type CollectDiagnosticsBroadcastHandler,
|
|
38
|
+
createCollectDiagnosticsBroadcastHandler,
|
|
39
|
+
createDiagnostics,
|
|
40
|
+
} from '../diagnostics';
|
|
31
41
|
import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
|
|
32
42
|
import { InvitationsServiceImpl } from '../invitations';
|
|
33
43
|
import { Lock, type ResourceLock } from '../locks';
|
|
@@ -46,6 +56,7 @@ export type ClientServicesHostParams = {
|
|
|
46
56
|
signalManager?: SignalManager;
|
|
47
57
|
connectionLog?: boolean;
|
|
48
58
|
storage?: Storage;
|
|
59
|
+
level?: LevelDB;
|
|
49
60
|
lockKey?: string;
|
|
50
61
|
callbacks?: ClientServicesHostCallbacks;
|
|
51
62
|
runtimeParams?: ServiceContextRuntimeParams;
|
|
@@ -72,6 +83,7 @@ export class ClientServicesHost {
|
|
|
72
83
|
private readonly _systemService: SystemServiceImpl;
|
|
73
84
|
private readonly _loggingService: LoggingServiceImpl;
|
|
74
85
|
private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
|
|
86
|
+
private _queryService!: QueryServiceImpl;
|
|
75
87
|
|
|
76
88
|
private _config?: Config;
|
|
77
89
|
private readonly _statusUpdate = new Event<void>();
|
|
@@ -84,6 +96,7 @@ export class ClientServicesHost {
|
|
|
84
96
|
|
|
85
97
|
private _serviceContext!: ServiceContext;
|
|
86
98
|
private readonly _runtimeParams?: ServiceContextRuntimeParams;
|
|
99
|
+
private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
|
|
87
100
|
|
|
88
101
|
@Trace.info()
|
|
89
102
|
private _opening = false;
|
|
@@ -96,12 +109,14 @@ export class ClientServicesHost {
|
|
|
96
109
|
transportFactory,
|
|
97
110
|
signalManager,
|
|
98
111
|
storage,
|
|
112
|
+
level,
|
|
99
113
|
// TODO(wittjosiah): Turn this on by default.
|
|
100
114
|
lockKey,
|
|
101
115
|
callbacks,
|
|
102
116
|
runtimeParams,
|
|
103
117
|
}: ClientServicesHostParams = {}) {
|
|
104
118
|
this._storage = storage;
|
|
119
|
+
this._level = level;
|
|
105
120
|
this._callbacks = callbacks;
|
|
106
121
|
this._runtimeParams = runtimeParams;
|
|
107
122
|
|
|
@@ -141,6 +156,7 @@ export class ClientServicesHost {
|
|
|
141
156
|
},
|
|
142
157
|
});
|
|
143
158
|
|
|
159
|
+
this.diagnosticsBroadcastHandler = createCollectDiagnosticsBroadcastHandler(this._systemService);
|
|
144
160
|
this._loggingService = new LoggingServiceImpl();
|
|
145
161
|
|
|
146
162
|
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
@@ -233,6 +249,8 @@ export class ClientServicesHost {
|
|
|
233
249
|
if (!this._level) {
|
|
234
250
|
this._level = await createLevel(this._config.get('runtime.client.storage', {})!);
|
|
235
251
|
}
|
|
252
|
+
await this._level.open();
|
|
253
|
+
|
|
236
254
|
await this._resourceLock?.acquire();
|
|
237
255
|
|
|
238
256
|
await this._loggingService.open();
|
|
@@ -245,6 +263,12 @@ export class ClientServicesHost {
|
|
|
245
263
|
this._runtimeParams,
|
|
246
264
|
);
|
|
247
265
|
|
|
266
|
+
this._queryService = new QueryServiceImpl({
|
|
267
|
+
indexer: this._serviceContext.indexer,
|
|
268
|
+
automergeHost: this._serviceContext.automergeHost,
|
|
269
|
+
});
|
|
270
|
+
await this._queryService.open(ctx);
|
|
271
|
+
|
|
248
272
|
this._serviceRegistry.setServices({
|
|
249
273
|
SystemService: this._systemService,
|
|
250
274
|
|
|
@@ -255,11 +279,7 @@ export class ClientServicesHost {
|
|
|
255
279
|
(profile) => this._serviceContext.broadcastProfileUpdate(profile),
|
|
256
280
|
),
|
|
257
281
|
|
|
258
|
-
InvitationsService: new InvitationsServiceImpl(
|
|
259
|
-
this._serviceContext.invitations,
|
|
260
|
-
(invitation) => this._serviceContext.getInvitationHandler(invitation),
|
|
261
|
-
this._serviceContext.metadataStore,
|
|
262
|
-
),
|
|
282
|
+
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
263
283
|
|
|
264
284
|
DevicesService: new DevicesServiceImpl(this._serviceContext.identityManager),
|
|
265
285
|
|
|
@@ -274,10 +294,7 @@ export class ClientServicesHost {
|
|
|
274
294
|
|
|
275
295
|
DataService: new DataServiceImpl(this._serviceContext.automergeHost),
|
|
276
296
|
|
|
277
|
-
|
|
278
|
-
indexer: this._serviceContext.indexer,
|
|
279
|
-
automergeHost: this._serviceContext.automergeHost,
|
|
280
|
-
}),
|
|
297
|
+
QueryService: this._queryService,
|
|
281
298
|
|
|
282
299
|
NetworkService: new NetworkServiceImpl(this._serviceContext.networkManager, this._serviceContext.signalManager),
|
|
283
300
|
|
|
@@ -293,11 +310,6 @@ export class ClientServicesHost {
|
|
|
293
310
|
});
|
|
294
311
|
|
|
295
312
|
await this._serviceContext.open(ctx);
|
|
296
|
-
// TODO(nf): move to InvitationManager in ServiceContext?
|
|
297
|
-
invariant(this.serviceRegistry.services.InvitationsService);
|
|
298
|
-
const loadedInvitations = await this.serviceRegistry.services.InvitationsService.loadPersistentInvitations();
|
|
299
|
-
|
|
300
|
-
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
301
313
|
|
|
302
314
|
const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
|
|
303
315
|
if (devtoolsProxy) {
|
|
@@ -309,6 +321,7 @@ export class ClientServicesHost {
|
|
|
309
321
|
});
|
|
310
322
|
void this._devtoolsProxy.open();
|
|
311
323
|
}
|
|
324
|
+
this.diagnosticsBroadcastHandler.start();
|
|
312
325
|
|
|
313
326
|
this._opening = false;
|
|
314
327
|
this._open = true;
|
|
@@ -327,9 +340,11 @@ export class ClientServicesHost {
|
|
|
327
340
|
|
|
328
341
|
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
329
342
|
log('closing...', { deviceKey });
|
|
343
|
+
this.diagnosticsBroadcastHandler.stop();
|
|
330
344
|
await this._devtoolsProxy?.close();
|
|
331
345
|
this._serviceRegistry.setServices({ SystemService: this._systemService });
|
|
332
346
|
await this._loggingService.close();
|
|
347
|
+
await this._queryService.close();
|
|
333
348
|
await this._serviceContext.close();
|
|
334
349
|
await this._level?.close();
|
|
335
350
|
this._open = false;
|
|
@@ -364,7 +379,7 @@ export class ClientServicesHost {
|
|
|
364
379
|
// TODO(dmaretskyi): Better API for low-level data access.
|
|
365
380
|
const properties: ObjectStructure = {
|
|
366
381
|
system: {
|
|
367
|
-
type: encodeReference(
|
|
382
|
+
type: encodeReference(getTypeReference(Properties)!),
|
|
368
383
|
},
|
|
369
384
|
data: {
|
|
370
385
|
[defaultKey]: identity.identityKey.toHex(),
|