@dxos/client-services 0.8.4-main.fffef41 → 0.9.0
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/{chunk-I2RGLVJF.mjs → chunk-37BKOM5O.mjs} +2870 -3767
- package/dist/lib/browser/chunk-37BKOM5O.mjs.map +7 -0
- package/dist/lib/browser/chunk-QCWEHHJW.mjs +24 -0
- package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +7 -0
- package/dist/lib/browser/chunk-XJRPB3GA.mjs +22 -0
- package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +576 -139
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +88 -0
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/browser/packlets/locks/browser.mjs +86 -0
- package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/browser/packlets/locks/node.mjs +48 -0
- package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/browser/testing/index.mjs +58 -53
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +22 -0
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-QTUURCR4.mjs → chunk-2ONS4DLO.mjs} +2810 -3576
- package/dist/lib/node-esm/chunk-2ONS4DLO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +576 -139
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +88 -0
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/locks/browser.mjs +86 -0
- package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs +48 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/node-esm/testing/index.mjs +58 -53
- 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/agents/edge-agent-service.d.ts +2 -1
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts +7 -3
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/keys.d.ts +2 -2
- package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
- 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/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +2 -3
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
- package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +3 -3
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +10 -10
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +14 -9
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-service.d.ts +7 -11
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +10 -13
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +7 -6
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +7 -4
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +5 -5
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +6 -5
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/index.d.ts +1 -1
- package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/node.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 +5 -4
- package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/client-rpc-server.d.ts +5 -5
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts +75 -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/index.d.ts +1 -0
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +22 -19
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +20 -13
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/packlets/services/sqlite-storage.d.ts +27 -0
- package/dist/types/src/packlets/services/sqlite-storage.d.ts.map +1 -0
- package/dist/types/src/packlets/services/util.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
- package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/index.d.ts +4 -1
- package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
- package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
- 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 +7 -1
- 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/automerge-space-state.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +49 -22
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +38 -13
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts +4 -3
- package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -9
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
- 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/level.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/profile-archive-sqlite.d.ts +24 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.d.ts.map +1 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.test.d.ts +2 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.test.d.ts.map +1 -0
- package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +20 -22
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -4
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-session.d.ts +2 -4
- package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
- package/dist/types/src/testing/setup.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 +71 -57
- package/src/index.ts +1 -0
- package/src/packlets/agents/edge-agent-manager.ts +8 -5
- package/src/packlets/agents/edge-agent-service.ts +4 -2
- package/src/packlets/devices/devices-service.test.ts +0 -1
- package/src/packlets/devtools/devtools.ts +28 -7
- package/src/packlets/devtools/feeds.ts +1 -1
- package/src/packlets/devtools/keys.ts +2 -2
- package/src/packlets/devtools/spaces.ts +1 -1
- package/src/packlets/diagnostics/diagnostics.ts +1 -2
- package/src/packlets/diagnostics/index.ts +1 -1
- package/src/packlets/identity/authenticator.ts +3 -3
- package/src/packlets/identity/contacts-service.ts +1 -2
- package/src/packlets/identity/identity-manager.test.ts +6 -6
- package/src/packlets/identity/identity-manager.ts +29 -28
- package/src/packlets/identity/identity-recovery-manager.ts +31 -22
- package/src/packlets/identity/identity-service.test.ts +6 -27
- package/src/packlets/identity/identity-service.ts +17 -83
- package/src/packlets/identity/identity.test.ts +3 -3
- package/src/packlets/identity/identity.ts +12 -35
- package/src/packlets/invitations/device-invitation-protocol.ts +10 -9
- package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
- package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
- package/src/packlets/invitations/invitation-host-extension.ts +13 -14
- package/src/packlets/invitations/invitation-protocol.ts +7 -4
- package/src/packlets/invitations/invitation-state.ts +1 -15
- package/src/packlets/invitations/invitations-handler.test.ts +4 -5
- package/src/packlets/invitations/invitations-handler.ts +74 -22
- package/src/packlets/invitations/invitations-manager.ts +42 -17
- package/src/packlets/invitations/invitations-service.ts +9 -9
- package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
- package/src/packlets/invitations/space-invitation-protocol.ts +13 -18
- package/src/packlets/locks/index.ts +1 -1
- package/src/packlets/logging/logging-service.ts +19 -15
- package/src/packlets/network/network-service.test.ts +0 -1
- package/src/packlets/network/network-service.ts +10 -8
- package/src/packlets/services/client-rpc-server.ts +19 -16
- package/src/packlets/services/feed-syncer.test.ts +376 -0
- package/src/packlets/services/feed-syncer.ts +536 -0
- package/src/packlets/services/index.ts +1 -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 +215 -78
- package/src/packlets/services/service-host.test.ts +8 -10
- package/src/packlets/services/service-host.ts +102 -70
- package/src/packlets/services/service-registry.test.ts +0 -1
- package/src/packlets/services/sqlite-storage.ts +390 -0
- package/src/packlets/space-export/archive-format.ts +42 -0
- package/src/packlets/space-export/index.ts +4 -1
- package/src/packlets/space-export/serialized-space-reader.ts +129 -0
- package/src/packlets/space-export/serialized-space-writer.ts +260 -0
- package/src/packlets/space-export/space-archive-reader.ts +64 -3
- package/src/packlets/space-export/space-archive-writer.ts +41 -4
- package/src/packlets/space-export/space-archive.test.ts +482 -0
- package/src/packlets/spaces/data-space-manager.test.ts +169 -14
- package/src/packlets/spaces/data-space-manager.ts +192 -127
- package/src/packlets/spaces/data-space.ts +89 -43
- package/src/packlets/spaces/edge-feed-replicator.test.ts +2 -2
- package/src/packlets/spaces/edge-feed-replicator.ts +11 -9
- package/src/packlets/spaces/epoch-migrations.ts +7 -6
- package/src/packlets/spaces/genesis.ts +9 -4
- package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.ts +10 -9
- package/src/packlets/spaces/spaces-service.test.ts +18 -11
- package/src/packlets/spaces/spaces-service.ts +130 -24
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/profile-archive-sqlite.test.ts +79 -0
- package/src/packlets/storage/profile-archive-sqlite.ts +100 -0
- package/src/packlets/storage/profile-archive.ts +3 -0
- package/src/packlets/storage/storage.ts +4 -4
- package/src/packlets/testing/invitation-utils.ts +10 -6
- package/src/packlets/testing/test-builder.ts +59 -40
- package/src/packlets/worker/worker-runtime.ts +173 -17
- package/src/packlets/worker/worker-session.ts +12 -18
- package/src/version.ts +5 -1
- package/dist/lib/browser/chunk-I2RGLVJF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QTUURCR4.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
|
@@ -8,10 +8,10 @@ import { Trigger } from '@dxos/async';
|
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { PublicKey } from '@dxos/keys';
|
|
10
10
|
import { type Space, type SpacesService } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
+
import { MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
11
12
|
|
|
12
13
|
import { type ServiceContext } from '../services';
|
|
13
14
|
import { createServiceContext } from '../testing';
|
|
14
|
-
|
|
15
15
|
import { SpacesServiceImpl } from './spaces-service';
|
|
16
16
|
|
|
17
17
|
describe('SpacesService', () => {
|
|
@@ -21,10 +21,15 @@ describe('SpacesService', () => {
|
|
|
21
21
|
beforeEach(async () => {
|
|
22
22
|
serviceContext = await createServiceContext();
|
|
23
23
|
await serviceContext.open(new Context());
|
|
24
|
-
spacesService = new SpacesServiceImpl(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
spacesService = new SpacesServiceImpl(
|
|
25
|
+
serviceContext.identityManager,
|
|
26
|
+
serviceContext.spaceManager,
|
|
27
|
+
serviceContext.echoHost,
|
|
28
|
+
async () => {
|
|
29
|
+
await serviceContext.initialized.wait();
|
|
30
|
+
return serviceContext.dataSpaceManager!;
|
|
31
|
+
},
|
|
32
|
+
);
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
afterEach(async () => {
|
|
@@ -33,12 +38,14 @@ describe('SpacesService', () => {
|
|
|
33
38
|
|
|
34
39
|
describe('createSpace', () => {
|
|
35
40
|
test('fails if no identity is available', async () => {
|
|
36
|
-
await expect(spacesService.createSpace()).rejects.toBeInstanceOf(
|
|
41
|
+
await expect(spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE })).rejects.toBeInstanceOf(
|
|
42
|
+
Error,
|
|
43
|
+
);
|
|
37
44
|
});
|
|
38
45
|
|
|
39
46
|
test('creates a new space', async () => {
|
|
40
47
|
await serviceContext.createIdentity();
|
|
41
|
-
const space = await spacesService.createSpace();
|
|
48
|
+
const space = await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
|
|
42
49
|
expect(space).to.exist;
|
|
43
50
|
expect(space.spaceKey).to.be.instanceof(PublicKey);
|
|
44
51
|
});
|
|
@@ -60,9 +67,9 @@ describe('SpacesService', () => {
|
|
|
60
67
|
test('returns list of existing spaces', async () => {
|
|
61
68
|
await serviceContext.createIdentity();
|
|
62
69
|
const existingSpaces = [
|
|
63
|
-
await spacesService.createSpace(),
|
|
64
|
-
await spacesService.createSpace(),
|
|
65
|
-
await spacesService.createSpace(),
|
|
70
|
+
await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
|
|
71
|
+
await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
|
|
72
|
+
await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE }),
|
|
66
73
|
];
|
|
67
74
|
|
|
68
75
|
const query = spacesService.querySpaces();
|
|
@@ -88,7 +95,7 @@ describe('SpacesService', () => {
|
|
|
88
95
|
expect(await result.wait()).to.be.length(0);
|
|
89
96
|
|
|
90
97
|
result.reset();
|
|
91
|
-
const space = await spacesService.createSpace();
|
|
98
|
+
const space = await spacesService.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
|
|
92
99
|
const spaces = await result.wait();
|
|
93
100
|
expect(spaces).to.be.length(1);
|
|
94
101
|
expect(spaces?.[0].spaceKey.equals(space.spaceKey)).to.be.true;
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import type { AutomergeUrl } from '@automerge/automerge-repo';
|
|
6
6
|
|
|
7
7
|
import { SubscriptionList, UpdateScheduler, scheduleTask } from '@dxos/async';
|
|
8
|
+
import { type RequestOptions } from '@dxos/codec-protobuf';
|
|
8
9
|
import { Stream } from '@dxos/codec-protobuf/stream';
|
|
10
|
+
import { Context } from '@dxos/context';
|
|
9
11
|
import {
|
|
10
12
|
type CredentialProcessor,
|
|
11
13
|
createAdmissionCredentials,
|
|
@@ -13,7 +15,8 @@ import {
|
|
|
13
15
|
getCredentialAssertion,
|
|
14
16
|
} from '@dxos/credentials';
|
|
15
17
|
import { raise } from '@dxos/debug';
|
|
16
|
-
import { type SpaceManager } from '@dxos/echo-
|
|
18
|
+
import { type EchoHost, type SpaceManager } from '@dxos/echo-host';
|
|
19
|
+
import { type DatabaseDirectory } from '@dxos/echo-protocol';
|
|
17
20
|
import { writeMessages } from '@dxos/feed-store';
|
|
18
21
|
import { assertArgument, assertState, invariant } from '@dxos/invariant';
|
|
19
22
|
import { SpaceId } from '@dxos/keys';
|
|
@@ -21,6 +24,7 @@ import { log } from '@dxos/log';
|
|
|
21
24
|
import {
|
|
22
25
|
ApiError,
|
|
23
26
|
AuthorizationError,
|
|
27
|
+
FeedProtocol,
|
|
24
28
|
IdentityNotInitializedError,
|
|
25
29
|
SpaceNotFoundError,
|
|
26
30
|
encodeError,
|
|
@@ -40,11 +44,13 @@ import {
|
|
|
40
44
|
type QueryCredentialsRequest,
|
|
41
45
|
type QuerySpacesResponse,
|
|
42
46
|
type Space,
|
|
47
|
+
SpaceArchive,
|
|
43
48
|
SpaceMember,
|
|
44
49
|
SpaceState,
|
|
45
50
|
type SpacesService,
|
|
46
51
|
type SubscribeMessagesRequest,
|
|
47
52
|
type UpdateMemberRoleRequest,
|
|
53
|
+
type CreateSpaceRequest,
|
|
48
54
|
type UpdateSpaceRequest,
|
|
49
55
|
type WriteCredentialsRequest,
|
|
50
56
|
} from '@dxos/protocols/proto/dxos/client/services';
|
|
@@ -54,8 +60,14 @@ import { trace } from '@dxos/tracing';
|
|
|
54
60
|
import { type Provider } from '@dxos/util';
|
|
55
61
|
|
|
56
62
|
import { type IdentityManager } from '../identity';
|
|
57
|
-
import {
|
|
58
|
-
|
|
63
|
+
import {
|
|
64
|
+
SpaceArchiveWriter,
|
|
65
|
+
detectSpaceArchiveFormat,
|
|
66
|
+
extractSpaceArchive,
|
|
67
|
+
readSerializedSpaceArchive,
|
|
68
|
+
writeSerializedSpaceArchive,
|
|
69
|
+
objJsonToObjectStructure,
|
|
70
|
+
} from '../space-export';
|
|
59
71
|
import { type DataSpace } from './data-space';
|
|
60
72
|
import { type DataSpaceManager } from './data-space-manager';
|
|
61
73
|
|
|
@@ -63,37 +75,48 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
63
75
|
constructor(
|
|
64
76
|
private readonly _identityManager: IdentityManager,
|
|
65
77
|
private readonly _spaceManager: SpaceManager,
|
|
78
|
+
private readonly _echoHost: EchoHost,
|
|
66
79
|
private readonly _getDataSpaceManager: Provider<Promise<DataSpaceManager>>,
|
|
67
80
|
) {}
|
|
68
81
|
|
|
69
|
-
async createSpace(): Promise<Space> {
|
|
82
|
+
async createSpace(request: CreateSpaceRequest, options?: RequestOptions): Promise<Space> {
|
|
70
83
|
this._requireIdentity();
|
|
84
|
+
const ctx = options?.ctx ?? new Context();
|
|
71
85
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
72
|
-
const space = await dataSpaceManager.createSpace(
|
|
86
|
+
const space = await dataSpaceManager.createSpace(ctx, {
|
|
87
|
+
tags: request?.tags,
|
|
88
|
+
membershipPolicy: request?.membershipPolicy,
|
|
89
|
+
});
|
|
73
90
|
await this._updateMetrics();
|
|
74
91
|
return this._serializeSpace(space);
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
async updateSpace({ spaceKey, state, edgeReplication }: UpdateSpaceRequest): Promise<void> {
|
|
94
|
+
async updateSpace({ spaceKey, state, edgeReplication }: UpdateSpaceRequest, options?: RequestOptions): Promise<void> {
|
|
95
|
+
const ctx = options?.ctx ?? Context.default();
|
|
78
96
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
79
97
|
const space = dataSpaceManager.spaces.get(spaceKey) ?? raise(new SpaceNotFoundError(spaceKey));
|
|
80
98
|
|
|
81
99
|
if (state) {
|
|
82
100
|
switch (state) {
|
|
83
101
|
case SpaceState.SPACE_ACTIVE:
|
|
84
|
-
await space.activate();
|
|
102
|
+
await space.activate(ctx);
|
|
85
103
|
break;
|
|
86
104
|
|
|
87
105
|
case SpaceState.SPACE_INACTIVE:
|
|
88
|
-
await space.deactivate();
|
|
106
|
+
await space.deactivate(ctx);
|
|
89
107
|
break;
|
|
108
|
+
|
|
109
|
+
case SpaceState.SPACE_DELETED:
|
|
110
|
+
await dataSpaceManager.markSpaceDeleted(ctx, spaceKey);
|
|
111
|
+
// The space is removed from the manager; skip any further mutations (e.g. edgeReplication).
|
|
112
|
+
return;
|
|
90
113
|
default:
|
|
91
|
-
throw new ApiError('Invalid space state');
|
|
114
|
+
throw new ApiError({ message: 'Invalid space state' });
|
|
92
115
|
}
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
if (edgeReplication !== undefined) {
|
|
96
|
-
await dataSpaceManager.setSpaceEdgeReplicationSetting(spaceKey, edgeReplication);
|
|
119
|
+
await dataSpaceManager.setSpaceEdgeReplicationSetting(ctx, spaceKey, edgeReplication);
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
|
|
@@ -104,9 +127,12 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
104
127
|
throw new SpaceNotFoundError(request.spaceKey);
|
|
105
128
|
}
|
|
106
129
|
if (!space.spaceState.hasMembershipManagementPermission(identity.identityKey)) {
|
|
107
|
-
throw new AuthorizationError(
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
throw new AuthorizationError({
|
|
131
|
+
message: 'No member management permission.',
|
|
132
|
+
context: {
|
|
133
|
+
spaceKey: space.key,
|
|
134
|
+
role: space.spaceState.getMemberRole(identity.identityKey),
|
|
135
|
+
},
|
|
110
136
|
});
|
|
111
137
|
}
|
|
112
138
|
const credentials = await createAdmissionCredentials(
|
|
@@ -259,18 +285,26 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
259
285
|
});
|
|
260
286
|
}
|
|
261
287
|
|
|
262
|
-
async joinBySpaceKey({ spaceKey }: JoinBySpaceKeyRequest): Promise<JoinSpaceResponse> {
|
|
288
|
+
async joinBySpaceKey({ spaceKey }: JoinBySpaceKeyRequest, options?: RequestOptions): Promise<JoinSpaceResponse> {
|
|
289
|
+
const ctx = options?.ctx ?? Context.default();
|
|
263
290
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
264
|
-
const credential = await dataSpaceManager.requestSpaceAdmissionCredential(spaceKey);
|
|
265
|
-
return this._joinByAdmission({ credential });
|
|
291
|
+
const credential = await dataSpaceManager.requestSpaceAdmissionCredential(ctx, spaceKey);
|
|
292
|
+
return this._joinByAdmission(ctx, { credential });
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
async exportSpace(request: ExportSpaceRequest): Promise<ExportSpaceResponse> {
|
|
269
|
-
await using writer = await new SpaceArchiveWriter().open();
|
|
270
296
|
assertArgument(SpaceId.isValid(request.spaceId), 'spaceId', 'Invalid space ID');
|
|
271
297
|
|
|
272
298
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
273
299
|
const space = dataSpaceManager.getSpaceById(request.spaceId) ?? raise(new Error('Space not found'));
|
|
300
|
+
|
|
301
|
+
const format = request.format ?? SpaceArchive.Format.BINARY;
|
|
302
|
+
if (format === SpaceArchive.Format.JSON) {
|
|
303
|
+
const archive = await writeSerializedSpaceArchive({ space, echoHost: this._echoHost });
|
|
304
|
+
return { archive };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await using writer = await new SpaceArchiveWriter().open();
|
|
274
308
|
await writer.begin({ spaceId: space.id });
|
|
275
309
|
const rootUrl = space.automergeSpaceState.lastEpoch?.subject.assertion.automergeRoot;
|
|
276
310
|
assertState(rootUrl, 'Space does not have a root URL');
|
|
@@ -280,23 +314,92 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
280
314
|
await writer.writeDocument(documentId, data);
|
|
281
315
|
}
|
|
282
316
|
|
|
317
|
+
const feeds = await space.getAllFeeds();
|
|
318
|
+
for (const feed of feeds) {
|
|
319
|
+
const archiveBlocks = feed.blocks.map((block) => ({
|
|
320
|
+
actorId: block.actorId,
|
|
321
|
+
sequence: block.sequence,
|
|
322
|
+
prevActorId: block.prevActorId,
|
|
323
|
+
prevSequence: block.prevSequence,
|
|
324
|
+
position: block.position,
|
|
325
|
+
timestamp: block.timestamp,
|
|
326
|
+
data: Buffer.from(block.data).toString('base64'),
|
|
327
|
+
}));
|
|
328
|
+
await writer.writeFeed(feed.feedId, feed.feedNamespace, archiveBlocks);
|
|
329
|
+
}
|
|
330
|
+
|
|
283
331
|
const archive = await writer.finish();
|
|
284
332
|
return { archive };
|
|
285
333
|
}
|
|
286
334
|
|
|
287
|
-
async importSpace(request: ImportSpaceRequest): Promise<ImportSpaceResponse> {
|
|
335
|
+
async importSpace(request: ImportSpaceRequest, options?: RequestOptions): Promise<ImportSpaceResponse> {
|
|
336
|
+
const ctx = options?.ctx ?? Context.default();
|
|
288
337
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
338
|
+
|
|
339
|
+
const format = request.archive.format ?? detectSpaceArchiveFormat(request.archive);
|
|
340
|
+
if (format === SpaceArchive.Format.JSON) {
|
|
341
|
+
const serialized = readSerializedSpaceArchive(request.archive);
|
|
342
|
+
const space = await dataSpaceManager.createSpace(ctx, { tags: request.tags });
|
|
343
|
+
await this._hydrateSpaceFromSerialized(space, serialized);
|
|
344
|
+
await this._updateMetrics();
|
|
345
|
+
return { newSpaceId: space.id };
|
|
346
|
+
}
|
|
347
|
+
|
|
289
348
|
const extracted = await extractSpaceArchive(request.archive);
|
|
290
349
|
invariant(extracted.metadata.echo?.currentRootUrl, 'Space archive does not contain a root URL');
|
|
291
|
-
const space = await dataSpaceManager.createSpace({
|
|
350
|
+
const space = await dataSpaceManager.createSpace(ctx, {
|
|
292
351
|
documents: extracted.documents,
|
|
293
352
|
rootUrl: extracted.metadata.echo?.currentRootUrl as AutomergeUrl,
|
|
353
|
+
tags: request.tags,
|
|
294
354
|
});
|
|
295
355
|
await this._updateMetrics();
|
|
296
356
|
return { newSpaceId: space.id };
|
|
297
357
|
}
|
|
298
358
|
|
|
299
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Populate a freshly-created space with the objects and feed messages described in a {@link SerializedSpace}.
|
|
361
|
+
*
|
|
362
|
+
* Objects are written directly into the space's automerge root document as inline
|
|
363
|
+
* {@link EntityStructure} entries; feed messages are appended to the appropriate queue
|
|
364
|
+
* via {@link EchoHost.queuesService}.
|
|
365
|
+
*/
|
|
366
|
+
private async _hydrateSpaceFromSerialized(
|
|
367
|
+
space: DataSpace,
|
|
368
|
+
serialized: ReturnType<typeof readSerializedSpaceArchive>,
|
|
369
|
+
): Promise<void> {
|
|
370
|
+
const databaseRoot = space.databaseRoot;
|
|
371
|
+
assertState(databaseRoot, 'Space database root is not ready');
|
|
372
|
+
|
|
373
|
+
databaseRoot.handle.change((doc: DatabaseDirectory) => {
|
|
374
|
+
if (!doc.objects) {
|
|
375
|
+
doc.objects = {};
|
|
376
|
+
}
|
|
377
|
+
for (const obj of serialized.objects) {
|
|
378
|
+
doc.objects[obj.id] = objJsonToObjectStructure(obj);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
for (const feed of serialized.feeds ?? []) {
|
|
383
|
+
if (feed.messages.length === 0) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const namespace =
|
|
388
|
+
feed.namespace === 'trace' ? FeedProtocol.WellKnownNamespaces.trace : FeedProtocol.WellKnownNamespaces.data;
|
|
389
|
+
try {
|
|
390
|
+
await this._echoHost.queuesService.insertIntoQueue({
|
|
391
|
+
spaceId: space.id,
|
|
392
|
+
queueId: feed.feedObjectId,
|
|
393
|
+
subspaceTag: namespace,
|
|
394
|
+
objects: feed.messages.map((message) => JSON.stringify(message)),
|
|
395
|
+
});
|
|
396
|
+
} catch (err) {
|
|
397
|
+
log.warn('failed to import feed data', { feedObjectId: feed.feedObjectId, error: err });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private async _joinByAdmission(ctx: Context, { credential }: ContactAdmission): Promise<JoinSpaceResponse> {
|
|
300
403
|
const assertion = getCredentialAssertion(credential);
|
|
301
404
|
invariant(assertion['@type'] === 'dxos.halo.credentials.SpaceMember', 'Invalid credential');
|
|
302
405
|
const myIdentity = this._identityManager.identity;
|
|
@@ -305,9 +408,10 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
305
408
|
const dataSpaceManager = await this._getDataSpaceManager();
|
|
306
409
|
let dataSpace = dataSpaceManager.spaces.get(assertion.spaceKey);
|
|
307
410
|
if (!dataSpace) {
|
|
308
|
-
dataSpace = await dataSpaceManager.acceptSpace({
|
|
411
|
+
dataSpace = await dataSpaceManager.acceptSpace(ctx, {
|
|
309
412
|
spaceKey: assertion.spaceKey,
|
|
310
413
|
genesisFeedKey: assertion.genesisFeedKey,
|
|
414
|
+
tags: assertion.tags,
|
|
311
415
|
});
|
|
312
416
|
await myIdentity.controlPipeline.writer.write({ credential: { credential } });
|
|
313
417
|
}
|
|
@@ -360,6 +464,8 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
360
464
|
}),
|
|
361
465
|
),
|
|
362
466
|
creator: space.inner.spaceState.creator?.key,
|
|
467
|
+
tags: space.tags,
|
|
468
|
+
membershipPolicy: space.membershipPolicy,
|
|
363
469
|
cache: space.cache,
|
|
364
470
|
metrics: space.metrics,
|
|
365
471
|
edgeReplication: space.getEdgeReplicationSetting(),
|
|
@@ -368,9 +474,9 @@ export class SpacesServiceImpl implements SpacesService {
|
|
|
368
474
|
|
|
369
475
|
private _requireIdentity() {
|
|
370
476
|
if (!this._identityManager.identity) {
|
|
371
|
-
throw new IdentityNotInitializedError(
|
|
372
|
-
'This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo',
|
|
373
|
-
);
|
|
477
|
+
throw new IdentityNotInitializedError({
|
|
478
|
+
message: 'This device has no HALO identity available. See https://docs.dxos.org/guide/platform/halo',
|
|
479
|
+
});
|
|
374
480
|
}
|
|
375
481
|
return this._identityManager.identity;
|
|
376
482
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { cbor } from '@automerge/automerge-repo';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { ProfileArchiveEntryType } from '@dxos/protocols';
|
|
9
|
+
|
|
10
|
+
import { decodeProfileArchive, encodeProfileArchive } from './profile-archive';
|
|
11
|
+
import {
|
|
12
|
+
createSqliteProfileArchive,
|
|
13
|
+
getSqliteProfileEntries,
|
|
14
|
+
isValidSqliteDatabase,
|
|
15
|
+
OPFS_SQLITE_DB_FILENAME,
|
|
16
|
+
} from './profile-archive-sqlite';
|
|
17
|
+
|
|
18
|
+
const sqliteHeader = (): Uint8Array => {
|
|
19
|
+
const bytes = new Uint8Array(16);
|
|
20
|
+
bytes.set(new TextEncoder().encode('SQLite format 3\u0000'));
|
|
21
|
+
return bytes;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('profile-archive-sqlite', () => {
|
|
25
|
+
test('isValidSqliteDatabase accepts SQLite header', ({ expect }) => {
|
|
26
|
+
expect(isValidSqliteDatabase(sqliteHeader())).toBe(true);
|
|
27
|
+
expect(isValidSqliteDatabase(new Uint8Array([1, 2, 3]))).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('createSqliteProfileArchive includes optional origin in meta', ({ expect }) => {
|
|
31
|
+
const archive = createSqliteProfileArchive(OPFS_SQLITE_DB_FILENAME, sqliteHeader(), {
|
|
32
|
+
origin: 'main.composer.space',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(archive.meta.origin).toBe('main.composer.space');
|
|
36
|
+
expect(archive.meta.timestamp).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('createSqliteProfileArchive round-trips through CBOR', ({ expect }) => {
|
|
40
|
+
const database = sqliteHeader();
|
|
41
|
+
const archive = createSqliteProfileArchive(OPFS_SQLITE_DB_FILENAME, database);
|
|
42
|
+
const decoded = decodeProfileArchive(encodeProfileArchive(archive));
|
|
43
|
+
|
|
44
|
+
expect(decoded.storage).toHaveLength(1);
|
|
45
|
+
expect(decoded.storage[0]?.type).toBe(ProfileArchiveEntryType.SQLITE_DATABASE);
|
|
46
|
+
expect(getSqliteProfileEntries(decoded)).toEqual([
|
|
47
|
+
{ opfsFilename: OPFS_SQLITE_DB_FILENAME, database: expect.any(Uint8Array) },
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('getSqliteProfileEntries skips invalid sqlite payloads', ({ expect }) => {
|
|
52
|
+
const archive = {
|
|
53
|
+
meta: { timestamp: '2026-01-01T00:00:00.000Z' },
|
|
54
|
+
storage: [
|
|
55
|
+
{
|
|
56
|
+
type: ProfileArchiveEntryType.SQLITE_DATABASE,
|
|
57
|
+
key: OPFS_SQLITE_DB_FILENAME,
|
|
58
|
+
value: new Uint8Array([0, 1, 2]),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
expect(getSqliteProfileEntries(archive)).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('createSqliteProfileArchive rejects invalid sqlite bytes', ({ expect }) => {
|
|
67
|
+
expect(() => createSqliteProfileArchive(OPFS_SQLITE_DB_FILENAME, new Uint8Array([0]))).toThrow(
|
|
68
|
+
/Invalid SQLite database/,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('CBOR decode preserves sqlite entry shape', ({ expect }) => {
|
|
73
|
+
const archive = createSqliteProfileArchive(OPFS_SQLITE_DB_FILENAME, sqliteHeader());
|
|
74
|
+
const encoded = cbor.encode(archive);
|
|
75
|
+
const decoded = cbor.decode(encoded) as ReturnType<typeof decodeProfileArchive>;
|
|
76
|
+
|
|
77
|
+
expect(getSqliteProfileEntries(decoded)).toHaveLength(1);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ProfileArchive, ProfileArchiveEntryType } from '@dxos/protocols';
|
|
6
|
+
|
|
7
|
+
/** Default OPFS SQLite database name used by Composer recovery and the OPFS worker. */
|
|
8
|
+
export const OPFS_SQLITE_DB_FILENAME = 'DXOS' as const;
|
|
9
|
+
|
|
10
|
+
const SQLITE_MAGIC = new TextEncoder().encode('SQLite format 3\u0000');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when `bytes` begins with the SQLite 3 file header.
|
|
14
|
+
*/
|
|
15
|
+
export const isValidSqliteDatabase = (bytes: Uint8Array): boolean => {
|
|
16
|
+
if (bytes.byteLength < SQLITE_MAGIC.byteLength) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
for (let index = 0; index < SQLITE_MAGIC.byteLength; index++) {
|
|
20
|
+
if (bytes[index] !== SQLITE_MAGIC[index]) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const coerceToUint8Array = (value: unknown): Uint8Array | undefined => {
|
|
28
|
+
if (value instanceof Uint8Array) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (value instanceof ArrayBuffer) {
|
|
32
|
+
return new Uint8Array(value);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return new Uint8Array(value);
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type SqliteProfileEntry = {
|
|
41
|
+
opfsFilename: string;
|
|
42
|
+
database: Uint8Array;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type CreateSqliteProfileArchiveOptions = {
|
|
46
|
+
/** Host where the export was created (stored in archive meta). */
|
|
47
|
+
origin?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a profile archive containing a single validated SQLITE_DATABASE entry.
|
|
52
|
+
*/
|
|
53
|
+
export const createSqliteProfileArchive = (
|
|
54
|
+
opfsFilename: string,
|
|
55
|
+
database: Uint8Array,
|
|
56
|
+
options?: CreateSqliteProfileArchiveOptions,
|
|
57
|
+
): ProfileArchive => {
|
|
58
|
+
if (!isValidSqliteDatabase(database)) {
|
|
59
|
+
throw new Error('Invalid SQLite database (missing SQLite format 3 header)');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
meta: {
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
...(options?.origin ? { origin: options.origin } : {}),
|
|
66
|
+
},
|
|
67
|
+
storage: [
|
|
68
|
+
{
|
|
69
|
+
type: ProfileArchiveEntryType.SQLITE_DATABASE,
|
|
70
|
+
key: opfsFilename,
|
|
71
|
+
value: database,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract validated SQLITE_DATABASE entries from a profile archive.
|
|
79
|
+
*/
|
|
80
|
+
export const getSqliteProfileEntries = (archive: ProfileArchive): SqliteProfileEntry[] => {
|
|
81
|
+
const entries: SqliteProfileEntry[] = [];
|
|
82
|
+
|
|
83
|
+
for (const entry of archive.storage) {
|
|
84
|
+
if (entry.type !== ProfileArchiveEntryType.SQLITE_DATABASE) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (typeof entry.key !== 'string') {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const database = coerceToUint8Array(entry.value);
|
|
92
|
+
if (!database || !isValidSqliteDatabase(database)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
entries.push({ opfsFilename: entry.key, database });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return entries;
|
|
100
|
+
};
|
|
@@ -90,6 +90,9 @@ export const importProfileData = async (
|
|
|
90
90
|
batch.put(entry.key, entry.value, { keyEncoding: 'binary', valueEncoding: 'binary' });
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
|
+
case ProfileArchiveEntryType.SQLITE_DATABASE:
|
|
94
|
+
log.warn('Skipping SQLITE_DATABASE entry (import via OPFS recovery API)', { key: entry.key });
|
|
95
|
+
break;
|
|
93
96
|
default:
|
|
94
97
|
throw new Error(`Invalid entry type: ${entry.type}`);
|
|
95
98
|
}
|
|
@@ -14,16 +14,16 @@ import StorageDriver = Runtime.Client.Storage.StorageDriver;
|
|
|
14
14
|
export const createStorageObjects = (config: Runtime.Client.Storage) => {
|
|
15
15
|
const { persistent = false, keyStore, dataStore } = config ?? {};
|
|
16
16
|
if (persistent && dataStore === StorageDriver.RAM) {
|
|
17
|
-
throw new InvalidConfigError('RAM storage cannot be used in persistent mode.');
|
|
17
|
+
throw new InvalidConfigError({ message: 'RAM storage cannot be used in persistent mode.' });
|
|
18
18
|
}
|
|
19
19
|
if (!persistent && dataStore !== undefined && dataStore !== StorageDriver.RAM) {
|
|
20
|
-
throw new InvalidConfigError('Cannot use a persistent storage in not persistent mode.');
|
|
20
|
+
throw new InvalidConfigError({ message: 'Cannot use a persistent storage in not persistent mode.' });
|
|
21
21
|
}
|
|
22
22
|
if (persistent && keyStore === StorageDriver.RAM) {
|
|
23
|
-
throw new InvalidConfigError('RAM key storage cannot be used in persistent mode.');
|
|
23
|
+
throw new InvalidConfigError({ message: 'RAM key storage cannot be used in persistent mode.' });
|
|
24
24
|
}
|
|
25
25
|
if (!persistent && keyStore !== StorageDriver.RAM && keyStore !== undefined) {
|
|
26
|
-
throw new InvalidConfigError('Cannot use a persistent key storage in not persistent mode.');
|
|
26
|
+
throw new InvalidConfigError({ message: 'Cannot use a persistent key storage in not persistent mode.' });
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
return {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
6
|
import { type AuthenticatingInvitation, type CancellableInvitation, InvitationEncoder } from '@dxos/client-protocol';
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
7
8
|
import { invariant } from '@dxos/invariant';
|
|
8
9
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
9
10
|
import { type DeviceProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
@@ -36,15 +37,15 @@ export type PerformInvitationCallbacks<T> = {
|
|
|
36
37
|
onError?: (value: T) => boolean | void;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
export type
|
|
40
|
+
export type PerformInvitationProps = {
|
|
40
41
|
host: ServiceContext | InvitationHost;
|
|
41
42
|
guest: ServiceContext | InvitationGuest;
|
|
43
|
+
guestDeviceProfile?: DeviceProfileDocument;
|
|
42
44
|
options?: Partial<Invitation>;
|
|
43
45
|
hooks?: {
|
|
44
46
|
host?: PerformInvitationCallbacks<CancellableInvitation>;
|
|
45
47
|
guest?: PerformInvitationCallbacks<AuthenticatingInvitation>;
|
|
46
48
|
};
|
|
47
|
-
guestDeviceProfile?: DeviceProfileDocument;
|
|
48
49
|
codeInputDelay?: number;
|
|
49
50
|
};
|
|
50
51
|
|
|
@@ -52,14 +53,17 @@ export type Result = { invitation?: Invitation; error?: Error };
|
|
|
52
53
|
|
|
53
54
|
// TODO(burdon): Make async.
|
|
54
55
|
// TODO(burdon): Rename startInvitation.
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
55
59
|
export const performInvitation = ({
|
|
56
60
|
host,
|
|
57
61
|
guest,
|
|
62
|
+
guestDeviceProfile,
|
|
58
63
|
options,
|
|
59
64
|
hooks,
|
|
60
|
-
guestDeviceProfile,
|
|
61
65
|
codeInputDelay,
|
|
62
|
-
}:
|
|
66
|
+
}: PerformInvitationProps): [Promise<Result>, Promise<Result>] => {
|
|
63
67
|
let guestError = false;
|
|
64
68
|
let guestConnected = false;
|
|
65
69
|
let wereConnected = false;
|
|
@@ -226,7 +230,7 @@ export const createInvitation = async (
|
|
|
226
230
|
};
|
|
227
231
|
|
|
228
232
|
if (host instanceof ServiceContext) {
|
|
229
|
-
return host.invitationsManager.createInvitation({
|
|
233
|
+
return host.invitationsManager.createInvitation(new Context(), {
|
|
230
234
|
kind: Invitation.Kind.SPACE,
|
|
231
235
|
...options,
|
|
232
236
|
});
|
|
@@ -243,7 +247,7 @@ export const acceptInvitation = (
|
|
|
243
247
|
invitation = sanitizeInvitation(invitation);
|
|
244
248
|
|
|
245
249
|
if (guest instanceof ServiceContext) {
|
|
246
|
-
return guest.invitationsManager.acceptInvitation({
|
|
250
|
+
return guest.invitationsManager.acceptInvitation(new Context(), {
|
|
247
251
|
invitation,
|
|
248
252
|
deviceProfile: guestDeviceProfile,
|
|
249
253
|
});
|