@dxos/client-services 0.1.22 → 0.1.24-next.6b1f434

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/lib/browser/chunk-ZPZBLOQW.mjs +4086 -0
  2. package/dist/lib/browser/chunk-ZPZBLOQW.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +68 -3027
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/packlets/testing/index.mjs +198 -0
  7. package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
  8. package/dist/lib/node/index.cjs +1694 -667
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/packlets/testing/index.cjs +3806 -0
  12. package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
  13. package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
  14. package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
  15. package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
  16. package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
  17. package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
  18. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  19. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  20. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  21. package/dist/types/src/packlets/devtools/network.d.ts +3 -3
  22. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  23. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/authenticator.d.ts +9 -9
  25. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  26. package/dist/types/src/packlets/identity/identity-manager.d.ts +8 -2
  27. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity.d.ts +5 -4
  29. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
  31. package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
  33. package/dist/types/src/packlets/invitations/invitations.d.ts.map +1 -1
  34. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +1 -1
  35. package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
  36. package/dist/types/src/packlets/services/service-context.d.ts +3 -1
  37. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
  39. package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
  40. package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
  41. package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
  42. package/dist/types/src/packlets/services/service-host.d.ts +9 -6
  43. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  44. package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
  45. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  46. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
  47. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  48. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts +2 -0
  49. package/dist/types/src/packlets/spaces/data-space-manager.test.d.ts.map +1 -0
  50. package/dist/types/src/packlets/spaces/data-space.d.ts +20 -4
  51. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  52. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
  53. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
  54. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
  55. package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
  56. package/dist/types/src/packlets/spaces/spaces-service.d.ts +3 -2
  57. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  58. package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
  59. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
  60. package/dist/types/src/packlets/testing/index.d.ts +2 -0
  61. package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
  62. package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
  63. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
  64. package/dist/types/src/packlets/testing/test-builder.d.ts +31 -2
  65. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  66. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +35 -0
  67. package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
  68. package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +9 -4
  69. package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
  70. package/dist/types/src/packlets/vault/index.d.ts +6 -1
  71. package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
  72. package/dist/types/src/packlets/vault/services.d.ts +9 -1
  73. package/dist/types/src/packlets/vault/services.d.ts.map +1 -1
  74. package/dist/types/src/packlets/vault/shell-controller.d.ts +24 -0
  75. package/dist/types/src/packlets/vault/shell-controller.d.ts.map +1 -0
  76. package/dist/types/src/packlets/vault/shell-runtime.d.ts +45 -0
  77. package/dist/types/src/packlets/vault/shell-runtime.d.ts.map +1 -0
  78. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
  79. package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
  80. package/dist/types/src/packlets/vault/worker-runtime.d.ts +2 -1
  81. package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
  82. package/dist/types/src/packlets/vault/worker-session.d.ts +7 -4
  83. package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
  84. package/package.json +30 -29
  85. package/src/packlets/deprecated/profile.ts +20 -2
  86. package/src/packlets/deprecated/space.ts +41 -37
  87. package/src/packlets/deprecated/system.ts +26 -13
  88. package/src/packlets/devtools/devtools.ts +4 -4
  89. package/src/packlets/devtools/feeds.ts +11 -6
  90. package/src/packlets/devtools/network.ts +20 -9
  91. package/src/packlets/devtools/spaces.ts +8 -7
  92. package/src/packlets/identity/authenticator.test.ts +4 -4
  93. package/src/packlets/identity/authenticator.ts +27 -17
  94. package/src/packlets/identity/identity-manager.test.ts +25 -16
  95. package/src/packlets/identity/identity-manager.ts +25 -13
  96. package/src/packlets/identity/identity.test.ts +22 -23
  97. package/src/packlets/identity/identity.ts +15 -10
  98. package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
  99. package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
  100. package/src/packlets/invitations/invitations.ts +2 -1
  101. package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
  102. package/src/packlets/invitations/space-invitations-handler.ts +9 -12
  103. package/src/packlets/services/service-context.test.ts +56 -0
  104. package/src/packlets/services/service-context.ts +62 -10
  105. package/src/packlets/services/service-definitions.ts +1 -1
  106. package/src/packlets/services/service-host.test.ts +74 -5
  107. package/src/packlets/services/service-host.ts +86 -24
  108. package/src/packlets/services/service-registry.test.ts +2 -1
  109. package/src/packlets/services/service-registry.ts +5 -1
  110. package/src/packlets/spaces/data-space-manager.test.ts +131 -0
  111. package/src/packlets/spaces/data-space-manager.ts +61 -15
  112. package/src/packlets/spaces/data-space.ts +125 -14
  113. package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
  114. package/src/packlets/spaces/notarization-plugin.ts +211 -0
  115. package/src/packlets/spaces/spaces-service.ts +10 -2
  116. package/src/packlets/testing/credential-utils.ts +28 -0
  117. package/src/packlets/testing/index.ts +2 -0
  118. package/src/packlets/testing/invitation-utils.ts +44 -0
  119. package/src/packlets/testing/test-builder.ts +96 -14
  120. package/src/packlets/vault/iframe-host-runtime.ts +131 -0
  121. package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +18 -3
  122. package/src/packlets/vault/index.ts +6 -1
  123. package/src/packlets/vault/services.ts +17 -1
  124. package/src/packlets/vault/shell-controller.ts +77 -0
  125. package/src/packlets/vault/shell-runtime.ts +133 -0
  126. package/src/packlets/vault/vault-resource-lock.ts +76 -0
  127. package/src/packlets/vault/worker-runtime.ts +16 -4
  128. package/src/packlets/vault/worker-session.ts +36 -26
  129. package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { createCredential } from '@dxos/credentials';
