@dxos/client-services 0.1.23 → 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 +66 -3066
- 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 +1655 -669
- 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 +2 -2
- 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 +3 -2
- 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.d.ts +18 -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 +2 -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.ts +12 -7
- package/src/packlets/identity/identity-manager.test.ts +25 -16
- package/src/packlets/identity/identity-manager.ts +23 -9
- package/src/packlets/identity/identity.test.ts +22 -23
- package/src/packlets/identity/identity.ts +11 -6
- 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 -6
- 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 +72 -15
- package/src/packlets/spaces/data-space-manager.ts +53 -12
- package/src/packlets/spaces/data-space.ts +113 -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 +14 -18
- 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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
|
|
7
|
+
import { Trigger } from '@dxos/async';
|
|
8
|
+
import { raise } from '@dxos/debug';
|
|
9
|
+
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
10
|
+
|
|
11
|
+
import { InvitationsHandler } from '../invitations';
|
|
12
|
+
|
|
13
|
+
export const performInvitation = async <T>(host: InvitationsHandler<T>, guest: InvitationsHandler<T>, context: T) => {
|
|
14
|
+
const done1 = new Trigger<Invitation>(); // peer 1 connected.
|
|
15
|
+
const done2 = new Trigger<Invitation>(); // peer 2 connected.
|
|
16
|
+
const observable1 = await host.createInvitation(context, { type: Invitation.Type.INTERACTIVE_TESTING });
|
|
17
|
+
observable1.subscribe({
|
|
18
|
+
onConnecting: async (invitation1: Invitation) => {
|
|
19
|
+
const observable2 = await guest.acceptInvitation(invitation1, { type: Invitation.Type.INTERACTIVE_TESTING });
|
|
20
|
+
observable2.subscribe({
|
|
21
|
+
onConnecting: async (invitation2: Invitation) => {
|
|
22
|
+
assert(invitation1.swarmKey!.equals(invitation2.swarmKey!));
|
|
23
|
+
},
|
|
24
|
+
onConnected: async (invitation2: Invitation) => {},
|
|
25
|
+
onSuccess: (invitation) => {
|
|
26
|
+
done2.wake(invitation);
|
|
27
|
+
},
|
|
28
|
+
onCancelled: () => raise(new Error()),
|
|
29
|
+
onTimeout: (err: Error) => raise(err),
|
|
30
|
+
onError: (err: Error) => raise(err)
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
onConnected: async (invitation1: Invitation) => {},
|
|
34
|
+
onCancelled: () => raise(new Error('cancelled')),
|
|
35
|
+
onSuccess: (invitation) => {
|
|
36
|
+
done1.wake(invitation);
|
|
37
|
+
},
|
|
38
|
+
onTimeout: (err: Error) => raise(err),
|
|
39
|
+
onError: (err: Error) => raise(err)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await done1.wait();
|
|
43
|
+
await done2.wait();
|
|
44
|
+
};
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from 'chai';
|
|
6
|
-
|
|
7
5
|
import { Config } from '@dxos/config';
|
|
8
6
|
import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
|
|
9
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
SnapshotStore,
|
|
9
|
+
DataPipelineControllerImpl,
|
|
10
|
+
MetadataStore,
|
|
11
|
+
SigningContext,
|
|
12
|
+
SpaceManager,
|
|
13
|
+
valueEncoding
|
|
14
|
+
} from '@dxos/echo-pipeline';
|
|
15
|
+
import { testLocalDatabase } from '@dxos/echo-pipeline/testing';
|
|
10
16
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
11
17
|
import { Keyring } from '@dxos/keyring';
|
|
12
18
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -66,20 +72,9 @@ export const createIdentity = async (peer: ServiceContext) => {
|
|
|
66
72
|
|
|
67
73
|
// TODO(burdon): Remove @dxos/client-testing.
|
|
68
74
|
// TODO(burdon): Create builder and make configurable.
|
|
69
|
-
export const syncItems = async (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const item1 = await space1.database!.createItem({ type: 'type-1' });
|
|
73
|
-
const item2 = await space2.database!.waitForItem({ type: 'type-1' });
|
|
74
|
-
expect(item1.id).to.eq(item2.id);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
// Check item replicated from 2 => 1.
|
|
79
|
-
const item1 = await space2.database!.createItem({ type: 'type-2' });
|
|
80
|
-
const item2 = await space1.database!.waitForItem({ type: 'type-2' });
|
|
81
|
-
expect(item1.id).to.eq(item2.id);
|
|
82
|
-
}
|
|
75
|
+
export const syncItems = async (db1: DataPipelineControllerImpl, db2: DataPipelineControllerImpl) => {
|
|
76
|
+
await testLocalDatabase(db1, db2);
|
|
77
|
+
await testLocalDatabase(db2, db1);
|
|
83
78
|
};
|
|
84
79
|
|
|
85
80
|
export class TestBuilder {
|
|
@@ -161,6 +156,7 @@ export const createSigningContext = async (keyring: Keyring): Promise<SigningCon
|
|
|
161
156
|
credential: await new CredentialGenerator(keyring, identityKey, deviceKey).createDeviceAuthorization(deviceKey)
|
|
162
157
|
},
|
|
163
158
|
deviceKey
|
|
164
|
-
)
|
|
159
|
+
),
|
|
160
|
+
recordCredential: async () => {} // No-op.
|
|
165
161
|
};
|
|
166
162
|
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Trigger } from '@dxos/async';
|
|
6
|
+
import { Config } from '@dxos/config';
|
|
7
|
+
import { log, logInfo } from '@dxos/log';
|
|
8
|
+
import { MemorySignalManager, MemorySignalManagerContext, WebsocketSignalManager } from '@dxos/messaging';
|
|
9
|
+
import { createWebRTCTransportFactory, NetworkManager } from '@dxos/network-manager';
|
|
10
|
+
import { createProtoRpcPeer, ProtoRpcPeer, RpcPort } from '@dxos/rpc';
|
|
11
|
+
import { getAsyncValue, Provider } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { clientServiceBundle, ClientServices, ClientServicesHost } from '../services';
|
|
14
|
+
import { ShellRuntime, ShellRuntimeImpl } from './shell-runtime';
|
|
15
|
+
|
|
16
|
+
const LOCK_KEY = 'DXOS_RESOURCE_LOCK';
|
|
17
|
+
|
|
18
|
+
export type IFrameHostRuntimeParams = {
|
|
19
|
+
configProvider: Config | Provider<Promise<Config>>;
|
|
20
|
+
appPort: RpcPort;
|
|
21
|
+
shellPort?: RpcPort;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs the client services in the main thread.
|
|
26
|
+
*
|
|
27
|
+
* Holds a lock over the client services such that only one instance can run at a time.
|
|
28
|
+
* This should only be used when SharedWorker is not available.
|
|
29
|
+
*/
|
|
30
|
+
export class IFrameHostRuntime {
|
|
31
|
+
private readonly _configProvider: Config | Provider<Promise<Config>>;
|
|
32
|
+
private readonly _transportFactory = createWebRTCTransportFactory();
|
|
33
|
+
private readonly _ready = new Trigger<Error | undefined>();
|
|
34
|
+
private readonly _getService: <Service>(
|
|
35
|
+
find: (services: Partial<ClientServices>) => Service | undefined
|
|
36
|
+
) => Promise<Service>;
|
|
37
|
+
|
|
38
|
+
private readonly _appPort: RpcPort;
|
|
39
|
+
private readonly _shellPort?: RpcPort;
|
|
40
|
+
private _config!: Config;
|
|
41
|
+
private _clientServices!: ClientServicesHost;
|
|
42
|
+
private _clientRpc!: ProtoRpcPeer<ClientServices>;
|
|
43
|
+
private _shellRuntime?: ShellRuntimeImpl;
|
|
44
|
+
|
|
45
|
+
@logInfo
|
|
46
|
+
public origin?: string;
|
|
47
|
+
|
|
48
|
+
constructor({ configProvider, appPort, shellPort }: IFrameHostRuntimeParams) {
|
|
49
|
+
this._configProvider = configProvider;
|
|
50
|
+
this._appPort = appPort;
|
|
51
|
+
this._shellPort = shellPort;
|
|
52
|
+
|
|
53
|
+
this._getService = async (find) => {
|
|
54
|
+
const error = await this._ready.wait();
|
|
55
|
+
if (error) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const service = await find(this._clientServices.services);
|
|
60
|
+
if (!service) {
|
|
61
|
+
throw new Error('Service not found');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return service;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (this._shellPort) {
|
|
68
|
+
this._shellRuntime = new ShellRuntimeImpl(this._shellPort);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get services() {
|
|
73
|
+
return this._clientServices;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get shell(): ShellRuntime | undefined {
|
|
77
|
+
return this._shellRuntime;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async start() {
|
|
81
|
+
log('starting...');
|
|
82
|
+
try {
|
|
83
|
+
this._config = await getAsyncValue(this._configProvider);
|
|
84
|
+
const signalServer = this._config.get('runtime.services.signal.server');
|
|
85
|
+
this._clientServices = new ClientServicesHost({
|
|
86
|
+
lockKey: LOCK_KEY,
|
|
87
|
+
config: this._config,
|
|
88
|
+
networkManager: new NetworkManager({
|
|
89
|
+
log: true,
|
|
90
|
+
signalManager: signalServer
|
|
91
|
+
? new WebsocketSignalManager([signalServer])
|
|
92
|
+
: new MemorySignalManager(new MemorySignalManagerContext()), // TODO(dmaretskyi): Inject this context.
|
|
93
|
+
transportFactory: this._transportFactory
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
this._clientRpc = createProtoRpcPeer({
|
|
98
|
+
exposed: clientServiceBundle,
|
|
99
|
+
handlers: {
|
|
100
|
+
DataService: async () => await this._getService((services) => services.DataService),
|
|
101
|
+
DevicesService: async () => await this._getService((services) => services.DevicesService),
|
|
102
|
+
DevtoolsHost: async () => await this._getService((services) => services.DevtoolsHost),
|
|
103
|
+
HaloInvitationsService: async () => await this._getService((services) => services.HaloInvitationsService),
|
|
104
|
+
NetworkService: async () => await this._getService((services) => services.NetworkService),
|
|
105
|
+
ProfileService: async () => await this._getService((services) => services.ProfileService),
|
|
106
|
+
SpaceInvitationsService: async () => await this._getService((services) => services.SpaceInvitationsService),
|
|
107
|
+
SpaceService: async () => await this._getService((services) => services.SpaceService),
|
|
108
|
+
SpacesService: async () => await this._getService((services) => services.SpacesService),
|
|
109
|
+
SystemService: async () => await this._getService((services) => services.SystemService),
|
|
110
|
+
TracingService: async () => await this._getService((services) => services.TracingService)
|
|
111
|
+
},
|
|
112
|
+
port: this._appPort
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await Promise.all([this._clientServices.open(), this._clientRpc.open(), this._shellRuntime?.open()]);
|
|
116
|
+
this._ready.wake(undefined);
|
|
117
|
+
log('started');
|
|
118
|
+
} catch (err: any) {
|
|
119
|
+
this._ready.wake(err);
|
|
120
|
+
log.catch(err);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async stop() {
|
|
125
|
+
log('stopping...');
|
|
126
|
+
await this._clientRpc.close();
|
|
127
|
+
await this._clientServices.close();
|
|
128
|
+
await this._shellRuntime?.close();
|
|
129
|
+
log('stopped');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -7,28 +7,33 @@ import { BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
|
7
7
|
import { createProtoRpcPeer, ProtoRpcPeer, RpcPort } from '@dxos/rpc';
|
|
8
8
|
|
|
9
9
|
import { iframeServiceBundle, WorkerServiceBundle, workerServiceBundle } from './services';
|
|
10
|
+
import { ShellRuntime, ShellRuntimeImpl } from './shell-runtime';
|
|
10
11
|
|
|
11
12
|
// NOTE: Keep as RpcPorts to avoid dependency on @dxos/rpc-tunnel so we don't depend on browser-specific apis.
|
|
12
|
-
export type
|
|
13
|
+
export type IFrameProxyRuntimeParams = {
|
|
13
14
|
systemPort: RpcPort;
|
|
14
15
|
workerAppPort: RpcPort;
|
|
15
16
|
windowAppPort: RpcPort;
|
|
17
|
+
shellPort?: RpcPort;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Manages the client connection to the shared worker.
|
|
20
22
|
*/
|
|
21
|
-
export class
|
|
23
|
+
export class IFrameProxyRuntime {
|
|
22
24
|
private readonly _systemPort: RpcPort;
|
|
23
25
|
private readonly _windowAppPort: RpcPort;
|
|
24
26
|
private readonly _workerAppPort: RpcPort;
|
|
27
|
+
private readonly _shellPort?: RpcPort;
|
|
25
28
|
private readonly _systemRpc: ProtoRpcPeer<WorkerServiceBundle>;
|
|
29
|
+
private readonly _shellRuntime?: ShellRuntimeImpl;
|
|
26
30
|
private readonly _transportService = new WebRTCTransportService();
|
|
27
31
|
|
|
28
|
-
constructor({ systemPort, workerAppPort, windowAppPort }:
|
|
32
|
+
constructor({ systemPort, workerAppPort, windowAppPort, shellPort }: IFrameProxyRuntimeParams) {
|
|
29
33
|
this._systemPort = systemPort;
|
|
30
34
|
this._windowAppPort = windowAppPort;
|
|
31
35
|
this._workerAppPort = workerAppPort;
|
|
36
|
+
this._shellPort = shellPort;
|
|
32
37
|
|
|
33
38
|
this._systemRpc = createProtoRpcPeer({
|
|
34
39
|
requested: workerServiceBundle,
|
|
@@ -47,14 +52,24 @@ export class IFrameRuntime {
|
|
|
47
52
|
|
|
48
53
|
this._workerAppPort.subscribe((msg) => this._windowAppPort.send(msg));
|
|
49
54
|
this._windowAppPort.subscribe((msg) => this._workerAppPort.send(msg));
|
|
55
|
+
|
|
56
|
+
if (this._shellPort) {
|
|
57
|
+
this._shellRuntime = new ShellRuntimeImpl(this._shellPort);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get shell(): ShellRuntime | undefined {
|
|
62
|
+
return this._shellRuntime;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
async open(origin: string) {
|
|
53
66
|
await this._systemRpc.open();
|
|
54
67
|
await this._systemRpc.rpc.WorkerService.start({ origin });
|
|
68
|
+
await this._shellRuntime?.open();
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
async close() {
|
|
72
|
+
await this._shellRuntime?.close();
|
|
58
73
|
await this._systemRpc.rpc.WorkerService.stop();
|
|
59
74
|
await this._systemRpc.close();
|
|
60
75
|
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * from './iframe-runtime';
|
|
5
|
+
export * from './iframe-host-runtime';
|
|
6
|
+
export * from './iframe-proxy-runtime';
|
|
7
|
+
export * from './services';
|
|
8
|
+
export * from './shell-controller';
|
|
9
|
+
export * from './shell-runtime';
|
|
10
|
+
export * from './vault-resource-lock';
|
|
6
11
|
export * from './worker-runtime';
|
|
7
12
|
export * from './worker-session';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { schema } from '@dxos/protocols';
|
|
6
|
-
import type { IframeService, WorkerService } from '@dxos/protocols/proto/dxos/iframe';
|
|
6
|
+
import type { IframeService, AppService, ShellService, WorkerService } from '@dxos/protocols/proto/dxos/iframe';
|
|
7
7
|
import type { BridgeService } from '@dxos/protocols/proto/dxos/mesh/bridge';
|
|
8
8
|
import { ServiceBundle } from '@dxos/rpc';
|
|
9
9
|
|
|
@@ -24,3 +24,19 @@ export type WorkerServiceBundle = {
|
|
|
24
24
|
export const workerServiceBundle: ServiceBundle<WorkerServiceBundle> = {
|
|
25
25
|
WorkerService: schema.getService('dxos.iframe.WorkerService')
|
|
26
26
|
};
|
|
27
|
+
|
|
28
|
+
export type AppServiceBundle = {
|
|
29
|
+
AppService: AppService;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const appServiceBundle: ServiceBundle<AppServiceBundle> = {
|
|
33
|
+
AppService: schema.getService('dxos.iframe.AppService')
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ShellServiceBundle = {
|
|
37
|
+
ShellService: ShellService;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const shellServiceBundle: ServiceBundle<ShellServiceBundle> = {
|
|
41
|
+
ShellService: schema.getService('dxos.iframe.ShellService')
|
|
42
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Event } from '@dxos/async';
|
|
6
|
+
import { PublicKey } from '@dxos/keys';
|
|
7
|
+
import { log } from '@dxos/log';
|
|
8
|
+
import { AppContextRequest, LayoutRequest, ShellDisplay, ShellLayout } from '@dxos/protocols/proto/dxos/iframe';
|
|
9
|
+
import { createProtoRpcPeer, ProtoRpcPeer, RpcPort } from '@dxos/rpc';
|
|
10
|
+
|
|
11
|
+
import { appServiceBundle, shellServiceBundle, ShellServiceBundle } from './services';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provide access to the shell via RPC connection.
|
|
15
|
+
*/
|
|
16
|
+
export class ShellController {
|
|
17
|
+
readonly contextUpdate = new Event<AppContextRequest>();
|
|
18
|
+
private _shellRpc?: ProtoRpcPeer<ShellServiceBundle>;
|
|
19
|
+
private _display = ShellDisplay.NONE;
|
|
20
|
+
private _spaceKey?: PublicKey;
|
|
21
|
+
private _invitationCode?: string;
|
|
22
|
+
|
|
23
|
+
constructor(private readonly _port: RpcPort) {}
|
|
24
|
+
|
|
25
|
+
get display() {
|
|
26
|
+
return this._display;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get spaceKey() {
|
|
30
|
+
return this._spaceKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setSpace(key?: PublicKey) {
|
|
34
|
+
this._spaceKey = key;
|
|
35
|
+
this._emitUpdate();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async setLayout(layout: ShellLayout, options: Omit<LayoutRequest, 'layout'> = {}) {
|
|
39
|
+
this._display = ShellDisplay.FULLSCREEN;
|
|
40
|
+
this._emitUpdate();
|
|
41
|
+
await this._shellRpc?.rpc.ShellService.setLayout({ layout, ...options });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async open() {
|
|
45
|
+
this._shellRpc = createProtoRpcPeer({
|
|
46
|
+
requested: shellServiceBundle,
|
|
47
|
+
exposed: appServiceBundle,
|
|
48
|
+
handlers: {
|
|
49
|
+
AppService: {
|
|
50
|
+
setContext: async (request) => {
|
|
51
|
+
log('set context', request);
|
|
52
|
+
this._display = request.display;
|
|
53
|
+
this._spaceKey = request.spaceKey;
|
|
54
|
+
this._invitationCode = request.invitationCode;
|
|
55
|
+
this.contextUpdate.emit(request);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
port: this._port
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await this._shellRpc.open();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async close() {
|
|
66
|
+
await this._shellRpc?.close();
|
|
67
|
+
this._shellRpc = undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _emitUpdate() {
|
|
71
|
+
this.contextUpdate.emit({
|
|
72
|
+
display: this._display,
|
|
73
|
+
spaceKey: this._spaceKey,
|
|
74
|
+
invitationCode: this._invitationCode
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
|
|
7
|
+
import { Event } from '@dxos/async';
|
|
8
|
+
import { PublicKey } from '@dxos/keys';
|
|
9
|
+
import { AppContextRequest, LayoutRequest, ShellLayout } from '@dxos/protocols/proto/dxos/iframe';
|
|
10
|
+
import { createProtoRpcPeer, ProtoRpcPeer, RpcPort } from '@dxos/rpc';
|
|
11
|
+
|
|
12
|
+
import { AppServiceBundle, appServiceBundle, shellServiceBundle } from './services';
|
|
13
|
+
|
|
14
|
+
export interface ShellRuntime {
|
|
15
|
+
layoutUpdate: Event<LayoutRequest>;
|
|
16
|
+
layout: ShellLayout;
|
|
17
|
+
invitationCode?: string;
|
|
18
|
+
spaceKey?: PublicKey;
|
|
19
|
+
setLayout: (layout: ShellLayout, options?: Omit<LayoutRequest, 'layout'>) => void;
|
|
20
|
+
setAppContext: (context: AppContextRequest) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Endpoint that handles shell services.
|
|
25
|
+
*/
|
|
26
|
+
export class ShellRuntimeImpl implements ShellRuntime {
|
|
27
|
+
readonly layoutUpdate = new Event<LayoutRequest>();
|
|
28
|
+
private _appRpc?: ProtoRpcPeer<AppServiceBundle>;
|
|
29
|
+
private _layout = ShellLayout.DEFAULT;
|
|
30
|
+
private _invitationCode?: string;
|
|
31
|
+
private _spaceKey?: PublicKey;
|
|
32
|
+
|
|
33
|
+
constructor(private readonly _port: RpcPort) {}
|
|
34
|
+
|
|
35
|
+
get layout() {
|
|
36
|
+
return this._layout;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get invitationCode() {
|
|
40
|
+
return this._invitationCode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get spaceKey() {
|
|
44
|
+
return this._spaceKey;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setLayout(layout: ShellLayout, options: Omit<LayoutRequest, 'layout'> = {}) {
|
|
48
|
+
this._layout = layout;
|
|
49
|
+
this._invitationCode = options.invitationCode;
|
|
50
|
+
this._spaceKey = options.spaceKey;
|
|
51
|
+
this.layoutUpdate.emit({ layout, ...options });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async setAppContext(context: AppContextRequest) {
|
|
55
|
+
assert(this._appRpc, 'runtime not open');
|
|
56
|
+
|
|
57
|
+
if (context.spaceKey) {
|
|
58
|
+
this._spaceKey = context.spaceKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (context.invitationCode) {
|
|
62
|
+
this._invitationCode = context.invitationCode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await this._appRpc.rpc.AppService.setContext({
|
|
66
|
+
...context,
|
|
67
|
+
spaceKey: this._spaceKey,
|
|
68
|
+
invitationCode: this._invitationCode
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async open() {
|
|
73
|
+
this._appRpc = createProtoRpcPeer({
|
|
74
|
+
requested: appServiceBundle,
|
|
75
|
+
exposed: shellServiceBundle,
|
|
76
|
+
handlers: {
|
|
77
|
+
ShellService: {
|
|
78
|
+
setLayout: async (request) => {
|
|
79
|
+
this._layout = request.layout;
|
|
80
|
+
this._invitationCode = request.invitationCode;
|
|
81
|
+
this._spaceKey = request.spaceKey;
|
|
82
|
+
this.layoutUpdate.emit(request);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
port: this._port
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await this._appRpc.open();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async close() {
|
|
93
|
+
await this._appRpc?.close();
|
|
94
|
+
this._appRpc = undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class MemoryShellRuntime implements ShellRuntime {
|
|
99
|
+
readonly layoutUpdate = new Event<LayoutRequest>();
|
|
100
|
+
readonly contextUpdate = new Event<AppContextRequest>();
|
|
101
|
+
private _layout: ShellLayout;
|
|
102
|
+
private _invitationCode?: string;
|
|
103
|
+
private _spaceKey?: PublicKey;
|
|
104
|
+
|
|
105
|
+
constructor({ layout, invitationCode, spaceKey }: Partial<LayoutRequest> & Partial<AppContextRequest> = {}) {
|
|
106
|
+
this._layout = layout ?? ShellLayout.DEFAULT;
|
|
107
|
+
this._invitationCode = invitationCode;
|
|
108
|
+
this._spaceKey = spaceKey;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get layout() {
|
|
112
|
+
return this._layout;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get invitationCode() {
|
|
116
|
+
return this._invitationCode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get spaceKey() {
|
|
120
|
+
return this._spaceKey;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setLayout(layout: ShellLayout, options: Omit<LayoutRequest, 'layout'> = {}) {
|
|
124
|
+
this._layout = layout;
|
|
125
|
+
this._invitationCode = options.invitationCode;
|
|
126
|
+
this._spaceKey = options.spaceKey;
|
|
127
|
+
this.layoutUpdate.emit({ layout, ...options });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async setAppContext(context: AppContextRequest) {
|
|
131
|
+
this.contextUpdate.emit(context);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { asyncTimeout, Trigger } from '@dxos/async';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
7
|
+
import { MaybePromise } from '@dxos/util';
|
|
8
|
+
|
|
9
|
+
enum Message {
|
|
10
|
+
ACQUIRING = 'acquiring'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type VaultResourceLockOptions = {
|
|
14
|
+
lockKey: string;
|
|
15
|
+
onAcquire?: () => MaybePromise<void>;
|
|
16
|
+
onRelease?: () => MaybePromise<void>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class VaultResourceLock {
|
|
20
|
+
private readonly _broadcastChannel = new BroadcastChannel('vault-resource-lock');
|
|
21
|
+
private readonly _lockKey: string;
|
|
22
|
+
private readonly _onAcquire: VaultResourceLockOptions['onAcquire'];
|
|
23
|
+
private readonly _onRelease: VaultResourceLockOptions['onRelease'];
|
|
24
|
+
private _releaseTrigger = new Trigger();
|
|
25
|
+
|
|
26
|
+
constructor({ lockKey, onAcquire, onRelease }: VaultResourceLockOptions) {
|
|
27
|
+
this._lockKey = lockKey;
|
|
28
|
+
this._onAcquire = onAcquire;
|
|
29
|
+
this._onRelease = onRelease;
|
|
30
|
+
this._broadcastChannel.onmessage = this._onMessage.bind(this);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async acquire() {
|
|
34
|
+
this._broadcastChannel.postMessage({
|
|
35
|
+
message: Message.ACQUIRING
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await asyncTimeout(this._requestLock(), 3_000);
|
|
40
|
+
} catch {
|
|
41
|
+
await this._requestLock(true);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async release() {
|
|
46
|
+
this._releaseTrigger.wake();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private _onMessage(event: MessageEvent<any>) {
|
|
50
|
+
if (event.data.message === Message.ACQUIRING) {
|
|
51
|
+
this._releaseTrigger.wake();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async _requestLock(steal = false) {
|
|
56
|
+
log('requesting lock...', { steal });
|
|
57
|
+
const acquired = new Trigger();
|
|
58
|
+
|
|
59
|
+
void navigator.locks
|
|
60
|
+
.request(this._lockKey, { steal }, async () => {
|
|
61
|
+
await this._onAcquire?.();
|
|
62
|
+
acquired.wake();
|
|
63
|
+
this._releaseTrigger = new Trigger();
|
|
64
|
+
await this._releaseTrigger.wait();
|
|
65
|
+
log('releasing lock...');
|
|
66
|
+
await this._onRelease?.();
|
|
67
|
+
log('released lock');
|
|
68
|
+
})
|
|
69
|
+
.catch(async () => {
|
|
70
|
+
await this._onRelease?.();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await acquired.wait();
|
|
74
|
+
log('recieved lock', { steal });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Trigger } from '@dxos/async';
|
|
6
6
|
import { Config } from '@dxos/config';
|
|
7
|
+
import { log } from '@dxos/log';
|
|
7
8
|
import { MemorySignalManager, MemorySignalManagerContext, WebsocketSignalManager } from '@dxos/messaging';
|
|
8
9
|
import { NetworkManager, WebRTCTransportProxyFactory } from '@dxos/network-manager';
|
|
9
10
|
import { RpcPort } from '@dxos/rpc';
|
|
@@ -16,6 +17,7 @@ import { WorkerSession } from './worker-session';
|
|
|
16
17
|
export type CreateSessionParams = {
|
|
17
18
|
appPort: RpcPort;
|
|
18
19
|
systemPort: RpcPort;
|
|
20
|
+
shellPort: RpcPort;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -37,6 +39,7 @@ export class WorkerRuntime {
|
|
|
37
39
|
) { }
|
|
38
40
|
|
|
39
41
|
async start() {
|
|
42
|
+
log('starting...');
|
|
40
43
|
try {
|
|
41
44
|
this._config = await this._configProvider();
|
|
42
45
|
const signalServer = this._config.get('runtime.services.signal.server');
|
|
@@ -53,8 +56,10 @@ export class WorkerRuntime {
|
|
|
53
56
|
|
|
54
57
|
await this._clientServices.open();
|
|
55
58
|
this._ready.wake(undefined);
|
|
59
|
+
log('started');
|
|
56
60
|
} catch (err: any) {
|
|
57
61
|
this._ready.wake(err);
|
|
62
|
+
log.catch(err);
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -66,17 +71,24 @@ export class WorkerRuntime {
|
|
|
66
71
|
/**
|
|
67
72
|
* Create a new session.
|
|
68
73
|
*/
|
|
69
|
-
async createSession({ appPort, systemPort }: CreateSessionParams) {
|
|
74
|
+
async createSession({ appPort, systemPort, shellPort }: CreateSessionParams) {
|
|
70
75
|
const session = new WorkerSession({
|
|
71
|
-
|
|
76
|
+
getService: async (find) => {
|
|
72
77
|
const error = await this._ready.wait();
|
|
73
|
-
if (error
|
|
78
|
+
if (error) {
|
|
74
79
|
throw error;
|
|
75
80
|
}
|
|
76
|
-
|
|
81
|
+
|
|
82
|
+
const service = find(this._clientServices.services);
|
|
83
|
+
if (!service) {
|
|
84
|
+
throw new Error('Service not found');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return service;
|
|
77
88
|
},
|
|
78
89
|
appPort,
|
|
79
90
|
systemPort,
|
|
91
|
+
shellPort,
|
|
80
92
|
readySignal: this._ready
|
|
81
93
|
});
|
|
82
94
|
|