@ai-switch/tauri-plugin-runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -0
  3. package/THIRD_PARTY_NOTICES.md +210 -0
  4. package/dist/bridge/handshake.d.ts +11 -0
  5. package/dist/bridge/json-codec.d.ts +4 -0
  6. package/dist/bridge/rpc-peer.d.ts +7 -0
  7. package/dist/bridge/rpc-types.d.ts +20 -0
  8. package/dist/chunk-CFQSUF5W.js +1794 -0
  9. package/dist/chunk-JSBRDJBE.js +30 -0
  10. package/dist/chunk-K5YWIJN6.js +389 -0
  11. package/dist/chunk-PW7XHHWG.js +403 -0
  12. package/dist/chunk-RKQEK7DY.js +13879 -0
  13. package/dist/chunk-WT62GTMC.js +18 -0
  14. package/dist/chunk-YI6NQ6WR.js +513 -0
  15. package/dist/host/event-router.d.ts +19 -0
  16. package/dist/host/file-handles.d.ts +15 -0
  17. package/dist/host/host.d.ts +2 -0
  18. package/dist/host/index.d.ts +4 -0
  19. package/dist/host/index.js +791 -0
  20. package/dist/host/policy.d.ts +7 -0
  21. package/dist/host/requests.d.ts +13 -0
  22. package/dist/host/types.d.ts +19 -0
  23. package/dist/host/view.d.ts +18 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +9 -0
  26. package/dist/node/buffer.d.ts +7 -0
  27. package/dist/node/buffer.js +8 -0
  28. package/dist/node/events.d.ts +12 -0
  29. package/dist/node/events.js +458 -0
  30. package/dist/node/fs/backend.d.ts +8 -0
  31. package/dist/node/fs/callback-types.d.ts +43 -0
  32. package/dist/node/fs/callbacks.d.ts +3 -0
  33. package/dist/node/fs/client.d.ts +3 -0
  34. package/dist/node/fs/errors.d.ts +5 -0
  35. package/dist/node/fs/lifecycle.d.ts +11 -0
  36. package/dist/node/fs/options.d.ts +7 -0
  37. package/dist/node/fs/promises.d.ts +33 -0
  38. package/dist/node/fs/promises.js +30 -0
  39. package/dist/node/fs/transfer.d.ts +7 -0
  40. package/dist/node/fs/types.d.ts +86 -0
  41. package/dist/node/fs.d.ts +36 -0
  42. package/dist/node/fs.js +92 -0
  43. package/dist/node/path.d.ts +20 -0
  44. package/dist/node/path.js +434 -0
  45. package/dist/plugin/client.d.ts +4 -0
  46. package/dist/plugin/connection.d.ts +6 -0
  47. package/dist/plugin/errors.d.ts +3 -0
  48. package/dist/plugin/index.d.ts +6 -0
  49. package/dist/plugin/index.js +12 -0
  50. package/dist/plugin/types.d.ts +28 -0
  51. package/dist/protocol/capabilities.d.ts +8 -0
  52. package/dist/protocol/errors.d.ts +14 -0
  53. package/dist/protocol/generated/capabilities.generated.d.ts +56 -0
  54. package/dist/protocol/generated/fs.generated.d.ts +178 -0
  55. package/dist/protocol/generated/host-event.generated.d.ts +17 -0
  56. package/dist/protocol/generated/manifest.generated.d.ts +64 -0
  57. package/dist/protocol/generated/session.generated.d.ts +93 -0
  58. package/dist/protocol/generated/types.generated.d.ts +6 -0
  59. package/dist/protocol/generated/validators.generated.d.mts +24 -0
  60. package/dist/protocol/generated/wire.generated.d.ts +63 -0
  61. package/dist/protocol/index.d.ts +18 -0
  62. package/dist/protocol/index.js +746 -0
  63. package/dist/protocol/json-safety.d.ts +3 -0
  64. package/dist/protocol/limits.d.ts +11 -0
  65. package/dist/protocol/manifest.d.ts +5 -0
  66. package/dist/protocol/path-policy.d.ts +2 -0
  67. package/dist/protocol/schema/capabilities.schema.json +301 -0
  68. package/dist/protocol/schema/fs.schema.json +900 -0
  69. package/dist/protocol/schema/host-event.schema.json +116 -0
  70. package/dist/protocol/schema/manifest.schema.json +60 -0
  71. package/dist/protocol/schema/session.schema.json +145 -0
  72. package/dist/protocol/schema/wire.schema.json +358 -0
  73. package/dist/protocol/session.d.ts +3 -0
  74. package/dist/protocol/types.d.ts +27 -0
  75. package/dist/protocol/validation.d.ts +2 -0
  76. package/dist/protocol/wire.d.ts +16 -0
  77. package/package.json +95 -0
