@dxos/client-services 0.1.22 → 0.1.24-next.6b1f434
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-ZPZBLOQW.mjs +4086 -0
- package/dist/lib/browser/chunk-ZPZBLOQW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +68 -3027
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +198 -0
- package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +1694 -667
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +3806 -0
- package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
- package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
- package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
- package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/network.d.ts +3 -3
- package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +9 -9
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +8 -2
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +5 -4
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
- package/dist/types/src/packlets/invitations/invitations.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +3 -1
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
- package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +9 -6
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
- package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space.d.ts +20 -4
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +3 -2
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
- package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/index.d.ts +2 -0
- package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/test-builder.d.ts +31 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +35 -0
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +9 -4
- package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/index.d.ts +6 -1
- package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/services.d.ts +9 -1
- package/dist/types/src/packlets/vault/services.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/shell-controller.d.ts +24 -0
- package/dist/types/src/packlets/vault/shell-controller.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/shell-runtime.d.ts +45 -0
- package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -1
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts +7 -4
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/package.json +30 -29
- package/src/packlets/deprecated/profile.ts +20 -2
- package/src/packlets/deprecated/space.ts +41 -37
- package/src/packlets/deprecated/system.ts +26 -13
- package/src/packlets/devtools/devtools.ts +4 -4
- package/src/packlets/devtools/feeds.ts +11 -6
- package/src/packlets/devtools/network.ts +20 -9
- package/src/packlets/devtools/spaces.ts +8 -7
- package/src/packlets/identity/authenticator.test.ts +4 -4
- package/src/packlets/identity/authenticator.ts +27 -17
- package/src/packlets/identity/identity-manager.test.ts +25 -16
- package/src/packlets/identity/identity-manager.ts +25 -13
- package/src/packlets/identity/identity.test.ts +22 -23
- package/src/packlets/identity/identity.ts +15 -10
- package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
- package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
- package/src/packlets/invitations/invitations.ts +2 -1
- package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
- package/src/packlets/invitations/space-invitations-handler.ts +9 -12
- package/src/packlets/services/service-context.test.ts +56 -0
- package/src/packlets/services/service-context.ts +62 -10
- package/src/packlets/services/service-definitions.ts +1 -1
- package/src/packlets/services/service-host.test.ts +74 -5
- package/src/packlets/services/service-host.ts +86 -24
- package/src/packlets/services/service-registry.test.ts +2 -1
- package/src/packlets/services/service-registry.ts +5 -1
- package/src/packlets/spaces/data-space-manager.test.ts +131 -0
- package/src/packlets/spaces/data-space-manager.ts +61 -15
- package/src/packlets/spaces/data-space.ts +125 -14
- package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
- package/src/packlets/spaces/notarization-plugin.ts +211 -0
- package/src/packlets/spaces/spaces-service.ts +10 -2
- package/src/packlets/testing/credential-utils.ts +28 -0
- package/src/packlets/testing/index.ts +2 -0
- package/src/packlets/testing/invitation-utils.ts +44 -0
- package/src/packlets/testing/test-builder.ts +96 -14
- package/src/packlets/vault/iframe-host-runtime.ts +131 -0
- package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +18 -3
- package/src/packlets/vault/index.ts +6 -1
- package/src/packlets/vault/services.ts +17 -1
- package/src/packlets/vault/shell-controller.ts +77 -0
- package/src/packlets/vault/shell-runtime.ts +133 -0
- package/src/packlets/vault/vault-resource-lock.ts +76 -0
- package/src/packlets/vault/worker-runtime.ts +16 -4
- package/src/packlets/vault/worker-session.ts +36 -26
- package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
|
@@ -3,33 +3,32 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { CredentialConsumer, getCredentialAssertion } from '@dxos/credentials';
|
|
6
7
|
import { failUndefined } from '@dxos/debug';
|
|
7
8
|
import {
|
|
8
|
-
MOCK_AUTH_PROVIDER,
|
|
9
|
-
MOCK_AUTH_VERIFIER,
|
|
10
9
|
valueEncoding,
|
|
11
|
-
DataServiceSubscriptions,
|
|
12
10
|
MetadataStore,
|
|
13
11
|
SpaceManager,
|
|
14
12
|
SigningContext,
|
|
13
|
+
DataServiceSubscriptions,
|
|
15
14
|
SnapshotStore
|
|
16
|
-
} from '@dxos/echo-
|
|
15
|
+
} from '@dxos/echo-pipeline';
|
|
17
16
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
18
17
|
import { Keyring } from '@dxos/keyring';
|
|
19
18
|
import { log } from '@dxos/log';
|
|
20
19
|
import { ModelFactory } from '@dxos/model-factory';
|
|
21
20
|
import { NetworkManager } from '@dxos/network-manager';
|
|
22
21
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
|
+
import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
23
|
import { Storage } from '@dxos/random-access-storage';
|
|
24
24
|
|
|
25
|
-
import { CreateIdentityOptions, IdentityManager } from '../identity';
|
|
25
|
+
import { CreateIdentityOptions, IdentityManager, JoinIdentityParams } from '../identity';
|
|
26
26
|
import { HaloInvitationsHandler, SpaceInvitationsHandler } from '../invitations';
|
|
27
27
|
import { DataSpaceManager } from '../spaces/data-space-manager';
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Shared backend for all client services.
|
|
31
31
|
*/
|
|
32
|
-
|
|
33
32
|
// TODO(burdon): Rename/break-up into smaller components. And/or make members private.
|
|
34
33
|
export class ServiceContext {
|
|
35
34
|
public readonly initialized = new Trigger();
|
|
@@ -42,6 +41,8 @@ export class ServiceContext {
|
|
|
42
41
|
public readonly identityManager: IdentityManager;
|
|
43
42
|
public readonly haloInvitations: HaloInvitationsHandler;
|
|
44
43
|
|
|
44
|
+
private _deviceSpaceSync?: CredentialConsumer<any>;
|
|
45
|
+
|
|
45
46
|
// Initialized after identity is initialized.
|
|
46
47
|
public dataSpaceManager?: DataSpaceManager;
|
|
47
48
|
public spaceInvitations?: SpaceInvitationsHandler;
|
|
@@ -66,19 +67,27 @@ export class ServiceContext {
|
|
|
66
67
|
}
|
|
67
68
|
})
|
|
68
69
|
});
|
|
70
|
+
|
|
69
71
|
this.spaceManager = new SpaceManager({
|
|
70
72
|
feedStore: this.feedStore,
|
|
71
73
|
networkManager: this.networkManager
|
|
72
74
|
});
|
|
75
|
+
|
|
73
76
|
this.identityManager = new IdentityManager(
|
|
74
77
|
this.metadataStore,
|
|
75
78
|
this.keyring,
|
|
79
|
+
this.feedStore,
|
|
76
80
|
this.spaceManager
|
|
77
81
|
);
|
|
78
82
|
|
|
79
83
|
// TODO(burdon): _initialize called in multiple places.
|
|
80
84
|
// TODO(burdon): Call _initialize on success.
|
|
81
|
-
this.haloInvitations = new HaloInvitationsHandler(
|
|
85
|
+
this.haloInvitations = new HaloInvitationsHandler({
|
|
86
|
+
networkManager: this.networkManager,
|
|
87
|
+
getIdentity: () => this.identityManager.identity ?? failUndefined(),
|
|
88
|
+
acceptIdentity: this._acceptIdentity.bind(this),
|
|
89
|
+
keyring: this.keyring
|
|
90
|
+
});
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
async open() {
|
|
@@ -94,6 +103,7 @@ export class ServiceContext {
|
|
|
94
103
|
|
|
95
104
|
async close() {
|
|
96
105
|
log('closing...');
|
|
106
|
+
await this._deviceSpaceSync?.close();
|
|
97
107
|
await this.dataSpaceManager?.close();
|
|
98
108
|
await this.identityManager.close();
|
|
99
109
|
await this.spaceManager.close();
|
|
@@ -117,17 +127,25 @@ export class ServiceContext {
|
|
|
117
127
|
return identity;
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
private async _acceptIdentity(params: JoinIdentityParams) {
|
|
131
|
+
const identity = await this.identityManager.acceptIdentity(params);
|
|
132
|
+
|
|
133
|
+
await this._initialize();
|
|
134
|
+
return identity;
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
// Called when identity is created.
|
|
121
138
|
private async _initialize() {
|
|
122
139
|
log('initializing spaces...');
|
|
123
140
|
const identity = this.identityManager.identity ?? failUndefined();
|
|
124
141
|
const signingContext: SigningContext = {
|
|
125
|
-
credentialProvider: MOCK_AUTH_PROVIDER,
|
|
126
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER,
|
|
127
142
|
credentialSigner: identity.getIdentityCredentialSigner(),
|
|
128
143
|
identityKey: identity.identityKey,
|
|
129
144
|
deviceKey: identity.deviceKey,
|
|
130
|
-
profile: identity.profileDocument
|
|
145
|
+
profile: identity.profileDocument,
|
|
146
|
+
recordCredential: async (credential) => {
|
|
147
|
+
await identity.controlPipeline.writer.write({ credential: { credential } });
|
|
148
|
+
}
|
|
131
149
|
};
|
|
132
150
|
|
|
133
151
|
this.dataSpaceManager = new DataSpaceManager(
|
|
@@ -137,9 +155,11 @@ export class ServiceContext {
|
|
|
137
155
|
this.keyring,
|
|
138
156
|
signingContext,
|
|
139
157
|
this.modelFactory,
|
|
158
|
+
this.feedStore,
|
|
140
159
|
this.snapshotStore
|
|
141
160
|
);
|
|
142
161
|
await this.dataSpaceManager.open();
|
|
162
|
+
|
|
143
163
|
this.spaceInvitations = new SpaceInvitationsHandler(
|
|
144
164
|
this.networkManager,
|
|
145
165
|
this.dataSpaceManager,
|
|
@@ -147,5 +167,37 @@ export class ServiceContext {
|
|
|
147
167
|
this.keyring
|
|
148
168
|
);
|
|
149
169
|
this.initialized.wake();
|
|
170
|
+
|
|
171
|
+
this._deviceSpaceSync = identity.space.spaceState.registerProcessor({
|
|
172
|
+
process: async (credential: Credential) => {
|
|
173
|
+
const assertion = getCredentialAssertion(credential);
|
|
174
|
+
if (assertion['@type'] !== 'dxos.halo.credentials.SpaceMember') {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (assertion.spaceKey.equals(identity.space.key)) {
|
|
178
|
+
// ignore halo space
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (!this.dataSpaceManager) {
|
|
182
|
+
log('dataSpaceManager not initialized yet, ignoring space admission', { details: assertion });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (this.dataSpaceManager.spaces.has(assertion.spaceKey)) {
|
|
186
|
+
log('space already exists, ignoring space admission', { details: assertion });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
log('accepting space recorded in halo', { details: assertion });
|
|
192
|
+
await this.dataSpaceManager.acceptSpace({
|
|
193
|
+
spaceKey: assertion.spaceKey,
|
|
194
|
+
genesisFeedKey: assertion.genesisFeedKey
|
|
195
|
+
});
|
|
196
|
+
} catch (err) {
|
|
197
|
+
log.catch(err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
await this._deviceSpaceSync.open();
|
|
150
202
|
}
|
|
151
203
|
}
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { latch, Trigger } from '@dxos/async';
|
|
6
8
|
import { Config } from '@dxos/config';
|
|
9
|
+
import { verifyPresentation } from '@dxos/credentials';
|
|
10
|
+
import { PublicKey } from '@dxos/keys';
|
|
7
11
|
import { MemorySignalManagerContext } from '@dxos/messaging';
|
|
12
|
+
import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
8
13
|
import { afterTest, describe, test } from '@dxos/test';
|
|
9
14
|
|
|
10
|
-
import { createServiceHost } from '../testing';
|
|
15
|
+
import { createMockCredential, createServiceHost } from '../testing';
|
|
11
16
|
|
|
12
17
|
describe('ClientServicesHost', () => {
|
|
13
18
|
test('queryCredentials', async () => {
|
|
@@ -15,10 +20,10 @@ describe('ClientServicesHost', () => {
|
|
|
15
20
|
await host.open();
|
|
16
21
|
afterTest(() => host.close());
|
|
17
22
|
|
|
18
|
-
await host.services.ProfileService
|
|
19
|
-
const { publicKey: spaceKey } = await host.services.SpaceService
|
|
23
|
+
await host.services.ProfileService!.createProfile({});
|
|
24
|
+
const { publicKey: spaceKey } = await host.services.SpaceService!.createSpace();
|
|
20
25
|
|
|
21
|
-
const stream = host.services.SpacesService
|
|
26
|
+
const stream = host.services.SpacesService!.queryCredentials({ spaceKey });
|
|
22
27
|
const [done, tick] = latch({ count: 3 });
|
|
23
28
|
stream.subscribe((credential) => {
|
|
24
29
|
tick();
|
|
@@ -28,4 +33,68 @@ describe('ClientServicesHost', () => {
|
|
|
28
33
|
|
|
29
34
|
await done();
|
|
30
35
|
});
|
|
36
|
+
|
|
37
|
+
test('write and query credentials', async () => {
|
|
38
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
39
|
+
await host.open();
|
|
40
|
+
afterTest(() => host.close());
|
|
41
|
+
|
|
42
|
+
await host.services.ProfileService!.createProfile({});
|
|
43
|
+
|
|
44
|
+
const testCredential = await createMockCredential({
|
|
45
|
+
signer: host._serviceContext.keyring,
|
|
46
|
+
issuer: host._serviceContext.identityManager.identity!.deviceKey
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Test if Profile exposes haloSpace key.
|
|
50
|
+
const haloSpace = new Trigger<PublicKey>();
|
|
51
|
+
host.services.ProfileService!.subscribeProfile()!.subscribe(({ profile }) => {
|
|
52
|
+
if (profile?.haloSpace) {
|
|
53
|
+
haloSpace.wake(profile.haloSpace);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await host.services.SpacesService?.writeCredentials({
|
|
58
|
+
spaceKey: await haloSpace.wait(),
|
|
59
|
+
credentials: [testCredential]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const credentials = host.services.SpacesService!.queryCredentials({ spaceKey: await haloSpace.wait() });
|
|
63
|
+
const queriedCredential = new Trigger<Credential>();
|
|
64
|
+
credentials.subscribe((credential) => {
|
|
65
|
+
if (credential.subject.id.equals(testCredential.subject.id)) {
|
|
66
|
+
queriedCredential.wake(credential);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
afterTest(() => credentials.close());
|
|
70
|
+
|
|
71
|
+
await queriedCredential.wait();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sign presentation', async () => {
|
|
75
|
+
const host = createServiceHost(new Config(), new MemorySignalManagerContext());
|
|
76
|
+
await host.open();
|
|
77
|
+
afterTest(() => host.close());
|
|
78
|
+
|
|
79
|
+
await host.services.ProfileService!.createProfile({});
|
|
80
|
+
|
|
81
|
+
const testCredential = await createMockCredential({
|
|
82
|
+
signer: host._serviceContext.keyring,
|
|
83
|
+
issuer: host._serviceContext.identityManager.identity!.deviceKey
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const nonce = new Uint8Array([0, 0, 0, 0]);
|
|
87
|
+
|
|
88
|
+
const presentation = await host.services.ProfileService!.signPresentation({
|
|
89
|
+
presentation: {
|
|
90
|
+
credentials: [testCredential]
|
|
91
|
+
},
|
|
92
|
+
nonce
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(presentation.proofs?.[0].nonce).to.deep.equal(nonce);
|
|
96
|
+
expect(await verifyPresentation(presentation)).to.deep.equal({
|
|
97
|
+
kind: 'pass'
|
|
98
|
+
});
|
|
99
|
+
});
|
|
31
100
|
});
|
|
@@ -4,21 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
import { Config } from '@dxos/config';
|
|
6
6
|
import { raise } from '@dxos/debug';
|
|
7
|
-
import {
|
|
7
|
+
import { DocumentModel } from '@dxos/document-model';
|
|
8
|
+
import { DataServiceImpl } from '@dxos/echo-pipeline';
|
|
8
9
|
import { log } from '@dxos/log';
|
|
9
10
|
import { ModelFactory } from '@dxos/model-factory';
|
|
10
11
|
import { NetworkManager } from '@dxos/network-manager';
|
|
11
|
-
import {
|
|
12
|
+
import { Status } from '@dxos/protocols/proto/dxos/client';
|
|
12
13
|
import { Storage } from '@dxos/random-access-storage';
|
|
13
14
|
import { TextModel } from '@dxos/text-model';
|
|
14
15
|
|
|
15
|
-
import { SpaceServiceImpl, ProfileServiceImpl,
|
|
16
|
+
import { SpaceServiceImpl, ProfileServiceImpl, TracingServiceImpl, SystemServiceImpl } from '../deprecated';
|
|
16
17
|
import { DevtoolsServiceImpl, DevtoolsHostEvents } from '../devtools';
|
|
17
18
|
import { DevicesServiceImpl } from '../identity/devices-service-impl';
|
|
18
19
|
import { HaloInvitationsServiceImpl, SpaceInvitationsServiceImpl } from '../invitations';
|
|
19
20
|
import { NetworkServiceImpl } from '../network';
|
|
20
21
|
import { SpacesServiceImpl } from '../spaces';
|
|
21
22
|
import { createStorageObjects } from '../storage';
|
|
23
|
+
import { VaultResourceLock } from '../vault';
|
|
22
24
|
import { ServiceContext } from './service-context';
|
|
23
25
|
import { ClientServicesProvider, ClientServices, clientServiceBundle } from './service-definitions';
|
|
24
26
|
import { ServiceRegistry } from './service-registry';
|
|
@@ -26,7 +28,7 @@ import { ServiceRegistry } from './service-registry';
|
|
|
26
28
|
// TODO(burdon): Factor out to spaces.
|
|
27
29
|
// TODO(burdon): Defaults (with TextModel).
|
|
28
30
|
export const createDefaultModelFactory = () => {
|
|
29
|
-
return new ModelFactory().registerModel(
|
|
31
|
+
return new ModelFactory().registerModel(DocumentModel).registerModel(TextModel);
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type ClientServicesHostParams = {
|
|
@@ -34,31 +36,77 @@ type ClientServicesHostParams = {
|
|
|
34
36
|
modelFactory?: ModelFactory;
|
|
35
37
|
networkManager: NetworkManager;
|
|
36
38
|
storage?: Storage;
|
|
39
|
+
lockKey?: string;
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Remote service implementation.
|
|
41
44
|
*/
|
|
42
45
|
export class ClientServicesHost implements ClientServicesProvider {
|
|
43
|
-
private
|
|
44
|
-
private _serviceRegistry
|
|
46
|
+
private readonly _resourceLock?: VaultResourceLock;
|
|
47
|
+
private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
|
|
48
|
+
private readonly _systemService: SystemServiceImpl;
|
|
45
49
|
|
|
46
50
|
private readonly _config: Config;
|
|
47
51
|
private readonly _modelFactory: ModelFactory;
|
|
48
52
|
private readonly _networkManager: NetworkManager;
|
|
49
|
-
private _storage: Storage;
|
|
53
|
+
private readonly _storage: Storage;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
_serviceContext!: ServiceContext;
|
|
59
|
+
private _open = false;
|
|
50
60
|
|
|
51
61
|
constructor({
|
|
52
62
|
config,
|
|
53
63
|
modelFactory = createDefaultModelFactory(),
|
|
54
64
|
// TODO(burdon): Create ApolloLink abstraction (see Client).
|
|
55
65
|
networkManager,
|
|
56
|
-
storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage
|
|
66
|
+
storage = createStorageObjects(config.get('runtime.client.storage', {})!).storage,
|
|
67
|
+
lockKey
|
|
57
68
|
}: ClientServicesHostParams) {
|
|
58
69
|
this._config = config;
|
|
59
70
|
this._modelFactory = modelFactory;
|
|
60
71
|
this._networkManager = networkManager;
|
|
61
72
|
this._storage = storage;
|
|
73
|
+
|
|
74
|
+
this._resourceLock = lockKey
|
|
75
|
+
? new VaultResourceLock({
|
|
76
|
+
lockKey,
|
|
77
|
+
onAcquire: () => this.open(),
|
|
78
|
+
onRelease: () => this.close()
|
|
79
|
+
})
|
|
80
|
+
: undefined;
|
|
81
|
+
|
|
82
|
+
this._systemService = new SystemServiceImpl({
|
|
83
|
+
config: this._config,
|
|
84
|
+
|
|
85
|
+
onInit: async () => {
|
|
86
|
+
await this._resourceLock?.acquire();
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
onStatus: async () => {
|
|
90
|
+
if (!this.isOpen) {
|
|
91
|
+
return { status: Status.INACTIVE };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { status: Status.ACTIVE };
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
onReset: async () => {
|
|
98
|
+
await this._serviceContext?.reset();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// TODO(burdon): Start to think of DMG (dynamic services).
|
|
103
|
+
this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
|
|
104
|
+
SystemService: this._systemService
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get isOpen() {
|
|
109
|
+
return this._open;
|
|
62
110
|
}
|
|
63
111
|
|
|
64
112
|
get descriptors() {
|
|
@@ -70,27 +118,21 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
async open() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
77
|
-
log('opened', { deviceKey });
|
|
78
|
-
}
|
|
121
|
+
if (this._open) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
82
|
-
log('closing...', { deviceKey });
|
|
83
|
-
await this._serviceContext.close();
|
|
84
|
-
log('closed', { deviceKey });
|
|
85
|
-
}
|
|
125
|
+
log('opening...');
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
async _initialize() {
|
|
127
|
+
// TODO(wittjosiah): Make re-entrant.
|
|
89
128
|
// TODO(burdon): Break into components.
|
|
90
129
|
this._serviceContext = new ServiceContext(this._storage, this._networkManager, this._modelFactory);
|
|
91
130
|
|
|
92
131
|
// TODO(burdon): Start to think of DMG (dynamic services).
|
|
93
|
-
this._serviceRegistry
|
|
132
|
+
this._serviceRegistry.setServices({
|
|
133
|
+
// TODO(burdon): Move to new protobuf definitions.
|
|
134
|
+
SystemService: this._systemService,
|
|
135
|
+
|
|
94
136
|
HaloInvitationsService: new HaloInvitationsServiceImpl(
|
|
95
137
|
this._serviceContext.identityManager,
|
|
96
138
|
this._serviceContext.haloInvitations
|
|
@@ -112,8 +154,10 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
112
154
|
|
|
113
155
|
// TODO(burdon): Move to new protobuf definitions.
|
|
114
156
|
ProfileService: new ProfileServiceImpl(this._serviceContext),
|
|
157
|
+
|
|
158
|
+
// TODO(burdon): Port old SubscribeSpaces to QueryServices>
|
|
115
159
|
SpaceService: new SpaceServiceImpl(this._serviceContext),
|
|
116
|
-
|
|
160
|
+
|
|
117
161
|
TracingService: new TracingServiceImpl(this._config),
|
|
118
162
|
DevtoolsHost: new DevtoolsServiceImpl({
|
|
119
163
|
events: new DevtoolsHostEvents(),
|
|
@@ -121,5 +165,23 @@ export class ClientServicesHost implements ClientServicesProvider {
|
|
|
121
165
|
context: this._serviceContext
|
|
122
166
|
})
|
|
123
167
|
});
|
|
168
|
+
|
|
169
|
+
await this._serviceContext.open();
|
|
170
|
+
this._open = true;
|
|
171
|
+
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
172
|
+
log('opened', { deviceKey });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async close() {
|
|
176
|
+
if (!this._open) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
|
|
181
|
+
log('closing...', { deviceKey });
|
|
182
|
+
this._serviceRegistry.setServices({ SystemService: this._systemService });
|
|
183
|
+
await this._serviceContext.close();
|
|
184
|
+
this._open = false;
|
|
185
|
+
log('closed', { deviceKey });
|
|
124
186
|
}
|
|
125
187
|
}
|
|
@@ -13,6 +13,7 @@ import { describe, test } from '@dxos/test';
|
|
|
13
13
|
|
|
14
14
|
import { SpaceInvitationsProxy, SpaceInvitationsServiceImpl } from '../invitations';
|
|
15
15
|
import { createServiceContext } from '../testing';
|
|
16
|
+
import { ClientServices } from './service-definitions';
|
|
16
17
|
import { ServiceRegistry } from './service-registry';
|
|
17
18
|
|
|
18
19
|
// TODO(burdon): Create TestService (that doesn't require peers).
|
|
@@ -54,7 +55,7 @@ describe('service registry', () => {
|
|
|
54
55
|
|
|
55
56
|
const server = createProtoRpcPeer({
|
|
56
57
|
exposed: serviceRegistry.descriptors,
|
|
57
|
-
handlers: serviceRegistry.services,
|
|
58
|
+
handlers: serviceRegistry.services as ClientServices,
|
|
58
59
|
port: serverPort
|
|
59
60
|
});
|
|
60
61
|
|
|
@@ -11,7 +11,7 @@ export class ServiceRegistry<Services> {
|
|
|
11
11
|
// prettier-ignore
|
|
12
12
|
constructor (
|
|
13
13
|
private readonly _serviceBundle: ServiceBundle<Services>,
|
|
14
|
-
private
|
|
14
|
+
private _handlers: Partial<Services>
|
|
15
15
|
) {}
|
|
16
16
|
|
|
17
17
|
get descriptors() {
|
|
@@ -21,4 +21,8 @@ export class ServiceRegistry<Services> {
|
|
|
21
21
|
get services() {
|
|
22
22
|
return this._handlers;
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
setServices(services: Partial<Services>) {
|
|
26
|
+
this._handlers = services;
|
|
27
|
+
}
|
|
24
28
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { createAdmissionCredentials } from '@dxos/credentials';
|
|
8
|
+
import { DocumentModel } from '@dxos/document-model';
|
|
9
|
+
import { AuthStatus, DataServiceSubscriptions } from '@dxos/echo-pipeline';
|
|
10
|
+
import { writeMessages } from '@dxos/feed-store';
|
|
11
|
+
import { log } from '@dxos/log';
|
|
12
|
+
import { ModelFactory } from '@dxos/model-factory';
|
|
13
|
+
import { test, describe } from '@dxos/test';
|
|
14
|
+
|
|
15
|
+
import { createSigningContext, syncItems, TestBuilder } from '../testing';
|
|
16
|
+
import { DataSpaceManager } from './data-space-manager';
|
|
17
|
+
|
|
18
|
+
describe('DataSpaceManager', () => {
|
|
19
|
+
test('create space', async () => {
|
|
20
|
+
const builder = new TestBuilder();
|
|
21
|
+
|
|
22
|
+
const peer = builder.createPeer();
|
|
23
|
+
const identity = await createSigningContext(peer.keyring);
|
|
24
|
+
const dataSpaceManager = new DataSpaceManager(
|
|
25
|
+
peer.spaceManager,
|
|
26
|
+
peer.metadataStore,
|
|
27
|
+
new DataServiceSubscriptions(),
|
|
28
|
+
peer.keyring,
|
|
29
|
+
identity,
|
|
30
|
+
new ModelFactory().registerModel(DocumentModel),
|
|
31
|
+
peer.feedStore,
|
|
32
|
+
peer.snapshotStore
|
|
33
|
+
);
|
|
34
|
+
const space = await dataSpaceManager.createSpace();
|
|
35
|
+
|
|
36
|
+
// Process all written mutations.
|
|
37
|
+
await space.inner.controlPipeline.state.waitUntilTimeframe(space.inner.controlPipeline.state.endTimeframe);
|
|
38
|
+
|
|
39
|
+
expect(space.inner.spaceState.genesisCredential).to.exist;
|
|
40
|
+
expect(space.inner.spaceState.members.size).to.equal(1);
|
|
41
|
+
expect(space.inner.spaceState.feeds.size).to.equal(2);
|
|
42
|
+
expect(space.inner.protocol.feeds.size).to.equal(2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('sync between peers', async () => {
|
|
46
|
+
const builder = new TestBuilder();
|
|
47
|
+
|
|
48
|
+
const peer1 = builder.createPeer();
|
|
49
|
+
const identity1 = await createSigningContext(peer1.keyring);
|
|
50
|
+
const dataSpaceManager1 = new DataSpaceManager(
|
|
51
|
+
peer1.spaceManager,
|
|
52
|
+
peer1.metadataStore,
|
|
53
|
+
new DataServiceSubscriptions(),
|
|
54
|
+
peer1.keyring,
|
|
55
|
+
identity1,
|
|
56
|
+
new ModelFactory().registerModel(DocumentModel),
|
|
57
|
+
peer1.feedStore,
|
|
58
|
+
peer1.snapshotStore
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const peer2 = builder.createPeer();
|
|
62
|
+
const identity2 = await createSigningContext(peer2.keyring);
|
|
63
|
+
const dataSpaceManager2 = new DataSpaceManager(
|
|
64
|
+
peer2.spaceManager,
|
|
65
|
+
peer2.metadataStore,
|
|
66
|
+
new DataServiceSubscriptions(),
|
|
67
|
+
peer2.keyring,
|
|
68
|
+
identity2,
|
|
69
|
+
new ModelFactory().registerModel(DocumentModel),
|
|
70
|
+
peer2.feedStore,
|
|
71
|
+
peer1.snapshotStore
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const space1 = await dataSpaceManager1.createSpace();
|
|
75
|
+
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
76
|
+
|
|
77
|
+
// Admit peer2 to space1.
|
|
78
|
+
await writeMessages(
|
|
79
|
+
space1.inner.controlPipeline.writer,
|
|
80
|
+
await createAdmissionCredentials(
|
|
81
|
+
identity1.credentialSigner,
|
|
82
|
+
identity2.identityKey,
|
|
83
|
+
space1.key,
|
|
84
|
+
space1.inner.genesisFeedKey
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Accept must be called after admission so that the peer can authenticate for notarization.
|
|
89
|
+
const space2 = await dataSpaceManager2.acceptSpace({
|
|
90
|
+
spaceKey: space1.key,
|
|
91
|
+
genesisFeedKey: space1.inner.genesisFeedKey
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
log('', {
|
|
95
|
+
peer1: {
|
|
96
|
+
identity: identity1.identityKey,
|
|
97
|
+
device: identity1.deviceKey,
|
|
98
|
+
control: space1.inner.controlFeedKey,
|
|
99
|
+
data: space1.inner.dataFeedKey
|
|
100
|
+
},
|
|
101
|
+
peer2: {
|
|
102
|
+
identity: identity2.identityKey,
|
|
103
|
+
device: identity2.deviceKey,
|
|
104
|
+
control: space2.inner.controlFeedKey,
|
|
105
|
+
data: space2.inner.dataFeedKey
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await space1.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
110
|
+
await space2.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.endTimeframe);
|
|
111
|
+
|
|
112
|
+
log('', {
|
|
113
|
+
space1: {
|
|
114
|
+
timeframe: space1.inner.controlPipeline.state.timeframe,
|
|
115
|
+
endTimeframe: space1.inner.controlPipeline.state.endTimeframe
|
|
116
|
+
},
|
|
117
|
+
space2: {
|
|
118
|
+
timeframe: space2.inner.controlPipeline.state.timeframe,
|
|
119
|
+
endTimeframe: space2.inner.controlPipeline.state.endTimeframe
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
log.break();
|
|
123
|
+
|
|
124
|
+
await syncItems(space1.dataPipelineController, space2.dataPipelineController);
|
|
125
|
+
|
|
126
|
+
expect(space1.inner.protocol.sessions.get(identity2.deviceKey)).to.exist;
|
|
127
|
+
expect(space1.inner.protocol.sessions.get(identity2.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
128
|
+
expect(space2.inner.protocol.sessions.get(identity1.deviceKey)).to.exist;
|
|
129
|
+
expect(space2.inner.protocol.sessions.get(identity1.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
130
|
+
});
|
|
131
|
+
});
|