@dxos/client-services 0.5.9-main.a50ff17 → 0.5.9-main.aa46ff0
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-K6NPH6LF.mjs → chunk-3CWG7YPJ.mjs} +818 -448
- package/dist/lib/browser/chunk-3CWG7YPJ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +13 -2
- 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 +10 -3
- package/dist/lib/browser/packlets/testing/index.mjs.map +1 -1
- package/dist/lib/node/{chunk-E6AQJD5I.cjs → chunk-F4GQDNYS.cjs} +830 -460
- package/dist/lib/node/chunk-F4GQDNYS.cjs.map +7 -0
- package/dist/lib/node/index.cjs +53 -42
- 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 +17 -10
- package/dist/lib/node/packlets/testing/index.cjs.map +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts +14 -0
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +10 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +4 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/index.d.ts +1 -0
- package/dist/types/src/packlets/storage/index.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/profile-archive.d.ts +14 -0
- package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/package.json +36 -36
- package/src/packlets/identity/contacts-service.ts +85 -0
- package/src/packlets/invitations/invitations-handler.ts +13 -5
- package/src/packlets/invitations/space-invitation-protocol.ts +11 -32
- package/src/packlets/services/service-host.ts +12 -4
- package/src/packlets/spaces/data-space-manager.ts +55 -2
- package/src/packlets/spaces/data-space.ts +5 -5
- package/src/packlets/spaces/spaces-service.ts +50 -2
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/profile-archive.ts +97 -0
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-K6NPH6LF.mjs.map +0 -7
- package/dist/lib/node/chunk-E6AQJD5I.cjs.map +0 -7
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
createDiagnostics,
|
|
29
29
|
} from '../diagnostics';
|
|
30
30
|
import { IdentityServiceImpl, type CreateIdentityOptions } from '../identity';
|
|
31
|
+
import { ContactsServiceImpl } from '../identity/contacts-service';
|
|
31
32
|
import { InvitationsServiceImpl } from '../invitations';
|
|
32
33
|
import { Lock, type ResourceLock } from '../locks';
|
|
33
34
|
import { LoggingServiceImpl } from '../logging';
|
|
@@ -251,6 +252,11 @@ export class ClientServicesHost {
|
|
|
251
252
|
this._runtimeParams,
|
|
252
253
|
);
|
|
253
254
|
|
|
255
|
+
const dataSpaceManagerProvider = async () => {
|
|
256
|
+
await this._serviceContext.initialized.wait();
|
|
257
|
+
return this._serviceContext.dataSpaceManager!;
|
|
258
|
+
};
|
|
259
|
+
|
|
254
260
|
const identityService = new IdentityServiceImpl(
|
|
255
261
|
this._serviceContext.identityManager,
|
|
256
262
|
this._serviceContext.keyring,
|
|
@@ -262,6 +268,11 @@ export class ClientServicesHost {
|
|
|
262
268
|
this._serviceRegistry.setServices({
|
|
263
269
|
SystemService: this._systemService,
|
|
264
270
|
IdentityService: identityService,
|
|
271
|
+
ContactsService: new ContactsServiceImpl(
|
|
272
|
+
this._serviceContext.identityManager,
|
|
273
|
+
this._serviceContext.spaceManager,
|
|
274
|
+
dataSpaceManagerProvider,
|
|
275
|
+
),
|
|
265
276
|
|
|
266
277
|
InvitationsService: new InvitationsServiceImpl(this._serviceContext.invitationsManager),
|
|
267
278
|
|
|
@@ -270,10 +281,7 @@ export class ClientServicesHost {
|
|
|
270
281
|
SpacesService: new SpacesServiceImpl(
|
|
271
282
|
this._serviceContext.identityManager,
|
|
272
283
|
this._serviceContext.spaceManager,
|
|
273
|
-
|
|
274
|
-
await this._serviceContext.initialized.wait();
|
|
275
|
-
return this._serviceContext.dataSpaceManager!;
|
|
276
|
-
},
|
|
284
|
+
dataSpaceManagerProvider,
|
|
277
285
|
),
|
|
278
286
|
|
|
279
287
|
DataService: this._serviceContext.echoHost.dataService,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getCredentialAssertion,
|
|
12
12
|
type CredentialSigner,
|
|
13
13
|
type DelegateInvitationCredential,
|
|
14
|
+
createAdmissionCredentials,
|
|
14
15
|
type MemberInfo,
|
|
15
16
|
} from '@dxos/credentials';
|
|
16
17
|
import { convertLegacyReferences, findInlineObjectOfType, type EchoHost } from '@dxos/echo-db';
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
type SpaceProtocol,
|
|
23
24
|
type SpaceProtocolSession,
|
|
24
25
|
} from '@dxos/echo-pipeline';
|
|
26
|
+
import { CredentialServerExtension } from '@dxos/echo-pipeline';
|
|
25
27
|
import {
|
|
26
28
|
LEGACY_TYPE_PROPERTIES,
|
|
27
29
|
SpaceDocVersion,
|
|
@@ -30,12 +32,12 @@ import {
|
|
|
30
32
|
type SpaceDoc,
|
|
31
33
|
} from '@dxos/echo-protocol';
|
|
32
34
|
import { TYPE_PROPERTIES, generateEchoId, getTypeReference } from '@dxos/echo-schema';
|
|
33
|
-
import { type FeedStore } from '@dxos/feed-store';
|
|
35
|
+
import { type FeedStore, writeMessages } from '@dxos/feed-store';
|
|
34
36
|
import { invariant } from '@dxos/invariant';
|
|
35
37
|
import { type Keyring } from '@dxos/keyring';
|
|
36
38
|
import { PublicKey } from '@dxos/keys';
|
|
37
39
|
import { log } from '@dxos/log';
|
|
38
|
-
import { trace as Trace } from '@dxos/protocols';
|
|
40
|
+
import { trace as Trace, AlreadyJoinedError } from '@dxos/protocols';
|
|
39
41
|
import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
40
42
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
41
43
|
import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
@@ -84,6 +86,14 @@ export type AcceptSpaceOptions = {
|
|
|
84
86
|
dataTimeframe?: Timeframe;
|
|
85
87
|
};
|
|
86
88
|
|
|
89
|
+
export type AdmitMemberOptions = {
|
|
90
|
+
spaceKey: PublicKey;
|
|
91
|
+
identityKey: PublicKey;
|
|
92
|
+
role: SpaceMember.Role;
|
|
93
|
+
profile?: ProfileDocument;
|
|
94
|
+
delegationCredentialId?: PublicKey;
|
|
95
|
+
};
|
|
96
|
+
|
|
87
97
|
export type DataSpaceManagerRuntimeParams = {
|
|
88
98
|
spaceMemberPresenceAnnounceInterval?: number;
|
|
89
99
|
spaceMemberPresenceOfflineTimeout?: number;
|
|
@@ -287,6 +297,35 @@ export class DataSpaceManager {
|
|
|
287
297
|
return space;
|
|
288
298
|
}
|
|
289
299
|
|
|
300
|
+
async admitMember(options: AdmitMemberOptions): Promise<Credential> {
|
|
301
|
+
const space = this._spaceManager.spaces.get(options.spaceKey);
|
|
302
|
+
invariant(space);
|
|
303
|
+
|
|
304
|
+
if (space.spaceState.getMemberRole(options.identityKey) !== SpaceMember.Role.REMOVED) {
|
|
305
|
+
throw new AlreadyJoinedError();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// TODO(burdon): Check if already admitted.
|
|
309
|
+
const credentials: FeedMessage.Payload[] = await createAdmissionCredentials(
|
|
310
|
+
this._signingContext.credentialSigner,
|
|
311
|
+
options.identityKey,
|
|
312
|
+
space.key,
|
|
313
|
+
space.genesisFeedKey,
|
|
314
|
+
options.role,
|
|
315
|
+
space.spaceState.membershipChainHeads,
|
|
316
|
+
options.profile,
|
|
317
|
+
options.delegationCredentialId,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// TODO(dmaretskyi): Refactor.
|
|
321
|
+
invariant(credentials[0].credential);
|
|
322
|
+
const spaceMemberCredential = credentials[0].credential.credential;
|
|
323
|
+
invariant(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
|
|
324
|
+
await writeMessages(space.controlPipeline.writer, credentials);
|
|
325
|
+
|
|
326
|
+
return spaceMemberCredential;
|
|
327
|
+
}
|
|
328
|
+
|
|
290
329
|
/**
|
|
291
330
|
* Wait until the space data pipeline is fully initialized.
|
|
292
331
|
* Used by invitation handler.
|
|
@@ -302,6 +341,19 @@ export class DataSpaceManager {
|
|
|
302
341
|
);
|
|
303
342
|
}
|
|
304
343
|
|
|
344
|
+
public async requestSpaceAdmissionCredential(spaceKey: PublicKey): Promise<Credential> {
|
|
345
|
+
return this._spaceManager.requestSpaceAdmissionCredential({
|
|
346
|
+
spaceKey,
|
|
347
|
+
identityKey: this._signingContext.identityKey,
|
|
348
|
+
timeout: 15_000,
|
|
349
|
+
swarmIdentity: {
|
|
350
|
+
peerKey: this._signingContext.deviceKey,
|
|
351
|
+
credentialProvider: createAuthProvider(this._signingContext.credentialSigner),
|
|
352
|
+
credentialAuthenticator: async () => true,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
305
357
|
private async _constructSpace(metadata: SpaceMetadata) {
|
|
306
358
|
log('construct space', { metadata });
|
|
307
359
|
const gossip = new Gossip({
|
|
@@ -331,6 +383,7 @@ export class DataSpaceManager {
|
|
|
331
383
|
credentialAuthenticator: deferFunction(() => dataSpace.authVerifier.verifier),
|
|
332
384
|
},
|
|
333
385
|
onAuthorizedConnection: (session) => {
|
|
386
|
+
session.addExtension('dxos.mesh.teleport.admission-discovery', new CredentialServerExtension(space));
|
|
334
387
|
session.addExtension(
|
|
335
388
|
'dxos.mesh.teleport.gossip',
|
|
336
389
|
gossip.createExtension({ remotePeerId: session.remotePeerId }),
|
|
@@ -386,9 +386,7 @@ export class DataSpace {
|
|
|
386
386
|
|
|
387
387
|
private _onNewAutomergeRoot(rootUrl: string) {
|
|
388
388
|
log('loading automerge root doc for space', { space: this.key, rootUrl });
|
|
389
|
-
|
|
390
|
-
// Workaround for https://github.com/automerge/automerge-repo/pull/292
|
|
391
|
-
this._echoHost.replicateDocument(rootUrl);
|
|
389
|
+
|
|
392
390
|
const handle = this._echoHost.automergeRepo.find(rootUrl as any);
|
|
393
391
|
|
|
394
392
|
// TODO(dmaretskyi): Make this single-threaded (but doc loading should still be parallel to not block epoch processing).
|
|
@@ -417,8 +415,10 @@ export class DataSpace {
|
|
|
417
415
|
const root = await this._echoHost.openSpaceRoot(handle.url);
|
|
418
416
|
this._databaseRoot = root;
|
|
419
417
|
if (root.getVersion() !== SpaceDocVersion.CURRENT) {
|
|
420
|
-
this._state
|
|
421
|
-
|
|
418
|
+
if (this._state !== SpaceState.REQUIRES_MIGRATION) {
|
|
419
|
+
this._state = SpaceState.REQUIRES_MIGRATION;
|
|
420
|
+
this.stateUpdate.emit();
|
|
421
|
+
}
|
|
422
422
|
} else {
|
|
423
423
|
if (this._state !== SpaceState.READY) {
|
|
424
424
|
await this._enterReadyState();
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
type UpdateSpaceRequest,
|
|
31
31
|
type WriteCredentialsRequest,
|
|
32
32
|
type UpdateMemberRoleRequest,
|
|
33
|
+
type AdmitContactRequest,
|
|
34
|
+
type ContactAdmission,
|
|
35
|
+
type JoinSpaceResponse,
|
|
36
|
+
type JoinBySpaceKeyRequest,
|
|
33
37
|
type CreateEpochResponse,
|
|
34
38
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
35
39
|
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
@@ -126,8 +130,18 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
126
130
|
subscriptions.clear();
|
|
127
131
|
|
|
128
132
|
for (const space of dataSpaceManager.spaces.values()) {
|
|
129
|
-
|
|
130
|
-
subscriptions.add(
|
|
133
|
+
let lastState: SpaceState | undefined;
|
|
134
|
+
subscriptions.add(
|
|
135
|
+
space.stateUpdate.on(ctx, () => {
|
|
136
|
+
// Always send a separate update if the space state has changed.
|
|
137
|
+
if (space.state !== lastState) {
|
|
138
|
+
scheduler.forceTrigger();
|
|
139
|
+
} else {
|
|
140
|
+
scheduler.trigger();
|
|
141
|
+
}
|
|
142
|
+
lastState = space.state;
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
131
145
|
|
|
132
146
|
subscriptions.add(space.presence.updated.on(ctx, () => scheduler.trigger()));
|
|
133
147
|
subscriptions.add(space.automergeSpaceState.onNewEpoch.on(ctx, () => scheduler.trigger()));
|
|
@@ -216,6 +230,40 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
216
230
|
return { epochCredential: credential ?? undefined };
|
|
217
231
|
}
|
|
218
232
|
|
|
233
|
+
async admitContact(request: AdmitContactRequest): Promise<void> {
|
|
234
|
+
const dataSpaceManager = await this._getDataSpaceManager();
|
|
235
|
+
await dataSpaceManager.admitMember({
|
|
236
|
+
spaceKey: request.spaceKey,
|
|
237
|
+
identityKey: request.contact.identityKey,
|
|
238
|
+
role: request.role,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async joinBySpaceKey({ spaceKey }: JoinBySpaceKeyRequest): Promise<JoinSpaceResponse> {
|
|
243
|
+
const dataSpaceManager = await this._getDataSpaceManager();
|
|
244
|
+
const credential = await dataSpaceManager.requestSpaceAdmissionCredential(spaceKey);
|
|
245
|
+
return this._joinByAdmission({ credential });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private async _joinByAdmission({ credential }: ContactAdmission): Promise<JoinSpaceResponse> {
|
|
249
|
+
const assertion = getCredentialAssertion(credential);
|
|
250
|
+
invariant(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
|
|
251
|
+
const myIdentity = this._identityManager.identity;
|
|
252
|
+
invariant(myIdentity && credential.subject.id.equals(myIdentity.identityKey));
|
|
253
|
+
|
|
254
|
+
const dataSpaceManager = await this._getDataSpaceManager();
|
|
255
|
+
let dataSpace = dataSpaceManager.spaces.get(assertion.spaceKey);
|
|
256
|
+
if (!dataSpace) {
|
|
257
|
+
dataSpace = await dataSpaceManager.acceptSpace({
|
|
258
|
+
spaceKey: assertion.spaceKey,
|
|
259
|
+
genesisFeedKey: assertion.genesisFeedKey,
|
|
260
|
+
});
|
|
261
|
+
await myIdentity.controlPipeline.writer.write({ credential: { credential } });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { space: this._serializeSpace(dataSpace) };
|
|
265
|
+
}
|
|
266
|
+
|
|
219
267
|
private _serializeSpace(space: DataSpace): Space {
|
|
220
268
|
return {
|
|
221
269
|
id: space.id,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { cbor } from '@dxos/automerge/automerge-repo';
|
|
6
|
+
import { invariant } from '@dxos/invariant';
|
|
7
|
+
import type { LevelDB } from '@dxos/kv-store';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { ProfileArchiveEntryType, type ProfileArchive } from '@dxos/protocols';
|
|
10
|
+
import type { Storage } from '@dxos/random-access-storage';
|
|
11
|
+
import { arrayToBuffer } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
export const encodeProfileArchive = (profile: ProfileArchive): Uint8Array => cbor.encode(profile);
|
|
14
|
+
|
|
15
|
+
export const decodeProfileArchive = (data: Uint8Array): ProfileArchive => cbor.decode(data);
|
|
16
|
+
|
|
17
|
+
export const exportProfileData = async ({
|
|
18
|
+
storage,
|
|
19
|
+
level,
|
|
20
|
+
}: {
|
|
21
|
+
storage: Storage;
|
|
22
|
+
level: LevelDB;
|
|
23
|
+
}): Promise<ProfileArchive> => {
|
|
24
|
+
const archive: ProfileArchive = { storage: [], meta: { timestamp: new Date().toISOString() } };
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
const directory = await storage.createDirectory();
|
|
28
|
+
const files = await directory.list();
|
|
29
|
+
|
|
30
|
+
log.info('begin exporting files', { count: files.length });
|
|
31
|
+
for (const filename of files) {
|
|
32
|
+
const file = await directory.getOrCreateFile(filename);
|
|
33
|
+
const { size } = await file.stat();
|
|
34
|
+
const data = await file.read(0, size);
|
|
35
|
+
archive.storage.push({
|
|
36
|
+
type: ProfileArchiveEntryType.FILE,
|
|
37
|
+
key: filename,
|
|
38
|
+
value: data,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
log.info('done exporting files', { count: files.length });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
log.info('begin exporting kv pairs');
|
|
46
|
+
const iter = await level.iterator<Uint8Array, Uint8Array>({ keyEncoding: 'binary', valueEncoding: 'binary' });
|
|
47
|
+
let count = 0;
|
|
48
|
+
for await (const [key, value] of iter) {
|
|
49
|
+
archive.storage.push({
|
|
50
|
+
type: ProfileArchiveEntryType.KEY_VALUE,
|
|
51
|
+
key,
|
|
52
|
+
value,
|
|
53
|
+
});
|
|
54
|
+
count++;
|
|
55
|
+
}
|
|
56
|
+
log.info('done exporting kv pairs', { count });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return archive;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const importProfileData = async (
|
|
63
|
+
{
|
|
64
|
+
storage,
|
|
65
|
+
level,
|
|
66
|
+
}: {
|
|
67
|
+
storage: Storage;
|
|
68
|
+
level: LevelDB;
|
|
69
|
+
},
|
|
70
|
+
archive: ProfileArchive,
|
|
71
|
+
): Promise<void> => {
|
|
72
|
+
const batch = level.batch();
|
|
73
|
+
|
|
74
|
+
for (const entry of archive.storage) {
|
|
75
|
+
switch (entry.type) {
|
|
76
|
+
case ProfileArchiveEntryType.FILE: {
|
|
77
|
+
const directory = await storage.createDirectory();
|
|
78
|
+
invariant(typeof entry.key === 'string', 'Invalid key type');
|
|
79
|
+
const file = await directory.getOrCreateFile(entry.key);
|
|
80
|
+
invariant(entry.value instanceof Uint8Array, 'Invalid value type');
|
|
81
|
+
await file.write(0, arrayToBuffer(entry.value));
|
|
82
|
+
await file.close();
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case ProfileArchiveEntryType.KEY_VALUE: {
|
|
86
|
+
invariant(entry.key instanceof Uint8Array, 'Invalid key type');
|
|
87
|
+
invariant(entry.value instanceof Uint8Array, 'Invalid value type');
|
|
88
|
+
batch.put(entry.key, entry.value, { keyEncoding: 'binary', valueEncoding: 'binary' });
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(`Invalid entry type: ${entry.type}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await batch.write();
|
|
97
|
+
};
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DXOS_VERSION = "0.5.9-main.
|
|
1
|
+
export const DXOS_VERSION = "0.5.9-main.aa46ff0";
|