@dxos/client-services 0.4.10-main.4c8c73c → 0.4.10-main.4d48ccf
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-F4LOSBRJ.mjs → chunk-VQZR7ZZQ.mjs} +356 -288
- package/dist/lib/browser/chunk-VQZR7ZZQ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +125 -112
- package/dist/lib/browser/packlets/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-IPO3WM44.cjs → chunk-G4BQZF43.cjs} +448 -380
- package/dist/lib/node/chunk-G4BQZF43.cjs.map +7 -0
- package/dist/lib/node/index.cjs +43 -43
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +125 -115
- 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/indexing/util.d.ts +0 -5
- 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/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 +4 -2
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +9 -7
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- 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.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/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +3 -0
- 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 +34 -34
- package/src/packlets/identity/identity-manager.ts +1 -0
- package/src/packlets/identity/identity.test.ts +3 -0
- package/src/packlets/indexing/util.ts +1 -65
- package/src/packlets/invitations/device-invitation-protocol.ts +6 -1
- package/src/packlets/invitations/invitation-protocol.ts +7 -1
- package/src/packlets/invitations/invitations-handler.ts +10 -71
- package/src/packlets/invitations/invitations-manager.ts +114 -40
- package/src/packlets/invitations/invitations-service.ts +4 -2
- package/src/packlets/invitations/space-invitation-protocol.ts +45 -3
- package/src/packlets/services/automerge-host.test.ts +1 -1
- package/src/packlets/services/service-context.ts +3 -2
- package/src/packlets/spaces/data-space-manager.ts +48 -2
- package/src/packlets/spaces/data-space.ts +1 -1
- package/src/packlets/testing/invitation-utils.ts +100 -97
- package/src/packlets/testing/test-builder.ts +19 -1
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-F4LOSBRJ.mjs.map +0 -7
- package/dist/lib/node/chunk-IPO3WM44.cjs.map +0 -7
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Event } from '@dxos/async';
|
|
6
|
-
import
|
|
7
|
-
|
|
5
|
+
import { Event, PushStream } from '@dxos/async';
|
|
6
|
+
import {
|
|
7
|
+
type AuthenticatingInvitation,
|
|
8
|
+
AUTHENTICATION_CODE_LENGTH,
|
|
9
|
+
CancellableInvitation,
|
|
10
|
+
INVITATION_TIMEOUT,
|
|
11
|
+
} from '@dxos/client-protocol';
|
|
12
|
+
import { Context } from '@dxos/context';
|
|
13
|
+
import { generatePasscode } from '@dxos/credentials';
|
|
8
14
|
import { hasInvitationExpired, type MetadataStore } from '@dxos/echo-pipeline';
|
|
9
15
|
import { invariant } from '@dxos/invariant';
|
|
16
|
+
import { PublicKey } from '@dxos/keys';
|
|
10
17
|
import { log } from '@dxos/log';
|
|
11
18
|
import {
|
|
12
19
|
type AcceptInvitationRequest,
|
|
13
|
-
type Invitation,
|
|
14
20
|
type AuthenticationRequest,
|
|
21
|
+
Invitation,
|
|
15
22
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
16
23
|
|
|
17
24
|
import type { InvitationProtocol } from './invitation-protocol';
|
|
18
|
-
import type
|
|
25
|
+
import { createAdmissionKeypair, type InvitationsHandler } from './invitations-handler';
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Entry point for creating and accepting invitations, keeps track of existing invitation set and
|
|
@@ -36,36 +43,44 @@ export class InvitationsManager {
|
|
|
36
43
|
|
|
37
44
|
constructor(
|
|
38
45
|
private readonly _invitationsHandler: InvitationsHandler,
|
|
39
|
-
private readonly _getHandler: (invitation: Invitation) => InvitationProtocol,
|
|
46
|
+
private readonly _getHandler: (invitation: Partial<Invitation> & Pick<Invitation, 'kind'>) => InvitationProtocol,
|
|
40
47
|
private readonly _metadataStore: MetadataStore,
|
|
41
48
|
) {}
|
|
42
49
|
|
|
43
|
-
createInvitation(options: Invitation): CancellableInvitation {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
async createInvitation(options: Partial<Invitation> & Pick<Invitation, 'kind'>): Promise<CancellableInvitation> {
|
|
51
|
+
if (options.invitationId) {
|
|
52
|
+
const existingInvitation = this._createInvitations.get(options.invitationId);
|
|
53
|
+
if (existingInvitation) {
|
|
54
|
+
return existingInvitation;
|
|
55
|
+
}
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
const handler = this._getHandler(options);
|
|
50
|
-
const invitation = this.
|
|
51
|
-
this.
|
|
52
|
-
this.invitationCreated.emit(invitation.get());
|
|
53
|
-
|
|
54
|
-
const saveInvitationTask = invitation.get().persistent
|
|
55
|
-
? this._safePersistInBackground(invitation)
|
|
56
|
-
: Promise.resolve();
|
|
59
|
+
const invitation = this._createInvitation(handler, options);
|
|
60
|
+
const { ctx, stream, observableInvitation } = this._createObservableInvitation(handler, invitation);
|
|
57
61
|
|
|
62
|
+
this._createInvitations.set(invitation.invitationId, observableInvitation);
|
|
63
|
+
this.invitationCreated.emit(invitation);
|
|
58
64
|
// onComplete is called on cancel, expiration, or redemption of a single-use invitation
|
|
59
|
-
this._onInvitationComplete(
|
|
60
|
-
this._createInvitations.delete(
|
|
61
|
-
this.removedCreated.emit(
|
|
62
|
-
if (
|
|
63
|
-
await
|
|
64
|
-
await this._safeDeleteInvitation(invitation.get());
|
|
65
|
+
this._onInvitationComplete(observableInvitation, async () => {
|
|
66
|
+
this._createInvitations.delete(observableInvitation.get().invitationId);
|
|
67
|
+
this.removedCreated.emit(observableInvitation.get());
|
|
68
|
+
if (observableInvitation.get().persistent) {
|
|
69
|
+
await this._safeDeleteInvitation(observableInvitation.get());
|
|
65
70
|
}
|
|
66
71
|
});
|
|
67
72
|
|
|
68
|
-
|
|
73
|
+
try {
|
|
74
|
+
await this._persistIfRequired(handler, stream, invitation);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
log.catch(err);
|
|
77
|
+
await observableInvitation.cancel();
|
|
78
|
+
return observableInvitation;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this._invitationsHandler.handleInvitationFlow(ctx, stream, handler, observableInvitation.get());
|
|
82
|
+
|
|
83
|
+
return observableInvitation;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
async loadPersistentInvitations(): Promise<{ invitations: Invitation[] }> {
|
|
@@ -78,12 +93,13 @@ export class InvitationsManager {
|
|
|
78
93
|
// get saved persistent invitations, filter and remove from storage those that have expired.
|
|
79
94
|
const freshInvitations = persistentInvitations.filter((invitation) => !hasInvitationExpired(invitation));
|
|
80
95
|
|
|
81
|
-
const
|
|
96
|
+
const loadTasks = freshInvitations.map((persistentInvitation) => {
|
|
82
97
|
invariant(!this._createInvitations.get(persistentInvitation.invitationId), 'invitation already exists');
|
|
83
|
-
return this.createInvitation({ ...persistentInvitation, persistent: false })
|
|
98
|
+
return this.createInvitation({ ...persistentInvitation, persistent: false });
|
|
84
99
|
});
|
|
100
|
+
const cInvitations = await Promise.all(loadTasks);
|
|
85
101
|
|
|
86
|
-
return { invitations: cInvitations };
|
|
102
|
+
return { invitations: cInvitations.map((invitation) => invitation.get()) };
|
|
87
103
|
} catch (err) {
|
|
88
104
|
log.catch(err);
|
|
89
105
|
return { invitations: [] };
|
|
@@ -163,20 +179,78 @@ export class InvitationsManager {
|
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
private
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
private _createInvitation(protocol: InvitationProtocol, options?: Partial<Invitation>): Invitation {
|
|
183
|
+
const {
|
|
184
|
+
invitationId = PublicKey.random().toHex(),
|
|
185
|
+
type = Invitation.Type.INTERACTIVE,
|
|
186
|
+
authMethod = Invitation.AuthMethod.SHARED_SECRET,
|
|
187
|
+
state = Invitation.State.INIT,
|
|
188
|
+
timeout = INVITATION_TIMEOUT,
|
|
189
|
+
swarmKey = PublicKey.random(),
|
|
190
|
+
persistent = options?.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY, // default no not storing keypairs
|
|
191
|
+
created = new Date(),
|
|
192
|
+
guestKeypair = undefined,
|
|
193
|
+
lifetime = 86400, // 1 day,
|
|
194
|
+
multiUse = false,
|
|
195
|
+
} = options ?? {};
|
|
196
|
+
const authCode =
|
|
197
|
+
options?.authCode ??
|
|
198
|
+
(authMethod === Invitation.AuthMethod.SHARED_SECRET ? generatePasscode(AUTHENTICATION_CODE_LENGTH) : undefined);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
invitationId,
|
|
202
|
+
type,
|
|
203
|
+
authMethod,
|
|
204
|
+
state,
|
|
205
|
+
swarmKey,
|
|
206
|
+
authCode,
|
|
207
|
+
timeout,
|
|
208
|
+
persistent: persistent && type !== Invitation.Type.DELEGATED, // delegated invitations are persisted in control feed
|
|
209
|
+
guestKeypair:
|
|
210
|
+
guestKeypair ?? (authMethod === Invitation.AuthMethod.KNOWN_PUBLIC_KEY ? createAdmissionKeypair() : undefined),
|
|
211
|
+
created,
|
|
212
|
+
lifetime,
|
|
213
|
+
multiUse,
|
|
214
|
+
delegationCredentialId: options?.delegationCredentialId,
|
|
215
|
+
...protocol.getInvitationContext(),
|
|
216
|
+
} satisfies Invitation;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private _createObservableInvitation(handler: InvitationProtocol, invitation: Invitation) {
|
|
220
|
+
const stream = new PushStream<Invitation>();
|
|
221
|
+
const ctx = new Context({
|
|
222
|
+
onError: (err) => {
|
|
223
|
+
stream.error(err);
|
|
224
|
+
void ctx.dispose();
|
|
225
|
+
},
|
|
179
226
|
});
|
|
227
|
+
ctx.onDispose(() => {
|
|
228
|
+
log('complete', { ...handler.toJSON() });
|
|
229
|
+
stream.complete();
|
|
230
|
+
});
|
|
231
|
+
const observableInvitation = new CancellableInvitation({
|
|
232
|
+
initialInvitation: invitation,
|
|
233
|
+
subscriber: stream.observable,
|
|
234
|
+
onCancel: async () => {
|
|
235
|
+
stream.next({ ...invitation, state: Invitation.State.CANCELLED });
|
|
236
|
+
await ctx.dispose();
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
return { ctx, stream, observableInvitation };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async _persistIfRequired(
|
|
243
|
+
handler: InvitationProtocol,
|
|
244
|
+
changeStream: PushStream<Invitation>,
|
|
245
|
+
invitation: Invitation,
|
|
246
|
+
): Promise<void> {
|
|
247
|
+
if (invitation.type === Invitation.Type.DELEGATED && invitation.delegationCredentialId == null) {
|
|
248
|
+
const delegationCredentialId = await handler.delegate(invitation);
|
|
249
|
+
changeStream.next({ ...invitation, delegationCredentialId });
|
|
250
|
+
} else if (invitation.persistent) {
|
|
251
|
+
await this._metadataStore.addInvitation(invitation);
|
|
252
|
+
this.saved.emit(invitation);
|
|
253
|
+
}
|
|
180
254
|
}
|
|
181
255
|
|
|
182
256
|
private async _safeDeleteInvitation(invitation: Invitation): Promise<void> {
|
|
@@ -27,9 +27,11 @@ export class InvitationsServiceImpl implements InvitationsService {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
createInvitation(options: Invitation): Stream<Invitation> {
|
|
30
|
-
const invitation = this._invitationsManager.createInvitation(options);
|
|
31
30
|
return new Stream<Invitation>(({ next, close }) => {
|
|
32
|
-
|
|
31
|
+
void this._invitationsManager
|
|
32
|
+
.createInvitation(options)
|
|
33
|
+
.then((invitation) => invitation.subscribe(next, close, close))
|
|
34
|
+
.catch(close);
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -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
10
|
import { createTestLevel } from '@dxos/echo-pipeline/testing';
|
|
10
|
-
import { AutomergeContext } from '@dxos/echo-schema';
|
|
11
11
|
import { afterTest, describe, test } from '@dxos/test';
|
|
12
12
|
|
|
13
13
|
describe('AutomergeHost', () => {
|
|
@@ -32,7 +32,7 @@ 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,
|
|
@@ -131,10 +131,10 @@ export class ServiceContext extends Resource {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
this.indexer = new Indexer({
|
|
134
|
+
db: this.level,
|
|
134
135
|
indexStore: new IndexStore({ db: level.sublevel('index-storage') }),
|
|
135
136
|
metadataStore: this.indexMetadata,
|
|
136
137
|
loadDocuments: createSelectedDocumentsIterator(this.automergeHost),
|
|
137
|
-
getAllDocuments: createDocumentsIterator(this.automergeHost),
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
this.invitations = new InvitationsHandler(this.networkManager);
|
|
@@ -256,6 +256,7 @@ export class ServiceContext extends Resource {
|
|
|
256
256
|
signingContext,
|
|
257
257
|
this.feedStore,
|
|
258
258
|
this.automergeHost,
|
|
259
|
+
this.invitationsManager,
|
|
259
260
|
this._runtimeParams as DataSpaceManagerRuntimeParams,
|
|
260
261
|
);
|
|
261
262
|
await this.dataSpaceManager.open();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { Context, cancelWithContext } from '@dxos/context';
|
|
7
|
-
import { getCredentialAssertion, type CredentialSigner } from '@dxos/credentials';
|
|
7
|
+
import { getCredentialAssertion, type CredentialSigner, type DelegateInvitationCredential } from '@dxos/credentials';
|
|
8
8
|
import { type AutomergeHost, type MetadataStore, type Space, type SpaceManager } from '@dxos/echo-pipeline';
|
|
9
9
|
import { type FeedStore } from '@dxos/feed-store';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
@@ -12,10 +12,11 @@ import { type Keyring } from '@dxos/keyring';
|
|
|
12
12
|
import { PublicKey } from '@dxos/keys';
|
|
13
13
|
import { log } from '@dxos/log';
|
|
14
14
|
import { trace } from '@dxos/protocols';
|
|
15
|
-
import { SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
15
|
+
import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
16
16
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
17
17
|
import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
18
18
|
import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
19
|
+
import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
19
20
|
import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
|
|
20
21
|
import { type Timeframe } from '@dxos/timeframe';
|
|
21
22
|
import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
|
|
@@ -23,6 +24,7 @@ import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
|
|
|
23
24
|
import { DataSpace } from './data-space';
|
|
24
25
|
import { spaceGenesis } from './genesis';
|
|
25
26
|
import { createAuthProvider } from '../identity';
|
|
27
|
+
import { type InvitationsManager } from '../invitations';
|
|
26
28
|
|
|
27
29
|
const PRESENCE_ANNOUNCE_INTERVAL = 10_000;
|
|
28
30
|
const PRESENCE_OFFLINE_TIMEOUT = 20_000;
|
|
@@ -78,6 +80,7 @@ export class DataSpaceManager {
|
|
|
78
80
|
private readonly _signingContext: SigningContext,
|
|
79
81
|
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
80
82
|
private readonly _automergeHost: AutomergeHost,
|
|
83
|
+
private readonly _invitationsManager: InvitationsManager,
|
|
81
84
|
params?: DataSpaceManagerRuntimeParams,
|
|
82
85
|
) {
|
|
83
86
|
const {
|
|
@@ -247,6 +250,9 @@ export class DataSpaceManager {
|
|
|
247
250
|
log.warn('auth failure');
|
|
248
251
|
},
|
|
249
252
|
memberKey: this._signingContext.identityKey,
|
|
253
|
+
onDelegatedInvitationStatusChange: (invitation, isActive) => {
|
|
254
|
+
return this._handleInvitationStatusChange(dataSpace, invitation, isActive);
|
|
255
|
+
},
|
|
250
256
|
});
|
|
251
257
|
controlFeed && (await space.setControlFeed(controlFeed));
|
|
252
258
|
dataFeed && (await space.setDataFeed(dataFeed));
|
|
@@ -267,6 +273,7 @@ export class DataSpaceManager {
|
|
|
267
273
|
afterReady: async () => {
|
|
268
274
|
log('after space ready', { space: space.key, open: this._isOpen });
|
|
269
275
|
if (this._isOpen) {
|
|
276
|
+
await this._createDelegatedInvitations(dataSpace, [...space.spaceState.invitations.entries()]);
|
|
270
277
|
this.updated.emit();
|
|
271
278
|
}
|
|
272
279
|
},
|
|
@@ -289,4 +296,43 @@ export class DataSpaceManager {
|
|
|
289
296
|
this._spaces.set(metadata.key, dataSpace);
|
|
290
297
|
return dataSpace;
|
|
291
298
|
}
|
|
299
|
+
|
|
300
|
+
private async _handleInvitationStatusChange(
|
|
301
|
+
dataSpace: DataSpace | undefined,
|
|
302
|
+
delegatedInvitation: DelegateInvitationCredential,
|
|
303
|
+
isActive: boolean,
|
|
304
|
+
): Promise<void> {
|
|
305
|
+
if (dataSpace?.state !== SpaceState.READY) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (isActive) {
|
|
309
|
+
await this._createDelegatedInvitations(dataSpace, [
|
|
310
|
+
[delegatedInvitation.credentialId, delegatedInvitation.invitation],
|
|
311
|
+
]);
|
|
312
|
+
} else {
|
|
313
|
+
await this._invitationsManager.cancelInvitation(delegatedInvitation.invitation);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async _createDelegatedInvitations(
|
|
318
|
+
space: DataSpace,
|
|
319
|
+
invitations: Array<[PublicKey, DelegateSpaceInvitation]>,
|
|
320
|
+
): Promise<void> {
|
|
321
|
+
const tasks = invitations.map(([credentialId, invitation]) => {
|
|
322
|
+
return this._invitationsManager.createInvitation({
|
|
323
|
+
type: Invitation.Type.DELEGATED,
|
|
324
|
+
kind: Invitation.Kind.SPACE,
|
|
325
|
+
spaceKey: space.key,
|
|
326
|
+
authMethod: invitation.authMethod,
|
|
327
|
+
invitationId: invitation.invitationId,
|
|
328
|
+
swarmKey: invitation.swarmKey,
|
|
329
|
+
guestKeypair: invitation.guestKey ? { publicKey: invitation.guestKey } : undefined,
|
|
330
|
+
lifetime: invitation.expiresOn ? invitation.expiresOn.getTime() - Date.now() : undefined,
|
|
331
|
+
multiUse: invitation.multiUse,
|
|
332
|
+
delegationCredentialId: credentialId,
|
|
333
|
+
persistent: false,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
await Promise.all(tasks);
|
|
337
|
+
}
|
|
292
338
|
}
|
|
@@ -6,7 +6,6 @@ import { Event, asyncTimeout, scheduleTask, sleep, synchronized, trackLeaks } fr
|
|
|
6
6
|
import { AUTH_TIMEOUT } from '@dxos/client-protocol';
|
|
7
7
|
import { cancelWithContext, Context, ContextDisposedError } from '@dxos/context';
|
|
8
8
|
import { timed, warnAfterTimeout } from '@dxos/debug';
|
|
9
|
-
import { TYPE_PROPERTIES } from '@dxos/echo-db';
|
|
10
9
|
import {
|
|
11
10
|
type MetadataStore,
|
|
12
11
|
type Space,
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
type SpaceDoc,
|
|
16
15
|
} from '@dxos/echo-pipeline';
|
|
17
16
|
import { AutomergeDocumentLoaderImpl } from '@dxos/echo-pipeline';
|
|
17
|
+
import { TYPE_PROPERTIES } from '@dxos/echo-schema';
|
|
18
18
|
import { type FeedStore } from '@dxos/feed-store';
|
|
19
19
|
import { failedInvariant, invariant } from '@dxos/invariant';
|
|
20
20
|
import { type Keyring } from '@dxos/keyring';
|