@hosty-sdk/app 0.29.0 → 0.30.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.
- package/dist/consumed-url.d.ts +2 -0
- package/dist/consumed-url.js +14 -0
- package/dist/react.js +4 -3
- package/dist/sandbox-react.js +4 -1
- package/dist/sandbox-server.d.ts +15 -1
- package/dist/sandbox-server.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Remove consumed launch parameters immediately, then synchronize routers installed by parent effects. */
|
|
2
|
+
export function replaceConsumedUrl(url) {
|
|
3
|
+
const browser = window;
|
|
4
|
+
const route = `${url.pathname}${url.search}${url.hash}`;
|
|
5
|
+
browser.history.replaceState(null, "", route);
|
|
6
|
+
// Next installs its History API adapter in a parent passive effect, after the SDK's child
|
|
7
|
+
// effects. Repeat the final cleanup once that effect flush ends so its canonical URL also
|
|
8
|
+
// changes. Otherwise HMR restores the original, already-consumed authorization callback.
|
|
9
|
+
// A later cleanup or navigation owns its own URL; never replace it with this older snapshot.
|
|
10
|
+
queueMicrotask(() => {
|
|
11
|
+
if (browser.location.href === url.href)
|
|
12
|
+
browser.history.replaceState(browser.history.state, "", route);
|
|
13
|
+
});
|
|
14
|
+
}
|
package/dist/react.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { captureChromeHints } from "./ui-composition.js";
|
|
4
|
+
import { replaceConsumedUrl } from "./consumed-url.js";
|
|
4
5
|
// React slice: the client identity bridge — probe, silent recovery, and the fallback
|
|
5
6
|
// cards. Prop-less by design: the app id and browser-reachable Core origin arrive in the
|
|
6
7
|
// probe response's `recovery` field (request-time values, never build-time props).
|
|
@@ -161,7 +162,7 @@ export function HostLaunchBridge() {
|
|
|
161
162
|
// ignored must not survive into a link someone copies.
|
|
162
163
|
if (url.searchParams.has(LAUNCH_MODE_PARAM)) {
|
|
163
164
|
url.searchParams.delete(LAUNCH_MODE_PARAM);
|
|
164
|
-
|
|
165
|
+
replaceConsumedUrl(url);
|
|
165
166
|
}
|
|
166
167
|
}, []);
|
|
167
168
|
return null;
|
|
@@ -403,7 +404,7 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
|
|
|
403
404
|
url.searchParams.delete("state");
|
|
404
405
|
if (isSilentError || protocolRequired)
|
|
405
406
|
url.searchParams.delete("error");
|
|
406
|
-
|
|
407
|
+
replaceConsumedUrl(url);
|
|
407
408
|
}
|
|
408
409
|
if (code && !error && !upgradedLegacy && attempt && !exchangeRef.current) {
|
|
409
410
|
exchangeRef.current = {
|
|
@@ -550,7 +551,7 @@ export function HostThemeBridge({ followSystem = true, onTheme } = {}) {
|
|
|
550
551
|
if (url.searchParams.has(THEME_PARAM) || url.searchParams.has(THEME_PREFERENCE_PARAM)) {
|
|
551
552
|
url.searchParams.delete(THEME_PARAM);
|
|
552
553
|
url.searchParams.delete(THEME_PREFERENCE_PARAM);
|
|
553
|
-
|
|
554
|
+
replaceConsumedUrl(url);
|
|
554
555
|
}
|
|
555
556
|
const handleMessage = (event) => {
|
|
556
557
|
const message = parseShellThemeMessage(event, window);
|
package/dist/sandbox-react.js
CHANGED
|
@@ -15,6 +15,7 @@ function link(value) {
|
|
|
15
15
|
}
|
|
16
16
|
/** Shared operator panel; each host supplies its authenticated same-origin Core transport. */
|
|
17
17
|
export function CoreEnvironmentPanel({ request, storageKey, workspace, workspaceHref }) {
|
|
18
|
+
const [consents, setConsents] = useState([]);
|
|
18
19
|
const [environments, setEnvironments] = useState([]);
|
|
19
20
|
const [apps, setApps] = useState({});
|
|
20
21
|
const [loading, setLoading] = useState(true);
|
|
@@ -45,6 +46,7 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
|
|
|
45
46
|
if (signal?.aborted || !active.current || sequence !== refreshSequence.current)
|
|
46
47
|
return;
|
|
47
48
|
const selected = result.environments.filter(e => !workspace || e.context?.workspaceId === workspace.id);
|
|
49
|
+
setConsents(result.consents ?? []);
|
|
48
50
|
setEnvironments(selected.filter(e => e.state !== "finished"));
|
|
49
51
|
setLoading(false);
|
|
50
52
|
setReadError("");
|
|
@@ -77,6 +79,7 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
|
|
|
77
79
|
catch (cause) {
|
|
78
80
|
if (!signal?.aborted && active.current && sequence === refreshSequence.current) {
|
|
79
81
|
setEnvironments([]);
|
|
82
|
+
setConsents([]);
|
|
80
83
|
setApps({});
|
|
81
84
|
setLoading(false);
|
|
82
85
|
setReadError(message(cause));
|
|
@@ -158,7 +161,7 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
|
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
const disabled = busy || pending !== null;
|
|
161
|
-
return _jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-4", "aria-label": "Sandbox environments", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: "Sandbox environments" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error || readError }), pending && _jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsx("span", { children: busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed." }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Loading sandboxes\u2026" }) : !environments.length && !error && !readError && _jsx("p", { className: "text-sm text-muted-foreground", children: "No active sandbox environments." }), environments.map(item => _jsxs("article", { className: "space-y-3 rounded-md border p-3", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-medium", children: [item.label, " ", _jsx("span", { className: "ml-2 rounded border px-2 py-0.5 text-xs font-normal", children: item.state })] }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "underline", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " text-destructive", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.limits && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Reserved: ", (item.limits.memoryBytes / 1073741824).toFixed(1), " GiB container memory \u00B7 ", item.limits.cpus, " CPUs \u00B7 ", item.limits.pids, " PIDs \u00B7 ", (item.limits.diskBytes / 1073741824).toFixed(1), " GiB soft disk limit. Released after deletion."] }), item.usage && _jsx("p", { className: "text-xs text-muted-foreground", children: item.usage.state === "unknown" ? "Resource usage unavailable; the reservation remains active." : `Observed: ${(item.usage.containerMemoryBytes / 1048576).toFixed(0)} MiB containers · ${(item.usage.coreMemoryBytes / 1048576).toFixed(0)} MiB Core process · ${(item.usage.ownedDiskBytes / 1073741824).toFixed(2)} GiB owned data.` }), item.error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "space-y-3 rounded-md border border-destructive p-3", children: [_jsx("p", { className: "text-sm", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button + " text-destructive", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
|
|
164
|
+
return _jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-4", "aria-label": "Sandbox environments", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: "Sandbox environments" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error || readError }), pending && _jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsx("span", { children: busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed." }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Loading sandboxes\u2026" }) : !environments.length && !error && !readError && _jsx("p", { className: "text-sm text-muted-foreground", children: "No active sandbox environments." }), !workspace && consents.length > 0 && _jsxs("details", { className: "rounded-md border p-3", children: [_jsxs("summary", { className: "cursor-pointer text-sm font-medium", children: ["Saved launch permissions (", consents.length, ")"] }), _jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Revocation affects all matching launches and delegated access. Running children and their own accounts remain; use Stop or Delete sandbox to end them." }), _jsx("ul", { className: "divide-y", children: consents.map(grant => _jsxs("li", { className: "space-y-2 py-3 text-sm", children: [_jsxs("p", { children: [grant.appId, " \u00B7 ", grant.mode, " \u00B7 ", grant.state] }), _jsx("p", { className: "break-all text-xs text-muted-foreground", children: grant.target }), _jsxs("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/consents/${grant.id}/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: ["Revoke permission for ", grant.appId] })] }, grant.id)) })] }), environments.map(item => _jsxs("article", { className: "space-y-3 rounded-md border p-3", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-medium", children: [item.label, " ", _jsx("span", { className: "ml-2 rounded border px-2 py-0.5 text-xs font-normal", children: item.state })] }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "underline", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " text-destructive", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.limits && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Reserved: ", (item.limits.memoryBytes / 1073741824).toFixed(1), " GiB container memory \u00B7 ", item.limits.cpus, " CPUs \u00B7 ", item.limits.pids, " PIDs \u00B7 ", (item.limits.diskBytes / 1073741824).toFixed(1), " GiB soft disk limit. Released after deletion."] }), item.usage && _jsx("p", { className: "text-xs text-muted-foreground", children: item.usage.state === "unknown" ? "Resource usage unavailable; the reservation remains active." : `Observed: ${(item.usage.containerMemoryBytes / 1048576).toFixed(0)} MiB containers · ${(item.usage.coreMemoryBytes / 1048576).toFixed(0)} MiB Core process · ${(item.usage.ownedDiskBytes / 1073741824).toFixed(2)} GiB owned data.` }), item.access && _jsxs("div", { className: "space-y-2 text-sm", "aria-label": "Sandbox consent", children: [_jsx("p", { children: item.access.state === "operator" ? "Operator launch" : item.access.state === "active" ? item.access.saved ? "Saved launch permission" : "Permission for this run" : item.access.state === "target-changed" ? "Core target changed or unavailable; review launch permission" : "Launch permission revoked or unavailable" }), _jsx("p", { className: "text-xs text-muted-foreground", children: item.access.description }), item.access.grantId && ["active", "target-changed"].includes(item.access.state) && _jsx("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/${item.id}/consent/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: "Revoke launch permission" })] }), item.error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "space-y-3 rounded-md border border-destructive p-3", children: [_jsx("p", { className: "text-sm", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button + " text-destructive", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
|
|
162
165
|
}
|
|
163
166
|
function EnvironmentApplications({ value }) {
|
|
164
167
|
if (!value)
|
package/dist/sandbox-server.d.ts
CHANGED
|
@@ -131,8 +131,16 @@ export declare class SandboxClient {
|
|
|
131
131
|
}): Promise<SandboxInstance>;
|
|
132
132
|
call<T = unknown>(action: SandboxAction, input: Record<string, unknown>): Promise<T>;
|
|
133
133
|
}
|
|
134
|
-
export declare const coreEnvironmentActions: readonly ["environment-create", "environment-list", "environment-status", "environment-start", "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api"];
|
|
134
|
+
export declare const coreEnvironmentActions: readonly ["environment-consent", "environment-create", "environment-list", "environment-status", "environment-start", "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api"];
|
|
135
135
|
export type CoreEnvironmentAction = typeof coreEnvironmentActions[number];
|
|
136
|
+
export interface CoreEnvironmentSavedConsent {
|
|
137
|
+
id: string;
|
|
138
|
+
appId: string;
|
|
139
|
+
label: string;
|
|
140
|
+
mode: string;
|
|
141
|
+
target: string;
|
|
142
|
+
state: string;
|
|
143
|
+
}
|
|
136
144
|
export interface CoreEnvironment {
|
|
137
145
|
id: string;
|
|
138
146
|
label: string;
|
|
@@ -159,6 +167,12 @@ export interface CoreEnvironment {
|
|
|
159
167
|
applicationCpus: number;
|
|
160
168
|
applicationPids: number;
|
|
161
169
|
};
|
|
170
|
+
access?: {
|
|
171
|
+
state: string;
|
|
172
|
+
grantId?: string;
|
|
173
|
+
saved: boolean;
|
|
174
|
+
description: string;
|
|
175
|
+
};
|
|
162
176
|
usage?: {
|
|
163
177
|
ownedDiskBytes: number;
|
|
164
178
|
containerMemoryBytes: number;
|
package/dist/sandbox-server.js
CHANGED
|
@@ -93,7 +93,7 @@ export class SandboxClient {
|
|
|
93
93
|
return result;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
export const coreEnvironmentActions = ["environment-create", "environment-list", "environment-status", "environment-start",
|
|
96
|
+
export const coreEnvironmentActions = ["environment-consent", "environment-create", "environment-list", "environment-status", "environment-start",
|
|
97
97
|
"environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api"];
|
|
98
98
|
/** Full child Core transport. Requires core.environments permissions and live administrator authority.
|
|
99
99
|
* Setup uses ordinary password authentication. Parent credentials are never forwarded to the child. */
|
|
@@ -116,8 +116,8 @@ export class CoreEnvironmentClient {
|
|
|
116
116
|
throw new SandboxError("environment_operation_invalid", "Unknown environment operation.", 400);
|
|
117
117
|
const { environmentId, ...body } = input;
|
|
118
118
|
const read = action === "environment-list" || action === "environment-status";
|
|
119
|
-
let suffix = "";
|
|
120
|
-
if (action !== "environment-list" && action !== "environment-create") {
|
|
119
|
+
let suffix = action === "environment-consent" ? "/consent" : "";
|
|
120
|
+
if (action !== "environment-list" && action !== "environment-create" && action !== "environment-consent") {
|
|
121
121
|
if (typeof environmentId !== "string" || !/^[a-f0-9]{32}$/.test(environmentId))
|
|
122
122
|
throw new SandboxError("environment_id_invalid", "Select a Core-issued environment ID.", 400);
|
|
123
123
|
suffix = `/${environmentId}` + (action === "environment-status" ? "" : action === "environment-api" ? "/api"
|
package/package.json
CHANGED