@peers-app/peers-sdk 0.26.4 → 0.26.6
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/contracts/__tests__/contract-resolvers.test.js +24 -0
- package/dist/contracts/__tests__/legacy-removal.test.js +38 -0
- package/dist/contracts/__tests__/remote-contract-transport.test.js +23 -32
- package/dist/contracts/__tests__/system-contracts.test.js +80 -0
- package/dist/contracts/contract-resolvers.d.ts +1 -1
- package/dist/contracts/contract-resolvers.js +3 -2
- package/dist/contracts/create-local-consumer.d.ts +2 -1
- package/dist/contracts/create-local-consumer.js +1 -1
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/remote-contract-transport.d.ts +3 -3
- package/dist/contracts/remote-contract-transport.js +19 -17
- package/dist/contracts/system/host-shell-contract.d.ts +191 -0
- package/dist/contracts/system/host-shell-contract.js +190 -0
- package/dist/contracts/system/index.d.ts +4 -0
- package/dist/contracts/system/index.js +4 -0
- package/dist/contracts/system/packages-contract.d.ts +257 -0
- package/dist/contracts/system/packages-contract.js +213 -0
- package/dist/data/create-group.d.ts +1 -1
- package/dist/data/create-group.js +1 -1
- package/dist/data/group-members.d.ts +6 -1
- package/dist/data/group-members.js +12 -4
- package/dist/data/groups.d.ts +6 -1
- package/dist/data/groups.js +12 -4
- package/dist/data/package-version-resolver.d.ts +8 -2
- package/dist/data/package-version-resolver.js +28 -2
- package/dist/data/package-version-resolver.test.js +42 -0
- package/dist/data/package-versions.d.ts +5 -1
- package/dist/data/package-versions.js +11 -4
- package/dist/data/packages.d.ts +7 -1
- package/dist/data/packages.js +13 -4
- package/dist/data/table-signing.test.d.ts +1 -0
- package/dist/data/table-signing.test.js +118 -0
- package/dist/data/users.d.ts +1 -2
- package/dist/data/users.js +8 -5
- package/dist/package-installer/package-author-signer.d.ts +19 -0
- package/dist/package-installer/package-author-signer.js +24 -0
- package/dist/package-installer/package-creator.d.ts +1 -7
- package/dist/package-installer/package-creator.js +0 -12
- package/dist/package-installer/package-creator.test.js +0 -16
- package/dist/package-installer/package-installer.d.ts +5 -0
- package/dist/package-installer/package-installer.js +60 -37
- package/dist/package-installer/package-installer.test.js +129 -0
- package/dist/package-installer/types.d.ts +5 -11
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-runtime.d.ts +18 -0
- package/dist/package-loader/package-loader.d.ts +67 -4
- package/dist/package-loader/package-loader.js +300 -52
- package/dist/package-loader/package-loader.test.js +413 -1
- package/dist/package-loader/package-runtime-state.d.ts +112 -0
- package/dist/package-loader/package-runtime-state.js +18 -0
- package/dist/package-loader/resolve-consumed-contracts.js +5 -4
- package/dist/package-loader/resolve-consumed-contracts.test.js +1 -1
- package/dist/rpc-types.d.ts +0 -121
- package/dist/rpc-types.js +0 -39
- package/dist/system-ids.d.ts +13 -0
- package/dist/system-ids.js +14 -1
- package/package.json +1 -1
|
@@ -345,6 +345,23 @@ describe("remote contract call authorization (group members)", () => {
|
|
|
345
345
|
});
|
|
346
346
|
await expect((0, index_1.authorizeRemoteContractCall)({ ...toolCall, targetDeviceId: (0, utils_1.newid)() }, userContext, remoteIdentity)).rejects.toThrow("cannot relay");
|
|
347
347
|
});
|
|
348
|
+
it("lets a Self-trusted device administer System Packages on this device", async () => {
|
|
349
|
+
const { remoteIdentity, userContext } = setupGroupMember({ callerLevel: access_level_1.AccessLevel.None });
|
|
350
|
+
mockTrustGet.mockResolvedValue({ trustLevel: socket_type_1.TrustLevel.Self });
|
|
351
|
+
const route = await (0, index_1.authorizeRemoteContractCall)({ ...toolCall, contractId: system_ids_1.packagesContractId, name: "activatePackageVersion" }, userContext, remoteIdentity);
|
|
352
|
+
expect(route.dataContextId).toBe(groupId);
|
|
353
|
+
expect(route.context?.callerDeviceId).toBe(remoteIdentity.deviceId);
|
|
354
|
+
});
|
|
355
|
+
it.each([
|
|
356
|
+
["Admin", access_level_1.AccessLevel.Admin],
|
|
357
|
+
["Owner", access_level_1.AccessLevel.Owner],
|
|
358
|
+
])("refuses System Packages to a group %s who is not trusted as Self", async (_role, level) => {
|
|
359
|
+
const { remoteIdentity, userContext, groupDataContext } = setupGroupMember({
|
|
360
|
+
callerLevel: level,
|
|
361
|
+
});
|
|
362
|
+
groupDataContext.packageLoader.contractRegistry.getDefinition.mockReturnValue((0, index_1.getPackagesContractDefinition)());
|
|
363
|
+
await expect((0, index_1.authorizeRemoteContractCall)({ ...toolCall, contractId: system_ids_1.packagesContractId, name: "listPackages" }, userContext, remoteIdentity)).rejects.toThrow("tool is not remotely callable");
|
|
364
|
+
});
|
|
348
365
|
it("keeps full access for Self-trusted callers without consulting remoteAccessLevel", async () => {
|
|
349
366
|
const { remoteIdentity, userContext } = setupGroupMember({ callerLevel: access_level_1.AccessLevel.None });
|
|
350
367
|
mockTrustGet.mockResolvedValue({ trustLevel: socket_type_1.TrustLevel.Self });
|
|
@@ -352,6 +369,13 @@ describe("remote contract call authorization (group members)", () => {
|
|
|
352
369
|
expect(route.dataContextId).toBe(groupId);
|
|
353
370
|
expect(accessLevelMock.calls).toEqual([]);
|
|
354
371
|
});
|
|
372
|
+
it("refuses a Self-trusted caller naming a group this device does not have", async () => {
|
|
373
|
+
const { remoteIdentity, userContext } = setupGroupMember({ callerLevel: access_level_1.AccessLevel.None });
|
|
374
|
+
mockTrustGet.mockResolvedValue({ trustLevel: socket_type_1.TrustLevel.Self });
|
|
375
|
+
const unknownGroupId = (0, utils_1.newid)();
|
|
376
|
+
await expect((0, index_1.authorizeRemoteContractCall)({ ...toolCall, dataContextId: unknownGroupId }, userContext, remoteIdentity)).rejects.toThrow("not available locally");
|
|
377
|
+
expect(userContext.getDataContext).not.toHaveBeenCalledWith(unknownGroupId);
|
|
378
|
+
});
|
|
355
379
|
});
|
|
356
380
|
describe("local user-context contract routing", () => {
|
|
357
381
|
function setupLocalRouter() {
|
|
@@ -18,6 +18,44 @@ test("the SDK exports no executable registry", () => {
|
|
|
18
18
|
for (const name of removedRegistryExports)
|
|
19
19
|
expect(sdk).not.toHaveProperty(name);
|
|
20
20
|
});
|
|
21
|
+
test("package administration has no RPC stubs; System Packages replaces them", () => {
|
|
22
|
+
for (const name of [
|
|
23
|
+
"addOrUpdatePackage",
|
|
24
|
+
"seedBundledPeersCore",
|
|
25
|
+
"signPackageAuthorVersion",
|
|
26
|
+
"injectUIBundle",
|
|
27
|
+
])
|
|
28
|
+
expect(sdk.rpcServerCalls).not.toHaveProperty(name);
|
|
29
|
+
expect(sdk).not.toHaveProperty("unwrapAddOrUpdatePackageResult");
|
|
30
|
+
expect(sdk.getPackagesContractDefinition().name).toBe("System Packages");
|
|
31
|
+
});
|
|
32
|
+
test("the host shell has no RPC stubs; System Host Shell replaces them", () => {
|
|
33
|
+
for (const name of [
|
|
34
|
+
"openLinkInBrowser",
|
|
35
|
+
"openPath",
|
|
36
|
+
"openPackage",
|
|
37
|
+
"copyPvarValueToClipboard",
|
|
38
|
+
"appQuit",
|
|
39
|
+
"appRestart",
|
|
40
|
+
"uiReload",
|
|
41
|
+
"uiInspect",
|
|
42
|
+
"uiClick",
|
|
43
|
+
"uiSet",
|
|
44
|
+
"uiScroll",
|
|
45
|
+
])
|
|
46
|
+
expect(sdk.rpcServerCalls).not.toHaveProperty(name);
|
|
47
|
+
for (const name of [
|
|
48
|
+
"setClientPath",
|
|
49
|
+
"openThread",
|
|
50
|
+
"uiGetState",
|
|
51
|
+
"uiDoClick",
|
|
52
|
+
"uiDoSet",
|
|
53
|
+
"uiDoScroll",
|
|
54
|
+
])
|
|
55
|
+
expect(sdk.rpcClientCalls).not.toHaveProperty(name);
|
|
56
|
+
expect(sdk).not.toHaveProperty("UIState");
|
|
57
|
+
expect(sdk.getHostShellContractDefinition().name).toBe("System Host Shell");
|
|
58
|
+
});
|
|
21
59
|
test("old SDK assignments warn once and never produce executables", () => {
|
|
22
60
|
const builder = new builder_1.ContractBuilder((0, utils_1.newid)(), 1, "Old package");
|
|
23
61
|
const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
|
|
@@ -30,6 +30,17 @@ afterEach(() => {
|
|
|
30
30
|
(0, index_1.setRemoteContractTransportFactory)(undefined);
|
|
31
31
|
});
|
|
32
32
|
describe("remote contract endpoint", () => {
|
|
33
|
+
it("selects the current transport for every call on a cached endpoint", async () => {
|
|
34
|
+
const first = makeTransport();
|
|
35
|
+
const second = makeTransport();
|
|
36
|
+
let current = first.transport;
|
|
37
|
+
const endpoint = (0, index_1.createRemoteContractEndpoint)(makeRequest(), () => current);
|
|
38
|
+
await endpoint.handleCall(toolCall, {});
|
|
39
|
+
current = second.transport;
|
|
40
|
+
await endpoint.handleCall(toolCall, {});
|
|
41
|
+
expect(first.emit).toHaveBeenCalledTimes(1);
|
|
42
|
+
expect(second.emit).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
33
44
|
it("strips targetDeviceId and pins the authorized data context before emitting", async () => {
|
|
34
45
|
const { transport, emit } = makeTransport();
|
|
35
46
|
const factory = jest.fn(() => transport);
|
|
@@ -50,13 +61,21 @@ describe("remote contract endpoint", () => {
|
|
|
50
61
|
await expect(endpoint.handleCall(toolCall, {})).rejects.toThrow("disposed");
|
|
51
62
|
expect(emit).not.toHaveBeenCalled();
|
|
52
63
|
});
|
|
53
|
-
it(
|
|
64
|
+
it.each([
|
|
65
|
+
["System App Updates", system_ids_1.appUpdatesContractId],
|
|
66
|
+
["System Packages", system_ids_1.packagesContractId],
|
|
67
|
+
["Device Operations", system_ids_1.deviceOperationsContractId],
|
|
68
|
+
])("refuses to forward a package's call to %s", async (_name, contractId) => {
|
|
54
69
|
const { transport, emit } = makeTransport();
|
|
55
70
|
const packageContext = { callerPackageId: "package-a" };
|
|
56
|
-
expect(() => (0, index_1.createRemoteContractEndpoint)(makeRequest({ contractId
|
|
57
|
-
|
|
58
|
-
await expect(operator.handleCall({ ...toolCall, contractId: system_ids_1.appUpdatesContractId }, {})).resolves.toBe("pong");
|
|
71
|
+
expect(() => (0, index_1.createRemoteContractEndpoint)(makeRequest({ contractId, context: packageContext }), () => transport)).toThrow("packages cannot call system contract");
|
|
72
|
+
// A package endpoint for its own contract cannot be steered at a system contract.
|
|
59
73
|
const packageTool = (0, index_1.createRemoteContractEndpoint)(makeRequest({ context: packageContext }), () => transport);
|
|
74
|
+
await expect(packageTool.handleCall({ ...toolCall, contractId }, packageContext)).rejects.toThrow("packages cannot call system contract");
|
|
75
|
+
expect(emit).not.toHaveBeenCalled();
|
|
76
|
+
// The user (no package) still reaches it, and the package still reaches its own contract.
|
|
77
|
+
const operator = (0, index_1.createRemoteContractEndpoint)(makeRequest({ contractId }), () => transport);
|
|
78
|
+
await expect(operator.handleCall({ ...toolCall, contractId }, {})).resolves.toBe("pong");
|
|
60
79
|
await expect(packageTool.handleCall(toolCall, packageContext)).resolves.toBe("pong");
|
|
61
80
|
expect(emit).toHaveBeenCalledTimes(2);
|
|
62
81
|
});
|
|
@@ -65,34 +84,6 @@ describe("remote contract endpoint", () => {
|
|
|
65
84
|
expect(() => (0, index_1.createRemoteContractEndpoint)(makeRequest({ dataContextId: undefined }), () => makeTransport().transport)).toThrow("data context");
|
|
66
85
|
expect(() => (0, index_1.createRemoteContractEndpoint)(makeRequest({ targetDeviceId: undefined }), () => makeTransport().transport)).toThrow("target device");
|
|
67
86
|
});
|
|
68
|
-
it("refuses package runTool forwarding that could invoke App Updates indirectly", async () => {
|
|
69
|
-
const { transport, emit } = makeTransport();
|
|
70
|
-
const packageContext = { callerPackageId: "package-a" };
|
|
71
|
-
const endpoint = (0, index_1.createRemoteContractEndpoint)(makeRequest({ contractId: system_ids_1.deviceOperationsContractId, context: packageContext }), () => transport);
|
|
72
|
-
for (const toolName of ["checkForUpdates", "installUpdateAndRestart"]) {
|
|
73
|
-
const call = {
|
|
74
|
-
...toolCall,
|
|
75
|
-
contractId: system_ids_1.deviceOperationsContractId,
|
|
76
|
-
name: "runTool",
|
|
77
|
-
args: [
|
|
78
|
-
{
|
|
79
|
-
toolIdOrName: (0, index_1.deriveToolCatalogId)({
|
|
80
|
-
contractId: system_ids_1.appUpdatesContractId,
|
|
81
|
-
version: index_1.APP_UPDATES_CONTRACT_VERSION,
|
|
82
|
-
toolName,
|
|
83
|
-
}),
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
};
|
|
87
|
-
await expect(endpoint.handleCall(call, packageContext)).rejects.toThrow("packages cannot call Device Operations runTool");
|
|
88
|
-
}
|
|
89
|
-
expect(emit).not.toHaveBeenCalled();
|
|
90
|
-
// Read-only tools on the same cached package endpoint still work.
|
|
91
|
-
await expect(endpoint.handleCall({ ...toolCall, contractId: system_ids_1.deviceOperationsContractId, name: "getDeviceStatus" }, packageContext)).resolves.toBe("pong");
|
|
92
|
-
const operator = (0, index_1.createRemoteContractEndpoint)(makeRequest({ contractId: system_ids_1.deviceOperationsContractId }), () => transport);
|
|
93
|
-
await expect(operator.handleCall({ ...toolCall, contractId: system_ids_1.deviceOperationsContractId, name: "runTool" }, {})).resolves.toBe("pong");
|
|
94
|
-
expect(emit).toHaveBeenCalledTimes(2);
|
|
95
|
-
});
|
|
96
87
|
});
|
|
97
88
|
describe("resolveRemoteTargetEndpoint", () => {
|
|
98
89
|
it("returns undefined for local routes and for the local device itself", () => {
|
|
@@ -4,9 +4,12 @@ const system_ids_1 = require("../../system-ids");
|
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const contract_resolvers_1 = require("../contract-resolvers");
|
|
6
6
|
const registry_1 = require("../registry");
|
|
7
|
+
const host_shell_contract_1 = require("../system/host-shell-contract");
|
|
7
8
|
const identity_contract_1 = require("../system/identity-contract");
|
|
9
|
+
const packages_contract_1 = require("../system/packages-contract");
|
|
8
10
|
const system_contracts_1 = require("../system-contracts");
|
|
9
11
|
const tool_catalog_1 = require("../tool-catalog");
|
|
12
|
+
const tool_catalog_service_1 = require("../tool-catalog-service");
|
|
10
13
|
// Populate the catalog with built-in system contracts.
|
|
11
14
|
require("../system");
|
|
12
15
|
describe("system contract catalog", () => {
|
|
@@ -25,6 +28,7 @@ describe("system contract catalog", () => {
|
|
|
25
28
|
system_ids_1.networkControlContractId,
|
|
26
29
|
system_ids_1.toolAccessControlContractId,
|
|
27
30
|
system_ids_1.toolCatalogContractId,
|
|
31
|
+
system_ids_1.packagesContractId,
|
|
28
32
|
]));
|
|
29
33
|
});
|
|
30
34
|
it("registers the Logs live resolver", async () => {
|
|
@@ -165,3 +169,79 @@ test("host and desktop workflow members are present with workflow-only policy",
|
|
|
165
169
|
expect(definition?.tools.find((tool) => tool.name === name)?.requiresWorkflowRun).toBe(true);
|
|
166
170
|
}
|
|
167
171
|
});
|
|
172
|
+
test("System Packages is remote only for Self and never workflow-invocable", () => {
|
|
173
|
+
const registry = new registry_1.ContractRegistry();
|
|
174
|
+
(0, system_contracts_1.installSystemContracts)(registry);
|
|
175
|
+
const definition = registry.resolve(system_ids_1.packagesContractId, 1);
|
|
176
|
+
expect(definition?.name).toBe("System Packages");
|
|
177
|
+
expect(registry.getProviderPackageId(system_ids_1.packagesContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
178
|
+
expect((0, contract_resolvers_1.isLocalOnlyContractId)(system_ids_1.packagesContractId)).toBe(false);
|
|
179
|
+
for (const tool of definition?.tools ?? []) {
|
|
180
|
+
expect(tool.remoteAccessLevel).toBeUndefined();
|
|
181
|
+
expect((0, tool_catalog_service_1.isWorkflowInvocableTarget)({
|
|
182
|
+
contractId: system_ids_1.packagesContractId,
|
|
183
|
+
version: 1,
|
|
184
|
+
toolName: tool.name,
|
|
185
|
+
})).toBe(false);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
test("System Host Shell is remote only for Self and never workflow-invocable", () => {
|
|
189
|
+
const registry = new registry_1.ContractRegistry();
|
|
190
|
+
(0, system_contracts_1.installSystemContracts)(registry);
|
|
191
|
+
const definition = registry.resolve(system_ids_1.hostShellContractId, 1);
|
|
192
|
+
expect(definition?.name).toBe("System Host Shell");
|
|
193
|
+
expect(registry.getProviderPackageId(system_ids_1.hostShellContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
|
|
194
|
+
expect((0, contract_resolvers_1.isLocalOnlyContractId)(system_ids_1.hostShellContractId)).toBe(false);
|
|
195
|
+
expect(definition?.tools.map((tool) => tool.name)).toContain("screenshot");
|
|
196
|
+
for (const tool of definition?.tools ?? []) {
|
|
197
|
+
expect(tool.remoteAccessLevel).toBeUndefined();
|
|
198
|
+
expect((0, tool_catalog_service_1.isWorkflowInvocableTarget)({
|
|
199
|
+
contractId: system_ids_1.hostShellContractId,
|
|
200
|
+
version: 1,
|
|
201
|
+
toolName: tool.name,
|
|
202
|
+
})).toBe(false);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it.each([
|
|
206
|
+
["https://peers.app/docs", { kind: "browser", url: "https://peers.app/docs" }],
|
|
207
|
+
[" http://localhost:3000 ", { kind: "browser", url: "http://localhost:3000" }],
|
|
208
|
+
["mailto:someone@example.com", { kind: "browser", url: "mailto:someone@example.com" }],
|
|
209
|
+
["~/peers/packages", { kind: "path", path: "~/peers/packages" }],
|
|
210
|
+
["/tmp/a b", { kind: "path", path: "/tmp/a b" }],
|
|
211
|
+
["C:\\Users\\me", { kind: "path", path: "C:\\Users\\me" }],
|
|
212
|
+
["relative/dir", { kind: "path", path: "relative/dir" }],
|
|
213
|
+
])("open routes %p", (target, expected) => {
|
|
214
|
+
expect((0, host_shell_contract_1.classifyHostOpenTarget)(target)).toEqual(expected);
|
|
215
|
+
});
|
|
216
|
+
it.each([
|
|
217
|
+
"file:///etc/passwd",
|
|
218
|
+
"javascript:alert(1)",
|
|
219
|
+
"vscode://file/x",
|
|
220
|
+
"smb://host/share",
|
|
221
|
+
"",
|
|
222
|
+
])("open refuses %p", (target) => {
|
|
223
|
+
expect(() => (0, host_shell_contract_1.classifyHostOpenTarget)(target)).toThrow(/open (refuses|requires)/);
|
|
224
|
+
});
|
|
225
|
+
it.each([
|
|
226
|
+
[undefined, ["beta", "stable"]],
|
|
227
|
+
["", ["beta", "stable"]],
|
|
228
|
+
["dev", ["beta", "stable"]],
|
|
229
|
+
["beta", ["stable"]],
|
|
230
|
+
["stable", []],
|
|
231
|
+
["nightly", []],
|
|
232
|
+
])("promotion targets for %p are %p", (tag, targets) => {
|
|
233
|
+
expect((0, packages_contract_1.packageVersionPromotionTargets)(tag)).toEqual(targets);
|
|
234
|
+
});
|
|
235
|
+
it.each([
|
|
236
|
+
["https://github.com/peers-app/x.git", true],
|
|
237
|
+
["http://host/x", true],
|
|
238
|
+
["git@github.com:peers-app/x", true],
|
|
239
|
+
[" git@github.com:peers-app/x.git", true],
|
|
240
|
+
["git@notes", false],
|
|
241
|
+
["../x.git", false],
|
|
242
|
+
["/abs/x.git", false],
|
|
243
|
+
["My Package", false],
|
|
244
|
+
["00mvdhk66tdqrj3hy0bacmj32", false],
|
|
245
|
+
])("routes %p to clonePackage: %p", (target, expected) => {
|
|
246
|
+
expect((0, packages_contract_1.isPackageRepositoryUrl)(target)).toBe(expected);
|
|
247
|
+
});
|
|
@@ -100,7 +100,7 @@ export declare function isLocalOnlyContractId(contractId: string): boolean;
|
|
|
100
100
|
* 2. The caller's keys must match a valid signed Users record known to the provider.
|
|
101
101
|
* 3. A Self-trusted caller (the provider's own account, or an account granted exact
|
|
102
102
|
* `TrustLevel.Self`) keeps full access; an omitted data context selects the provider's
|
|
103
|
-
* personal context.
|
|
103
|
+
* personal context, and a named one must be a group this device already has.
|
|
104
104
|
* 4. Any other verified caller is treated as a **group member**: the call must name a
|
|
105
105
|
* group the provider belongs to, must be a tool run, the contract definition registered
|
|
106
106
|
* for that group must mark the tool with {@link IContractTool.remoteAccessLevel}, and
|
|
@@ -239,7 +239,7 @@ function isLocalOnlyContractId(contractId) {
|
|
|
239
239
|
* 2. The caller's keys must match a valid signed Users record known to the provider.
|
|
240
240
|
* 3. A Self-trusted caller (the provider's own account, or an account granted exact
|
|
241
241
|
* `TrustLevel.Self`) keeps full access; an omitted data context selects the provider's
|
|
242
|
-
* personal context.
|
|
242
|
+
* personal context, and a named one must be a group this device already has.
|
|
243
243
|
* 4. Any other verified caller is treated as a **group member**: the call must name a
|
|
244
244
|
* group the provider belongs to, must be a tool run, the contract definition registered
|
|
245
245
|
* for that group must mark the tool with {@link IContractTool.remoteAccessLevel}, and
|
|
@@ -269,7 +269,8 @@ async function authorizeRemoteContractCall(call, userContext, remoteIdentity) {
|
|
|
269
269
|
callerUserId: remoteIdentity.userId,
|
|
270
270
|
localUserId: userContext.userId,
|
|
271
271
|
callerDeviceId: remoteIdentity.deviceId,
|
|
272
|
-
|
|
272
|
+
// Never create a context the caller names: only this device's own groups.
|
|
273
|
+
dataContext: getAuthorizedLocalDataContext(userContext, dataContextId),
|
|
273
274
|
},
|
|
274
275
|
};
|
|
275
276
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IContractConsumer, type IContractResolution } from "./contract-proxy";
|
|
1
|
+
import { type IContractCallContext, type IContractConsumer, type IContractResolution } from "./contract-proxy";
|
|
2
2
|
import type { IContractDefinition } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Build a same-process contract consumer wired to a live provider resolution.
|
|
@@ -9,4 +9,5 @@ import type { IContractDefinition } from "./types";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function createLocalContractConsumer(definition: IContractDefinition, resolution: IContractResolution, opts?: {
|
|
11
11
|
dataContextId?: string;
|
|
12
|
+
context?: IContractCallContext;
|
|
12
13
|
}): IContractConsumer;
|
|
@@ -12,7 +12,7 @@ const contract_transport_1 = require("./contract-transport");
|
|
|
12
12
|
*/
|
|
13
13
|
function createLocalContractConsumer(definition, resolution, opts) {
|
|
14
14
|
const [consumerTransport, providerTransport] = (0, contract_transport_1.inProcessTransportPair)();
|
|
15
|
-
(0, contract_proxy_1.createContractProviderSession)(resolution, providerTransport);
|
|
15
|
+
(0, contract_proxy_1.createContractProviderSession)(resolution, providerTransport, { context: opts?.context });
|
|
16
16
|
return (0, contract_proxy_1.createContractConsumer)(definition, consumerTransport, {
|
|
17
17
|
dataContextId: opts?.dataContextId,
|
|
18
18
|
});
|
|
@@ -17,6 +17,7 @@ export * from "./system/app-updates-contract";
|
|
|
17
17
|
export { CONTRACT_CATALOG_VERSION, contractCatalogProviderPackageId, getContractCatalogDefinition, type IContractCatalogConsumer, type IContractProviderInfo, type IListContractProvidersInput, } from "./system/contract-catalog-contract";
|
|
18
18
|
export * from "./system/desktop-tools-contract";
|
|
19
19
|
export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./system/device-operations-contract";
|
|
20
|
+
export * from "./system/host-shell-contract";
|
|
20
21
|
export * from "./system/host-tools-contract";
|
|
21
22
|
export { getHttpContractDefinition, HTTP_CONTRACT_VERSION, HTTP_DEFAULT_TIMEOUT_MS, HTTP_MAX_BODY_BYTES, HTTP_MAX_TIMEOUT_MS, httpProviderPackageId, type IHttpCancelInput, type IHttpConsumer, type IHttpCredentialInfo, type IHttpMultipartPart, type IHttpPolicyInfo, type IHttpRequestInput, type IHttpRequestResult, } from "./system/http-contract";
|
|
22
23
|
export { getIdentityContractDefinition, IDENTITY_CONTRACT_VERSION, type IIdentityConsumer, type IIdentityKeyInfo, type IIdentityRegistryKey, type IIdentityRotateKeyInput, type IIdentityRotateKeyResult, type IIdentitySessionInfo, } from "./system/identity-contract";
|
|
@@ -24,6 +25,7 @@ export { getInvitesContractDefinition, type IAcceptInviteResult, type ICreatedIn
|
|
|
24
25
|
export { getLocalFoldersContractDefinition, type ILocalFolderAssembleInput, type ILocalFolderAssembleResult, type ILocalFolderChange, type ILocalFolderChangedEvent, type ILocalFolderEntry, type ILocalFolderFileBlocks, type ILocalFolderFileBlocksInput, type ILocalFolderGrantInfo, type ILocalFolderIndexInput, type ILocalFolderIndexResult, type ILocalFolderJobFinishedEvent, type ILocalFolderJobHandle, type ILocalFolderJobInfo, type ILocalFolderJobProgressEvent, type ILocalFolderListInput, type ILocalFolderManifestBlock, type ILocalFolderMoveInput, type ILocalFolderPathInput, type ILocalFolderPurgeTrashInput, type ILocalFolderReadRangeInput, type ILocalFolderReadRangeResult, type ILocalFolderRequestGrantInput, type ILocalFolderStatInput, LOCAL_FOLDERS_ASSEMBLE_STALL_MS, LOCAL_FOLDERS_CHUNK_AVG, LOCAL_FOLDERS_CHUNK_MAX, LOCAL_FOLDERS_CHUNK_MIN, LOCAL_FOLDERS_CONTRACT_VERSION, LOCAL_FOLDERS_GETBLOCKS_MAX_BLOCKS, LOCAL_FOLDERS_GETBLOCKS_MAX_BYTES, LOCAL_FOLDERS_HAVE_BATCH, LOCAL_FOLDERS_JOB_TTL_MS, LOCAL_FOLDERS_READ_RANGE_MAX, LOCAL_FOLDERS_RESERVED_DIR, type LocalFolderChangeKind, type LocalFolderEntryKind, type LocalFolderJobKind, type LocalFolderMode, localFoldersProviderPackageId, } from "./system/local-folders-contract";
|
|
25
26
|
export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
|
|
26
27
|
export * from "./system/network-contracts";
|
|
28
|
+
export * from "./system/packages-contract";
|
|
27
29
|
export { getSchedulerContractDefinition, type IScheduleInfo, type IScheduleInput, type ISchedulerConsumer, SCHEDULER_CONTRACT_VERSION, SCHEDULER_MAX_DELAY_MS, schedulerProviderPackageId, } from "./system/scheduler-contract";
|
|
28
30
|
export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
|
|
29
31
|
export * from "./system/tool-catalog-contract";
|
package/dist/contracts/index.js
CHANGED
|
@@ -98,6 +98,7 @@ Object.defineProperty(exports, "DEVICE_OPERATIONS_MAX_TOOLS", { enumerable: true
|
|
|
98
98
|
Object.defineProperty(exports, "deviceOperationsProviderPackageId", { enumerable: true, get: function () { return device_operations_contract_1.deviceOperationsProviderPackageId; } });
|
|
99
99
|
Object.defineProperty(exports, "deviceOperationsToolIds", { enumerable: true, get: function () { return device_operations_contract_1.deviceOperationsToolIds; } });
|
|
100
100
|
Object.defineProperty(exports, "getDeviceOperationsContractDefinition", { enumerable: true, get: function () { return device_operations_contract_1.getDeviceOperationsContractDefinition; } });
|
|
101
|
+
__exportStar(require("./system/host-shell-contract"), exports);
|
|
101
102
|
__exportStar(require("./system/host-tools-contract"), exports);
|
|
102
103
|
var http_contract_1 = require("./system/http-contract");
|
|
103
104
|
Object.defineProperty(exports, "getHttpContractDefinition", { enumerable: true, get: function () { return http_contract_1.getHttpContractDefinition; } });
|
|
@@ -131,6 +132,7 @@ Object.defineProperty(exports, "getLogsContractDefinition", { enumerable: true,
|
|
|
131
132
|
Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get: function () { return logs_contract_1.LOGS_CONTRACT_VERSION; } });
|
|
132
133
|
Object.defineProperty(exports, "resolveLogsContract", { enumerable: true, get: function () { return logs_contract_1.resolveLogsContract; } });
|
|
133
134
|
__exportStar(require("./system/network-contracts"), exports);
|
|
135
|
+
__exportStar(require("./system/packages-contract"), exports);
|
|
134
136
|
var scheduler_contract_1 = require("./system/scheduler-contract");
|
|
135
137
|
Object.defineProperty(exports, "getSchedulerContractDefinition", { enumerable: true, get: function () { return scheduler_contract_1.getSchedulerContractDefinition; } });
|
|
136
138
|
Object.defineProperty(exports, "SCHEDULER_CONTRACT_VERSION", { enumerable: true, get: function () { return scheduler_contract_1.SCHEDULER_CONTRACT_VERSION; } });
|
|
@@ -61,12 +61,12 @@ export declare function resolveRemoteTargetEndpoint(request: IContractProviderEn
|
|
|
61
61
|
* emitting so the receiving device sees an ordinary call addressed to itself and the
|
|
62
62
|
* message is signed with this device's own identity. Only tool runs are forwarded; the
|
|
63
63
|
* router rejects other members before reaching this endpoint. Package callers cannot
|
|
64
|
-
* reach contracts
|
|
65
|
-
*
|
|
64
|
+
* reach system contracts on another device (System Packages, App Updates, Device
|
|
65
|
+
* Operations, …): the target would see only this device, acting for the user.
|
|
66
66
|
*
|
|
67
67
|
* @param request Authorized route whose `targetDeviceId` and `dataContextId` are set.
|
|
68
68
|
* @param factory Transport factory; defaults to the process-wide registration.
|
|
69
69
|
* @throws If no factory is available, the route lacks a target device or data context, or
|
|
70
|
-
* a package targets a contract
|
|
70
|
+
* a package targets a system contract.
|
|
71
71
|
*/
|
|
72
72
|
export declare function createRemoteContractEndpoint(request: IContractProviderEndpointRequest, factory?: RemoteContractTransportFactory | undefined): IContractProviderEndpoint;
|
|
@@ -4,14 +4,18 @@ exports.setRemoteContractTransportFactory = setRemoteContractTransportFactory;
|
|
|
4
4
|
exports.getRemoteContractTransportFactory = getRemoteContractTransportFactory;
|
|
5
5
|
exports.resolveRemoteTargetEndpoint = resolveRemoteTargetEndpoint;
|
|
6
6
|
exports.createRemoteContractEndpoint = createRemoteContractEndpoint;
|
|
7
|
-
const system_ids_1 = require("../system-ids");
|
|
8
7
|
const contract_transport_1 = require("./contract-transport");
|
|
8
|
+
const system_contracts_1 = require("./system-contracts");
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* signed by this device alone, so the target cannot
|
|
12
|
-
*
|
|
10
|
+
* Whether this host refuses to forward a package's call to another device. A forwarded
|
|
11
|
+
* call reaches the target signed by this device alone, as the user, so the target cannot
|
|
12
|
+
* tell that a package made it and would grant it the user's full Self authority. Packages
|
|
13
|
+
* may still call their own (and other packages') contracts there; system contracts, whose
|
|
14
|
+
* tools administer the device, are refused here instead.
|
|
13
15
|
*/
|
|
14
|
-
|
|
16
|
+
function refusesForwardedPackageCall(context, contractId) {
|
|
17
|
+
return !!context?.callerPackageId && (0, system_contracts_1.isSystemContractId)(contractId);
|
|
18
|
+
}
|
|
15
19
|
const factoriesByUserContext = new WeakMap();
|
|
16
20
|
let defaultFactory;
|
|
17
21
|
/**
|
|
@@ -66,21 +70,21 @@ function resolveRemoteTargetEndpoint(request, host) {
|
|
|
66
70
|
* emitting so the receiving device sees an ordinary call addressed to itself and the
|
|
67
71
|
* message is signed with this device's own identity. Only tool runs are forwarded; the
|
|
68
72
|
* router rejects other members before reaching this endpoint. Package callers cannot
|
|
69
|
-
* reach contracts
|
|
70
|
-
*
|
|
73
|
+
* reach system contracts on another device (System Packages, App Updates, Device
|
|
74
|
+
* Operations, …): the target would see only this device, acting for the user.
|
|
71
75
|
*
|
|
72
76
|
* @param request Authorized route whose `targetDeviceId` and `dataContextId` are set.
|
|
73
77
|
* @param factory Transport factory; defaults to the process-wide registration.
|
|
74
78
|
* @throws If no factory is available, the route lacks a target device or data context, or
|
|
75
|
-
* a package targets a contract
|
|
79
|
+
* a package targets a system contract.
|
|
76
80
|
*/
|
|
77
81
|
function createRemoteContractEndpoint(request, factory = getRemoteContractTransportFactory()) {
|
|
78
82
|
const { targetDeviceId, dataContextId } = request;
|
|
79
83
|
if (!targetDeviceId) {
|
|
80
84
|
throw new Error("Remote contract endpoint requires a target device id");
|
|
81
85
|
}
|
|
82
|
-
if (request.context
|
|
83
|
-
throw new Error(`Permission denied: packages cannot call contract ${request.contractId} on another device`);
|
|
86
|
+
if (refusesForwardedPackageCall(request.context, request.contractId)) {
|
|
87
|
+
throw new Error(`Permission denied: packages cannot call system contract ${request.contractId} on another device`);
|
|
84
88
|
}
|
|
85
89
|
if (!dataContextId) {
|
|
86
90
|
throw new Error("Remote contract endpoint requires an authorized data context id");
|
|
@@ -88,7 +92,6 @@ function createRemoteContractEndpoint(request, factory = getRemoteContractTransp
|
|
|
88
92
|
if (!factory) {
|
|
89
93
|
throw new Error(`Cannot reach device ${targetDeviceId}: no remote contract transport is registered on this host`);
|
|
90
94
|
}
|
|
91
|
-
const transport = factory({ deviceId: targetDeviceId, dataContextId });
|
|
92
95
|
let disposed = false;
|
|
93
96
|
return {
|
|
94
97
|
handleCall(call) {
|
|
@@ -98,14 +101,13 @@ function createRemoteContractEndpoint(request, factory = getRemoteContractTransp
|
|
|
98
101
|
if (call.member !== "tool" || call.operation !== "run") {
|
|
99
102
|
return Promise.reject(new Error(`Contract ${call.member} '${call.name}'.${call.operation} cannot be forwarded to a remote device`));
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (request.context.callerPackageId &&
|
|
104
|
-
request.contractId === system_ids_1.deviceOperationsContractId &&
|
|
105
|
-
call.name === "runTool") {
|
|
106
|
-
return Promise.reject(new Error("Permission denied: packages cannot call Device Operations runTool on another device"));
|
|
104
|
+
if (refusesForwardedPackageCall(request.context, call.contractId)) {
|
|
105
|
+
return Promise.reject(new Error(`Permission denied: packages cannot call system contract ${call.contractId} on another device`));
|
|
107
106
|
}
|
|
108
107
|
const { targetDeviceId: _stripped, ...outbound } = call;
|
|
108
|
+
// Endpoints are cached longer than connections. Select the current route for each
|
|
109
|
+
// call, without replaying a call whose previous transport may have executed it.
|
|
110
|
+
const transport = factory({ deviceId: targetDeviceId, dataContextId });
|
|
109
111
|
return transport.emit(contract_transport_1.CONTRACT_REQUEST_CHANNEL, { ...outbound, dataContextId });
|
|
110
112
|
},
|
|
111
113
|
dispose() {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { IContractConsumerBase } from "../contract-proxy";
|
|
2
|
+
import type { IContractDefinition } from "../types";
|
|
3
|
+
/** Version of the built-in System Host Shell contract. */
|
|
4
|
+
export declare const HOST_SHELL_CONTRACT_VERSION = 1;
|
|
5
|
+
/** What the host behind a System Host Shell provider can do. Unsupported tools throw. */
|
|
6
|
+
export interface IHostShellCapabilities {
|
|
7
|
+
/** The host has a UI window it can inspect, click, type into, scroll, and reload. */
|
|
8
|
+
ui: boolean;
|
|
9
|
+
/** The host can capture its UI window as an image. */
|
|
10
|
+
screenshot: boolean;
|
|
11
|
+
/** The host can quit and restart itself. */
|
|
12
|
+
appLifecycle: boolean;
|
|
13
|
+
/** The host can open web links and local paths (`open`). */
|
|
14
|
+
open: boolean;
|
|
15
|
+
/** The host can open a folder in a code editor (`openInEditor`). */
|
|
16
|
+
editor: boolean;
|
|
17
|
+
/** The host can copy a variable's value to the system clipboard. */
|
|
18
|
+
clipboard: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Serializable description of one interactive UI element. */
|
|
21
|
+
export interface IUIElement {
|
|
22
|
+
/** Lower-case tag name. */
|
|
23
|
+
tag: string;
|
|
24
|
+
/** Visible text or accessible label. */
|
|
25
|
+
text?: string;
|
|
26
|
+
/** CSS selector that finds this element again (for `clickUI`, `setUIText`, `scrollUI`). */
|
|
27
|
+
selector: string;
|
|
28
|
+
/** Input type, for inputs. */
|
|
29
|
+
type?: string;
|
|
30
|
+
/** Current value, for inputs (never for passwords). */
|
|
31
|
+
value?: string;
|
|
32
|
+
/** Placeholder, for inputs. */
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
/** Target, for links. */
|
|
35
|
+
href?: string;
|
|
36
|
+
/** Whether the element is disabled. */
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
/** Position and size in CSS pixels, when requested. */
|
|
39
|
+
rect?: {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Snapshot of the UI window: location, visible text, and interactive elements. */
|
|
47
|
+
export interface IUIState {
|
|
48
|
+
/** Page URL. */
|
|
49
|
+
url: string;
|
|
50
|
+
/** Document title. */
|
|
51
|
+
title: string;
|
|
52
|
+
/** Current in-app route, when known. */
|
|
53
|
+
route?: string;
|
|
54
|
+
/** Visible text content. */
|
|
55
|
+
text: string;
|
|
56
|
+
/** Interactive elements. */
|
|
57
|
+
elements: IUIElement[];
|
|
58
|
+
}
|
|
59
|
+
/** Outcome of a UI action (`clickUI`, `setUIText`, `scrollUI`). */
|
|
60
|
+
export interface IUIActionResult {
|
|
61
|
+
/** Whether the action was performed. */
|
|
62
|
+
success: boolean;
|
|
63
|
+
/** Why it was not, e.g. no element matched the selector. */
|
|
64
|
+
error?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Direction for `scrollUI`. */
|
|
67
|
+
export type UIScrollDirection = "up" | "down" | "left" | "right";
|
|
68
|
+
/** Input for `scrollUI`: scroll an element (or the page) by a direction or offsets. */
|
|
69
|
+
export interface IUIScrollInput {
|
|
70
|
+
/** Element to scroll; the page when omitted. */
|
|
71
|
+
selector?: string;
|
|
72
|
+
/** Horizontal offset in pixels. */
|
|
73
|
+
x?: number;
|
|
74
|
+
/** Vertical offset in pixels. */
|
|
75
|
+
y?: number;
|
|
76
|
+
/** Direction to scroll by {@link amount}, instead of offsets. */
|
|
77
|
+
direction?: UIScrollDirection;
|
|
78
|
+
/** Pixels to scroll in {@link direction} (default 300). */
|
|
79
|
+
amount?: number;
|
|
80
|
+
}
|
|
81
|
+
/** Image format for `screenshot`. */
|
|
82
|
+
export type ScreenshotFormat = "png" | "jpeg";
|
|
83
|
+
/** A captured image of the UI window. */
|
|
84
|
+
export interface IHostScreenshot {
|
|
85
|
+
/** `image/png` or `image/jpeg`. */
|
|
86
|
+
mimeType: string;
|
|
87
|
+
/** Width in pixels. */
|
|
88
|
+
width: number;
|
|
89
|
+
/** Height in pixels. */
|
|
90
|
+
height: number;
|
|
91
|
+
/** Base64-encoded image bytes. */
|
|
92
|
+
data: string;
|
|
93
|
+
}
|
|
94
|
+
/** How `open` handled its target. */
|
|
95
|
+
export type HostOpenKind = "browser" | "path";
|
|
96
|
+
/** Typed consumer proxy for System Host Shell v1. */
|
|
97
|
+
export interface IHostShellConsumer extends IContractConsumerBase {
|
|
98
|
+
tools: {
|
|
99
|
+
/** Report what this host supports. */
|
|
100
|
+
getCapabilities(): Promise<{
|
|
101
|
+
capabilities: IHostShellCapabilities;
|
|
102
|
+
}>;
|
|
103
|
+
/** Return the UI window's location, visible text, and interactive elements. */
|
|
104
|
+
inspectUI(input?: {
|
|
105
|
+
includeRects?: boolean;
|
|
106
|
+
}): Promise<IUIState>;
|
|
107
|
+
/** Click the element matching a CSS selector. */
|
|
108
|
+
clickUI(input: {
|
|
109
|
+
selector: string;
|
|
110
|
+
}): Promise<IUIActionResult>;
|
|
111
|
+
/** Type text into the input matching a CSS selector. */
|
|
112
|
+
setUIText(input: {
|
|
113
|
+
selector: string;
|
|
114
|
+
text: string;
|
|
115
|
+
clear?: boolean;
|
|
116
|
+
pressEnter?: boolean;
|
|
117
|
+
}): Promise<IUIActionResult>;
|
|
118
|
+
/** Scroll an element or the page. */
|
|
119
|
+
scrollUI(input: IUIScrollInput): Promise<IUIActionResult>;
|
|
120
|
+
/** Reload the UI window. */
|
|
121
|
+
reloadUI(input?: {
|
|
122
|
+
ignoreCache?: boolean;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
reloaded: boolean;
|
|
125
|
+
}>;
|
|
126
|
+
/** Capture the UI window as an image. */
|
|
127
|
+
screenshot(input?: {
|
|
128
|
+
format?: ScreenshotFormat;
|
|
129
|
+
maxWidth?: number;
|
|
130
|
+
}): Promise<IHostScreenshot>;
|
|
131
|
+
/** Quit the app. */
|
|
132
|
+
quitApp(): Promise<{
|
|
133
|
+
quitting: boolean;
|
|
134
|
+
}>;
|
|
135
|
+
/** Restart the app. */
|
|
136
|
+
restartApp(): Promise<{
|
|
137
|
+
restarting: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
/** Open a web link in the browser, or a local file or folder with the OS. */
|
|
140
|
+
open(input: {
|
|
141
|
+
target: string;
|
|
142
|
+
}): Promise<{
|
|
143
|
+
opened: HostOpenKind;
|
|
144
|
+
}>;
|
|
145
|
+
/** Open a folder in a code editor, falling back to the OS. */
|
|
146
|
+
openInEditor(input: {
|
|
147
|
+
path: string;
|
|
148
|
+
}): Promise<{
|
|
149
|
+
editor: string;
|
|
150
|
+
}>;
|
|
151
|
+
/** Copy a persistent variable's value to the system clipboard. */
|
|
152
|
+
copyVariableToClipboard(input: {
|
|
153
|
+
persistentVarId: string;
|
|
154
|
+
groupId?: string;
|
|
155
|
+
}): Promise<{
|
|
156
|
+
copied: boolean;
|
|
157
|
+
clearsAfterMs?: number;
|
|
158
|
+
}>;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** URL schemes `open` hands to the browser or mail client; any other scheme is refused. */
|
|
162
|
+
export declare const HOST_OPEN_URL_SCHEMES: readonly string[];
|
|
163
|
+
/** Where `open` sends a target: a URL for the browser, or a local path for the OS. */
|
|
164
|
+
export type HostOpenTarget = {
|
|
165
|
+
kind: "browser";
|
|
166
|
+
url: string;
|
|
167
|
+
} | {
|
|
168
|
+
kind: "path";
|
|
169
|
+
path: string;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Decide how `open` handles a target. `http`, `https`, and `mailto` links go to the browser;
|
|
173
|
+
* any other URL scheme (`file:`, `javascript:`, custom app schemes, …) is refused; everything
|
|
174
|
+
* else is a local path (`~` is expanded by the host). Windows drive paths are paths.
|
|
175
|
+
*
|
|
176
|
+
* @param target - What the caller asked to open.
|
|
177
|
+
* @throws When the target is empty or uses a scheme that is not allowed.
|
|
178
|
+
*/
|
|
179
|
+
export declare function classifyHostOpenTarget(target: string): HostOpenTarget;
|
|
180
|
+
/**
|
|
181
|
+
* Return the System Host Shell contract shape.
|
|
182
|
+
*
|
|
183
|
+
* Tools carry no `remoteAccessLevel`, so only callers this device trusts as `Self` (the
|
|
184
|
+
* user's own devices and users explicitly granted Self) can reach them remotely; group
|
|
185
|
+
* members, Admins included, are refused. Package code and workflows are refused everywhere.
|
|
186
|
+
* `quitApp` and `restartApp` from another device also need the device's "Allow my other
|
|
187
|
+
* devices to update and restart this device" setting. Tools the host cannot perform throw
|
|
188
|
+
* `System Host Shell <tool> is not supported on this host (...)`; `getCapabilities` reports
|
|
189
|
+
* support up front.
|
|
190
|
+
*/
|
|
191
|
+
export declare function getHostShellContractDefinition(): IContractDefinition;
|