@floegence/redevplugin-ui 3.4.0 → 3.6.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.
@@ -0,0 +1,18 @@
1
+ /** A connector-scoped base URL for the application's own HTTP client. */
2
+ export interface PluginWebConnection {
3
+ readonly connectorId: string;
4
+ readonly connectionId: string;
5
+ readonly baseURL: string;
6
+ readonly httpMethods: readonly string[];
7
+ }
8
+ export interface PluginConnectionOptions {
9
+ /** Index in the signed fixed destination list; configurable connectors use 0. */
10
+ readonly destinationIndex?: number;
11
+ readonly signal?: AbortSignal;
12
+ }
13
+ /**
14
+ * Discover a connection using the current child-origin session. Use native fetch,
15
+ * Request and Response with baseURL. This helper never replaces window.fetch,
16
+ * grants permission, retries a failed mutation, or exposes host credentials.
17
+ */
18
+ export declare function openPluginConnection(connectorId: string, options?: PluginConnectionOptions): Promise<PluginWebConnection>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Discover a connection using the current child-origin session. Use native fetch,
3
+ * Request and Response with baseURL. This helper never replaces window.fetch,
4
+ * grants permission, retries a failed mutation, or exposes host credentials.
5
+ */
6
+ export async function openPluginConnection(connectorId, options = {}) {
7
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(connectorId))
8
+ throw new TypeError("Invalid connector ID");
9
+ const index = options.destinationIndex ?? 0;
10
+ if (!Number.isSafeInteger(index) || index < 0)
11
+ throw new TypeError("Invalid destination index");
12
+ const endpoint = new URL("/_redevplugin_web/connection", globalThis.location.origin);
13
+ endpoint.searchParams.set("connector_id", connectorId);
14
+ endpoint.searchParams.set("destination_index", String(index));
15
+ const response = await fetch(endpoint, { signal: options.signal, credentials: "same-origin", redirect: "error", cache: "no-store" });
16
+ if (!response.ok)
17
+ throw new Error("Connection unavailable; review its configuration and permissions");
18
+ const value = await response.json();
19
+ if (!value || typeof value !== "object")
20
+ throw new Error("Invalid connection response");
21
+ const item = value;
22
+ if (item.connector_id !== connectorId || typeof item.connection_id !== "string" || !/^(?:fixed-[0-9]+|[a-f0-9]{64})$/.test(item.connection_id) || !Array.isArray(item.http_methods) || item.http_methods.length === 0 || !item.http_methods.every(method => typeof method === "string" && /^(GET|HEAD|OPTIONS|POST|PUT|PATCH|DELETE)$/.test(method)))
23
+ throw new Error("Invalid connection response");
24
+ const expected = "/_redevplugin_web/connections/" + encodeURIComponent(connectorId) + "/" + item.connection_id + "/";
25
+ if (item.base_path !== expected)
26
+ throw new Error("Invalid connection base URL");
27
+ return Object.freeze({ connectorId, connectionId: item.connection_id, baseURL: new URL(expected, endpoint).href, httpMethods: Object.freeze([...item.http_methods]) });
28
+ }
@@ -1,4 +1,4 @@
1
- export declare const pluginPlatformErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED"];
2
- export declare const pluginBridgeErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED", "PLUGIN_CONFIRMATION_REJECTED", "PLUGIN_BRIDGE_CANCELLED", "PLUGIN_BRIDGE_TIMEOUT", "PLUGIN_BRIDGE_DISPOSED", "PLUGIN_BRIDGE_HANDSHAKE_FAILED", "PLUGIN_BRIDGE_HANDSHAKE_REQUIRED"];
3
- export declare const pluginClientErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED", "PLUGIN_CONFIRMATION_REJECTED", "PLUGIN_BRIDGE_CANCELLED", "PLUGIN_BRIDGE_TIMEOUT", "PLUGIN_BRIDGE_DISPOSED", "PLUGIN_BRIDGE_HANDSHAKE_FAILED", "PLUGIN_BRIDGE_HANDSHAKE_REQUIRED", "PLUGIN_PLATFORM_REQUEST_FAILED", "PLUGIN_STREAM_FAILED"];
1
+ export declare const pluginPlatformErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_CONNECTION_UNAVAILABLE", "PLUGIN_CONNECTION_CONFLICT", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_FILE_OPERATION_FAILED", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED"];
2
+ export declare const pluginBridgeErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_CONNECTION_UNAVAILABLE", "PLUGIN_CONNECTION_CONFLICT", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_FILE_OPERATION_FAILED", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED", "PLUGIN_CONFIRMATION_REJECTED", "PLUGIN_BRIDGE_CANCELLED", "PLUGIN_BRIDGE_TIMEOUT", "PLUGIN_BRIDGE_DISPOSED", "PLUGIN_BRIDGE_HANDSHAKE_FAILED", "PLUGIN_BRIDGE_HANDSHAKE_REQUIRED"];
3
+ export declare const pluginClientErrorCodes: readonly ["PLUGIN_INVALID_REQUEST", "PLUGIN_MANIFEST_INVALID", "PLUGIN_PACKAGE_INVALID", "PLUGIN_PACKAGE_TOO_LARGE", "PLUGIN_PACKAGE_PATH_FORBIDDEN", "PLUGIN_SIGNATURE_INVALID", "PLUGIN_TRUST_STATE_DENIED", "PLUGIN_TRUST_VERIFICATION_REQUIRED", "PLUGIN_TRUST_VERIFICATION_INVALID", "PLUGIN_RELEASE_REF_VERIFICATION_FAILED", "PLUGIN_RELEASE_REF_POLICY_DENIED", "PLUGIN_RELEASE_NETWORK", "PLUGIN_RELEASE_TIMEOUT", "PLUGIN_RELEASE_ASSET_MISSING", "PLUGIN_RELEASE_ASSET_INTEGRITY", "PLUGIN_INSTALL_INTERRUPTED", "PLUGIN_INSTALL_STATE_CONFLICT", "PLUGIN_RETAINED_DATA_INCOMPATIBLE", "PLUGIN_INTERNAL_FAILURE", "PLUGIN_DISABLED", "PLUGIN_PERMISSION_DENIED", "PLUGIN_ORIGIN_DENIED", "PLUGIN_ACTION_DENIED", "PLUGIN_OWNER_SCOPE_MISMATCH", "PLUGIN_SECRET_SCOPE_MISMATCH", "PLUGIN_STORAGE_SCOPE_MISMATCH", "PLUGIN_ADAPTER_FAILURE", "PLUGIN_SESSION_REVOKED", "PLUGIN_SESSION_TEARDOWN_INCOMPLETE", "PLUGIN_SESSION_FENCE_CAPACITY", "PLUGIN_CONFIRMATION_REQUIRED", "PLUGIN_CONFIRMATION_INVALID", "PLUGIN_TOKEN_EXPIRED", "PLUGIN_TOKEN_REPLAY", "PLUGIN_GATEWAY_TOKEN_INVALID", "PLUGIN_GATEWAY_TOKEN_REPLAYED", "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH", "PLUGIN_ASSET_TICKET_INVALID", "PLUGIN_ASSET_SESSION_INVALID", "PLUGIN_STREAM_TICKET_INVALID", "PLUGIN_STREAM_DELIVERY_INVALID", "PLUGIN_STREAM_CANCELLED", "PLUGIN_LEASE_INVALID", "PLUGIN_LEASE_REPLAYED", "PLUGIN_GRANT_INVALID", "PLUGIN_STORAGE_QUOTA_EXCEEDED", "PLUGIN_EXECUTION_BLOCKED", "PLUGIN_EXECUTION_NOT_FOUND", "PLUGIN_EXECUTION_NOT_CANCELABLE", "PLUGIN_CONNECTION_UNAVAILABLE", "PLUGIN_CONNECTION_CONFLICT", "PLUGIN_NETWORK_TARGET_DENIED", "PLUGIN_NETWORK_RATE_LIMITED", "PLUGIN_RUNTIME_UNAVAILABLE", "PLUGIN_RUNTIME_VERSION_MISMATCH", "PLUGIN_RUNTIME_CONTRACT_MISMATCH", "PLUGIN_UI_PROTOCOL_VIOLATION", "PLUGIN_SURFACE_QUIESCE_TIMEOUT", "PLUGIN_JSON_LIMIT_EXCEEDED", "PLUGIN_CAPABILITY_ERROR", "PLUGIN_WORKER_ERROR", "PLUGIN_FILE_OPERATION_FAILED", "PLUGIN_CONTRACT_MISMATCH", "PLUGIN_MANAGEMENT_REVISION_MISMATCH", "PLUGIN_AUTHORIZATION_REVISION_MISMATCH", "PLUGIN_BINDING_REVISION_MISMATCH", "PLUGIN_VALUES_REVISION_MISMATCH", "PLUGIN_CSRF_REQUIRED", "PLUGIN_CSRF_INVALID", "PLUGIN_FEATURE_NOT_CONFIGURED", "PLUGIN_CONFIRMATION_REJECTED", "PLUGIN_BRIDGE_CANCELLED", "PLUGIN_BRIDGE_TIMEOUT", "PLUGIN_BRIDGE_DISPOSED", "PLUGIN_BRIDGE_HANDSHAKE_FAILED", "PLUGIN_BRIDGE_HANDSHAKE_REQUIRED", "PLUGIN_PLATFORM_REQUEST_FAILED", "PLUGIN_STREAM_FAILED"];
4
4
  export declare const runtimeProcessFailureCodes: readonly ["RUNTIME_PROCESS_FAILED", "RUNTIME_PROCESS_EXIT_UNEXPECTED", "RUNTIME_PROCESS_EXIT_UNRECOGNIZED", "RUNTIME_PROCESS_SIGNALLED", "IPC_WRITER_CAPACITY_OVERFLOW", "IPC_WRITER_CAPACITY_LIMIT_EXCEEDED", "IPC_WRITER_START_FAILED", "IPC_WRITER_CLOSED", "IPC_WRITER_BATCH_SIZE_OVERFLOW", "IPC_WRITER_WRITE_FAILED", "IPC_WRITER_FLUSH_FAILED", "IPC_WRITER_PANICKED"];
