@mariozechner/pi-web-ui 0.5.44 → 0.5.45
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/README.md +178 -99
- package/dist/ChatPanel.d.ts +15 -10
- package/dist/ChatPanel.d.ts.map +1 -1
- package/dist/ChatPanel.js +68 -100
- package/dist/ChatPanel.js.map +1 -1
- package/dist/{state/agent-session.d.ts → agent/agent.d.ts} +23 -19
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/{state/agent-session.js → agent/agent.js} +50 -32
- package/dist/agent/agent.js.map +1 -0
- package/dist/{state → agent}/transports/AppTransport.d.ts +1 -3
- package/dist/agent/transports/AppTransport.d.ts.map +1 -0
- package/dist/{state → agent}/transports/AppTransport.js +5 -4
- package/dist/{state → agent}/transports/AppTransport.js.map +1 -1
- package/dist/{state → agent}/transports/ProviderTransport.d.ts +1 -3
- package/dist/agent/transports/ProviderTransport.d.ts.map +1 -0
- package/dist/{state → agent}/transports/ProviderTransport.js +6 -7
- package/dist/agent/transports/ProviderTransport.js.map +1 -0
- package/dist/{state → agent}/transports/index.d.ts.map +1 -1
- package/dist/agent/transports/index.js.map +1 -0
- package/dist/{state → agent}/transports/proxy-types.d.ts.map +1 -1
- package/dist/agent/transports/proxy-types.js.map +1 -0
- package/dist/agent/transports/types.d.ts +12 -0
- package/dist/agent/transports/types.d.ts.map +1 -0
- package/dist/{state → agent}/transports/types.js.map +1 -1
- package/dist/{state → agent}/types.d.ts.map +1 -1
- package/dist/{state → agent}/types.js.map +1 -1
- package/dist/app.css +1 -1
- package/dist/components/AgentInterface.d.ts +7 -4
- package/dist/components/AgentInterface.d.ts.map +1 -1
- package/dist/components/AgentInterface.js +29 -17
- package/dist/components/AgentInterface.js.map +1 -1
- package/dist/components/ConsoleBlock.d.ts +1 -0
- package/dist/components/ConsoleBlock.d.ts.map +1 -1
- package/dist/components/ConsoleBlock.js +7 -1
- package/dist/components/ConsoleBlock.js.map +1 -1
- package/dist/components/ExpandableSection.d.ts +15 -0
- package/dist/components/ExpandableSection.d.ts.map +1 -0
- package/dist/components/ExpandableSection.js +63 -0
- package/dist/components/ExpandableSection.js.map +1 -0
- package/dist/components/MessageEditor.d.ts +8 -1
- package/dist/components/MessageEditor.d.ts.map +1 -1
- package/dist/components/MessageEditor.js +149 -6
- package/dist/components/MessageEditor.js.map +1 -1
- package/dist/components/MessageList.d.ts +3 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +14 -1
- package/dist/components/MessageList.js.map +1 -1
- package/dist/components/Messages.d.ts +15 -6
- package/dist/components/Messages.d.ts.map +1 -1
- package/dist/components/Messages.js +17 -83
- package/dist/components/Messages.js.map +1 -1
- package/dist/components/ProviderKeyInput.d.ts.map +1 -1
- package/dist/components/ProviderKeyInput.js +6 -5
- package/dist/components/ProviderKeyInput.js.map +1 -1
- package/dist/components/SandboxedIframe.d.ts +29 -7
- package/dist/components/SandboxedIframe.d.ts.map +1 -1
- package/dist/components/SandboxedIframe.js +350 -282
- package/dist/components/SandboxedIframe.js.map +1 -1
- package/dist/components/message-renderer-registry.d.ts +12 -0
- package/dist/components/message-renderer-registry.d.ts.map +1 -0
- package/dist/components/message-renderer-registry.js +12 -0
- package/dist/components/message-renderer-registry.js.map +1 -0
- package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts +35 -0
- package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts.map +1 -0
- package/dist/components/sandbox/ArtifactsRuntimeProvider.js +189 -0
- package/dist/components/sandbox/ArtifactsRuntimeProvider.js.map +1 -0
- package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts +17 -0
- package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts.map +1 -0
- package/dist/components/sandbox/AttachmentsRuntimeProvider.js +64 -0
- package/dist/components/sandbox/AttachmentsRuntimeProvider.js.map +1 -0
- package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts +42 -0
- package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts.map +1 -0
- package/dist/components/sandbox/ConsoleRuntimeProvider.js +161 -0
- package/dist/components/sandbox/ConsoleRuntimeProvider.js.map +1 -0
- package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts +30 -0
- package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts.map +1 -0
- package/dist/components/sandbox/FileDownloadRuntimeProvider.js +97 -0
- package/dist/components/sandbox/FileDownloadRuntimeProvider.js.map +1 -0
- package/dist/components/sandbox/RuntimeMessageBridge.d.ts +19 -0
- package/dist/components/sandbox/RuntimeMessageBridge.d.ts.map +1 -0
- package/dist/components/sandbox/RuntimeMessageBridge.js +74 -0
- package/dist/components/sandbox/RuntimeMessageBridge.js.map +1 -0
- package/dist/components/sandbox/RuntimeMessageRouter.d.ts +65 -0
- package/dist/components/sandbox/RuntimeMessageRouter.d.ts.map +1 -0
- package/dist/components/sandbox/RuntimeMessageRouter.js +168 -0
- package/dist/components/sandbox/RuntimeMessageRouter.js.map +1 -0
- package/dist/components/sandbox/SandboxRuntimeProvider.d.ts +33 -0
- package/dist/components/sandbox/SandboxRuntimeProvider.d.ts.map +1 -0
- package/dist/components/sandbox/SandboxRuntimeProvider.js +2 -0
- package/dist/components/sandbox/SandboxRuntimeProvider.js.map +1 -0
- package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -1
- package/dist/dialogs/ApiKeyPromptDialog.js +2 -5
- package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -1
- package/dist/dialogs/ModelSelector.js.map +1 -1
- package/dist/dialogs/PersistentStorageDialog.d.ts +17 -0
- package/dist/dialogs/PersistentStorageDialog.d.ts.map +1 -0
- package/dist/dialogs/PersistentStorageDialog.js +144 -0
- package/dist/dialogs/PersistentStorageDialog.js.map +1 -0
- package/dist/dialogs/SessionListDialog.d.ts +19 -0
- package/dist/dialogs/SessionListDialog.d.ts.map +1 -0
- package/dist/dialogs/SessionListDialog.js +152 -0
- package/dist/dialogs/SessionListDialog.js.map +1 -0
- package/dist/dialogs/SettingsDialog.d.ts.map +1 -1
- package/dist/dialogs/SettingsDialog.js +1 -0
- package/dist/dialogs/SettingsDialog.js.map +1 -1
- package/dist/index.d.ts +34 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -14
- package/dist/index.js.map +1 -1
- package/dist/prompts/prompts.d.ts +11 -0
- package/dist/prompts/prompts.d.ts.map +1 -0
- package/dist/prompts/prompts.js +272 -0
- package/dist/prompts/prompts.js.map +1 -0
- package/dist/storage/app-storage.d.ts +17 -12
- package/dist/storage/app-storage.d.ts.map +1 -1
- package/dist/storage/app-storage.js +13 -20
- package/dist/storage/app-storage.js.map +1 -1
- package/dist/storage/backends/indexeddb-storage-backend.d.ts +27 -0
- package/dist/storage/backends/indexeddb-storage-backend.d.ts.map +1 -0
- package/dist/storage/backends/indexeddb-storage-backend.js +166 -0
- package/dist/storage/backends/indexeddb-storage-backend.js.map +1 -0
- package/dist/storage/store.d.ts +23 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +26 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/storage/stores/provider-keys-store.d.ts +14 -0
- package/dist/storage/stores/provider-keys-store.d.ts.map +1 -0
- package/dist/storage/stores/provider-keys-store.js +27 -0
- package/dist/storage/stores/provider-keys-store.js.map +1 -0
- package/dist/storage/stores/sessions-store.d.ts +31 -0
- package/dist/storage/stores/sessions-store.d.ts.map +1 -0
- package/dist/storage/stores/sessions-store.js +113 -0
- package/dist/storage/stores/sessions-store.js.map +1 -0
- package/dist/storage/stores/settings-store.d.ts +14 -0
- package/dist/storage/stores/settings-store.d.ts.map +1 -0
- package/dist/storage/stores/settings-store.js +28 -0
- package/dist/storage/stores/settings-store.js.map +1 -0
- package/dist/storage/types.d.ts +156 -22
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/tools/artifacts/ArtifactElement.d.ts +0 -1
- package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -1
- package/dist/tools/artifacts/ArtifactElement.js +0 -1
- package/dist/tools/artifacts/ArtifactElement.js.map +1 -1
- package/dist/tools/artifacts/ArtifactPill.d.ts +4 -0
- package/dist/tools/artifacts/ArtifactPill.d.ts.map +1 -0
- package/dist/tools/artifacts/ArtifactPill.js +22 -0
- package/dist/tools/artifacts/ArtifactPill.js.map +1 -0
- package/dist/tools/artifacts/Console.d.ts +18 -0
- package/dist/tools/artifacts/Console.d.ts.map +1 -0
- package/dist/tools/artifacts/Console.js +95 -0
- package/dist/tools/artifacts/Console.js.map +1 -0
- package/dist/tools/artifacts/DocxArtifact.d.ts +22 -0
- package/dist/tools/artifacts/DocxArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/DocxArtifact.js +208 -0
- package/dist/tools/artifacts/DocxArtifact.js.map +1 -0
- package/dist/tools/artifacts/ExcelArtifact.d.ts +24 -0
- package/dist/tools/artifacts/ExcelArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/ExcelArtifact.js +216 -0
- package/dist/tools/artifacts/ExcelArtifact.js.map +1 -0
- package/dist/tools/artifacts/GenericArtifact.d.ts +19 -0
- package/dist/tools/artifacts/GenericArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/GenericArtifact.js +117 -0
- package/dist/tools/artifacts/GenericArtifact.js.map +1 -0
- package/dist/tools/artifacts/HtmlArtifact.d.ts +8 -11
- package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/HtmlArtifact.js +56 -88
- package/dist/tools/artifacts/HtmlArtifact.js.map +1 -1
- package/dist/tools/artifacts/ImageArtifact.d.ts +20 -0
- package/dist/tools/artifacts/ImageArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/ImageArtifact.js +120 -0
- package/dist/tools/artifacts/ImageArtifact.js.map +1 -0
- package/dist/tools/artifacts/MarkdownArtifact.d.ts +0 -1
- package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/MarkdownArtifact.js +0 -4
- package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -1
- package/dist/tools/artifacts/PdfArtifact.d.ts +25 -0
- package/dist/tools/artifacts/PdfArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/PdfArtifact.js +184 -0
- package/dist/tools/artifacts/PdfArtifact.js.map +1 -0
- package/dist/tools/artifacts/SvgArtifact.d.ts +0 -1
- package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/SvgArtifact.js +0 -4
- package/dist/tools/artifacts/SvgArtifact.js.map +1 -1
- package/dist/tools/artifacts/TextArtifact.d.ts +0 -1
- package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -1
- package/dist/tools/artifacts/TextArtifact.js +0 -4
- package/dist/tools/artifacts/TextArtifact.js.map +1 -1
- package/dist/tools/artifacts/artifacts-tool-renderer.d.ts +11 -0
- package/dist/tools/artifacts/artifacts-tool-renderer.d.ts.map +1 -0
- package/dist/tools/artifacts/artifacts-tool-renderer.js +262 -0
- package/dist/tools/artifacts/artifacts-tool-renderer.js.map +1 -0
- package/dist/tools/artifacts/artifacts.d.ts +10 -13
- package/dist/tools/artifacts/artifacts.d.ts.map +1 -1
- package/dist/tools/artifacts/artifacts.js +166 -344
- package/dist/tools/artifacts/artifacts.js.map +1 -1
- package/dist/tools/artifacts/index.d.ts +1 -0
- package/dist/tools/artifacts/index.d.ts.map +1 -1
- package/dist/tools/artifacts/index.js +1 -0
- package/dist/tools/artifacts/index.js.map +1 -1
- package/dist/tools/extract-document.d.ts +24 -0
- package/dist/tools/extract-document.d.ts.map +1 -0
- package/dist/tools/extract-document.js +193 -0
- package/dist/tools/extract-document.js.map +1 -0
- package/dist/tools/index.d.ts +9 -7
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +17 -13
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/javascript-repl.d.ts +16 -15
- package/dist/tools/javascript-repl.d.ts.map +1 -1
- package/dist/tools/javascript-repl.js +101 -133
- package/dist/tools/javascript-repl.js.map +1 -1
- package/dist/tools/renderer-registry.d.ts +12 -0
- package/dist/tools/renderer-registry.d.ts.map +1 -1
- package/dist/tools/renderer-registry.js +78 -0
- package/dist/tools/renderer-registry.js.map +1 -1
- package/dist/tools/renderers/BashRenderer.d.ts +2 -4
- package/dist/tools/renderers/BashRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/BashRenderer.js +30 -26
- package/dist/tools/renderers/BashRenderer.js.map +1 -1
- package/dist/tools/renderers/CalculateRenderer.d.ts +2 -4
- package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/CalculateRenderer.js +32 -28
- package/dist/tools/renderers/CalculateRenderer.js.map +1 -1
- package/dist/tools/renderers/DefaultRenderer.d.ts +2 -4
- package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/DefaultRenderer.js +78 -18
- package/dist/tools/renderers/DefaultRenderer.js.map +1 -1
- package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +2 -4
- package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -1
- package/dist/tools/renderers/GetCurrentTimeRenderer.js +57 -21
- package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -1
- package/dist/tools/types.d.ts +5 -2
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/utils/i18n.d.ts +424 -1
- package/dist/utils/i18n.d.ts.map +1 -1
- package/dist/utils/i18n.js +131 -7
- package/dist/utils/i18n.js.map +1 -1
- package/example/package.json +2 -1
- package/example/src/custom-messages.ts +112 -0
- package/example/src/main.ts +391 -38
- package/package.json +48 -43
- package/scripts/count-prompt-tokens.ts +88 -0
- package/src/ChatPanel.ts +93 -101
- package/src/{state/agent-session.ts → agent/agent.ts} +80 -55
- package/src/{state → agent}/transports/AppTransport.ts +6 -6
- package/src/{state → agent}/transports/ProviderTransport.ts +13 -7
- package/src/{state → agent}/transports/types.ts +8 -2
- package/src/components/AgentInterface.ts +32 -16
- package/src/components/ConsoleBlock.ts +5 -1
- package/src/components/ExpandableSection.ts +46 -0
- package/src/components/MessageEditor.ts +159 -5
- package/src/components/MessageList.ts +18 -3
- package/src/components/Messages.ts +48 -89
- package/src/components/ProviderKeyInput.ts +6 -5
- package/src/components/SandboxedIframe.ts +412 -321
- package/src/components/message-renderer-registry.ts +28 -0
- package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
- package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
- package/src/components/sandbox/ConsoleRuntimeProvider.ts +187 -0
- package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
- package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
- package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
- package/src/components/sandbox/SandboxRuntimeProvider.ts +35 -0
- package/src/dialogs/ApiKeyPromptDialog.ts +2 -5
- package/src/dialogs/ModelSelector.ts +2 -2
- package/src/dialogs/PersistentStorageDialog.ts +141 -0
- package/src/dialogs/SessionListDialog.ts +148 -0
- package/src/dialogs/SettingsDialog.ts +1 -0
- package/src/index.ts +61 -20
- package/src/prompts/prompts.ts +282 -0
- package/src/storage/app-storage.ts +27 -24
- package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
- package/src/storage/store.ts +33 -0
- package/src/storage/stores/provider-keys-store.ts +33 -0
- package/src/storage/stores/sessions-store.ts +130 -0
- package/src/storage/stores/settings-store.ts +34 -0
- package/src/storage/types.ts +182 -22
- package/src/tools/artifacts/ArtifactElement.ts +0 -1
- package/src/tools/artifacts/ArtifactPill.ts +25 -0
- package/src/tools/artifacts/Console.ts +93 -0
- package/src/tools/artifacts/DocxArtifact.ts +213 -0
- package/src/tools/artifacts/ExcelArtifact.ts +231 -0
- package/src/tools/artifacts/GenericArtifact.ts +117 -0
- package/src/tools/artifacts/HtmlArtifact.ts +64 -94
- package/src/tools/artifacts/ImageArtifact.ts +116 -0
- package/src/tools/artifacts/MarkdownArtifact.ts +0 -1
- package/src/tools/artifacts/PdfArtifact.ts +201 -0
- package/src/tools/artifacts/SvgArtifact.ts +0 -1
- package/src/tools/artifacts/TextArtifact.ts +0 -1
- package/src/tools/artifacts/artifacts-tool-renderer.ts +298 -0
- package/src/tools/artifacts/artifacts.ts +190 -366
- package/src/tools/artifacts/index.ts +1 -0
- package/src/tools/extract-document.ts +250 -0
- package/src/tools/index.ts +25 -14
- package/src/tools/javascript-repl.ts +138 -160
- package/src/tools/renderer-registry.ts +98 -0
- package/src/tools/renderers/BashRenderer.ts +33 -30
- package/src/tools/renderers/CalculateRenderer.ts +36 -31
- package/src/tools/renderers/DefaultRenderer.ts +84 -21
- package/src/tools/renderers/GetCurrentTimeRenderer.ts +68 -23
- package/src/tools/types.ts +10 -2
- package/src/utils/i18n.ts +203 -8
- package/dist/state/agent-session.d.ts.map +0 -1
- package/dist/state/agent-session.js.map +0 -1
- package/dist/state/transports/AppTransport.d.ts.map +0 -1
- package/dist/state/transports/ProviderTransport.d.ts.map +0 -1
- package/dist/state/transports/ProviderTransport.js.map +0 -1
- package/dist/state/transports/index.js.map +0 -1
- package/dist/state/transports/proxy-types.js.map +0 -1
- package/dist/state/transports/types.d.ts +0 -11
- package/dist/state/transports/types.d.ts.map +0 -1
- package/dist/storage/backends/chrome-storage-backend.d.ts +0 -18
- package/dist/storage/backends/chrome-storage-backend.d.ts.map +0 -1
- package/dist/storage/backends/chrome-storage-backend.js +0 -67
- package/dist/storage/backends/chrome-storage-backend.js.map +0 -1
- package/dist/storage/backends/indexeddb-backend.d.ts +0 -20
- package/dist/storage/backends/indexeddb-backend.d.ts.map +0 -1
- package/dist/storage/backends/indexeddb-backend.js +0 -89
- package/dist/storage/backends/indexeddb-backend.js.map +0 -1
- package/dist/storage/backends/local-storage-backend.d.ts +0 -18
- package/dist/storage/backends/local-storage-backend.d.ts.map +0 -1
- package/dist/storage/backends/local-storage-backend.js +0 -69
- package/dist/storage/backends/local-storage-backend.js.map +0 -1
- package/dist/storage/repositories/provider-keys-repository.d.ts +0 -34
- package/dist/storage/repositories/provider-keys-repository.d.ts.map +0 -1
- package/dist/storage/repositories/provider-keys-repository.js +0 -50
- package/dist/storage/repositories/provider-keys-repository.js.map +0 -1
- package/dist/storage/repositories/settings-repository.d.ts +0 -34
- package/dist/storage/repositories/settings-repository.d.ts.map +0 -1
- package/dist/storage/repositories/settings-repository.js +0 -46
- package/dist/storage/repositories/settings-repository.js.map +0 -1
- package/src/storage/backends/chrome-storage-backend.ts +0 -82
- package/src/storage/backends/indexeddb-backend.ts +0 -107
- package/src/storage/backends/local-storage-backend.ts +0 -74
- package/src/storage/repositories/provider-keys-repository.ts +0 -55
- package/src/storage/repositories/settings-repository.ts +0 -51
- /package/dist/{state → agent}/transports/index.d.ts +0 -0
- /package/dist/{state → agent}/transports/index.js +0 -0
- /package/dist/{state → agent}/transports/proxy-types.d.ts +0 -0
- /package/dist/{state → agent}/transports/proxy-types.js +0 -0
- /package/dist/{state → agent}/transports/types.js +0 -0
- /package/dist/{state → agent}/types.d.ts +0 -0
- /package/dist/{state → agent}/types.js +0 -0
- /package/src/{state → agent}/transports/index.ts +0 -0
- /package/src/{state → agent}/transports/proxy-types.ts +0 -0
- /package/src/{state → agent}/types.ts +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { LitElement } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
|
-
import
|
|
3
|
+
import { ConsoleRuntimeProvider } from "./sandbox/ConsoleRuntimeProvider.js";
|
|
4
|
+
import { RuntimeMessageBridge } from "./sandbox/RuntimeMessageBridge.js";
|
|
5
|
+
import { type MessageConsumer, RUNTIME_MESSAGE_ROUTER } from "./sandbox/RuntimeMessageRouter.js";
|
|
6
|
+
import type { SandboxRuntimeProvider } from "./sandbox/SandboxRuntimeProvider.js";
|
|
4
7
|
|
|
5
8
|
export interface SandboxFile {
|
|
6
9
|
fileName: string;
|
|
@@ -13,6 +16,7 @@ export interface SandboxResult {
|
|
|
13
16
|
console: Array<{ type: string; text: string }>;
|
|
14
17
|
files?: SandboxFile[];
|
|
15
18
|
error?: { message: string; stack: string };
|
|
19
|
+
returnValue?: any;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -21,6 +25,25 @@ export interface SandboxResult {
|
|
|
21
25
|
*/
|
|
22
26
|
export type SandboxUrlProvider = () => string;
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for prepareHtmlDocument
|
|
30
|
+
*/
|
|
31
|
+
export interface PrepareHtmlOptions {
|
|
32
|
+
/** True if this is an HTML artifact (inject into existing HTML), false if REPL (wrap in HTML) */
|
|
33
|
+
isHtmlArtifact: boolean;
|
|
34
|
+
/** True if this is a standalone download (no runtime bridge, no navigation interceptor) */
|
|
35
|
+
isStandalone?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Escape HTML special sequences in code to prevent premature tag closure
|
|
40
|
+
* @param code Code that will be injected into <script> tags
|
|
41
|
+
* @returns Escaped code safe for injection
|
|
42
|
+
*/
|
|
43
|
+
function escapeScriptContent(code: string): string {
|
|
44
|
+
return code.replace(/<\/script/gi, "<\\/script");
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
@customElement("sandbox-iframe")
|
|
25
48
|
export class SandboxIframe extends LitElement {
|
|
26
49
|
private iframe?: HTMLIFrameElement;
|
|
@@ -42,6 +65,9 @@ export class SandboxIframe extends LitElement {
|
|
|
42
65
|
|
|
43
66
|
override disconnectedCallback() {
|
|
44
67
|
super.disconnectedCallback();
|
|
68
|
+
// Note: We don't unregister the sandbox here for loadContent() mode
|
|
69
|
+
// because the caller (HtmlArtifact) owns the sandbox lifecycle.
|
|
70
|
+
// For execute() mode, the sandbox is unregistered in the cleanup function.
|
|
45
71
|
this.iframe?.remove();
|
|
46
72
|
}
|
|
47
73
|
|
|
@@ -49,65 +75,157 @@ export class SandboxIframe extends LitElement {
|
|
|
49
75
|
* Load HTML content into sandbox and keep it displayed (for HTML artifacts)
|
|
50
76
|
* @param sandboxId Unique ID
|
|
51
77
|
* @param htmlContent Full HTML content
|
|
52
|
-
* @param
|
|
78
|
+
* @param providers Runtime providers to inject
|
|
79
|
+
* @param consumers Message consumers to register (optional)
|
|
53
80
|
*/
|
|
54
|
-
public loadContent(
|
|
55
|
-
|
|
81
|
+
public loadContent(
|
|
82
|
+
sandboxId: string,
|
|
83
|
+
htmlContent: string,
|
|
84
|
+
providers: SandboxRuntimeProvider[] = [],
|
|
85
|
+
consumers: MessageConsumer[] = [],
|
|
86
|
+
): void {
|
|
87
|
+
// Unregister previous sandbox if exists
|
|
88
|
+
try {
|
|
89
|
+
RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
|
|
90
|
+
} catch {
|
|
91
|
+
// Sandbox might not exist, that's ok
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
providers = [new ConsoleRuntimeProvider(), ...providers];
|
|
95
|
+
|
|
96
|
+
RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
|
|
97
|
+
|
|
98
|
+
// loadContent is always used for HTML artifacts (not standalone)
|
|
99
|
+
const completeHtml = this.prepareHtmlDocument(sandboxId, htmlContent, providers, {
|
|
100
|
+
isHtmlArtifact: true,
|
|
101
|
+
isStandalone: false,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Validate HTML before loading
|
|
105
|
+
const validationError = this.validateHtml(completeHtml);
|
|
106
|
+
if (validationError) {
|
|
107
|
+
console.error("HTML validation failed:", validationError);
|
|
108
|
+
// Show error in iframe instead of crashing
|
|
109
|
+
this.iframe?.remove();
|
|
110
|
+
this.iframe = document.createElement("iframe");
|
|
111
|
+
this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
112
|
+
this.iframe.srcdoc = `
|
|
113
|
+
<html>
|
|
114
|
+
<body style="font-family: monospace; padding: 20px; background: #fff; color: #000;">
|
|
115
|
+
<h3 style="color: #c00;">HTML Validation Error</h3>
|
|
116
|
+
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap;">${validationError}</pre>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
|
119
|
+
`;
|
|
120
|
+
this.appendChild(this.iframe);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Remove previous iframe if exists
|
|
125
|
+
this.iframe?.remove();
|
|
56
126
|
|
|
57
127
|
if (this.sandboxUrlProvider) {
|
|
58
128
|
// Browser extension mode: use sandbox.html with postMessage
|
|
59
|
-
this.loadViaSandboxUrl(sandboxId, completeHtml
|
|
129
|
+
this.loadViaSandboxUrl(sandboxId, completeHtml);
|
|
60
130
|
} else {
|
|
61
131
|
// Web mode: use srcdoc
|
|
62
|
-
this.loadViaSrcdoc(completeHtml);
|
|
132
|
+
this.loadViaSrcdoc(sandboxId, completeHtml);
|
|
63
133
|
}
|
|
64
134
|
}
|
|
65
135
|
|
|
66
|
-
private loadViaSandboxUrl(sandboxId: string, completeHtml: string
|
|
67
|
-
//
|
|
136
|
+
private loadViaSandboxUrl(sandboxId: string, completeHtml: string): void {
|
|
137
|
+
// Create iframe pointing to sandbox URL
|
|
138
|
+
this.iframe = document.createElement("iframe");
|
|
139
|
+
this.iframe.sandbox.add("allow-scripts");
|
|
140
|
+
this.iframe.sandbox.add("allow-modals");
|
|
141
|
+
this.iframe.style.width = "100%";
|
|
142
|
+
this.iframe.style.height = "100%";
|
|
143
|
+
this.iframe.style.border = "none";
|
|
144
|
+
this.iframe.src = this.sandboxUrlProvider!();
|
|
145
|
+
|
|
146
|
+
// Update router with iframe reference BEFORE appending to DOM
|
|
147
|
+
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
|
148
|
+
|
|
149
|
+
// Listen for open-external-url messages from iframe
|
|
150
|
+
const externalUrlHandler = (e: MessageEvent) => {
|
|
151
|
+
if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
|
|
152
|
+
// Use chrome.tabs API to open in new tab
|
|
153
|
+
const chromeAPI = (globalThis as any).chrome;
|
|
154
|
+
if (chromeAPI?.tabs) {
|
|
155
|
+
chromeAPI.tabs.create({ url: e.data.url });
|
|
156
|
+
} else {
|
|
157
|
+
// Fallback for non-extension context
|
|
158
|
+
window.open(e.data.url, "_blank");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
window.addEventListener("message", externalUrlHandler);
|
|
163
|
+
|
|
164
|
+
// Listen for sandbox-ready and sandbox-error messages directly
|
|
68
165
|
const readyHandler = (e: MessageEvent) => {
|
|
69
166
|
if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
|
|
70
167
|
window.removeEventListener("message", readyHandler);
|
|
168
|
+
window.removeEventListener("message", errorHandler);
|
|
169
|
+
|
|
170
|
+
// Send content to sandbox
|
|
71
171
|
this.iframe?.contentWindow?.postMessage(
|
|
72
172
|
{
|
|
73
173
|
type: "sandbox-load",
|
|
74
174
|
sandboxId,
|
|
75
175
|
code: completeHtml,
|
|
76
|
-
attachments,
|
|
77
176
|
},
|
|
78
177
|
"*",
|
|
79
178
|
);
|
|
80
179
|
}
|
|
81
180
|
};
|
|
82
|
-
window.addEventListener("message", readyHandler);
|
|
83
181
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.iframe.sandbox.add("allow-modals");
|
|
89
|
-
this.iframe.style.width = "100%";
|
|
90
|
-
this.iframe.style.height = "100%";
|
|
91
|
-
this.iframe.style.border = "none";
|
|
182
|
+
const errorHandler = (e: MessageEvent) => {
|
|
183
|
+
if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
|
|
184
|
+
window.removeEventListener("message", readyHandler);
|
|
185
|
+
window.removeEventListener("message", errorHandler);
|
|
92
186
|
|
|
93
|
-
|
|
187
|
+
// The sandbox.js already sent us the error via postMessage.
|
|
188
|
+
// We need to convert it to an execution-error message that the execute() consumer will handle.
|
|
189
|
+
// Simulate receiving an execution-error from the sandbox
|
|
190
|
+
window.postMessage(
|
|
191
|
+
{
|
|
192
|
+
sandboxId: sandboxId,
|
|
193
|
+
type: "execution-error",
|
|
194
|
+
error: { message: e.data.error, stack: e.data.stack },
|
|
195
|
+
},
|
|
196
|
+
"*",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
window.addEventListener("message", readyHandler);
|
|
202
|
+
window.addEventListener("message", errorHandler);
|
|
94
203
|
|
|
95
204
|
this.appendChild(this.iframe);
|
|
96
205
|
}
|
|
97
206
|
|
|
98
|
-
private loadViaSrcdoc(completeHtml: string): void {
|
|
99
|
-
//
|
|
100
|
-
this.iframe?.remove();
|
|
207
|
+
private loadViaSrcdoc(sandboxId: string, completeHtml: string): void {
|
|
208
|
+
// Create iframe with srcdoc
|
|
101
209
|
this.iframe = document.createElement("iframe");
|
|
102
210
|
this.iframe.sandbox.add("allow-scripts");
|
|
103
211
|
this.iframe.sandbox.add("allow-modals");
|
|
104
212
|
this.iframe.style.width = "100%";
|
|
105
213
|
this.iframe.style.height = "100%";
|
|
106
214
|
this.iframe.style.border = "none";
|
|
107
|
-
|
|
108
|
-
// Set content directly via srcdoc (no CSP restrictions in web apps)
|
|
109
215
|
this.iframe.srcdoc = completeHtml;
|
|
110
216
|
|
|
217
|
+
// Update router with iframe reference BEFORE appending to DOM
|
|
218
|
+
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
|
219
|
+
|
|
220
|
+
// Listen for open-external-url messages from iframe
|
|
221
|
+
const externalUrlHandler = (e: MessageEvent) => {
|
|
222
|
+
if (e.data.type === "open-external-url" && e.source === this.iframe?.contentWindow) {
|
|
223
|
+
// Fallback for non-extension context
|
|
224
|
+
window.open(e.data.url, "_blank");
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
window.addEventListener("message", externalUrlHandler);
|
|
228
|
+
|
|
111
229
|
this.appendChild(this.iframe);
|
|
112
230
|
}
|
|
113
231
|
|
|
@@ -115,159 +233,215 @@ export class SandboxIframe extends LitElement {
|
|
|
115
233
|
* Execute code in sandbox
|
|
116
234
|
* @param sandboxId Unique ID for this execution
|
|
117
235
|
* @param code User code (plain JS for REPL, or full HTML for artifacts)
|
|
118
|
-
* @param
|
|
236
|
+
* @param providers Runtime providers to inject
|
|
237
|
+
* @param consumers Additional message consumers (optional, execute has its own internal consumer)
|
|
119
238
|
* @param signal Abort signal
|
|
120
239
|
* @returns Promise resolving to execution result
|
|
121
240
|
*/
|
|
122
241
|
public async execute(
|
|
123
242
|
sandboxId: string,
|
|
124
243
|
code: string,
|
|
125
|
-
|
|
244
|
+
providers: SandboxRuntimeProvider[] = [],
|
|
245
|
+
consumers: MessageConsumer[] = [],
|
|
126
246
|
signal?: AbortSignal,
|
|
247
|
+
isHtmlArtifact: boolean = false,
|
|
127
248
|
): Promise<SandboxResult> {
|
|
128
249
|
if (signal?.aborted) {
|
|
129
250
|
throw new Error("Execution aborted");
|
|
130
251
|
}
|
|
131
252
|
|
|
132
|
-
|
|
133
|
-
|
|
253
|
+
const consoleProvider = new ConsoleRuntimeProvider();
|
|
254
|
+
providers = [consoleProvider, ...providers];
|
|
255
|
+
RUNTIME_MESSAGE_ROUTER.registerSandbox(sandboxId, providers, consumers);
|
|
256
|
+
|
|
257
|
+
const files: SandboxFile[] = [];
|
|
258
|
+
let completed = false;
|
|
134
259
|
|
|
135
|
-
// Wait for execution to complete
|
|
136
260
|
return new Promise((resolve, reject) => {
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console: logs,
|
|
162
|
-
files: files,
|
|
163
|
-
});
|
|
164
|
-
} else if (e.data.type === "execution-error") {
|
|
165
|
-
completed = true;
|
|
166
|
-
cleanup();
|
|
167
|
-
resolve({
|
|
168
|
-
success: false,
|
|
169
|
-
console: logs,
|
|
170
|
-
error: e.data.error,
|
|
171
|
-
files,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
261
|
+
// 4. Create execution consumer for lifecycle messages
|
|
262
|
+
const executionConsumer: MessageConsumer = {
|
|
263
|
+
async handleMessage(message: any): Promise<void> {
|
|
264
|
+
if (message.type === "file-returned") {
|
|
265
|
+
files.push({
|
|
266
|
+
fileName: message.fileName,
|
|
267
|
+
content: message.content,
|
|
268
|
+
mimeType: message.mimeType,
|
|
269
|
+
});
|
|
270
|
+
} else if (message.type === "execution-complete") {
|
|
271
|
+
completed = true;
|
|
272
|
+
cleanup();
|
|
273
|
+
resolve({
|
|
274
|
+
success: true,
|
|
275
|
+
console: consoleProvider.getLogs(),
|
|
276
|
+
files,
|
|
277
|
+
returnValue: message.returnValue,
|
|
278
|
+
});
|
|
279
|
+
} else if (message.type === "execution-error") {
|
|
280
|
+
completed = true;
|
|
281
|
+
cleanup();
|
|
282
|
+
resolve({ success: false, console: consoleProvider.getLogs(), error: message.error, files });
|
|
283
|
+
}
|
|
284
|
+
},
|
|
174
285
|
};
|
|
175
286
|
|
|
287
|
+
RUNTIME_MESSAGE_ROUTER.addConsumer(sandboxId, executionConsumer);
|
|
288
|
+
|
|
289
|
+
const cleanup = () => {
|
|
290
|
+
RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
|
|
291
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
292
|
+
clearTimeout(timeoutId);
|
|
293
|
+
this.iframe?.remove();
|
|
294
|
+
this.iframe = undefined;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Abort handler
|
|
176
298
|
const abortHandler = () => {
|
|
177
299
|
if (!completed) {
|
|
300
|
+
completed = true;
|
|
178
301
|
cleanup();
|
|
179
302
|
reject(new Error("Execution aborted"));
|
|
180
303
|
}
|
|
181
304
|
};
|
|
182
305
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
window.removeEventListener("message", messageHandler);
|
|
187
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
188
|
-
if (readyHandler) {
|
|
189
|
-
window.removeEventListener("message", readyHandler);
|
|
190
|
-
}
|
|
191
|
-
clearTimeout(timeoutId);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Set up listeners BEFORE creating iframe
|
|
195
|
-
window.addEventListener("message", messageHandler);
|
|
196
|
-
signal?.addEventListener("abort", abortHandler);
|
|
306
|
+
if (signal) {
|
|
307
|
+
signal.addEventListener("abort", abortHandler);
|
|
308
|
+
}
|
|
197
309
|
|
|
198
|
-
// Timeout
|
|
310
|
+
// Timeout handler (30 seconds)
|
|
199
311
|
const timeoutId = setTimeout(() => {
|
|
200
312
|
if (!completed) {
|
|
313
|
+
completed = true;
|
|
201
314
|
cleanup();
|
|
202
315
|
resolve({
|
|
203
316
|
success: false,
|
|
204
|
-
|
|
205
|
-
|
|
317
|
+
console: consoleProvider.getLogs(),
|
|
318
|
+
error: { message: "Execution timeout (120s)", stack: "" },
|
|
206
319
|
files,
|
|
207
320
|
});
|
|
208
321
|
}
|
|
209
|
-
},
|
|
322
|
+
}, 120000);
|
|
323
|
+
|
|
324
|
+
// 4. Prepare HTML and create iframe
|
|
325
|
+
const completeHtml = this.prepareHtmlDocument(sandboxId, code, providers, {
|
|
326
|
+
isHtmlArtifact,
|
|
327
|
+
isStandalone: false,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// 5. Validate HTML before sending to sandbox
|
|
331
|
+
const validationError = this.validateHtml(completeHtml);
|
|
332
|
+
if (validationError) {
|
|
333
|
+
reject(new Error(`HTML validation failed: ${validationError}`));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
210
336
|
|
|
211
337
|
if (this.sandboxUrlProvider) {
|
|
212
|
-
// Browser extension mode: wait for sandbox-ready
|
|
213
|
-
|
|
338
|
+
// Browser extension mode: wait for sandbox-ready
|
|
339
|
+
this.iframe = document.createElement("iframe");
|
|
340
|
+
this.iframe.sandbox.add("allow-scripts", "allow-modals");
|
|
341
|
+
this.iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
342
|
+
this.iframe.src = this.sandboxUrlProvider();
|
|
343
|
+
|
|
344
|
+
// Update router with iframe reference BEFORE appending to DOM
|
|
345
|
+
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
|
346
|
+
|
|
347
|
+
// Listen for sandbox-ready and sandbox-error messages
|
|
348
|
+
const readyHandler = (e: MessageEvent) => {
|
|
214
349
|
if (e.data.type === "sandbox-ready" && e.source === this.iframe?.contentWindow) {
|
|
215
|
-
window.removeEventListener("message", readyHandler
|
|
216
|
-
|
|
350
|
+
window.removeEventListener("message", readyHandler);
|
|
351
|
+
window.removeEventListener("message", errorHandler);
|
|
352
|
+
|
|
353
|
+
// Send content to sandbox
|
|
217
354
|
this.iframe?.contentWindow?.postMessage(
|
|
218
355
|
{
|
|
219
356
|
type: "sandbox-load",
|
|
220
357
|
sandboxId,
|
|
221
358
|
code: completeHtml,
|
|
222
|
-
attachments,
|
|
223
359
|
},
|
|
224
360
|
"*",
|
|
225
361
|
);
|
|
226
362
|
}
|
|
227
363
|
};
|
|
228
|
-
window.addEventListener("message", readyHandler);
|
|
229
364
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.iframe.sandbox.add("allow-modals");
|
|
235
|
-
this.iframe.style.width = "100%";
|
|
236
|
-
this.iframe.style.height = "100%";
|
|
237
|
-
this.iframe.style.border = "none";
|
|
365
|
+
const errorHandler = (e: MessageEvent) => {
|
|
366
|
+
if (e.data.type === "sandbox-error" && e.source === this.iframe?.contentWindow) {
|
|
367
|
+
window.removeEventListener("message", readyHandler);
|
|
368
|
+
window.removeEventListener("message", errorHandler);
|
|
238
369
|
|
|
239
|
-
|
|
370
|
+
// Convert sandbox-error to execution-error for the execution consumer
|
|
371
|
+
window.postMessage(
|
|
372
|
+
{
|
|
373
|
+
sandboxId: sandboxId,
|
|
374
|
+
type: "execution-error",
|
|
375
|
+
error: { message: e.data.error, stack: e.data.stack },
|
|
376
|
+
},
|
|
377
|
+
"*",
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
window.addEventListener("message", readyHandler);
|
|
383
|
+
window.addEventListener("message", errorHandler);
|
|
240
384
|
|
|
241
385
|
this.appendChild(this.iframe);
|
|
242
386
|
} else {
|
|
243
387
|
// Web mode: use srcdoc
|
|
244
|
-
this.iframe?.remove();
|
|
245
388
|
this.iframe = document.createElement("iframe");
|
|
246
|
-
this.iframe.sandbox.add("allow-scripts");
|
|
247
|
-
this.iframe.
|
|
248
|
-
this.iframe.style.width = "100%";
|
|
249
|
-
this.iframe.style.height = "100%";
|
|
250
|
-
this.iframe.style.border = "none";
|
|
251
|
-
|
|
252
|
-
// Set content via srcdoc BEFORE appending to DOM
|
|
389
|
+
this.iframe.sandbox.add("allow-scripts", "allow-modals");
|
|
390
|
+
this.iframe.style.cssText = "width: 100%; height: 100%; border: none; display: none;";
|
|
253
391
|
this.iframe.srcdoc = completeHtml;
|
|
254
392
|
|
|
393
|
+
// Update router with iframe reference BEFORE appending to DOM
|
|
394
|
+
RUNTIME_MESSAGE_ROUTER.setSandboxIframe(sandboxId, this.iframe);
|
|
395
|
+
|
|
255
396
|
this.appendChild(this.iframe);
|
|
256
397
|
}
|
|
257
398
|
});
|
|
258
399
|
}
|
|
259
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Validate HTML using DOMParser - returns error message if invalid, null if valid
|
|
403
|
+
* Note: JavaScript syntax validation is done in sandbox.js to avoid CSP restrictions
|
|
404
|
+
*/
|
|
405
|
+
private validateHtml(html: string): string | null {
|
|
406
|
+
try {
|
|
407
|
+
const parser = new DOMParser();
|
|
408
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
409
|
+
|
|
410
|
+
// Check for parser errors
|
|
411
|
+
const parserError = doc.querySelector("parsererror");
|
|
412
|
+
if (parserError) {
|
|
413
|
+
return parserError.textContent || "Unknown parse error";
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return null;
|
|
417
|
+
} catch (error: any) {
|
|
418
|
+
return error.message || "Unknown validation error";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
260
422
|
/**
|
|
261
423
|
* Prepare complete HTML document with runtime + user code
|
|
424
|
+
* PUBLIC so HtmlArtifact can use it for download button
|
|
262
425
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
426
|
+
public prepareHtmlDocument(
|
|
427
|
+
sandboxId: string,
|
|
428
|
+
userCode: string,
|
|
429
|
+
providers: SandboxRuntimeProvider[] = [],
|
|
430
|
+
options?: PrepareHtmlOptions,
|
|
431
|
+
): string {
|
|
432
|
+
// Default options
|
|
433
|
+
const opts: PrepareHtmlOptions = {
|
|
434
|
+
isHtmlArtifact: false,
|
|
435
|
+
isStandalone: false,
|
|
436
|
+
...options,
|
|
437
|
+
};
|
|
266
438
|
|
|
267
|
-
//
|
|
268
|
-
const
|
|
439
|
+
// Runtime script that will be injected
|
|
440
|
+
const runtime = this.getRuntimeScript(sandboxId, providers, opts.isStandalone || false);
|
|
269
441
|
|
|
270
|
-
if
|
|
442
|
+
// Only check for HTML tags if explicitly marked as HTML artifact
|
|
443
|
+
// For javascript_repl, userCode is JavaScript that may contain HTML in string literals
|
|
444
|
+
if (opts.isHtmlArtifact) {
|
|
271
445
|
// HTML Artifact - inject runtime into existing HTML
|
|
272
446
|
const headMatch = userCode.match(/<head[^>]*>/i);
|
|
273
447
|
if (headMatch) {
|
|
@@ -285,6 +459,9 @@ export class SandboxIframe extends LitElement {
|
|
|
285
459
|
return runtime + userCode;
|
|
286
460
|
} else {
|
|
287
461
|
// REPL - wrap code in HTML with runtime and call complete() when done
|
|
462
|
+
// Escape </script> in user code to prevent premature tag closure
|
|
463
|
+
const escapedUserCode = escapeScriptContent(userCode);
|
|
464
|
+
|
|
288
465
|
return `<!DOCTYPE html>
|
|
289
466
|
<html>
|
|
290
467
|
<head>
|
|
@@ -294,11 +471,35 @@ export class SandboxIframe extends LitElement {
|
|
|
294
471
|
<script type="module">
|
|
295
472
|
(async () => {
|
|
296
473
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
474
|
+
// Wrap user code in async function to capture return value
|
|
475
|
+
const userCodeFunc = async () => {
|
|
476
|
+
${escapedUserCode}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const returnValue = await userCodeFunc();
|
|
480
|
+
|
|
481
|
+
// Call completion callbacks before complete()
|
|
482
|
+
if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
|
|
483
|
+
try {
|
|
484
|
+
await Promise.all(window.__completionCallbacks.map(cb => cb(true)));
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.error('Completion callback error:', e);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await window.complete(null, returnValue);
|
|
299
491
|
} catch (error) {
|
|
300
|
-
|
|
301
|
-
|
|
492
|
+
|
|
493
|
+
// Call completion callbacks before complete() (error path)
|
|
494
|
+
if (window.__completionCallbacks && window.__completionCallbacks.length > 0) {
|
|
495
|
+
try {
|
|
496
|
+
await Promise.all(window.__completionCallbacks.map(cb => cb(false)));
|
|
497
|
+
} catch (e) {
|
|
498
|
+
console.error('Completion callback error:', e);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
await window.complete({
|
|
302
503
|
message: error?.message || String(error),
|
|
303
504
|
stack: error?.stack || new Error().stack
|
|
304
505
|
});
|
|
@@ -311,215 +512,105 @@ export class SandboxIframe extends LitElement {
|
|
|
311
512
|
}
|
|
312
513
|
|
|
313
514
|
/**
|
|
314
|
-
*
|
|
515
|
+
* Generate runtime script from providers
|
|
516
|
+
* @param sandboxId Unique sandbox ID
|
|
517
|
+
* @param providers Runtime providers
|
|
518
|
+
* @param isStandalone If true, skip runtime bridge and navigation interceptor (for standalone downloads)
|
|
315
519
|
*/
|
|
316
|
-
private getRuntimeScript(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Runtime function that will run in the sandbox (NO parameters - values injected before function)
|
|
328
|
-
const runtimeFunc = () => {
|
|
329
|
-
// Helper functions
|
|
330
|
-
(window as any).listFiles = () =>
|
|
331
|
-
(attachments || []).map((a: any) => ({
|
|
332
|
-
id: a.id,
|
|
333
|
-
fileName: a.fileName,
|
|
334
|
-
mimeType: a.mimeType,
|
|
335
|
-
size: a.size,
|
|
336
|
-
}));
|
|
337
|
-
|
|
338
|
-
(window as any).readTextFile = (attachmentId: string) => {
|
|
339
|
-
const a = (attachments || []).find((x: any) => x.id === attachmentId);
|
|
340
|
-
if (!a) throw new Error("Attachment not found: " + attachmentId);
|
|
341
|
-
if (a.extractedText) return a.extractedText;
|
|
342
|
-
try {
|
|
343
|
-
return atob(a.content);
|
|
344
|
-
} catch {
|
|
345
|
-
throw new Error("Failed to decode text content for: " + attachmentId);
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
(window as any).readBinaryFile = (attachmentId: string) => {
|
|
350
|
-
const a = (attachments || []).find((x: any) => x.id === attachmentId);
|
|
351
|
-
if (!a) throw new Error("Attachment not found: " + attachmentId);
|
|
352
|
-
const bin = atob(a.content);
|
|
353
|
-
const bytes = new Uint8Array(bin.length);
|
|
354
|
-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
355
|
-
return bytes;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
(window as any).returnFile = async (fileName: string, content: any, mimeType?: string) => {
|
|
359
|
-
let finalContent: any, finalMimeType: string;
|
|
360
|
-
|
|
361
|
-
if (content instanceof Blob) {
|
|
362
|
-
const arrayBuffer = await content.arrayBuffer();
|
|
363
|
-
finalContent = new Uint8Array(arrayBuffer);
|
|
364
|
-
finalMimeType = mimeType || content.type || "application/octet-stream";
|
|
365
|
-
if (!mimeType && !content.type) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
"returnFile: MIME type is required for Blob content. Please provide a mimeType parameter (e.g., 'image/png').",
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
} else if (content instanceof Uint8Array) {
|
|
371
|
-
finalContent = content;
|
|
372
|
-
if (!mimeType) {
|
|
373
|
-
throw new Error(
|
|
374
|
-
"returnFile: MIME type is required for Uint8Array content. Please provide a mimeType parameter (e.g., 'image/png').",
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
finalMimeType = mimeType;
|
|
378
|
-
} else if (typeof content === "string") {
|
|
379
|
-
finalContent = content;
|
|
380
|
-
finalMimeType = mimeType || "text/plain";
|
|
381
|
-
} else {
|
|
382
|
-
finalContent = JSON.stringify(content, null, 2);
|
|
383
|
-
finalMimeType = mimeType || "application/json";
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
window.parent.postMessage(
|
|
387
|
-
{
|
|
388
|
-
type: "file-returned",
|
|
389
|
-
sandboxId,
|
|
390
|
-
fileName,
|
|
391
|
-
content: finalContent,
|
|
392
|
-
mimeType: finalMimeType,
|
|
393
|
-
},
|
|
394
|
-
"*",
|
|
395
|
-
);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
// Console capture
|
|
399
|
-
const originalConsole = {
|
|
400
|
-
log: console.log,
|
|
401
|
-
error: console.error,
|
|
402
|
-
warn: console.warn,
|
|
403
|
-
info: console.info,
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
["log", "error", "warn", "info"].forEach((method) => {
|
|
407
|
-
(console as any)[method] = (...args: any[]) => {
|
|
408
|
-
const text = args
|
|
409
|
-
.map((arg) => {
|
|
410
|
-
try {
|
|
411
|
-
return typeof arg === "object" ? JSON.stringify(arg) : String(arg);
|
|
412
|
-
} catch {
|
|
413
|
-
return String(arg);
|
|
414
|
-
}
|
|
415
|
-
})
|
|
416
|
-
.join(" ");
|
|
417
|
-
|
|
418
|
-
window.parent.postMessage(
|
|
419
|
-
{
|
|
420
|
-
type: "console",
|
|
421
|
-
sandboxId,
|
|
422
|
-
method,
|
|
423
|
-
text,
|
|
424
|
-
},
|
|
425
|
-
"*",
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
(originalConsole as any)[method].apply(console, args);
|
|
429
|
-
};
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Track errors for HTML artifacts
|
|
433
|
-
let lastError: { message: string; stack: string } | null = null;
|
|
434
|
-
|
|
435
|
-
// Error handlers
|
|
436
|
-
window.addEventListener("error", (e) => {
|
|
437
|
-
const text =
|
|
438
|
-
(e.error?.stack || e.message || String(e)) + " at line " + (e.lineno || "?") + ":" + (e.colno || "?");
|
|
439
|
-
|
|
440
|
-
// Store the error
|
|
441
|
-
lastError = {
|
|
442
|
-
message: e.error?.message || e.message || String(e),
|
|
443
|
-
stack: e.error?.stack || text,
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
window.parent.postMessage(
|
|
447
|
-
{
|
|
448
|
-
type: "console",
|
|
449
|
-
sandboxId,
|
|
450
|
-
method: "error",
|
|
451
|
-
text,
|
|
452
|
-
},
|
|
453
|
-
"*",
|
|
454
|
-
);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
window.addEventListener("unhandledrejection", (e) => {
|
|
458
|
-
const text = "Unhandled promise rejection: " + (e.reason?.message || e.reason || "Unknown error");
|
|
459
|
-
|
|
460
|
-
// Store the error
|
|
461
|
-
lastError = {
|
|
462
|
-
message: e.reason?.message || String(e.reason) || "Unhandled promise rejection",
|
|
463
|
-
stack: e.reason?.stack || text,
|
|
464
|
-
};
|
|
520
|
+
private getRuntimeScript(
|
|
521
|
+
sandboxId: string,
|
|
522
|
+
providers: SandboxRuntimeProvider[] = [],
|
|
523
|
+
isStandalone: boolean = false,
|
|
524
|
+
): string {
|
|
525
|
+
// Collect all data from providers
|
|
526
|
+
const allData: Record<string, any> = {};
|
|
527
|
+
for (const provider of providers) {
|
|
528
|
+
Object.assign(allData, provider.getData());
|
|
529
|
+
}
|
|
465
530
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
"*",
|
|
474
|
-
);
|
|
475
|
-
});
|
|
531
|
+
// Generate bridge code (skip if standalone)
|
|
532
|
+
const bridgeCode = isStandalone
|
|
533
|
+
? ""
|
|
534
|
+
: RuntimeMessageBridge.generateBridgeCode({
|
|
535
|
+
context: "sandbox-iframe",
|
|
536
|
+
sandboxId,
|
|
537
|
+
});
|
|
476
538
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// Use provided error or last caught error
|
|
484
|
-
const finalError = error || lastError;
|
|
485
|
-
|
|
486
|
-
if (finalError) {
|
|
487
|
-
window.parent.postMessage(
|
|
488
|
-
{
|
|
489
|
-
type: "execution-error",
|
|
490
|
-
sandboxId,
|
|
491
|
-
error: finalError,
|
|
492
|
-
},
|
|
493
|
-
"*",
|
|
494
|
-
);
|
|
495
|
-
} else {
|
|
496
|
-
window.parent.postMessage(
|
|
497
|
-
{
|
|
498
|
-
type: "execution-complete",
|
|
499
|
-
sandboxId,
|
|
500
|
-
},
|
|
501
|
-
"*",
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
};
|
|
539
|
+
// Collect all runtime functions - pass sandboxId as string literal
|
|
540
|
+
const runtimeFunctions: string[] = [];
|
|
541
|
+
for (const provider of providers) {
|
|
542
|
+
runtimeFunctions.push(`(${provider.getRuntime().toString()})(${JSON.stringify(sandboxId)});`);
|
|
543
|
+
}
|
|
505
544
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
545
|
+
// Build script with HTML escaping
|
|
546
|
+
// Escape </script> to prevent premature tag closure in HTML parser
|
|
547
|
+
const dataInjection = Object.entries(allData)
|
|
548
|
+
.map(([key, value]) => {
|
|
549
|
+
const jsonStr = JSON.stringify(value).replace(/<\/script/gi, "<\\/script");
|
|
550
|
+
return `window.${key} = ${jsonStr};`;
|
|
551
|
+
})
|
|
552
|
+
.join("\n");
|
|
553
|
+
|
|
554
|
+
// TODO the font-size is needed, as chrome seems to inject a stylesheet into iframes
|
|
555
|
+
// found in an extension context like sidepanel, settin body { font-size: 75% }. It's
|
|
556
|
+
// definitely not our code doing that.
|
|
557
|
+
// See https://stackoverflow.com/questions/71480433/chrome-is-injecting-some-stylesheet-in-popup-ui-which-reduces-the-font-size-to-7
|
|
558
|
+
|
|
559
|
+
// Navigation interceptor (only if NOT standalone)
|
|
560
|
+
const navigationInterceptor = isStandalone
|
|
561
|
+
? ""
|
|
562
|
+
: `
|
|
563
|
+
// Navigation interceptor: prevent all navigation and open externally
|
|
564
|
+
(function() {
|
|
565
|
+
// Intercept link clicks
|
|
566
|
+
document.addEventListener('click', function(e) {
|
|
567
|
+
const link = e.target.closest('a');
|
|
568
|
+
if (link && link.href) {
|
|
569
|
+
// Check if it's an external link (not javascript: or #hash)
|
|
570
|
+
if (link.href.startsWith('http://') || link.href.startsWith('https://')) {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
e.stopPropagation();
|
|
573
|
+
window.parent.postMessage({ type: 'open-external-url', url: link.href }, '*');
|
|
513
574
|
}
|
|
514
|
-
}
|
|
575
|
+
}
|
|
576
|
+
}, true);
|
|
577
|
+
|
|
578
|
+
// Intercept form submissions
|
|
579
|
+
document.addEventListener('submit', function(e) {
|
|
580
|
+
const form = e.target;
|
|
581
|
+
if (form && form.action) {
|
|
582
|
+
e.preventDefault();
|
|
583
|
+
e.stopPropagation();
|
|
584
|
+
window.parent.postMessage({ type: 'open-external-url', url: form.action }, '*');
|
|
585
|
+
}
|
|
586
|
+
}, true);
|
|
587
|
+
|
|
588
|
+
// Prevent window.location changes (only if not already redefined)
|
|
589
|
+
try {
|
|
590
|
+
const originalLocation = window.location;
|
|
591
|
+
Object.defineProperty(window, 'location', {
|
|
592
|
+
get: function() { return originalLocation; },
|
|
593
|
+
set: function(url) {
|
|
594
|
+
window.parent.postMessage({ type: 'open-external-url', url: url.toString() }, '*');
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
} catch (e) {
|
|
598
|
+
// Already defined, skip
|
|
599
|
+
}
|
|
600
|
+
})();
|
|
601
|
+
`;
|
|
515
602
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
603
|
+
return `<style>
|
|
604
|
+
html, body {
|
|
605
|
+
font-size: initial;
|
|
606
|
+
}
|
|
607
|
+
</style>
|
|
608
|
+
<script>
|
|
609
|
+
window.sandboxId = ${JSON.stringify(sandboxId)};
|
|
610
|
+
${dataInjection}
|
|
611
|
+
${bridgeCode}
|
|
612
|
+
${runtimeFunctions.join("\n")}
|
|
613
|
+
${navigationInterceptor}
|
|
614
|
+
</script>`;
|
|
524
615
|
}
|
|
525
616
|
}
|