@@ -0,0 +1,403 @@
1
+ import {
2
+ AplgError,
3
+ errorCodes,
4
+ satisfiesApiRange,
5
+ standardCapabilities,
6
+ toErrorPayload,
7
+ validateJson,
8
+ validateSessionDescriptor,
9
+ validateWireMessage
10
+ } from "./chunk-RKQEK7DY.js";
11
+ import {
12
+ apiVersion,
13
+ limits,
14
+ protocolVersion
15
+ } from "./chunk-WT62GTMC.js";
16
+
17
+ // src/plugin/errors.ts
18
+ function fromErrorPayload(payload) {
19
+ if (errorCodes.includes(payload.code)) {
20
+ return new AplgError(payload.code, payload.message, payload.details);
21
+ }
22
+ return Object.assign(new Error(payload.message), {
23
+ name: "RemoteCapabilityError",
24
+ code: payload.code,
25
+ ...payload.details === void 0 ? {} : { details: payload.details }
26
+ });
27
+ }
28
+
29
+ // src/bridge/json-codec.ts
30
+ var encoder = new TextEncoder();
31
+ function checkedByteLimit(value) {
32
+ if (!Number.isSafeInteger(value) || value < 1 || value > limits.controlBytes) {
33
+ throw new AplgError("E_INVALID_ARGUMENT", "Invalid RPC message size limit.");
34
+ }
35
+ return value;
36
+ }
37
+ function encodeWireMessage(value, maxBytes = limits.controlBytes) {
38
+ checkedByteLimit(maxBytes);
39
+ const safe = validateJson(value, maxBytes);
40
+ if (!safe.ok) {
41
+ const sizeExceeded = safe.diagnostics.some((diagnostic) => diagnostic.message.includes("size"));
42
+ throw new AplgError(sizeExceeded ? "E_LIMIT_EXCEEDED" : "E_INVALID_MESSAGE", "The outgoing RPC message is invalid.");
43
+ }
44
+ if (!validateWireMessage(value).ok) throw new AplgError("E_INVALID_MESSAGE", "The outgoing RPC message does not match the protocol.");
45
+ return JSON.stringify(value);
46
+ }
47
+ function decodeWireMessage(frame, maxBytes = limits.controlBytes) {
48
+ checkedByteLimit(maxBytes);
49
+ if (typeof frame !== "string") throw new AplgError("E_INVALID_MESSAGE", "RPC frames must be JSON strings.");
50
+ if (frame.length > maxBytes || encoder.encode(frame).byteLength > maxBytes) {
51
+ throw new AplgError("E_LIMIT_EXCEEDED", "The RPC frame exceeds the negotiated byte limit.");
52
+ }
53
+ let value;
54
+ try {
55
+ value = JSON.parse(frame);
56
+ } catch {
57
+ throw new AplgError("E_INVALID_MESSAGE", "The RPC frame is not valid JSON.");
58
+ }
59
+ const result = validateWireMessage(value);
60
+ if (!result.ok) throw new AplgError("E_INVALID_MESSAGE", "The RPC frame does not match the protocol.");
61
+ return result.value;
62
+ }
63
+
64
+ // src/bridge/rpc-peer.ts
65
+ function validTimeout(value) {
66
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647) {
67
+ throw new AplgError("E_INVALID_ARGUMENT", "RPC timeout must be a positive timer-safe integer.");
68
+ }
69
+ return value;
70
+ }
71
+ function requestId() {
72
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
73
+ bytes[6] = bytes[6] & 15 | 64;
74
+ bytes[8] = bytes[8] & 63 | 128;
75
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
76
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
77
+ }
78
+ function createRpcPeer(port, options = {}) {
79
+ const timeoutMs = validTimeout(options.timeoutMs ?? 3e4);
80
+ const maxInflight = options.maxInflight ?? 64;
81
+ const maxBytes = checkedByteLimit(options.maxBytes ?? limits.controlBytes);
82
+ if (!Number.isSafeInteger(maxInflight) || maxInflight < 1 || maxInflight > 64) {
83
+ throw new AplgError("E_INVALID_ARGUMENT", "RPC concurrency must be between 1 and 64.");
84
+ }
85
+ const pending = /* @__PURE__ */ new Map();
86
+ const incoming = /* @__PURE__ */ new Map();
87
+ const recentIds = /* @__PURE__ */ new Set();
88
+ const events = /* @__PURE__ */ new Set();
89
+ let handler;
90
+ let closed;
91
+ function settle(id, action) {
92
+ const entry = pending.get(id);
93
+ if (!entry) return;
94
+ pending.delete(id);
95
+ clearTimeout(entry.timer);
96
+ entry.removeAbortListener();
97
+ action(entry);
98
+ }
99
+ function emit(event) {
100
+ for (const callback of [...events]) {
101
+ try {
102
+ callback(event);
103
+ } catch {
104
+ }
105
+ }
106
+ }
107
+ function terminate(reason, notify) {
108
+ if (closed) return;
109
+ closed = reason;
110
+ if (notify) {
111
+ try {
112
+ port.postMessage(encodeWireMessage({ protocol: protocolVersion, kind: "connection", state: "closed" }, maxBytes));
113
+ } catch {
114
+ }
115
+ }
116
+ port.removeEventListener("message", receive);
117
+ port.removeEventListener("messageerror", messageError);
118
+ port.removeEventListener("close", portClosed);
119
+ for (const id of pending.keys()) settle(id, (entry) => entry.reject(reason));
120
+ for (const controller of incoming.values()) controller.abort(reason);
121
+ incoming.clear();
122
+ recentIds.clear();
123
+ handler = void 0;
124
+ emit({ protocol: protocolVersion, kind: "connection", state: "closed" });
125
+ events.clear();
126
+ port.close();
127
+ }
128
+ function post(message) {
129
+ if (closed) throw closed;
130
+ const frame = encodeWireMessage(message, maxBytes);
131
+ try {
132
+ port.postMessage(frame);
133
+ } catch {
134
+ const error = new AplgError("E_SESSION_CLOSED", "The RPC port is unavailable.");
135
+ terminate(error, false);
136
+ throw error;
137
+ }
138
+ }
139
+ function reply(message) {
140
+ if (closed) return;
141
+ try {
142
+ post(message);
143
+ } catch (error) {
144
+ if (closed) return;
145
+ try {
146
+ post({ protocol: protocolVersion, kind: "error", id: "id" in message ? message.id : requestId(), error: toErrorPayload(error) });
147
+ } catch {
148
+ terminate(new AplgError("E_INVALID_MESSAGE", "The RPC reply could not be encoded."), true);
149
+ }
150
+ }
151
+ }
152
+ function cancelRemote(id) {
153
+ if (closed) return;
154
+ try {
155
+ post({ protocol: protocolVersion, kind: "request", id: requestId(), operation: "request.cancel", args: { requestId: id } });
156
+ } catch {
157
+ }
158
+ }
159
+ function dispatch(request) {
160
+ if (request.operation === "request.cancel") {
161
+ const controller2 = incoming.get(request.args.requestId);
162
+ if (controller2) {
163
+ incoming.delete(request.args.requestId);
164
+ controller2.abort(new AplgError("E_CANCELLED", "The request was cancelled."));
165
+ }
166
+ reply({ protocol: protocolVersion, kind: "result", id: request.id, value: null });
167
+ return;
168
+ }
169
+ if (incoming.has(request.id) || recentIds.has(request.id)) {
170
+ terminate(new AplgError("E_INVALID_MESSAGE", "Duplicate RPC request identifier."), true);
171
+ return;
172
+ }
173
+ recentIds.add(request.id);
174
+ if (recentIds.size > 1024) recentIds.delete(recentIds.values().next().value);
175
+ if (incoming.size >= maxInflight) {
176
+ reply({ protocol: protocolVersion, kind: "error", id: request.id, error: toErrorPayload(new AplgError("E_LIMIT_EXCEEDED", "Too many incoming RPC requests.")) });
177
+ return;
178
+ }
179
+ const controller = new AbortController();
180
+ const currentHandler = handler;
181
+ incoming.set(request.id, controller);
182
+ void Promise.resolve().then(() => {
183
+ if (controller.signal.aborted) throw controller.signal.reason;
184
+ if (!currentHandler) throw new AplgError("E_HOST_UNAVAILABLE", "No handler is registered for this RPC peer.");
185
+ return currentHandler({ ...request, signal: controller.signal });
186
+ }).then(
187
+ (value) => {
188
+ if (!controller.signal.aborted) reply({ protocol: protocolVersion, kind: "result", id: request.id, value });
189
+ },
190
+ (error) => {
191
+ if (!controller.signal.aborted) reply({ protocol: protocolVersion, kind: "error", id: request.id, error: toErrorPayload(error) });
192
+ }
193
+ ).finally(() => {
194
+ if (incoming.get(request.id) === controller) incoming.delete(request.id);
195
+ });
196
+ }
197
+ function receive(event) {
198
+ if (closed) return;
199
+ let message;
200
+ try {
201
+ message = decodeWireMessage(event.data, maxBytes);
202
+ } catch {
203
+ terminate(new AplgError("E_INVALID_MESSAGE", "The remote RPC frame was invalid."), true);
204
+ return;
205
+ }
206
+ if (message.kind === "request") {
207
+ dispatch(message);
208
+ return;
209
+ }
210
+ if (message.kind === "result") {
211
+ settle(message.id, (entry) => entry.resolve(message.value));
212
+ return;
213
+ }
214
+ if (message.kind === "error") {
215
+ settle(message.id, (entry) => entry.reject(fromErrorPayload(message.error)));
216
+ return;
217
+ }
218
+ if (message.kind === "connection" && message.state === "closed") {
219
+ terminate(new AplgError("E_SESSION_CLOSED", "The remote RPC peer closed."), false);
220
+ return;
221
+ }
222
+ emit(message);
223
+ }
224
+ const messageError = () => terminate(new AplgError("E_INVALID_MESSAGE", "The RPC frame could not be deserialized."), true);
225
+ const portClosed = () => terminate(new AplgError("E_SESSION_CLOSED", "The RPC port closed."), false);
226
+ port.addEventListener("message", receive);
227
+ port.addEventListener("messageerror", messageError);
228
+ port.addEventListener("close", portClosed);
229
+ port.start();
230
+ return {
231
+ async request(operation, args, callOptions = {}) {
232
+ if (closed) throw closed;
233
+ if (callOptions.signal?.aborted) throw new AplgError("E_CANCELLED", "The request was cancelled.");
234
+ const deadline = validTimeout(callOptions.timeoutMs ?? timeoutMs);
235
+ if (pending.size >= maxInflight) throw new AplgError("E_LIMIT_EXCEEDED", "Too many pending RPC requests.");
236
+ const id = requestId();
237
+ const frame = encodeWireMessage({ protocol: protocolVersion, kind: "request", id, operation, args }, maxBytes);
238
+ return new Promise((resolve, reject) => {
239
+ const abort = () => {
240
+ settle(id, (entry) => entry.reject(new AplgError("E_CANCELLED", "The request was cancelled.")));
241
+ cancelRemote(id);
242
+ };
243
+ const timer = setTimeout(() => {
244
+ settle(id, (entry) => entry.reject(new AplgError("E_TIMEOUT", "The RPC request timed out.")));
245
+ cancelRemote(id);
246
+ }, deadline);
247
+ pending.set(id, { timer, removeAbortListener: () => callOptions.signal?.removeEventListener("abort", abort), resolve, reject });
248
+ callOptions.signal?.addEventListener("abort", abort, { once: true });
249
+ try {
250
+ port.postMessage(frame);
251
+ } catch {
252
+ terminate(new AplgError("E_SESSION_CLOSED", "The RPC port is unavailable."), false);
253
+ }
254
+ });
255
+ },
256
+ onRequest(callback) {
257
+ if (closed) throw closed;
258
+ if (handler) throw new AplgError("E_INVALID_ARGUMENT", "An RPC request handler is already registered.");
259
+ handler = callback;
260
+ return () => {
261
+ if (handler === callback) handler = void 0;
262
+ };
263
+ },
264
+ onEvent(callback) {
265
+ if (closed) {
266
+ try {
267
+ callback({ protocol: protocolVersion, kind: "connection", state: "closed" });
268
+ } catch {
269
+ }
270
+ return () => {
271
+ };
272
+ }
273
+ events.add(callback);
274
+ return () => {
275
+ events.delete(callback);
276
+ };
277
+ },
278
+ sendEvent(event) {
279
+ if (event.kind === "connection" && event.state === "closed") {
280
+ terminate(new AplgError("E_SESSION_CLOSED", "The RPC peer closed."), true);
281
+ return;
282
+ }
283
+ post(event);
284
+ },
285
+ close(reason = new AplgError("E_SESSION_CLOSED", "The RPC peer closed.")) {
286
+ terminate(reason, true);
287
+ }
288
+ };
289
+ }
290
+
291
+ // src/bridge/handshake.ts
292
+ var marker = ";aplg=";
293
+ function encodeBase64Url(value) {
294
+ return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
295
+ }
296
+ function decodeBase64Url(value) {
297
+ if (!/^[a-zA-Z0-9_-]+$/.test(value)) throw new Error("Invalid base64url.");
298
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
299
+ const result = atob(padded);
300
+ if (encodeBase64Url(result) !== value) throw new Error("Noncanonical base64url.");
301
+ return result;
302
+ }
303
+ function validNonce(value) {
304
+ if (typeof value !== "string" || value.length !== 43) return false;
305
+ try {
306
+ return decodeBase64Url(value).length === 32;
307
+ } catch {
308
+ return false;
309
+ }
310
+ }
311
+ function validOrigin(origin) {
312
+ if (typeof origin !== "string") return false;
313
+ try {
314
+ const url = new URL(origin);
315
+ return url.origin === origin && (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password;
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+ function appendBootstrapHint(assetUrl, nonce, parentOrigin) {
321
+ if (!validNonce(nonce) || !validOrigin(parentOrigin)) throw new AplgError("E_INVALID_ARGUMENT", "Invalid bootstrap context.");
322
+ const url = new URL(assetUrl);
323
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new AplgError("E_INVALID_ARGUMENT", "Invalid plugin asset URL.");
324
+ const hint = encodeBase64Url(JSON.stringify({ aplgNonce: nonce, aplgParentOrigin: parentOrigin }));
325
+ url.hash = `${url.hash}${marker}${hint}`;
326
+ return url.href;
327
+ }
328
+ function readBootstrapHint(href) {
329
+ try {
330
+ const url = new URL(href);
331
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("Unsupported asset URL.");
332
+ const offset = url.hash.lastIndexOf(marker);
333
+ if (offset < 0) throw new Error("Missing bootstrap context.");
334
+ const encoded = url.hash.slice(offset + marker.length);
335
+ if (encoded.length > 4096) throw new Error("Oversized context.");
336
+ const value = JSON.parse(decodeBase64Url(encoded));
337
+ if (!validateJson(value, 4096).ok || value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid context.");
338
+ const hint = value;
339
+ if (Object.keys(hint).length !== 2 || !validNonce(hint.aplgNonce) || !validOrigin(hint.aplgParentOrigin)) throw new Error("Invalid identity.");
340
+ const originalHash = url.hash.slice(0, offset);
341
+ return { nonce: hint.aplgNonce, parentOrigin: hint.aplgParentOrigin, originalHash: originalHash === "#" ? "" : originalHash };
342
+ } catch {
343
+ throw new AplgError("E_HOST_UNAVAILABLE", "This page is not connected to an APLG host.");
344
+ }
345
+ }
346
+ function validatePublicSessionInfo(input) {
347
+ if (!validateJson(input).ok || input === null || typeof input !== "object" || Array.isArray(input)) {
348
+ throw new AplgError("E_PROTOCOL_MISMATCH", "Invalid public session information.");
349
+ }
350
+ const candidate = input;
351
+ if (candidate.protocol !== protocolVersion || !satisfiesApiRange(candidate.apiVersion, `^${apiVersion}`)) {
352
+ throw new AplgError("E_PROTOCOL_MISMATCH", "The host API version is incompatible.");
353
+ }
354
+ const required = {};
355
+ if (candidate.capabilities && typeof candidate.capabilities === "object") {
356
+ for (const [name, value] of Object.entries(candidate.capabilities)) {
357
+ if (!value || typeof value.version !== "string") throw new AplgError("E_PROTOCOL_MISMATCH", "Invalid capability information.");
358
+ required[name] = value.version;
359
+ if (name.startsWith("aplg.") && !Object.hasOwn(standardCapabilities, name)) throw new AplgError("E_PROTOCOL_MISMATCH", "Unknown standard capability.");
360
+ }
361
+ }
362
+ const descriptor = {
363
+ sessionId: "public-info-validation",
364
+ assetUrl: "https://aplg.invalid/dist/index.html",
365
+ info: input,
366
+ manifest: {
367
+ manifestVersion: 1,
368
+ id: candidate.plugin?.id,
369
+ name: "Public info validation",
370
+ version: candidate.plugin?.version,
371
+ description: "",
372
+ license: "MIT",
373
+ engines: { aplg: `^${apiVersion}` },
374
+ entry: "dist/index.html",
375
+ activation: "view",
376
+ requires: required,
377
+ optional: {},
378
+ permissions: { filesystem: [], network: [], native: false },
379
+ contributes: { views: [{ id: "main", title: "Main" }] }
380
+ }
381
+ };
382
+ if (!validateSessionDescriptor(descriptor).ok) throw new AplgError("E_PROTOCOL_MISMATCH", "Invalid public session information.");
383
+ return candidate;
384
+ }
385
+ function validateConnectMessage(value, nonce) {
386
+ if (!validateJson(value).ok || value === null || typeof value !== "object" || Array.isArray(value)) {
387
+ throw new AplgError("E_PROTOCOL_MISMATCH", "Invalid bootstrap response.");
388
+ }
389
+ const message = value;
390
+ const keys = ["channel", "protocol", "kind", "nonce", "info"];
391
+ if (Object.keys(message).length !== keys.length || !keys.every((key) => Object.hasOwn(message, key)) || message.channel !== "aplg.bootstrap" || message.protocol !== protocolVersion || message.kind !== "connect" || message.nonce !== nonce) {
392
+ throw new AplgError("E_PROTOCOL_MISMATCH", "Bootstrap identity or protocol mismatch.");
393
+ }
394
+ return validatePublicSessionInfo(message.info);
395
+ }
396
+
397
+ export {
398
+ createRpcPeer,
399
+ appendBootstrapHint,
400
+ readBootstrapHint,
401
+ validatePublicSessionInfo,
402
+ validateConnectMessage
403
+ };