@dxos/client-services 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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-J33W6T4Q.mjs → chunk-5A3KX2RY.mjs} +1745 -1208
- package/dist/lib/browser/chunk-5A3KX2RY.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +33 -16
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +14 -7
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-34HKLADW.mjs → chunk-FNPO5UMU.mjs} +1745 -1208
- package/dist/lib/node-esm/chunk-FNPO5UMU.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +33 -16
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +14 -7
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -3
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +5 -4
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-service.d.ts +1 -6
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +6 -9
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
- package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
- package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
- package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
- package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
- package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +3 -4
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
- package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +6 -0
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
- package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +17 -10
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +22 -6
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -3
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -43
- package/src/index.ts +1 -0
- package/src/packlets/agents/edge-agent-manager.ts +8 -5
- package/src/packlets/agents/edge-agent-service.ts +2 -2
- package/src/packlets/identity/identity-manager.test.ts +5 -5
- package/src/packlets/identity/identity-manager.ts +16 -13
- package/src/packlets/identity/identity-recovery-manager.ts +20 -16
- package/src/packlets/identity/identity-service.test.ts +6 -26
- package/src/packlets/identity/identity-service.ts +5 -76
- package/src/packlets/identity/identity.test.ts +2 -2
- package/src/packlets/identity/identity.ts +7 -29
- package/src/packlets/invitations/edge-invitation-handler.ts +4 -3
- package/src/packlets/invitations/invitations-handler.test.ts +4 -4
- package/src/packlets/invitations/invitations-handler.ts +3 -3
- package/src/packlets/invitations/invitations-manager.ts +37 -14
- package/src/packlets/invitations/invitations-service.ts +4 -4
- package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
- package/src/packlets/invitations/space-invitation-protocol.ts +3 -1
- package/src/packlets/logging/logging-service.ts +4 -0
- package/src/packlets/network/network-service.ts +5 -4
- package/src/packlets/services/feed-syncer.test.ts +340 -0
- package/src/packlets/services/feed-syncer.ts +337 -0
- package/src/packlets/services/platform.ts +7 -1
- package/src/packlets/services/service-context.test.ts +3 -2
- package/src/packlets/services/service-context.ts +106 -31
- package/src/packlets/services/service-host.test.ts +8 -7
- package/src/packlets/services/service-host.ts +9 -7
- package/src/packlets/space-export/space-archive-reader.ts +64 -3
- package/src/packlets/space-export/space-archive-writer.ts +36 -1
- package/src/packlets/space-export/space-archive.test.ts +287 -0
- package/src/packlets/spaces/data-space-manager.test.ts +79 -13
- package/src/packlets/spaces/data-space-manager.ts +71 -103
- package/src/packlets/spaces/data-space.ts +46 -23
- package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
- package/src/packlets/spaces/edge-feed-replicator.ts +8 -7
- package/src/packlets/spaces/epoch-migrations.ts +3 -3
- package/src/packlets/spaces/genesis.ts +6 -1
- package/src/packlets/spaces/notarization-plugin.ts +2 -1
- package/src/packlets/spaces/spaces-service.test.ts +9 -6
- package/src/packlets/spaces/spaces-service.ts +30 -8
- package/src/packlets/testing/invitation-utils.ts +3 -2
- package/src/packlets/worker/worker-runtime.ts +14 -6
- package/src/packlets/worker/worker-session.ts +4 -4
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-J33W6T4Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-34HKLADW.mjs.map +0 -7
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
- package/src/packlets/identity/default-space-state-machine.ts +0 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/client-services",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bcb3aa67d6",
|
|
4
4
|
"description": "DXOS client services implementation",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -61,56 +61,56 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@automerge/automerge": "3.2.3",
|
|
63
63
|
"@automerge/automerge-repo": "2.5.1",
|
|
64
|
-
"@effect/experimental": "0.
|
|
65
|
-
"@effect/sql": "0.
|
|
66
|
-
"@effect/sql-sqlite-node": "0.
|
|
64
|
+
"@effect/experimental": "0.58.0",
|
|
65
|
+
"@effect/sql": "0.49.0",
|
|
66
|
+
"@effect/sql-sqlite-node": "0.50.1",
|
|
67
67
|
"@obsidize/tar-browserify": "^5.2.0",
|
|
68
68
|
"cbor-x": "^1.5.4",
|
|
69
|
-
"effect": "3.
|
|
69
|
+
"effect": "3.20.0",
|
|
70
70
|
"platform": "^1.3.6",
|
|
71
|
-
"@dxos/client-protocol": "0.8.4-main.
|
|
72
|
-
"@dxos/async": "0.8.4-main.
|
|
73
|
-
"@dxos/codec-protobuf": "0.8.4-main.
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/crypto": "0.8.4-main.
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/echo": "0.8.4-main.
|
|
80
|
-
"@dxos/echo-
|
|
81
|
-
"@dxos/echo-
|
|
82
|
-
"@dxos/echo-
|
|
83
|
-
"@dxos/edge-client": "0.8.4-main.
|
|
84
|
-
"@dxos/effect": "0.8.4-main.
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/invariant": "0.8.4-main.
|
|
87
|
-
"@dxos/feed
|
|
88
|
-
"@dxos/keyring": "0.8.4-main.
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/
|
|
91
|
-
"@dxos/
|
|
92
|
-
"@dxos/
|
|
93
|
-
"@dxos/
|
|
94
|
-
"@dxos/
|
|
95
|
-
"@dxos/
|
|
96
|
-
"@dxos/protocols": "0.8.4-main.
|
|
97
|
-
"@dxos/
|
|
98
|
-
"@dxos/
|
|
99
|
-
"@dxos/
|
|
100
|
-
"@dxos/
|
|
101
|
-
"@dxos/
|
|
102
|
-
"@dxos/
|
|
103
|
-
"@dxos/util": "0.8.4-main.
|
|
104
|
-
"@dxos/
|
|
105
|
-
"@dxos/
|
|
106
|
-
"@dxos/
|
|
71
|
+
"@dxos/client-protocol": "0.8.4-main.bcb3aa67d6",
|
|
72
|
+
"@dxos/async": "0.8.4-main.bcb3aa67d6",
|
|
73
|
+
"@dxos/codec-protobuf": "0.8.4-main.bcb3aa67d6",
|
|
74
|
+
"@dxos/context": "0.8.4-main.bcb3aa67d6",
|
|
75
|
+
"@dxos/credentials": "0.8.4-main.bcb3aa67d6",
|
|
76
|
+
"@dxos/crypto": "0.8.4-main.bcb3aa67d6",
|
|
77
|
+
"@dxos/config": "0.8.4-main.bcb3aa67d6",
|
|
78
|
+
"@dxos/debug": "0.8.4-main.bcb3aa67d6",
|
|
79
|
+
"@dxos/echo": "0.8.4-main.bcb3aa67d6",
|
|
80
|
+
"@dxos/echo-db": "0.8.4-main.bcb3aa67d6",
|
|
81
|
+
"@dxos/echo-protocol": "0.8.4-main.bcb3aa67d6",
|
|
82
|
+
"@dxos/echo-pipeline": "0.8.4-main.bcb3aa67d6",
|
|
83
|
+
"@dxos/edge-client": "0.8.4-main.bcb3aa67d6",
|
|
84
|
+
"@dxos/effect": "0.8.4-main.bcb3aa67d6",
|
|
85
|
+
"@dxos/feed-store": "0.8.4-main.bcb3aa67d6",
|
|
86
|
+
"@dxos/invariant": "0.8.4-main.bcb3aa67d6",
|
|
87
|
+
"@dxos/feed": "0.8.4-main.bcb3aa67d6",
|
|
88
|
+
"@dxos/keyring": "0.8.4-main.bcb3aa67d6",
|
|
89
|
+
"@dxos/keys": "0.8.4-main.bcb3aa67d6",
|
|
90
|
+
"@dxos/kv-store": "0.8.4-main.bcb3aa67d6",
|
|
91
|
+
"@dxos/log": "0.8.4-main.bcb3aa67d6",
|
|
92
|
+
"@dxos/lock-file": "0.8.4-main.bcb3aa67d6",
|
|
93
|
+
"@dxos/messaging": "0.8.4-main.bcb3aa67d6",
|
|
94
|
+
"@dxos/network-manager": "0.8.4-main.bcb3aa67d6",
|
|
95
|
+
"@dxos/node-std": "0.8.4-main.bcb3aa67d6",
|
|
96
|
+
"@dxos/protocols": "0.8.4-main.bcb3aa67d6",
|
|
97
|
+
"@dxos/rpc": "0.8.4-main.bcb3aa67d6",
|
|
98
|
+
"@dxos/teleport": "0.8.4-main.bcb3aa67d6",
|
|
99
|
+
"@dxos/sql-sqlite": "0.8.4-main.bcb3aa67d6",
|
|
100
|
+
"@dxos/random-access-storage": "0.8.4-main.bcb3aa67d6",
|
|
101
|
+
"@dxos/timeframe": "0.8.4-main.bcb3aa67d6",
|
|
102
|
+
"@dxos/teleport-extension-gossip": "0.8.4-main.bcb3aa67d6",
|
|
103
|
+
"@dxos/util": "0.8.4-main.bcb3aa67d6",
|
|
104
|
+
"@dxos/websocket-rpc": "0.8.4-main.bcb3aa67d6",
|
|
105
|
+
"@dxos/tracing": "0.8.4-main.bcb3aa67d6",
|
|
106
|
+
"@dxos/teleport-extension-object-sync": "0.8.4-main.bcb3aa67d6"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@types/platform": "^1.3.4",
|
|
110
110
|
"@types/readable-stream": "^2.3.9",
|
|
111
111
|
"get-port-please": "^3.1.1",
|
|
112
|
-
"@dxos/
|
|
113
|
-
"@dxos/
|
|
112
|
+
"@dxos/test-utils": "0.8.4-main.bcb3aa67d6",
|
|
113
|
+
"@dxos/signal": "0.8.4-main.bcb3aa67d6"
|
|
114
114
|
},
|
|
115
115
|
"publishConfig": {
|
|
116
116
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './packlets/identity';
|
|
|
8
8
|
export * from './packlets/invitations';
|
|
9
9
|
export * from './packlets/locks';
|
|
10
10
|
export * from './packlets/services';
|
|
11
|
+
export * from './packlets/space-export';
|
|
11
12
|
export * from './packlets/spaces';
|
|
12
13
|
export * from './packlets/storage';
|
|
13
14
|
export * from './packlets/worker';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Context } from '@dxos/context';
|
|
5
6
|
import { DeferredTask, Event, scheduleTask, synchronized } from '@dxos/async';
|
|
6
7
|
import { Resource } from '@dxos/context';
|
|
7
8
|
import { type EdgeHttpClient } from '@dxos/edge-client';
|
|
@@ -50,12 +51,12 @@ export class EdgeAgentManager extends Resource {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
@synchronized
|
|
53
|
-
public async createAgent(): Promise<void> {
|
|
54
|
+
public async createAgent(ctx: Context): Promise<void> {
|
|
54
55
|
invariant(this.isOpen);
|
|
55
56
|
invariant(this._edgeHttpClient);
|
|
56
57
|
invariant(this._edgeFeatures?.agents);
|
|
57
58
|
|
|
58
|
-
const response = await this._edgeHttpClient.createAgent({
|
|
59
|
+
const response = await this._edgeHttpClient.createAgent(ctx, {
|
|
59
60
|
identityKey: this._identity.identityKey.toHex(),
|
|
60
61
|
haloSpaceId: this._identity.haloSpaceId,
|
|
61
62
|
haloSpaceKey: this._identity.haloSpaceKey.toHex(),
|
|
@@ -92,7 +93,7 @@ export class EdgeAgentManager extends Resource {
|
|
|
92
93
|
|
|
93
94
|
this._lastKnownDeviceCount = this._identity.authorizedDeviceKeys.size;
|
|
94
95
|
this._fetchAgentStatusTask = new DeferredTask(this._ctx, async () => {
|
|
95
|
-
await this._fetchAgentStatus();
|
|
96
|
+
await this._fetchAgentStatus(this._ctx);
|
|
96
97
|
});
|
|
97
98
|
this._fetchAgentStatusTask.schedule();
|
|
98
99
|
|
|
@@ -117,11 +118,13 @@ export class EdgeAgentManager extends Resource {
|
|
|
117
118
|
this._lastKnownDeviceCount = 0;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
protected async _fetchAgentStatus(): Promise<void> {
|
|
121
|
+
protected async _fetchAgentStatus(ctx: Context): Promise<void> {
|
|
121
122
|
invariant(this._edgeHttpClient);
|
|
122
123
|
try {
|
|
123
124
|
log('fetching agent status');
|
|
124
|
-
const { agent } = await this._edgeHttpClient.getAgentStatus({
|
|
125
|
+
const { agent } = await this._edgeHttpClient.getAgentStatus(ctx, {
|
|
126
|
+
ownerIdentityKey: this._identity.identityKey,
|
|
127
|
+
});
|
|
125
128
|
const wasAgentCreatedDuringQuery = this._agentStatus === EdgeAgentStatus.ACTIVE;
|
|
126
129
|
if (!wasAgentCreatedDuringQuery) {
|
|
127
130
|
const deviceKey = agent.deviceKey ? PublicKey.fromHex(agent.deviceKey) : undefined;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
QueryAgentStatusResponse,
|
|
12
12
|
type QueryEdgeStatusResponse,
|
|
13
13
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
14
|
-
|
|
14
|
+
import { Context } from '@dxos/context';
|
|
15
15
|
import { type EdgeAgentManager } from './edge-agent-manager';
|
|
16
16
|
|
|
17
17
|
// TODO(wittjosiah): This service is not currently exposed on the client api, it must be called directly.
|
|
@@ -44,7 +44,7 @@ export class EdgeAgentServiceImpl implements EdgeAgentService {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async createAgent(): Promise<void> {
|
|
47
|
-
return (await this._agentManagerProvider()).createAgent();
|
|
47
|
+
return (await this._agentManagerProvider()).createAgent(Context.default());
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
queryAgentStatus(): Stream<QueryAgentStatusResponse> {
|
|
@@ -64,7 +64,7 @@ describe('identity/identity-manager', () => {
|
|
|
64
64
|
test('creates identity', async () => {
|
|
65
65
|
const { identityManager } = await setupPeer();
|
|
66
66
|
await identityManager.open(new Context());
|
|
67
|
-
onTestFinished(() => identityManager.close());
|
|
67
|
+
onTestFinished(() => identityManager.close(Context.default()));
|
|
68
68
|
|
|
69
69
|
const identity = await identityManager.createIdentity();
|
|
70
70
|
expect(identity).to.exist;
|
|
@@ -77,7 +77,7 @@ describe('identity/identity-manager', () => {
|
|
|
77
77
|
await peer1.metadataStore.load();
|
|
78
78
|
await peer1.identityManager.open(new Context());
|
|
79
79
|
const identity1 = await peer1.identityManager.createIdentity();
|
|
80
|
-
await peer1.identityManager.close();
|
|
80
|
+
await peer1.identityManager.close(Context.default());
|
|
81
81
|
await peer1.feedStore.close();
|
|
82
82
|
await peer1.metadataStore.close();
|
|
83
83
|
|
|
@@ -95,7 +95,7 @@ describe('identity/identity-manager', () => {
|
|
|
95
95
|
test('update profile', async () => {
|
|
96
96
|
const { identityManager } = await setupPeer();
|
|
97
97
|
await identityManager.open(new Context());
|
|
98
|
-
onTestFinished(() => identityManager.close());
|
|
98
|
+
onTestFinished(() => identityManager.close(Context.default()));
|
|
99
99
|
|
|
100
100
|
const identity = await identityManager.createIdentity();
|
|
101
101
|
expect(identity.profileDocument?.displayName).to.be.undefined;
|
|
@@ -112,7 +112,7 @@ describe('identity/identity-manager', () => {
|
|
|
112
112
|
peerKey: identity1.deviceKey.toHex(),
|
|
113
113
|
identityKey: identity1.identityKey.toHex(),
|
|
114
114
|
});
|
|
115
|
-
await identity1.joinNetwork();
|
|
115
|
+
await identity1.joinNetwork(Context.default());
|
|
116
116
|
|
|
117
117
|
const peer2 = await setupPeer({ signalContext });
|
|
118
118
|
|
|
@@ -148,7 +148,7 @@ describe('identity/identity-manager', () => {
|
|
|
148
148
|
peerKey: identity2.deviceKey.toHex(),
|
|
149
149
|
identityKey: identity2.identityKey.toHex(),
|
|
150
150
|
});
|
|
151
|
-
await identity2.joinNetwork();
|
|
151
|
+
await identity2.joinNetwork(Context.default());
|
|
152
152
|
|
|
153
153
|
// Identity2 is not yet ready at this point. Peer1 needs to admit peer2 device key and feed keys.
|
|
154
154
|
await peer2.identityManager.acceptIdentity(identity2, identityRecord);
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
|
|
29
29
|
import { Timeframe } from '@dxos/timeframe';
|
|
30
30
|
import { trace as Trace } from '@dxos/tracing';
|
|
31
|
-
import { deferFunction, isNode } from '@dxos/util';
|
|
31
|
+
import { deferFunction, isNode, isTauri } from '@dxos/util';
|
|
32
32
|
|
|
33
33
|
import { createAuthProvider } from './authenticator';
|
|
34
34
|
import { Identity } from './identity';
|
|
@@ -131,12 +131,11 @@ export class IdentityManager {
|
|
|
131
131
|
log.trace('dxos.halo.identity-manager.open', trace.end({ id: traceId }));
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
async close(): Promise<void> {
|
|
135
|
-
await this._identity?.close(
|
|
134
|
+
async close(ctx: Context): Promise<void> {
|
|
135
|
+
await this._identity?.close(ctx);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
async createIdentity({ profile, deviceProfile }: CreateIdentityOptions = {}): Promise<Identity> {
|
|
139
|
-
// TODO(nf): populate using context from ServiceContext?
|
|
138
|
+
async createIdentity({ profile, deviceProfile }: CreateIdentityOptions = {}, ctx?: Context): Promise<Identity> {
|
|
140
139
|
invariant(!this._identity, 'Identity already exists.');
|
|
141
140
|
log('creating identity...');
|
|
142
141
|
|
|
@@ -153,7 +152,7 @@ export class IdentityManager {
|
|
|
153
152
|
};
|
|
154
153
|
|
|
155
154
|
const identity = await this._constructIdentity(identityRecord);
|
|
156
|
-
await identity.open(
|
|
155
|
+
await identity.open(ctx ?? Context.default());
|
|
157
156
|
|
|
158
157
|
{
|
|
159
158
|
const generator = new CredentialGenerator(this._keyring, identityRecord.identityKey, identityRecord.deviceKey);
|
|
@@ -211,28 +210,32 @@ export class IdentityManager {
|
|
|
211
210
|
return identity;
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
// TODO(nf): receive platform info rather than generating it here.
|
|
215
213
|
createDefaultDeviceProfile(): DeviceProfileDocument {
|
|
214
|
+
// See TODOs in credentials.proto.
|
|
216
215
|
let type: DeviceType;
|
|
217
|
-
// TODO(nf): call Platform service instead?
|
|
218
216
|
if (isNode()) {
|
|
219
217
|
type = DeviceType.AGENT;
|
|
220
218
|
} else {
|
|
221
219
|
if (platform.name?.startsWith('iOS') || platform.name?.startsWith('Android')) {
|
|
222
220
|
type = DeviceType.MOBILE;
|
|
223
|
-
} else if ((
|
|
221
|
+
} else if (isTauri() || !platform.name) {
|
|
222
|
+
// Tauri's __TAURI__ global isn't available in web workers. Fallback: WKWebView
|
|
223
|
+
// (Tauri on macOS) reports null for platform.name; all standard browsers don't.
|
|
224
224
|
type = DeviceType.NATIVE;
|
|
225
225
|
} else {
|
|
226
226
|
type = DeviceType.BROWSER;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
const os = platform.os?.family === 'OS X' ? 'macOS' : platform.os?.family;
|
|
231
|
+
const name = type === DeviceType.NATIVE || type === DeviceType.MOBILE ? 'App' : platform.name;
|
|
232
|
+
|
|
230
233
|
return {
|
|
231
234
|
type,
|
|
232
|
-
platform:
|
|
235
|
+
platform: name,
|
|
233
236
|
platformVersion: platform.version,
|
|
234
237
|
architecture: typeof platform.os?.architecture === 'number' ? String(platform.os.architecture) : undefined,
|
|
235
|
-
os
|
|
238
|
+
os,
|
|
236
239
|
osVersion: platform.os?.version,
|
|
237
240
|
};
|
|
238
241
|
}
|
|
@@ -240,7 +243,7 @@ export class IdentityManager {
|
|
|
240
243
|
/**
|
|
241
244
|
* Prepare an identity object as the first step of acceptIdentity flow.
|
|
242
245
|
*/
|
|
243
|
-
async prepareIdentity(params: JoinIdentityProps) {
|
|
246
|
+
async prepareIdentity(params: JoinIdentityProps, ctx?: Context) {
|
|
244
247
|
log('accepting identity', { params });
|
|
245
248
|
invariant(!this._identity, 'Identity already exists.');
|
|
246
249
|
|
|
@@ -256,7 +259,7 @@ export class IdentityManager {
|
|
|
256
259
|
},
|
|
257
260
|
};
|
|
258
261
|
const identity = await this._constructIdentity(identityRecord);
|
|
259
|
-
await identity.open(
|
|
262
|
+
await identity.open(ctx ?? Context.default());
|
|
260
263
|
return { identity, identityRecord };
|
|
261
264
|
}
|
|
262
265
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Context } from '@dxos/context';
|
|
5
6
|
import { generateSeedPhrase, keyPairFromSeedPhrase } from '@dxos/credentials';
|
|
6
7
|
import { sign } from '@dxos/crypto';
|
|
7
8
|
import { type EdgeHttpClient } from '@dxos/edge-client';
|
|
@@ -72,7 +73,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
72
73
|
return { recoveryCode };
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
public async requestRecoveryChallenge() {
|
|
76
|
+
public async requestRecoveryChallenge(ctx: Context) {
|
|
76
77
|
invariant(this._edgeClient, 'Not connected to EDGE.');
|
|
77
78
|
|
|
78
79
|
const deviceKey = await this._keyring.createKey();
|
|
@@ -83,7 +84,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
try {
|
|
86
|
-
await this._edgeClient.recoverIdentity(request);
|
|
87
|
+
await this._edgeClient.recoverIdentity(ctx, request);
|
|
87
88
|
throw new Error('No challenge received.');
|
|
88
89
|
} catch (error: any) {
|
|
89
90
|
if (!(error instanceof EdgeAuthChallengeError)) {
|
|
@@ -97,14 +98,17 @@ export class EdgeIdentityRecoveryManager {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
public async recoverIdentityWithExternalSignature(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
public async recoverIdentityWithExternalSignature(
|
|
102
|
+
ctx: Context,
|
|
103
|
+
{
|
|
104
|
+
lookupKey,
|
|
105
|
+
deviceKey,
|
|
106
|
+
controlFeedKey,
|
|
107
|
+
signature,
|
|
108
|
+
clientDataJson,
|
|
109
|
+
authenticatorData,
|
|
110
|
+
}: RecoverIdentityRequest.ExternalSignature,
|
|
111
|
+
): Promise<void> {
|
|
108
112
|
invariant(this._edgeClient, 'Not connected to EDGE.');
|
|
109
113
|
|
|
110
114
|
const request: EdgeRecoverIdentityRequest = {
|
|
@@ -121,7 +125,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
121
125
|
: Buffer.from(signature).toString('base64'),
|
|
122
126
|
};
|
|
123
127
|
|
|
124
|
-
const response = await this._edgeClient.recoverIdentity(request);
|
|
128
|
+
const response = await this._edgeClient.recoverIdentity(ctx, request);
|
|
125
129
|
|
|
126
130
|
await this._acceptRecoveredIdentity({
|
|
127
131
|
authorizedDeviceCredential: decodeCredential(response.deviceAuthCredential),
|
|
@@ -137,7 +141,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
137
141
|
/**
|
|
138
142
|
* Recovery identity using an opaque token sent to the user's email.
|
|
139
143
|
*/
|
|
140
|
-
public async recoverIdentityWithToken({ token }: { token: string }): Promise<void> {
|
|
144
|
+
public async recoverIdentityWithToken(ctx: Context, { token }: { token: string }): Promise<void> {
|
|
141
145
|
invariant(this._edgeClient, 'Not connected to EDGE.');
|
|
142
146
|
|
|
143
147
|
const deviceKey = await this._keyring.createKey();
|
|
@@ -148,7 +152,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
148
152
|
token,
|
|
149
153
|
};
|
|
150
154
|
|
|
151
|
-
const response = await this._edgeClient.recoverIdentity(request);
|
|
155
|
+
const response = await this._edgeClient.recoverIdentity(ctx, request);
|
|
152
156
|
|
|
153
157
|
await this._acceptRecoveredIdentity({
|
|
154
158
|
authorizedDeviceCredential: decodeCredential(response.deviceAuthCredential),
|
|
@@ -161,7 +165,7 @@ export class EdgeIdentityRecoveryManager {
|
|
|
161
165
|
});
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
public async recoverIdentity({ recoveryCode }: { recoveryCode: string }): Promise<void> {
|
|
168
|
+
public async recoverIdentity(ctx: Context, { recoveryCode }: { recoveryCode: string }): Promise<void> {
|
|
165
169
|
invariant(this._edgeClient, 'Not connected to EDGE.');
|
|
166
170
|
|
|
167
171
|
const recoveryKeypair = keyPairFromSeedPhrase(recoveryCode);
|
|
@@ -176,13 +180,13 @@ export class EdgeIdentityRecoveryManager {
|
|
|
176
180
|
|
|
177
181
|
let response: RecoverIdentityResponseBody;
|
|
178
182
|
try {
|
|
179
|
-
response = await this._edgeClient.recoverIdentity(request);
|
|
183
|
+
response = await this._edgeClient.recoverIdentity(ctx, request);
|
|
180
184
|
} catch (error: any) {
|
|
181
185
|
if (!(error instanceof EdgeAuthChallengeError)) {
|
|
182
186
|
throw error;
|
|
183
187
|
}
|
|
184
188
|
const signature = sign(Buffer.from(error.challenge, 'base64'), recoveryKeypair.secretKey);
|
|
185
|
-
response = await this._edgeClient.recoverIdentity({
|
|
189
|
+
response = await this._edgeClient.recoverIdentity(ctx, {
|
|
186
190
|
...request,
|
|
187
191
|
signature: Buffer.from(signature).toString('base64'),
|
|
188
192
|
});
|
|
@@ -48,6 +48,12 @@ describe('IdentityService', () => {
|
|
|
48
48
|
await identityService.createIdentity({});
|
|
49
49
|
await expect(identityService.createIdentity({})).rejects.toThrowError('Identity already exists');
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test('creates identity with no spaces', async () => {
|
|
53
|
+
await identityService.createIdentity({});
|
|
54
|
+
const dataSpaces = [...(serviceContext.dataSpaceManager?.spaces?.values() ?? [])];
|
|
55
|
+
expect(dataSpaces.length).to.eq(0);
|
|
56
|
+
});
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
describe.skip('recoverIdentity', () => {});
|
|
@@ -89,37 +95,11 @@ describe('IdentityService', () => {
|
|
|
89
95
|
});
|
|
90
96
|
});
|
|
91
97
|
|
|
92
|
-
describe('open', () => {
|
|
93
|
-
test('identity without default space fixed', async () => {
|
|
94
|
-
const serviceContext = await createServiceContext();
|
|
95
|
-
await serviceContext.open(new Context());
|
|
96
|
-
const identity = await serviceContext.createIdentity();
|
|
97
|
-
const identityService = createIdentityService(serviceContext);
|
|
98
|
-
const getDataSpaces = () => [...(serviceContext.dataSpaceManager?.spaces?.values() ?? [])];
|
|
99
|
-
expect(getDataSpaces().length).to.eq(0);
|
|
100
|
-
expect(identity.defaultSpaceId).to.be.undefined;
|
|
101
|
-
await identityService.open();
|
|
102
|
-
expect(getDataSpaces()[0].id === identity.defaultSpaceId).to.be.true;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('identity without default space credential fixed', async () => {
|
|
106
|
-
const serviceContext = await createServiceContext();
|
|
107
|
-
await serviceContext.open(new Context());
|
|
108
|
-
const identity = await serviceContext.createIdentity();
|
|
109
|
-
const space = await serviceContext.dataSpaceManager!.createDefaultSpace();
|
|
110
|
-
const identityService = createIdentityService(serviceContext);
|
|
111
|
-
expect(identity.defaultSpaceId).to.be.undefined;
|
|
112
|
-
await identityService.open();
|
|
113
|
-
expect(identity.defaultSpaceId === space.id).to.be.true;
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
98
|
const createIdentityService = (serviceContext: ServiceContext) => {
|
|
118
99
|
return new IdentityServiceImpl(
|
|
119
100
|
serviceContext.identityManager,
|
|
120
101
|
serviceContext.recoveryManager,
|
|
121
102
|
serviceContext.keyring,
|
|
122
|
-
() => serviceContext.dataSpaceManager!,
|
|
123
103
|
(options) => serviceContext.createIdentity(options),
|
|
124
104
|
);
|
|
125
105
|
};
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Trigger, sleep } from '@dxos/async';
|
|
6
5
|
import { Stream } from '@dxos/codec-protobuf/stream';
|
|
7
|
-
import { Resource } from '@dxos/context';
|
|
6
|
+
import { Context, Resource } from '@dxos/context';
|
|
8
7
|
import { createCredential, signPresentation } from '@dxos/credentials';
|
|
9
8
|
import { invariant } from '@dxos/invariant';
|
|
10
9
|
import { type Keyring } from '@dxos/keyring';
|
|
11
|
-
import { log } from '@dxos/log';
|
|
12
10
|
import {
|
|
13
11
|
type CreateIdentityRequest,
|
|
14
12
|
type CreateRecoveryCredentialRequest,
|
|
@@ -17,52 +15,29 @@ import {
|
|
|
17
15
|
type QueryIdentityResponse,
|
|
18
16
|
type RecoverIdentityRequest,
|
|
19
17
|
type SignPresentationRequest,
|
|
20
|
-
SpaceState,
|
|
21
18
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
22
19
|
import { type Presentation, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
|
-
import { safeAwaitAll } from '@dxos/util';
|
|
24
|
-
|
|
25
|
-
import { type DataSpaceManager } from '../spaces';
|
|
26
20
|
|
|
27
21
|
import { type Identity } from './identity';
|
|
28
22
|
import { type CreateIdentityOptions, type IdentityManager } from './identity-manager';
|
|
29
23
|
import { type EdgeIdentityRecoveryManager } from './identity-recovery-manager';
|
|
30
24
|
|
|
31
|
-
const DEFAULT_SPACE_SEARCH_TIMEOUT = 10_000;
|
|
32
|
-
|
|
33
25
|
export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
34
26
|
constructor(
|
|
35
27
|
private readonly _identityManager: IdentityManager,
|
|
36
28
|
private readonly _recoveryManager: EdgeIdentityRecoveryManager,
|
|
37
29
|
private readonly _keyring: Keyring,
|
|
38
|
-
private readonly _dataSpaceManagerProvider: () => DataSpaceManager,
|
|
39
30
|
private readonly _createIdentity: (params: CreateIdentityOptions) => Promise<Identity>,
|
|
40
31
|
private readonly _onProfileUpdate?: (profile: ProfileDocument | undefined) => Promise<void>,
|
|
41
32
|
) {
|
|
42
33
|
super();
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
protected override async _open(): Promise<void> {
|
|
46
|
-
const identity = this._identityManager.identity;
|
|
47
|
-
if (identity && !identity.defaultSpaceId) {
|
|
48
|
-
await this._fixIdentityWithoutDefaultSpace(identity);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
36
|
async createIdentity(request: CreateIdentityRequest): Promise<IdentityProto> {
|
|
53
37
|
await this._createIdentity({ profile: request.profile, deviceProfile: request.deviceProfile });
|
|
54
|
-
const dataSpaceManager = this._dataSpaceManagerProvider();
|
|
55
|
-
await this._createDefaultSpace(dataSpaceManager);
|
|
56
38
|
return this._getIdentity()!;
|
|
57
39
|
}
|
|
58
40
|
|
|
59
|
-
private async _createDefaultSpace(dataSpaceManager: DataSpaceManager): Promise<void> {
|
|
60
|
-
const space = await dataSpaceManager!.createDefaultSpace();
|
|
61
|
-
const identity = this._identityManager.identity;
|
|
62
|
-
invariant(identity);
|
|
63
|
-
await identity.updateDefaultSpace(space.id);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
41
|
queryIdentity(): Stream<QueryIdentityResponse> {
|
|
67
42
|
return new Stream(({ next }) => {
|
|
68
43
|
const emitNext = () => next({ identity: this._getIdentity() });
|
|
@@ -97,16 +72,16 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
97
72
|
}
|
|
98
73
|
|
|
99
74
|
async requestRecoveryChallenge() {
|
|
100
|
-
return this._recoveryManager.requestRecoveryChallenge();
|
|
75
|
+
return this._recoveryManager.requestRecoveryChallenge(Context.default());
|
|
101
76
|
}
|
|
102
77
|
|
|
103
78
|
async recoverIdentity(request: RecoverIdentityRequest): Promise<IdentityProto> {
|
|
104
79
|
if (request.recoveryCode) {
|
|
105
|
-
await this._recoveryManager.recoverIdentity({ recoveryCode: request.recoveryCode });
|
|
80
|
+
await this._recoveryManager.recoverIdentity(Context.default(), { recoveryCode: request.recoveryCode });
|
|
106
81
|
} else if (request.external) {
|
|
107
|
-
await this._recoveryManager.recoverIdentityWithExternalSignature(request.external);
|
|
82
|
+
await this._recoveryManager.recoverIdentityWithExternalSignature(Context.default(), request.external);
|
|
108
83
|
} else if (request.token) {
|
|
109
|
-
await this._recoveryManager.recoverIdentityWithToken({ token: request.token });
|
|
84
|
+
await this._recoveryManager.recoverIdentityWithToken(Context.default(), { token: request.token });
|
|
110
85
|
} else {
|
|
111
86
|
throw new Error('Invalid request.');
|
|
112
87
|
}
|
|
@@ -141,50 +116,4 @@ export class IdentityServiceImpl extends Resource implements IdentityService {
|
|
|
141
116
|
signer: this._keyring,
|
|
142
117
|
});
|
|
143
118
|
}
|
|
144
|
-
|
|
145
|
-
private async _fixIdentityWithoutDefaultSpace(identity: Identity): Promise<void> {
|
|
146
|
-
let recodedDefaultSpace = false;
|
|
147
|
-
let foundDefaultSpace = false;
|
|
148
|
-
const dataSpaceManager = this._dataSpaceManagerProvider();
|
|
149
|
-
|
|
150
|
-
const recordedDefaultSpaceTrigger = new Trigger();
|
|
151
|
-
|
|
152
|
-
const allProcessed = safeAwaitAll(
|
|
153
|
-
dataSpaceManager.spaces.values(),
|
|
154
|
-
async (space) => {
|
|
155
|
-
if (space.state === SpaceState.SPACE_CLOSED) {
|
|
156
|
-
await space.open();
|
|
157
|
-
|
|
158
|
-
// Wait until the space is either READY or REQUIRES_MIGRATION.
|
|
159
|
-
// NOTE: Space could potentially never initialize if the space data is corrupted.
|
|
160
|
-
const requiresMigration = space.stateUpdate.waitForCondition(
|
|
161
|
-
() => space.state === SpaceState.SPACE_REQUIRES_MIGRATION,
|
|
162
|
-
);
|
|
163
|
-
await Promise.race([space.initializeDataPipeline(), requiresMigration]);
|
|
164
|
-
}
|
|
165
|
-
if (await dataSpaceManager.isDefaultSpace(space)) {
|
|
166
|
-
if (foundDefaultSpace) {
|
|
167
|
-
log.warn('Multiple default spaces found. Using the first one.', { duplicate: space.id });
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
foundDefaultSpace = true;
|
|
172
|
-
await identity.updateDefaultSpace(space.id);
|
|
173
|
-
recodedDefaultSpace = true;
|
|
174
|
-
recordedDefaultSpaceTrigger.wake();
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
(err) => {
|
|
178
|
-
log.catch(err);
|
|
179
|
-
},
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// Wait for all spaces to be processed or until the default space is recorded.
|
|
183
|
-
// If the timeout is reached, create a new default space.
|
|
184
|
-
await Promise.race([allProcessed, recordedDefaultSpaceTrigger.wait(), sleep(DEFAULT_SPACE_SEARCH_TIMEOUT)]);
|
|
185
|
-
|
|
186
|
-
if (!recodedDefaultSpace) {
|
|
187
|
-
await this._createDefaultSpace(dataSpaceManager);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
119
|
}
|
|
@@ -130,7 +130,7 @@ describe('identity/identity', () => {
|
|
|
130
130
|
onMessage: (_: MessageListener): (() => void) => {
|
|
131
131
|
return () => {};
|
|
132
132
|
},
|
|
133
|
-
send: async (_) => {
|
|
133
|
+
send: async (..._) => {
|
|
134
134
|
replicationStarted = true;
|
|
135
135
|
},
|
|
136
136
|
} as EdgeConnection,
|
|
@@ -218,7 +218,7 @@ describe('identity/identity', () => {
|
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
await identity.open(new Context());
|
|
221
|
-
await identity.joinNetwork();
|
|
221
|
+
await identity.joinNetwork(Context.default());
|
|
222
222
|
onTestFinished(() => identity.close(new Context()));
|
|
223
223
|
return { identity, identityKey, keyring, deviceKey, controlFeed, spaceKey, dataFeed };
|
|
224
224
|
};
|