@@ -49,6 +49,8 @@ export const pluginPlatformErrorCodes = [
49
49
  "PLUGIN_EXECUTION_BLOCKED",
50
50
  "PLUGIN_EXECUTION_NOT_FOUND",
51
51
  "PLUGIN_EXECUTION_NOT_CANCELABLE",
52
+ "PLUGIN_CONNECTION_UNAVAILABLE",
53
+ "PLUGIN_CONNECTION_CONFLICT",
52
54
  "PLUGIN_NETWORK_TARGET_DENIED",
53
55
  "PLUGIN_NETWORK_RATE_LIMITED",
54
56
  "PLUGIN_RUNTIME_UNAVAILABLE",
@@ -59,6 +61,7 @@ export const pluginPlatformErrorCodes = [
59
61
  "PLUGIN_JSON_LIMIT_EXCEEDED",
60
62
  "PLUGIN_CAPABILITY_ERROR",
61
63
  "PLUGIN_WORKER_ERROR",
64
+ "PLUGIN_FILE_OPERATION_FAILED",
62
65
  "PLUGIN_CONTRACT_MISMATCH",
63
66
  "PLUGIN_MANAGEMENT_REVISION_MISMATCH",
64
67
  "PLUGIN_AUTHORIZATION_REVISION_MISMATCH",
@@ -118,6 +121,8 @@ export const pluginBridgeErrorCodes = [
118
121
  "PLUGIN_EXECUTION_BLOCKED",
119
122
  "PLUGIN_EXECUTION_NOT_FOUND",
120
123
  "PLUGIN_EXECUTION_NOT_CANCELABLE",
124
+ "PLUGIN_CONNECTION_UNAVAILABLE",
125
+ "PLUGIN_CONNECTION_CONFLICT",
121
126
  "PLUGIN_NETWORK_TARGET_DENIED",
122
127
  "PLUGIN_NETWORK_RATE_LIMITED",
123
128
  "PLUGIN_RUNTIME_UNAVAILABLE",
@@ -128,6 +133,7 @@ export const pluginBridgeErrorCodes = [
128
133
  "PLUGIN_JSON_LIMIT_EXCEEDED",
129
134
  "PLUGIN_CAPABILITY_ERROR",
130
135
  "PLUGIN_WORKER_ERROR",
136
+ "PLUGIN_FILE_OPERATION_FAILED",
131
137
  "PLUGIN_CONTRACT_MISMATCH",
132
138
  "PLUGIN_MANAGEMENT_REVISION_MISMATCH",
133
139
  "PLUGIN_AUTHORIZATION_REVISION_MISMATCH",
@@ -193,6 +199,8 @@ export const pluginClientErrorCodes = [
193
199
  "PLUGIN_EXECUTION_BLOCKED",
194
200
  "PLUGIN_EXECUTION_NOT_FOUND",
195
201
  "PLUGIN_EXECUTION_NOT_CANCELABLE",
202
+ "PLUGIN_CONNECTION_UNAVAILABLE",
203
+ "PLUGIN_CONNECTION_CONFLICT",
196
204
  "PLUGIN_NETWORK_TARGET_DENIED",
197
205
  "PLUGIN_NETWORK_RATE_LIMITED",
198
206
  "PLUGIN_RUNTIME_UNAVAILABLE",
@@ -203,6 +211,7 @@ export const pluginClientErrorCodes = [
203
211
  "PLUGIN_JSON_LIMIT_EXCEEDED",
204
212
  "PLUGIN_CAPABILITY_ERROR",
205
213
  "PLUGIN_WORKER_ERROR",
214
+ "PLUGIN_FILE_OPERATION_FAILED",
206
215
  "PLUGIN_CONTRACT_MISMATCH",
207
216
  "PLUGIN_MANAGEMENT_REVISION_MISMATCH",
208
217
  "PLUGIN_AUTHORIZATION_REVISION_MISMATCH",
@@ -0,0 +1,66 @@
1
+ import type { components } from "./openapi.gen.js";
2
+ import { PluginBridgeError } from "./errors.js";
3
+ import type { PluginBridgeRequestOptions } from "./surface.js";
4
+ type Schemas = components["schemas"];
5
+ export type PluginFileResource = Schemas["FileResource"];
6
+ export type PluginFileResourceReview = Schemas["FileResourceReview"];
7
+ export type PluginFileSelectionOptions = Schemas["FileSelectionOptions"];
8
+ export type PluginFileIORequest = Schemas["FileIORequest"];
9
+ export type PluginFileIOResult = Schemas["FileIOResult"];
10
+ export type PluginFileOpenOptions = Extract<PluginFileIORequest, {
11
+ operation: "fs.open";
12
+ }>["arguments"]["options"];
13
+ export type PluginFileAccessConfirmation = (review: Readonly<PluginFileResourceReview & {
14
+ signal: AbortSignal;
15
+ }>) => boolean | Promise<boolean>;
16
+ export type PluginFileStat = {
17
+ uri: string;
18
+ kind: "file" | "directory";
19
+ size: bigint;
20
+ mode: number;
21
+ modified_unix_ms: number;
22
+ created_unix_ms?: number;
23
+ };
24
+ export type PluginDirectoryEntry = {
25
+ name: string;
26
+ uri: string;
27
+ kind: "file" | "directory" | "symlink" | "other";
28
+ };
29
+ type FileCall = (request: PluginFileIORequest, options?: PluginBridgeRequestOptions) => Promise<PluginFileIOResult>;
30
+ export declare function isPluginFileResourceReview(value: unknown): value is PluginFileResourceReview;
31
+ export declare function isPluginFileResource(value: unknown): value is PluginFileResource;
32
+ /** Thin client over the Host's selected-resource authority and shared I/O table. */
33
+ export declare class PluginFilesClient {
34
+ private readonly call;
35
+ constructor(call: FileCall);
36
+ select(selection: PluginFileSelectionOptions, options?: PluginBridgeRequestOptions): Promise<PluginFileResource | null>;
37
+ open(resourceURI: string, flags?: PluginFileOpenOptions, options?: PluginBridgeRequestOptions): Promise<PluginFileHandle>;
38
+ stat(resourceURI: string, options?: PluginBridgeRequestOptions): Promise<PluginFileStat>;
39
+ entries(resourceURI: string, options?: PluginBridgeRequestOptions): AsyncGenerator<PluginDirectoryEntry>;
40
+ mkdir(resourceURI: string, recursive?: boolean, options?: PluginBridgeRequestOptions): Promise<void>;
41
+ remove(resourceURI: string, recursive?: boolean, options?: PluginBridgeRequestOptions): Promise<void>;
42
+ rename(from: string, to: string, overwrite?: boolean, options?: PluginBridgeRequestOptions): Promise<void>;
43
+ copy(from: string, to: string, overwrite?: boolean, options?: PluginBridgeRequestOptions): Promise<void>;
44
+ }
45
+ export declare class PluginFileHandle {
46
+ #private;
47
+ private readonly call;
48
+ private readonly id;
49
+ constructor(call: FileCall, id: string);
50
+ read(length?: number, options?: PluginBridgeRequestOptions): Promise<{
51
+ bytes: Uint8Array;
52
+ eof: boolean;
53
+ }>;
54
+ write(bytes: Uint8Array, options?: PluginBridgeRequestOptions): Promise<number>;
55
+ seek(offset: bigint, whence?: 0 | 1 | 2, options?: PluginBridgeRequestOptions): Promise<bigint>;
56
+ sync(options?: PluginBridgeRequestOptions): Promise<void>;
57
+ close(): Promise<void>;
58
+ }
59
+ /** A rejected write may have changed the file. bytesWritten counts acknowledged bytes only. */
60
+ export declare class PluginFileWriteError extends PluginBridgeError {
61
+ readonly bytesWritten: number;
62
+ readonly cause: unknown;
63
+ constructor(bytesWritten: number, cause: unknown);
64
+ }
65
+ export declare function validFileIORequest(value: unknown): value is PluginFileIORequest;
66
+ export {};
package/dist/files.js ADDED
@@ -0,0 +1,255 @@
1
+ import { PluginBridgeError, pluginMutationOutcome } from "./errors.js";
2
+ import { hasAllowedKeys, hasExactKeys, isRecord } from "./http.js";
3
+ const chunkBytes = 65536;
4
+ function invalid(message) { throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", message); }
5
+ function handle(value) {
6
+ if (typeof value !== "string" || !/^[1-9][0-9]{0,18}$/.test(value) || BigInt(value) > 9223372036854775807n)
7
+ return invalid("File handle is invalid");
8
+ return value;
9
+ }
10
+ function uri(value) {
11
+ if (!validURI(value))
12
+ throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Selected file URI is invalid");
13
+ return value;
14
+ }
15
+ export function isPluginFileResourceReview(value) {
16
+ return hasExactKeys(value, ["review_id", "plugin_instance_id", "name", "kind", "recursive", "writable", "review_expires_at", "resource_expires_at"]) &&
17
+ typeof value.review_id === "string" && /^[0-9a-f]{64}$/.test(value.review_id) &&
18
+ typeof value.plugin_instance_id === "string" && value.plugin_instance_id.length > 0 && value.plugin_instance_id.length <= 256 &&
19
+ typeof value.name === "string" && value.name.length > 0 && [...value.name].length <= 255 &&
20
+ (value.kind === "file" || value.kind === "directory") && typeof value.recursive === "boolean" && (value.kind !== "file" || !value.recursive) && typeof value.writable === "boolean" &&
21
+ typeof value.review_expires_at === "string" && Number.isFinite(Date.parse(value.review_expires_at)) &&
22
+ typeof value.resource_expires_at === "string" && Number.isFinite(Date.parse(value.resource_expires_at));
23
+ }
24
+ export function isPluginFileResource(value) {
25
+ return hasExactKeys(value, ["resource_id", "uri", "name", "kind", "recursive", "writable", "revision", "expires_at", "status"]) &&
26
+ typeof value.resource_id === "string" && /^selected-[0-9a-f]{64}$/.test(value.resource_id) && value.uri === `redevfs://${value.resource_id}/` &&
27
+ typeof value.name === "string" && value.name.length > 0 && [...value.name].length <= 255 && (value.kind === "file" || value.kind === "directory") &&
28
+ typeof value.recursive === "boolean" && (value.kind !== "file" || !value.recursive) && typeof value.writable === "boolean" && Number.isSafeInteger(value.revision) && Number(value.revision) >= 1 &&
29
+ typeof value.expires_at === "string" && Number.isFinite(Date.parse(value.expires_at)) &&
30
+ ["active", "revoked", "expired", "unavailable"].includes(String(value.status));
31
+ }
32
+ /** Thin client over the Host's selected-resource authority and shared I/O table. */
33
+ export class PluginFilesClient {
34
+ call;
35
+ constructor(call) {
36
+ this.call = call;
37
+ }
38
+ async select(selection, options) {
39
+ const result = await this.call({ operation: "select", arguments: selection }, options);
40
+ if (hasExactKeys(result, ["canceled"]) && result.canceled === true)
41
+ return null;
42
+ if (!hasExactKeys(result, ["resource"]) || !isPluginFileResource(result.resource))
43
+ return invalid("File selection response is invalid");
44
+ return result.resource;
45
+ }
46
+ async open(resourceURI, flags = { read: true }, options) {
47
+ const result = await this.call({ operation: "fs.open", arguments: { uri: uri(resourceURI), options: flags } }, options);
48
+ return new PluginFileHandle(this.call, handle(result.handle));
49
+ }
50
+ async stat(resourceURI, options) {
51
+ const result = await this.call({ operation: "fs.stat", arguments: { uri: uri(resourceURI) } }, options);
52
+ const value = result.value;
53
+ if (!hasExactKeys(result, ["value"]) || !isRecord(value) || !hasAllowedKeys(value, ["uri", "kind", "size", "mode", "modified_unix_ms", "created_unix_ms"]) || !sameFileURI(value.uri, resourceURI) || !["file", "directory"].includes(String(value.kind)) || typeof value.size !== "string" || !/^(0|[1-9][0-9]{0,18})$/.test(value.size) || BigInt(value.size) > 9223372036854775807n || !boundedInteger(value.mode, 0, 4294967295) || !Number.isSafeInteger(value.modified_unix_ms) || (value.created_unix_ms !== undefined && !Number.isSafeInteger(value.created_unix_ms)))
54
+ return invalid("File stat response is invalid");
55
+ return { ...value, size: BigInt(value.size) };
56
+ }
57
+ async *entries(resourceURI, options) {
58
+ const opened = await this.call({ operation: "fs.read_dir.open", arguments: { uri: uri(resourceURI) } }, options);
59
+ const id = handle(opened.handle);
60
+ let finished = false;
61
+ let failed = false;
62
+ try {
63
+ for (;;) {
64
+ const result = await this.call({ operation: "read_dir.next", arguments: { handle: id, limit: 128 } }, options);
65
+ const value = result.value;
66
+ if (!hasExactKeys(value, ["entries", "eof"]) || !Array.isArray(value.entries) || value.entries.length > 128 || typeof value.eof !== "boolean")
67
+ return invalid("Directory page is invalid");
68
+ for (const entry of value.entries) {
69
+ if (!hasExactKeys(entry, ["name", "uri", "kind"]) || typeof entry.name !== "string" || typeof entry.uri !== "string" || !["file", "directory", "symlink", "other"].includes(String(entry.kind)))
70
+ return invalid("Directory entry is invalid");
71
+ yield entry;
72
+ }
73
+ if (value.eof) {
74
+ finished = true;
75
+ return;
76
+ }
77
+ if (value.entries.length === 0)
78
+ return invalid("Directory made no progress");
79
+ }
80
+ }
81
+ catch (error) {
82
+ failed = true;
83
+ throw error;
84
+ }
85
+ finally {
86
+ if (!finished) {
87
+ try {
88
+ await this.call({ operation: "close", arguments: { handle: id } });
89
+ }
90
+ catch (error) {
91
+ if (!failed)
92
+ throw error;
93
+ }
94
+ }
95
+ }
96
+ }
97
+ async mkdir(resourceURI, recursive = false, options) {
98
+ await this.call({ operation: "fs.mkdir", arguments: { uri: uri(resourceURI), recursive } }, options);
99
+ }
100
+ async remove(resourceURI, recursive = false, options) {
101
+ await this.call({ operation: "fs.remove", arguments: { uri: uri(resourceURI), recursive } }, options);
102
+ }
103
+ async rename(from, to, overwrite = false, options) {
104
+ await this.call({ operation: "fs.rename", arguments: { from: uri(from), to: uri(to), overwrite } }, options);
105
+ }
106
+ async copy(from, to, overwrite = false, options) {
107
+ await this.call({ operation: "fs.copy", arguments: { from: uri(from), to: uri(to), overwrite } }, options);
108
+ }
109
+ }
110
+ export class PluginFileHandle {
111
+ call;
112
+ id;
113
+ #closed = false;
114
+ constructor(call, id) {
115
+ this.call = call;
116
+ this.id = id;
117
+ }
118
+ #active() { if (this.#closed)
119
+ throw new PluginBridgeError("PLUGIN_BRIDGE_DISPOSED", "File handle is closed"); }
120
+ async read(length = chunkBytes, options) {
121
+ this.#active();
122
+ if (!Number.isSafeInteger(length) || length < 1 || length > chunkBytes)
123
+ throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Read length must be between 1 and 65536");
124
+ const result = await this.call({ operation: "read", arguments: { handle: this.id, length } }, options);
125
+ if (!hasExactKeys(result, ["data", "count", "eof"]) || typeof result.data !== "string" || typeof result.eof !== "boolean" || !Number.isSafeInteger(result.count))
126
+ return invalid("File read response is invalid");
127
+ let decoded;
128
+ try {
129
+ decoded = atob(result.data);
130
+ }
131
+ catch {
132
+ return invalid("File data is invalid");
133
+ }
134
+ if (decoded.length !== result.count || decoded.length > length || btoa(decoded) !== result.data)
135
+ return invalid("File read length is invalid");
136
+ return { bytes: Uint8Array.from(decoded, c => c.charCodeAt(0)), eof: result.eof };
137
+ }
138
+ async write(bytes, options) {
139
+ this.#active();
140
+ let written = 0;
141
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkBytes) {
142
+ const part = bytes.subarray(offset, offset + chunkBytes);
143
+ let binary = "";
144
+ for (const byte of part)
145
+ binary += String.fromCharCode(byte);
146
+ try {
147
+ const result = await this.call({ operation: "write", arguments: { handle: this.id, data: btoa(binary) } }, options);
148
+ if (!hasExactKeys(result, ["count"]) || result.count !== part.length) {
149
+ throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "File write length is invalid", undefined, undefined, "unknown");
150
+ }
151
+ written += result.count;
152
+ }
153
+ catch (error) {
154
+ if (written > 0 || pluginMutationOutcome(error) !== "not_committed")
155
+ throw new PluginFileWriteError(written, error);
156
+ throw error;
157
+ }
158
+ }
159
+ return written;
160
+ }
161
+ async seek(offset, whence = 0, options) {
162
+ this.#active();
163
+ const result = await this.call({ operation: "seek", arguments: { handle: this.id, offset: offset.toString(), whence } }, options);
164
+ if (!hasExactKeys(result, ["offset"]) || typeof result.offset !== "string" || !/^(0|[1-9][0-9]{0,18})$/.test(result.offset) || BigInt(result.offset) > 9223372036854775807n)
165
+ return invalid("File offset is invalid");
166
+ return BigInt(result.offset);
167
+ }
168
+ async sync(options) { this.#active(); await this.call({ operation: "sync", arguments: { handle: this.id } }, options); }
169
+ async close() {
170
+ if (this.#closed)
171
+ return;
172
+ await this.call({ operation: "close", arguments: { handle: this.id } });
173
+ this.#closed = true;
174
+ }
175
+ }
176
+ /** A rejected write may have changed the file. bytesWritten counts acknowledged bytes only. */
177
+ export class PluginFileWriteError extends PluginBridgeError {
178
+ bytesWritten;
179
+ cause;
180
+ constructor(bytesWritten, cause) {
181
+ super(cause instanceof PluginBridgeError ? cause.errorCode : "PLUGIN_FILE_OPERATION_FAILED", "File write did not finish; inspect the file before retrying", undefined, cause instanceof PluginBridgeError ? cause.details : undefined, "unknown");
182
+ this.bytesWritten = bytesWritten;
183
+ this.name = "PluginFileWriteError";
184
+ this.cause = cause;
185
+ }
186
+ }
187
+ function validURI(value) {
188
+ if (typeof value !== "string" || value.length > 4096 || !/^redevfs:\/\/selected-[0-9a-f]{64}\//.test(value))
189
+ return false;
190
+ const path = value.slice(value.indexOf("/", 10) + 1);
191
+ if (path === "")
192
+ return true;
193
+ try {
194
+ return path.split("/").every(part => {
195
+ if (/[?#]/.test(part))
196
+ return false;
197
+ const decoded = decodeURIComponent(part);
198
+ return decoded !== "" && decoded !== "." && decoded !== ".." && !/[\\/\u0000]/.test(decoded);
199
+ });
200
+ }
201
+ catch {
202
+ return false;
203
+ }
204
+ }
205
+ function sameFileURI(actual, expected) {
206
+ if (!validURI(actual) || !validURI(expected))
207
+ return false;
208
+ const normalized = (value) => value.split("/").map(part => decodeURIComponent(part).normalize("NFC")).join("/");
209
+ return normalized(actual) === normalized(expected);
210
+ }
211
+ function boundedInteger(value, min, max) { return Number.isSafeInteger(value) && Number(value) >= min && Number(value) <= max; }
212
+ function decimal(value, signed = false) {
213
+ return typeof value === "string" && (signed ? /^(0|-?[1-9][0-9]{0,18})$/ : /^[1-9][0-9]{0,18}$/).test(value) && BigInt(value) <= 9223372036854775807n && BigInt(value) >= (signed ? -9223372036854775808n : 1n);
214
+ }
215
+ function base64(value) {
216
+ if (typeof value !== "string" || value.length > 87384)
217
+ return false;
218
+ try {
219
+ const raw = atob(value);
220
+ return raw.length <= chunkBytes && btoa(raw) === value;
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ }
226
+ export function validFileIORequest(value) {
227
+ if (!hasExactKeys(value, ["operation", "arguments"]) || !isRecord(value.arguments))
228
+ return false;
229
+ const a = value.arguments;
230
+ const keys = (required, optional = []) => required.every(key => Object.hasOwn(a, key)) && hasAllowedKeys(a, [...required, ...optional]);
231
+ const flags = (...names) => names.every(name => a[name] === undefined || typeof a[name] === "boolean");
232
+ switch (value.operation) {
233
+ case "select": return keys(["kind"], ["recursive", "writable"]) && ["file", "directory"].includes(String(a.kind)) && flags("recursive", "writable") && (a.kind !== "file" || !a.recursive);
234
+ case "fs.open": {
235
+ if (!keys(["uri", "options"]) || !validURI(a.uri) || !isRecord(a.options) || !hasAllowedKeys(a.options, ["read", "write", "create", "create_new", "truncate", "append", "mode"]))
236
+ return false;
237
+ const o = a.options;
238
+ return ["read", "write", "create", "create_new", "truncate", "append"].every(key => o[key] === undefined || typeof o[key] === "boolean") && (o.mode === undefined || boundedInteger(o.mode, 0, 511));
239
+ }
240
+ case "fs.stat": return keys(["uri"], ["follow_symlinks"]) && validURI(a.uri) && flags("follow_symlinks");
241
+ case "fs.read_dir.open": return keys(["uri"]) && validURI(a.uri);
242
+ case "fs.mkdir": return keys(["uri"], ["recursive", "mode"]) && validURI(a.uri) && flags("recursive") && (a.mode === undefined || boundedInteger(a.mode, 0, 511));
243
+ case "fs.remove": return keys(["uri"], ["recursive"]) && validURI(a.uri) && flags("recursive");
244
+ case "fs.rename":
245
+ case "fs.copy": return keys(["from", "to"], ["overwrite"]) && validURI(a.from) && validURI(a.to) && flags("overwrite");
246
+ case "fs.set_times": return keys(["uri", "accessed_unix_ms", "modified_unix_ms"]) && validURI(a.uri) && Number.isSafeInteger(a.accessed_unix_ms) && Number.isSafeInteger(a.modified_unix_ms);
247
+ case "read": return keys(["handle", "length"]) && decimal(a.handle) && boundedInteger(a.length, 1, chunkBytes);
248
+ case "write": return keys(["handle", "data"]) && decimal(a.handle) && base64(a.data);
249
+ case "seek": return keys(["handle", "offset"], ["whence"]) && decimal(a.handle) && decimal(a.offset, true) && (a.whence === undefined || boundedInteger(a.whence, 0, 2));
250
+ case "close":
251
+ case "sync": return keys(["handle"]) && decimal(a.handle);
252
+ case "read_dir.next": return keys(["handle", "limit"]) && decimal(a.handle) && boundedInteger(a.limit, 1, 256);
253
+ default: return false;
254
+ }
255
+ }
package/dist/http.js CHANGED
@@ -187,6 +187,10 @@ function isPlatformErrorDetails(code, value) {
187
187
  Array.from(value.worker_error_message).length <= 4096 &&
188
188
  ["runtime", "hostcall", "plugin"].includes(String(value.worker_error_origin));
189
189
  }
190
+ if (code === "PLUGIN_FILE_OPERATION_FAILED") {
191
+ return hasExactKeys(value, ["file_error_code", "file_retryable"]) && typeof value.file_retryable === "boolean" &&
192
+ ["INVALID_ARGUMENT", "PERMISSION_DENIED", "NOT_FOUND", "NOT_EMPTY", "ALREADY_EXISTS", "RESOURCE_CLOSED", "RESOURCE_LIMIT", "CROSS_DEVICE", "MOUNT_UNAVAILABLE", "RUNTIME_UNAVAILABLE", "CANCELED", "TIMEOUT", "IO_ERROR"].includes(String(value.file_error_code));
193
+ }
190
194
  if (code === "PLUGIN_JSON_LIMIT_EXCEEDED") {
191
195
  return hasExactKeys(value, ["reason"]) &&
192
196
  typeof value.reason === "string" && jsonLimitReasons.includes(value.reason);