@dxos/client-services 0.5.9-main.bf0ae3e → 0.5.9-main.bf3bb8f
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-4IR3JP4U.mjs → chunk-IUSAD4RP.mjs} +1405 -824
- package/dist/lib/browser/chunk-IUSAD4RP.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +13 -4
- 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-ZBIDLLZ4.cjs → chunk-5PALJZPW.cjs} +1534 -956
- package/dist/lib/node/chunk-5PALJZPW.cjs.map +7 -0
- package/dist/lib/node/index.cjs +53 -44
- 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/identity/identity-service.d.ts.map +1 -1
- 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/automerge-space-state.d.ts +4 -1
- package/dist/types/src/packlets/spaces/automerge-space-state.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/data-space.d.ts +9 -9
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +23 -0
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +5 -2
- 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/identity/identity-service.ts +45 -13
- 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/automerge-space-state.ts +11 -2
- package/src/packlets/spaces/data-space-manager.ts +90 -16
- package/src/packlets/spaces/data-space.ts +78 -148
- package/src/packlets/spaces/epoch-migrations.ts +154 -0
- package/src/packlets/spaces/spaces-service.ts +56 -4
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/profile-archive.ts +111 -0
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-4IR3JP4U.mjs.map +0 -7
- package/dist/lib/node/chunk-ZBIDLLZ4.cjs.map +0 -7
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EventSubscriptions, scheduleTask, UpdateScheduler } from '@dxos/async';
|
|
6
|
+
import { Stream } from '@dxos/codec-protobuf';
|
|
7
|
+
import { type MemberInfo } from '@dxos/credentials';
|
|
8
|
+
import type { SpaceManager } from '@dxos/echo-pipeline';
|
|
9
|
+
import { PublicKey } from '@dxos/keys';
|
|
10
|
+
import { type Contact, type ContactBook, type ContactsService } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
+
import { ComplexMap, ComplexSet } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { type IdentityManager } from './identity-manager';
|
|
14
|
+
import { type DataSpaceManager } from '../spaces';
|
|
15
|
+
|
|
16
|
+
export class ContactsServiceImpl implements ContactsService {
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly _identityManager: IdentityManager,
|
|
19
|
+
private readonly _spaceManager: SpaceManager,
|
|
20
|
+
private readonly _dataSpaceManagerProvider: () => Promise<DataSpaceManager>,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
async getContacts(): Promise<ContactBook> {
|
|
24
|
+
const identity = this._identityManager.identity;
|
|
25
|
+
if (identity == null) {
|
|
26
|
+
return { contacts: [] };
|
|
27
|
+
}
|
|
28
|
+
const contacts = [...this._spaceManager.spaces.values()]
|
|
29
|
+
.flatMap((s) => [...s.spaceState.members.values()].map((m) => [s.key, m]))
|
|
30
|
+
.reduce((acc, v) => {
|
|
31
|
+
const [spaceKey, memberInfo] = v as [PublicKey, MemberInfo];
|
|
32
|
+
if (memberInfo.key.equals(identity.identityKey)) {
|
|
33
|
+
return acc;
|
|
34
|
+
}
|
|
35
|
+
const existing = acc.get(memberInfo.key);
|
|
36
|
+
if (existing != null) {
|
|
37
|
+
existing.profile ??= memberInfo.profile;
|
|
38
|
+
existing.commonSpaces?.push(spaceKey);
|
|
39
|
+
} else {
|
|
40
|
+
acc.set(memberInfo.key, {
|
|
41
|
+
identityKey: memberInfo.key,
|
|
42
|
+
profile: memberInfo.profile,
|
|
43
|
+
commonSpaces: [spaceKey],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}, new ComplexMap<PublicKey, Contact>(PublicKey.hash));
|
|
48
|
+
return {
|
|
49
|
+
contacts: [...contacts.values()],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
queryContacts(): Stream<ContactBook> {
|
|
54
|
+
const subscribedSpaceKeySet = new ComplexSet(PublicKey.hash);
|
|
55
|
+
return new Stream<ContactBook>(({ next, ctx }) => {
|
|
56
|
+
const pushUpdateTask = new UpdateScheduler(
|
|
57
|
+
ctx,
|
|
58
|
+
async () => {
|
|
59
|
+
const contacts = await this.getContacts();
|
|
60
|
+
next(contacts);
|
|
61
|
+
},
|
|
62
|
+
{ maxFrequency: 2 },
|
|
63
|
+
);
|
|
64
|
+
scheduleTask(ctx, async () => {
|
|
65
|
+
const subscriptions = new EventSubscriptions();
|
|
66
|
+
ctx.onDispose(() => subscriptions.clear());
|
|
67
|
+
const subscribeToSpaceAndUpdate = () => {
|
|
68
|
+
const oldSetSize = subscribedSpaceKeySet.size;
|
|
69
|
+
for (const space of this._spaceManager.spaces.values()) {
|
|
70
|
+
if (!subscribedSpaceKeySet.has(space.key)) {
|
|
71
|
+
subscriptions.add(space.stateUpdate.on(ctx, () => pushUpdateTask.trigger()));
|
|
72
|
+
subscribedSpaceKeySet.add(space.key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (oldSetSize !== subscribedSpaceKeySet.size) {
|
|
76
|
+
pushUpdateTask.trigger();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const unsubscribe = (await this._dataSpaceManagerProvider()).updated.on(ctx, subscribeToSpaceAndUpdate);
|
|
80
|
+
ctx.onDispose(unsubscribe);
|
|
81
|
+
subscribeToSpaceAndUpdate();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Trigger, sleep } from '@dxos/async';
|
|
5
6
|
import { Stream } from '@dxos/codec-protobuf';
|
|
6
7
|
import { Resource } from '@dxos/context';
|
|
7
8
|
import { signPresentation } from '@dxos/credentials';
|
|
8
9
|
import { todo } from '@dxos/debug';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { type Keyring } from '@dxos/keyring';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
11
13
|
import {
|
|
12
14
|
type CreateIdentityRequest,
|
|
13
15
|
type Identity as IdentityProto,
|
|
@@ -18,11 +20,14 @@ import {
|
|
|
18
20
|
SpaceState,
|
|
19
21
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
20
22
|
import { type Presentation, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
|
+
import { safeAwaitAll } from '@dxos/util';
|
|
21
24
|
|
|
22
25
|
import { type Identity } from './identity';
|
|
23
26
|
import { type CreateIdentityOptions, type IdentityManager } from './identity-manager';
|
|
24
27
|
import { type DataSpaceManager } from '../spaces';
|
|
25
28
|
|
|
29
|
+
const DEFAULT_SPACE_SEARCH_TIMEOUT = 10_000;
|
|
30
|
+
|
|
26
31
|
export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
27
32
|
constructor(
|
|
28
33
|
private readonly _identityManager: IdentityManager,
|
|
@@ -100,20 +105,47 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
private async _fixIdentityWithoutDefaultSpace(identity: Identity) {
|
|
103
|
-
let
|
|
108
|
+
let recodedDefaultSpace = false;
|
|
109
|
+
let foundDefaultSpace = false;
|
|
104
110
|
const dataSpaceManager = this._dataSpaceManagerProvider();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
|
|
112
|
+
const recordedDefaultSpaceTrigger = new Trigger();
|
|
113
|
+
|
|
114
|
+
const allProcessed = safeAwaitAll(
|
|
115
|
+
dataSpaceManager.spaces.values(),
|
|
116
|
+
async (space) => {
|
|
117
|
+
if (space.state === SpaceState.CLOSED) {
|
|
118
|
+
await space.open();
|
|
119
|
+
|
|
120
|
+
// Wait until the space is either READY or REQUIRES_MIGRATION.
|
|
121
|
+
// NOTE: Space could potentially never initialize if the space data is corrupted.
|
|
122
|
+
const requiresMigration = space.stateUpdate.waitForCondition(
|
|
123
|
+
() => space.state === SpaceState.REQUIRES_MIGRATION,
|
|
124
|
+
);
|
|
125
|
+
await Promise.race([space.initializeDataPipeline(), requiresMigration]);
|
|
126
|
+
}
|
|
127
|
+
if (await dataSpaceManager.isDefaultSpace(space)) {
|
|
128
|
+
if (foundDefaultSpace) {
|
|
129
|
+
log.warn('Multiple default spaces found. Using the first one.', { duplicate: space.id });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
foundDefaultSpace = true;
|
|
134
|
+
await identity.updateDefaultSpace(space.id);
|
|
135
|
+
recodedDefaultSpace = true;
|
|
136
|
+
recordedDefaultSpaceTrigger.wake();
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
(err) => {
|
|
140
|
+
log.catch(err);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Wait for all spaces to be processed or until the default space is recorded.
|
|
145
|
+
// If the timeout is reached, create a new default space.
|
|
146
|
+
await Promise.race([allProcessed, recordedDefaultSpaceTrigger.wait(), sleep(DEFAULT_SPACE_SEARCH_TIMEOUT)]);
|
|
147
|
+
|
|
148
|
+
if (!recodedDefaultSpace) {
|
|
117
149
|
await this._createDefaultSpace(dataSpaceManager);
|
|
118
150
|
}
|
|
119
151
|
}
|
|
@@ -435,11 +435,19 @@ export class InvitationsHandler {
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
private _logStateUpdate(invitation: Invitation, actor: any, newState: Invitation.State) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
438
|
+
if (this._isNotTerminal(newState)) {
|
|
439
|
+
log('invitation state update', {
|
|
440
|
+
actor: actor?.constructor.name,
|
|
441
|
+
newState: stateToString(newState),
|
|
442
|
+
oldState: stateToString(invitation.state),
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
445
|
+
log.info('invitation state update', {
|
|
446
|
+
actor: actor?.constructor.name,
|
|
447
|
+
newState: stateToString(newState),
|
|
448
|
+
oldState: stateToString(invitation.state),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
private _isNotTerminal(currentState: Invitation.State): boolean {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
createAdmissionCredentials,
|
|
7
6
|
createCancelDelegatedSpaceInvitationCredential,
|
|
8
7
|
createDelegatedSpaceInvitationCredential,
|
|
9
8
|
getCredentialAssertion,
|
|
@@ -21,7 +20,6 @@ import {
|
|
|
21
20
|
SpaceNotFoundError,
|
|
22
21
|
} from '@dxos/protocols';
|
|
23
22
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
24
|
-
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
25
23
|
import { SpaceMember, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
26
24
|
import {
|
|
27
25
|
type AdmissionRequest,
|
|
@@ -73,41 +71,22 @@ export class SpaceInvitationProtocol implements InvitationProtocol {
|
|
|
73
71
|
request: AdmissionRequest,
|
|
74
72
|
guestProfile?: ProfileDocument | undefined,
|
|
75
73
|
): Promise<AdmissionResponse> {
|
|
76
|
-
invariant(this._spaceKey);
|
|
77
|
-
|
|
78
|
-
invariant(space);
|
|
79
|
-
|
|
80
|
-
invariant(request.space);
|
|
81
|
-
const { identityKey, deviceKey } = request.space;
|
|
74
|
+
invariant(this._spaceKey && request.space);
|
|
75
|
+
log('writing guest credentials', { host: this._signingContext.deviceKey, guest: request.space.deviceKey });
|
|
82
76
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this._signingContext.credentialSigner,
|
|
91
|
-
identityKey,
|
|
92
|
-
space.key,
|
|
93
|
-
space.inner.genesisFeedKey,
|
|
94
|
-
invitation.role ?? SpaceMember.Role.ADMIN,
|
|
95
|
-
space.inner.spaceState.membershipChainHeads,
|
|
96
|
-
guestProfile,
|
|
97
|
-
invitation.delegationCredentialId,
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
// TODO(dmaretskyi): Refactor.
|
|
101
|
-
invariant(credentials[0].credential);
|
|
102
|
-
const spaceMemberCredential = credentials[0].credential.credential;
|
|
103
|
-
invariant(getCredentialAssertion(spaceMemberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
|
|
104
|
-
|
|
105
|
-
await writeMessages(space.inner.controlPipeline.writer, credentials);
|
|
77
|
+
const spaceMemberCredential = await this._spaceManager.admitMember({
|
|
78
|
+
spaceKey: this._spaceKey,
|
|
79
|
+
identityKey: request.space.identityKey,
|
|
80
|
+
role: invitation.role ?? SpaceMember.Role.ADMIN,
|
|
81
|
+
profile: guestProfile,
|
|
82
|
+
delegationCredentialId: invitation.delegationCredentialId,
|
|
83
|
+
});
|
|
106
84
|
|
|
85
|
+
const space = this._spaceManager.spaces.get(this._spaceKey);
|
|
107
86
|
return {
|
|
108
87
|
space: {
|
|
109
88
|
credential: spaceMemberCredential,
|
|
110
|
-
controlTimeframe: space
|
|
89
|
+
controlTimeframe: space?.inner.controlPipeline.state.timeframe,
|
|
111
90
|
},
|
|
112
91
|
};
|
|
113
92
|
}
|
|
@@ -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,
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Event } from '@dxos/async';
|
|
6
|
+
import { Resource, type Context } from '@dxos/context';
|
|
6
7
|
import { type CredentialProcessor, type SpecificCredential, checkCredentialType } from '@dxos/credentials';
|
|
7
8
|
import { type Credential, type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
8
9
|
|
|
9
|
-
export class AutomergeSpaceState implements CredentialProcessor {
|
|
10
|
+
export class AutomergeSpaceState extends Resource implements CredentialProcessor {
|
|
10
11
|
public rootUrl: string | undefined = undefined;
|
|
11
12
|
public lastEpoch: SpecificCredential<Epoch> | undefined = undefined;
|
|
12
13
|
|
|
@@ -14,7 +15,15 @@ export class AutomergeSpaceState implements CredentialProcessor {
|
|
|
14
15
|
|
|
15
16
|
private _isProcessingRootDocs = false;
|
|
16
17
|
|
|
17
|
-
constructor(private readonly _onNewRoot: (rootUrl: string) => void) {
|
|
18
|
+
constructor(private readonly _onNewRoot: (rootUrl: string) => void) {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected override async _open(ctx: Context): Promise<void> {}
|
|
23
|
+
|
|
24
|
+
protected override async _close(ctx: Context): Promise<void> {
|
|
25
|
+
this._isProcessingRootDocs = false;
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
async processCredential(credential: Credential) {
|
|
20
29
|
if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { type Doc } from '@dxos/automerge/automerge';
|
|
7
|
-
import { type
|
|
7
|
+
import { type AutomergeUrl, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
8
8
|
import { PropertiesType } from '@dxos/client-protocol';
|
|
9
|
-
import {
|
|
9
|
+
import { Context, cancelWithContext } from '@dxos/context';
|
|
10
10
|
import {
|
|
11
|
+
getCredentialAssertion,
|
|
11
12
|
type CredentialSigner,
|
|
12
13
|
type DelegateInvitationCredential,
|
|
13
|
-
|
|
14
|
+
createAdmissionCredentials,
|
|
14
15
|
type MemberInfo,
|
|
15
16
|
} from '@dxos/credentials';
|
|
16
|
-
import { type EchoHost } from '@dxos/echo-db';
|
|
17
|
+
import { convertLegacyReferences, findInlineObjectOfType, type EchoHost } from '@dxos/echo-db';
|
|
17
18
|
import {
|
|
18
19
|
AuthStatus,
|
|
19
20
|
type MetadataStore,
|
|
@@ -22,26 +23,33 @@ import {
|
|
|
22
23
|
type SpaceProtocol,
|
|
23
24
|
type SpaceProtocolSession,
|
|
24
25
|
} from '@dxos/echo-pipeline';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
26
|
+
import { CredentialServerExtension } from '@dxos/echo-pipeline';
|
|
27
|
+
import {
|
|
28
|
+
LEGACY_TYPE_PROPERTIES,
|
|
29
|
+
SpaceDocVersion,
|
|
30
|
+
encodeReference,
|
|
31
|
+
type ObjectStructure,
|
|
32
|
+
type SpaceDoc,
|
|
33
|
+
} from '@dxos/echo-protocol';
|
|
34
|
+
import { TYPE_PROPERTIES, generateEchoId, getTypeReference } from '@dxos/echo-schema';
|
|
35
|
+
import { type FeedStore, writeMessages } from '@dxos/feed-store';
|
|
28
36
|
import { invariant } from '@dxos/invariant';
|
|
29
37
|
import { type Keyring } from '@dxos/keyring';
|
|
30
38
|
import { PublicKey } from '@dxos/keys';
|
|
31
39
|
import { log } from '@dxos/log';
|
|
32
|
-
import { trace as Trace } from '@dxos/protocols';
|
|
40
|
+
import { trace as Trace, AlreadyJoinedError } from '@dxos/protocols';
|
|
33
41
|
import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
34
42
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
35
43
|
import { type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
36
|
-
import { type Credential, type ProfileDocument
|
|
44
|
+
import { SpaceMember, type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
37
45
|
import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
38
46
|
import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
|
|
39
47
|
import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
|
|
40
48
|
import { type Timeframe } from '@dxos/timeframe';
|
|
41
49
|
import { trace } from '@dxos/tracing';
|
|
42
|
-
import {
|
|
50
|
+
import { ComplexMap, assignDeep, deferFunction, forEachAsync } from '@dxos/util';
|
|
43
51
|
|
|
44
|
-
import { DataSpace
|
|
52
|
+
import { DataSpace } from './data-space';
|
|
45
53
|
import { spaceGenesis } from './genesis';
|
|
46
54
|
import { createAuthProvider } from '../identity';
|
|
47
55
|
import { type InvitationsManager } from '../invitations';
|
|
@@ -78,6 +86,14 @@ export type AcceptSpaceOptions = {
|
|
|
78
86
|
dataTimeframe?: Timeframe;
|
|
79
87
|
};
|
|
80
88
|
|
|
89
|
+
export type AdmitMemberOptions = {
|
|
90
|
+
spaceKey: PublicKey;
|
|
91
|
+
identityKey: PublicKey;
|
|
92
|
+
role: SpaceMember.Role;
|
|
93
|
+
profile?: ProfileDocument;
|
|
94
|
+
delegationCredentialId?: PublicKey;
|
|
95
|
+
};
|
|
96
|
+
|
|
81
97
|
export type DataSpaceManagerRuntimeParams = {
|
|
82
98
|
spaceMemberPresenceAnnounceInterval?: number;
|
|
83
99
|
spaceMemberPresenceOfflineTimeout?: number;
|
|
@@ -113,7 +129,7 @@ export class DataSpaceManager {
|
|
|
113
129
|
const rootHandle = rootUrl ? this._echoHost.automergeRepo.find(rootUrl as AutomergeUrl) : undefined;
|
|
114
130
|
const rootDoc = rootHandle?.docSync() as Doc<SpaceDoc> | undefined;
|
|
115
131
|
|
|
116
|
-
const properties = rootDoc &&
|
|
132
|
+
const properties = rootDoc && findInlineObjectOfType(rootDoc, TYPE_PROPERTIES);
|
|
117
133
|
|
|
118
134
|
return {
|
|
119
135
|
key: space.key.toHex(),
|
|
@@ -204,9 +220,24 @@ export class DataSpaceManager {
|
|
|
204
220
|
}
|
|
205
221
|
|
|
206
222
|
async isDefaultSpace(space: DataSpace): Promise<boolean> {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
if (!space.databaseRoot) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
switch (space.databaseRoot.getVersion()) {
|
|
227
|
+
case SpaceDocVersion.CURRENT: {
|
|
228
|
+
const [_, properties] = findInlineObjectOfType(space.databaseRoot.docSync()!, TYPE_PROPERTIES) ?? [];
|
|
229
|
+
return properties?.data?.[DEFAULT_SPACE_KEY] === this._signingContext.identityKey.toHex();
|
|
230
|
+
}
|
|
231
|
+
case SpaceDocVersion.LEGACY: {
|
|
232
|
+
const convertedDoc = await convertLegacyReferences(space.databaseRoot.docSync()!);
|
|
233
|
+
const [_, properties] = findInlineObjectOfType(convertedDoc, LEGACY_TYPE_PROPERTIES) ?? [];
|
|
234
|
+
return properties?.data?.[DEFAULT_SPACE_KEY] === this._signingContext.identityKey.toHex();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
default:
|
|
238
|
+
log.warn('unknown space version', { version: space.databaseRoot.getVersion(), spaceId: space.id });
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
210
241
|
}
|
|
211
242
|
|
|
212
243
|
async createDefaultSpace() {
|
|
@@ -226,7 +257,7 @@ export class DataSpaceManager {
|
|
|
226
257
|
},
|
|
227
258
|
};
|
|
228
259
|
|
|
229
|
-
const propertiesId =
|
|
260
|
+
const propertiesId = generateEchoId();
|
|
230
261
|
document.change((doc: SpaceDoc) => {
|
|
231
262
|
assignDeep(doc, ['objects', propertiesId], properties);
|
|
232
263
|
});
|
|
@@ -266,6 +297,35 @@ export class DataSpaceManager {
|
|
|
266
297
|
return space;
|
|
267
298
|
}
|
|
268
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
|
+
|
|
269
329
|
/**
|
|
270
330
|
* Wait until the space data pipeline is fully initialized.
|
|
271
331
|
* Used by invitation handler.
|
|
@@ -281,6 +341,19 @@ export class DataSpaceManager {
|
|
|
281
341
|
);
|
|
282
342
|
}
|
|
283
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
|
+
|
|
284
357
|
private async _constructSpace(metadata: SpaceMetadata) {
|
|
285
358
|
log('construct space', { metadata });
|
|
286
359
|
const gossip = new Gossip({
|
|
@@ -310,6 +383,7 @@ export class DataSpaceManager {
|
|
|
310
383
|
credentialAuthenticator: deferFunction(() => dataSpace.authVerifier.verifier),
|
|
311
384
|
},
|
|
312
385
|
onAuthorizedConnection: (session) => {
|
|
386
|
+
session.addExtension('dxos.mesh.teleport.admission-discovery', new CredentialServerExtension(space));
|
|
313
387
|
session.addExtension(
|
|
314
388
|
'dxos.mesh.teleport.gossip',
|
|
315
389
|
gossip.createExtension({ remotePeerId: session.remotePeerId }),
|