6
+ import { Signer } from '@dxos/crypto';
7
+ import { PublicKey } from '@dxos/keys';
8
+ import { Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
9
+
10
+ export const createMockCredential = async ({
11
+ signer,
12
+ issuer
13
+ }: {
14
+ signer: Signer;
15
+ issuer: PublicKey;
16
+ }): Promise<Credential> =>
17
+ createCredential({
18
+ signer,
19
+ issuer,
20
+ subject: new PublicKey(Buffer.from('test')),
21
+ assertion: {
22
+ '@type': 'example.testing.rpc.MessageWithAny',
23
+ payload: {
24
+ '@type': 'google.protobuf.Any',
25
+ value: Buffer.from('test')
26
+ }
27
+ }
28
+ });
@@ -2,4 +2,6 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ export * from './credential-utils';
5
6
  export * from './test-builder';
7
+ export * from './invitation-utils';
@@ -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,10 +2,19 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { expect } from 'chai';
6
-
7
5
  import { Config } from '@dxos/config';
8
- import { ISpace } from '@dxos/echo-db';
6
+ import { createCredentialSignerWithChain, CredentialGenerator } from '@dxos/credentials';
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';
16
+ import { FeedFactory, FeedStore } from '@dxos/feed-store';
17
+ import { Keyring } from '@dxos/keyring';
9
18
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
10
19
  import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
11
20
  import { createStorage, Storage, StorageType } from '@dxos/random-access-storage';
@@ -63,18 +72,91 @@ export const createIdentity = async (peer: ServiceContext) => {
63
72
 
64
73
  // TODO(burdon): Remove @dxos/client-testing.
65
74
  // TODO(burdon): Create builder and make configurable.
66
- export const syncItems = async (space1: ISpace, space2: ISpace) => {
67
- {
68
- // Check item replicated from 1 => 2.
69
- const item1 = await space1.database!.createItem({ type: 'type-1' });
70
- const item2 = await space2.database!.waitForItem({ type: 'type-1' });
71
- expect(item1.id).to.eq(item2.id);
75
+ export const syncItems = async (db1: DataPipelineControllerImpl, db2: DataPipelineControllerImpl) => {
76
+ await testLocalDatabase(db1, db2);
77
+ await testLocalDatabase(db2, db1);
78
+ };
79
+
80
+ export class TestBuilder {
81
+ public readonly signalContext = new MemorySignalManagerContext();
82
+
83
+ createPeer(): TestPeer {
84
+ return new TestPeer(this.signalContext);
85
+ }
86
+ }
87
+
88
+ export type TestPeerProps = {
89
+ storage?: Storage;
90
+ feedStore?: FeedStore<any>;
91
+ metadataStore?: MetadataStore;
92
+ keyring?: Keyring;
93
+ networkManager?: NetworkManager;
94
+ spaceManager?: SpaceManager;
95
+ snapshotStore?: SnapshotStore;
96
+ };
97
+
98
+ export class TestPeer {
99
+ private _props: TestPeerProps = {};
100
+
101
+ constructor(private readonly signalContext: MemorySignalManagerContext) {}
102
+
103
+ get storage() {
104
+ return (this._props.storage ??= createStorage({ type: StorageType.RAM }));
72
105
  }
73
106
 
74
- {
75
- // Check item replicated from 2 => 1.
76
- const item1 = await space2.database!.createItem({ type: 'type-2' });
77
- const item2 = await space1.database!.waitForItem({ type: 'type-2' });
78
- expect(item1.id).to.eq(item2.id);
107
+ get keyring() {
108
+ return (this._props.keyring ??= new Keyring(this.storage.createDirectory('keyring')));
79
109
  }
110
+
111
+ get feedStore() {
112
+ return (this._props.feedStore ??= new FeedStore({
113
+ factory: new FeedFactory({
114
+ root: this.storage.createDirectory('feeds'),
115
+ signer: this.keyring,
116
+ hypercore: {
117
+ valueEncoding
118
+ }
119
+ })
120
+ }));
121
+ }
122
+
123
+ get metadataStore() {
124
+ return (this._props.metadataStore ??= new MetadataStore(this.storage.createDirectory('metadata')));
125
+ }
126
+
127
+ get snapshotStore() {
128
+ return (this._props.snapshotStore ??= new SnapshotStore(this.storage.createDirectory('snapshots')));
129
+ }
130
+
131
+ get networkManager() {
132
+ return (this._props.networkManager ??= new NetworkManager({
133
+ signalManager: new MemorySignalManager(this.signalContext),
134
+ transportFactory: MemoryTransportFactory
135
+ }));
136
+ }
137
+
138
+ get spaceManager() {
139
+ return (this._props.spaceManager ??= new SpaceManager({
140
+ feedStore: this.feedStore,
141
+ networkManager: this.networkManager
142
+ }));
143
+ }
144
+ }
145
+
146
+ export const createSigningContext = async (keyring: Keyring): Promise<SigningContext> => {
147
+ const identityKey = await keyring.createKey();
148
+ const deviceKey = await keyring.createKey();
149
+
150
+ return {
151
+ identityKey,
152
+ deviceKey,
153
+ credentialSigner: createCredentialSignerWithChain(
154
+ keyring,
155
+ {
156
+ credential: await new CredentialGenerator(keyring, identityKey, deviceKey).createDeviceAuthorization(deviceKey)
157
+ },
158
+ deviceKey
159
+ ),
160
+ recordCredential: async () => {} // No-op.
161
+ };
80
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 IframeRuntimeParams = {
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 IFrameRuntime {
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 }: IframeRuntimeParams) {
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
+ }