@sampleapp.ai/sdk 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/components/chat-bar.js +1 -2
  2. package/dist/components/guardian/app-layout-no-sidebar.js +8 -0
  3. package/dist/components/guardian/ask-ai-view.js +249 -0
  4. package/dist/components/guardian/code-focus-section.d.ts +41 -0
  5. package/dist/components/guardian/code-focus-section.js +174 -0
  6. package/dist/components/guardian/context/guardian-context.js +94 -0
  7. package/dist/components/guardian/context/vm-context.js +28 -0
  8. package/dist/components/guardian/default-guide-view.js +34 -0
  9. package/dist/components/guardian/demo/guardian-demo.js +35 -0
  10. package/dist/components/guardian/demo/left-view/toggle.js +28 -0
  11. package/dist/components/guardian/demo/left-view.js +49 -0
  12. package/dist/components/guardian/guardian-component.js +79 -0
  13. package/dist/components/guardian/guardian-demo.js +35 -0
  14. package/dist/components/guardian/guardian-home.d.ts +4 -0
  15. package/dist/components/guardian/guardian-home.js +61 -0
  16. package/dist/components/guardian/guardian-playground.js +45 -0
  17. package/dist/components/guardian/guardian-style-wrapper.js +29 -0
  18. package/dist/components/guardian/guardian-upload-spec.d.ts +14 -0
  19. package/dist/components/guardian/guardian-upload-spec.js +160 -0
  20. package/dist/components/guardian/header/glassmorphic-combobox.d.ts +15 -0
  21. package/dist/components/guardian/header/glassmorphic-combobox.js +30 -0
  22. package/dist/components/guardian/header.js +61 -0
  23. package/dist/components/guardian/hooks/use-frame-messages.js +65 -0
  24. package/dist/components/guardian/hooks/use-frame-params.js +44 -0
  25. package/dist/components/guardian/hooks/use-sandbox-url-loader.js +101 -0
  26. package/dist/components/guardian/ide/browser.js +538 -0
  27. package/dist/components/guardian/index.js +8 -0
  28. package/dist/components/guardian/layout/app-layout-no-sidebar.js +8 -0
  29. package/dist/components/guardian/layout/header/glassmorphic-combobox.js +48 -0
  30. package/dist/components/guardian/layout/header.js +63 -0
  31. package/dist/components/guardian/right-view/code-view.js +56 -0
  32. package/dist/components/guardian/right-view/pill-file-selector.js +233 -0
  33. package/dist/components/guardian/right-view/preview-control-bar.js +25 -0
  34. package/dist/components/guardian/right-view/right-panel-view.js +38 -0
  35. package/dist/components/guardian/right-view/right-top-down-view.js +289 -0
  36. package/dist/components/guardian/right-view/right-view.js +28 -0
  37. package/dist/components/guardian/right-view/simplified-editor.js +234 -0
  38. package/dist/components/guardian/types/ide-types.js +162 -0
  39. package/dist/components/guardian/types.js +3 -0
  40. package/dist/components/guardian/ui/ai-loader.js +48 -0
  41. package/dist/components/guardian/ui/badge.js +24 -0
  42. package/dist/components/guardian/ui/button.js +45 -0
  43. package/dist/components/guardian/ui/command.js +63 -0
  44. package/dist/components/guardian/ui/console-with-app.js +17 -0
  45. package/dist/components/guardian/ui/dialog.js +57 -0
  46. package/dist/components/guardian/ui/dropdown-menu.js +82 -0
  47. package/dist/components/guardian/ui/markdown.js +57 -0
  48. package/dist/components/guardian/ui/popover.js +25 -0
  49. package/dist/components/guardian/ui/tooltip.js +25 -0
  50. package/dist/components/guardian/utils.js +88 -0
  51. package/dist/components/guardian/zip-to-codebase.js +246 -0
  52. package/dist/components/guardian/zip-to-filetree.js +284 -0
  53. package/dist/components/icons.js +22 -0
  54. package/dist/components/sandbox/Sandbox.js +87 -0
  55. package/dist/components/sandbox/SandboxHome.js +141 -0
  56. package/dist/components/sandbox/api.js +108 -0
  57. package/dist/components/sandbox/guardian/app-layout-no-sidebar.js +8 -0
  58. package/dist/components/sandbox/guardian/ask-ai-view.js +249 -0
  59. package/dist/components/sandbox/guardian/code-focus-section.js +174 -0
  60. package/dist/components/sandbox/guardian/context/guardian-context.js +94 -0
  61. package/dist/components/sandbox/guardian/context/vm-context.js +28 -0
  62. package/dist/components/sandbox/guardian/default-guide-view.js +34 -0
  63. package/dist/components/sandbox/guardian/demo/guardian-demo.js +35 -0
  64. package/dist/components/sandbox/guardian/demo/left-view/toggle.js +28 -0
  65. package/dist/components/sandbox/guardian/demo/left-view.js +58 -0
  66. package/dist/components/sandbox/guardian/guardian-component.js +97 -0
  67. package/dist/components/sandbox/guardian/guardian-demo.js +35 -0
  68. package/dist/components/sandbox/guardian/guardian-home.d.ts +4 -0
  69. package/dist/components/sandbox/guardian/guardian-home.js +61 -0
  70. package/dist/components/sandbox/guardian/guardian-playground.js +45 -0
  71. package/dist/components/sandbox/guardian/guardian-style-wrapper.js +33 -0
  72. package/dist/components/sandbox/guardian/guardian-upload-spec.d.ts +14 -0
  73. package/dist/components/sandbox/guardian/guardian-upload-spec.js +160 -0
  74. package/dist/components/sandbox/guardian/header/glassmorphic-combobox.js +30 -0
  75. package/dist/components/sandbox/guardian/header.js +61 -0
  76. package/dist/components/sandbox/guardian/hooks/use-frame-messages.js +65 -0
  77. package/dist/components/sandbox/guardian/hooks/use-frame-params.js +44 -0
  78. package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +145 -0
  79. package/dist/components/sandbox/guardian/ide/browser.js +538 -0
  80. package/dist/components/sandbox/guardian/index.js +8 -0
  81. package/dist/components/sandbox/guardian/right-view/code-view.js +60 -0
  82. package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +233 -0
  83. package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +25 -0
  84. package/dist/components/sandbox/guardian/right-view/right-panel-view.js +38 -0
  85. package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +289 -0
  86. package/dist/components/sandbox/guardian/right-view/right-view.js +28 -0
  87. package/dist/components/sandbox/guardian/right-view/simplified-editor.js +234 -0
  88. package/dist/components/sandbox/guardian/types/ide-types.js +162 -0
  89. package/dist/components/sandbox/guardian/types.js +3 -0
  90. package/dist/components/sandbox/guardian/ui/ai-loader.js +48 -0
  91. package/dist/components/sandbox/guardian/ui/badge.js +24 -0
  92. package/dist/components/sandbox/guardian/ui/button.js +45 -0
  93. package/dist/components/sandbox/guardian/ui/command.js +63 -0
  94. package/dist/components/sandbox/guardian/ui/console-with-app.js +17 -0
  95. package/dist/components/sandbox/guardian/ui/dialog.js +57 -0
  96. package/dist/components/sandbox/guardian/ui/dropdown-menu.js +82 -0
  97. package/dist/components/sandbox/guardian/ui/markdown/accordion-group/accordion.js +62 -0
  98. package/dist/components/sandbox/guardian/ui/markdown/accordion-group.js +23 -0
  99. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-check.js +4 -0
  100. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-error.js +4 -0
  101. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-info.js +4 -0
  102. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-note.js +4 -0
  103. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-tip.js +4 -0
  104. package/dist/components/sandbox/guardian/ui/markdown/callout/callout-warning.js +4 -0
  105. package/dist/components/sandbox/guardian/ui/markdown/callout/shared/callout.js +9 -0
  106. package/dist/components/sandbox/guardian/ui/markdown/callout/shared/types.js +1 -0
  107. package/dist/components/sandbox/guardian/ui/markdown/card-group/card.js +18 -0
  108. package/dist/components/sandbox/guardian/ui/markdown/card-group.js +25 -0
  109. package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +87 -0
  110. package/dist/components/sandbox/guardian/ui/markdown/code-group.js +101 -0
  111. package/dist/components/sandbox/guardian/ui/markdown/icon.js +31 -0
  112. package/dist/components/sandbox/guardian/ui/markdown.js +786 -0
  113. package/dist/components/sandbox/guardian/ui/popover.js +25 -0
  114. package/dist/components/sandbox/guardian/ui/tooltip.js +25 -0
  115. package/dist/components/sandbox/guardian/utils.js +88 -0
  116. package/dist/components/sandbox/guardian/zip-to-codebase.js +259 -0
  117. package/dist/components/sandbox/guardian/zip-to-filetree.js +284 -0
  118. package/dist/components/sandbox/index.js +4 -0
  119. package/dist/components/sandbox/sandbox-control-bar.js +91 -0
  120. package/dist/components/sandbox/sandbox-header.js +52 -0
  121. package/dist/components/sandbox/sandbox-home/SandboxCard.js +59 -0
  122. package/dist/components/sandbox/sandbox-home/SandboxHome.js +174 -0
  123. package/dist/components/sandbox/sandbox-home/SearchBar.js +12 -0
  124. package/dist/components/sandbox/sandbox-home/index.js +3 -0
  125. package/dist/components/sandbox/sandbox-left-panel.js +248 -0
  126. package/dist/components/sandbox/sandbox-loading.js +48 -0
  127. package/dist/components/sandbox/sandbox-right-panel.js +247 -0
  128. package/dist/components/sandbox/types.js +1 -0
  129. package/dist/components/sandbox.js +32 -0
  130. package/dist/components/tailwind-example.js +46 -0
  131. package/dist/index.d.ts +336 -1
  132. package/dist/index.es.js +90151 -667
  133. package/dist/index.js +13 -2
  134. package/dist/index.standalone.js +61 -53
  135. package/dist/index.standalone.umd.js +17 -89
  136. package/dist/index.umd.js +86 -151
  137. package/dist/lib/api-client.example.js +60 -0
  138. package/dist/lib/api-client.js +98 -0
  139. package/dist/lib/generated-css.js +4 -0
  140. package/dist/lib/inject-styles.js +42 -0
  141. package/dist/lib/shadow-dom-wrapper.js +42 -0
  142. package/dist/lib/utils.js +5 -0
  143. package/dist/sdk.css +1 -1
  144. package/dist/tailwind.css +1 -0
  145. package/package.json +32 -5
@@ -0,0 +1,145 @@
1
+ "use client";
2
+ import { useCallback } from "react";
3
+ import { useVmContext } from "../context/vm-context";
4
+ import { createApiClient } from "../../../../lib/api-client";
5
+ /**
6
+ * Get the base URL for API requests
7
+ * In browser, use relative URL or environment variable
8
+ * In Node.js, use environment variable or default
9
+ */
10
+ function getBaseUrl() {
11
+ if (typeof window !== "undefined") {
12
+ // Browser: try to get from environment or use relative URL
13
+ return (window.__SAMPLEAPP_API_BASE_URL__ ||
14
+ process.env.NEXT_PUBLIC_FASTAPI_APP_URL ||
15
+ process.env.FASTAPI_APP_URL ||
16
+ "http://127.0.0.1:8000");
17
+ }
18
+ // Node.js: use environment variable or default
19
+ return (process.env.FASTAPI_APP_URL ||
20
+ process.env.NEXT_PUBLIC_FASTAPI_APP_URL ||
21
+ "http://127.0.0.1:8000");
22
+ }
23
+ /**
24
+ * Creates a cache key from sandboxUid and browserUrl.
25
+ * This allows caching different URLs for the same sandbox.
26
+ */
27
+ function getCacheKey(sandboxUid, browserUrl) {
28
+ return `${sandboxUid}::${browserUrl}`;
29
+ }
30
+ /**
31
+ * Hook that abstracts the logic for loading sandbox URLs.
32
+ * Returns functions to load URLs and check if they're already loaded.
33
+ */
34
+ export function useSandboxUrlLoader(startSandboxConfig) {
35
+ const { setVmUrl, getVmUrl, setVmError, hasError } = useVmContext();
36
+ /**
37
+ * Loads a sandbox URL. If useVm is true, fetches from the API.
38
+ * Otherwise, uses the browserUrl directly.
39
+ * Returns a promise that resolves to the final URL, or throws if loading fails.
40
+ *
41
+ * Caches by sandboxUid + browserUrl to support multiple URLs per sandbox.
42
+ */
43
+ const loadSandboxUrl = useCallback(async (config) => {
44
+ const { sandboxUid, browserUrl, useVm } = config;
45
+ const cacheKey = getCacheKey(sandboxUid, browserUrl);
46
+ // Check if URL is already loaded for this specific browserUrl
47
+ const existingUrl = getVmUrl(cacheKey);
48
+ if (existingUrl !== undefined) {
49
+ return existingUrl;
50
+ }
51
+ // Check if we've already attempted and failed for this specific browserUrl
52
+ if (hasError(cacheKey)) {
53
+ throw new Error(`Previously failed to load sandbox URL for ${sandboxUid} with ${browserUrl}`);
54
+ }
55
+ if (useVm) {
56
+ try {
57
+ // Fetch VM URL from API
58
+ // Note: This assumes the API endpoint is available at /api/auto-sandbox
59
+ // You may need to configure this based on your deployment
60
+ const apiBaseUrl = typeof window !== "undefined" ? window.location.origin : "";
61
+ const response = await fetch(`${apiBaseUrl}/api/auto-sandbox?url=${encodeURIComponent(browserUrl)}&mode=json`);
62
+ // Check if response is successful
63
+ if (!response.ok) {
64
+ const errorText = await response.text();
65
+ console.error(`Failed to fetch sandbox URL for ${sandboxUid}:`, errorText);
66
+ // Mark as error to prevent infinite retries
67
+ setVmError(cacheKey);
68
+ throw new Error(`Failed to create sandbox: ${response.status} ${response.statusText}`);
69
+ }
70
+ const data = await response.json();
71
+ if (!data.vncUrl) {
72
+ console.error(`No vncUrl in response for ${sandboxUid}:`, data);
73
+ setVmError(cacheKey);
74
+ throw new Error("No vncUrl in sandbox response");
75
+ }
76
+ const vncUrl = data.vncUrl;
77
+ setVmUrl(cacheKey, vncUrl);
78
+ return vncUrl;
79
+ }
80
+ catch (error) {
81
+ // Mark as error to prevent infinite retries
82
+ setVmError(cacheKey);
83
+ throw error;
84
+ }
85
+ }
86
+ else {
87
+ // Non-VM mode: call startSandbox API to get container URL
88
+ if (startSandboxConfig) {
89
+ try {
90
+ const baseUrl = getBaseUrl();
91
+ const client = createApiClient({
92
+ baseUrl,
93
+ apiKey: startSandboxConfig.apiKey,
94
+ });
95
+ const response = await client.sdk.startSandbox({
96
+ env: startSandboxConfig.env,
97
+ apiKey: startSandboxConfig.apiKey,
98
+ chatUid: startSandboxConfig.chatUid,
99
+ });
100
+ // Use the container_url from the response as the browserUrl
101
+ const containerUrl = response.container_url;
102
+ setVmUrl(cacheKey, containerUrl);
103
+ return containerUrl;
104
+ }
105
+ catch (error) {
106
+ // Mark as error to prevent infinite retries
107
+ setVmError(cacheKey);
108
+ console.error("Failed to start sandbox:", error);
109
+ throw new Error(`Failed to start sandbox: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ }
112
+ else {
113
+ // Fallback: Use browserUrl directly if startSandboxConfig is not provided
114
+ setVmUrl(cacheKey, browserUrl);
115
+ return browserUrl;
116
+ }
117
+ }
118
+ }, [getVmUrl, setVmUrl, hasError, setVmError, startSandboxConfig]);
119
+ /**
120
+ * Gets the URL for a sandbox + browserUrl combination if it's already loaded.
121
+ * Returns undefined if not loaded.
122
+ */
123
+ const getSandboxUrl = useCallback((sandboxUid, browserUrl) => {
124
+ const cacheKey = getCacheKey(sandboxUid, browserUrl);
125
+ return getVmUrl(cacheKey);
126
+ }, [getVmUrl]);
127
+ /**
128
+ * Preloads URLs for multiple sandboxes in parallel.
129
+ * Errors are logged but don't stop other URLs from loading.
130
+ */
131
+ const preloadSandboxUrls = useCallback(async (configs) => {
132
+ // Filter out configs that are already loaded or have errors
133
+ const configsToLoad = configs.filter((config) => {
134
+ const cacheKey = getCacheKey(config.sandboxUid, config.browserUrl);
135
+ return getVmUrl(cacheKey) === undefined && !hasError(cacheKey);
136
+ });
137
+ // Load all URLs in parallel, but catch errors individually
138
+ await Promise.allSettled(configsToLoad.map((config) => loadSandboxUrl(config)));
139
+ }, [getVmUrl, loadSandboxUrl, hasError]);
140
+ return {
141
+ loadSandboxUrl,
142
+ getSandboxUrl,
143
+ preloadSandboxUrls,
144
+ };
145
+ }
@@ -0,0 +1,538 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useGuardianContext } from "../context/guardian-context";
3
+ import { ChevronLeft, ChevronRight, RefreshCw, Lock, AlertTriangle, Maximize2, Minimize2, ExternalLink, MoreVertical, } from "lucide-react";
4
+ import { cn } from "../../../../lib/utils";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu";
6
+ export default function Browser({ previewUrl, setPreviewUrl, containerEndpoint, outerContainerClassName, children, reloadSignal, useVm, isGuardian = false, }) {
7
+ const { isBrowserMaximized, setIsBrowserMaximized } = useGuardianContext();
8
+ const iframeRef = useRef(null);
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const historyRef = useRef([]);
12
+ const historyIndexRef = useRef(-1);
13
+ const suppressHistoryPushRef = useRef(false);
14
+ const suppressIframeSrcUpdateRef = useRef(false);
15
+ const [historyIndex, setHistoryIndex] = useState(-1);
16
+ const [historyLength, setHistoryLength] = useState(0);
17
+ const [urlInput, setUrlInput] = useState("");
18
+ const [iframeSrc, setIframeSrc] = useState("");
19
+ const isIgnorableUrl = useCallback((href) => {
20
+ if (!href)
21
+ return true;
22
+ const value = href.toString();
23
+ return value === "about:blank" || value.startsWith("about:");
24
+ }, []);
25
+ /**
26
+ * Convert displayed URL back to original format. For dev mode you
27
+ * can just show the containerEndpoint if you prefer.
28
+ */
29
+ const getDisplayUrl = (proxyUrl) => {
30
+ try {
31
+ const currentUrl = new URL(proxyUrl);
32
+ // Return just the pathname
33
+ return currentUrl.pathname || "/";
34
+ }
35
+ catch (_a) {
36
+ return proxyUrl;
37
+ }
38
+ };
39
+ const normalizeUrl = useCallback((href) => {
40
+ try {
41
+ const u = new URL(href, window.location.href);
42
+ u.searchParams.delete("_ts");
43
+ return u.toString();
44
+ }
45
+ catch (_a) {
46
+ // Best-effort removal of _ts from plain strings
47
+ const withoutTs = href.replace(/([?&])_ts=\d+(&|$)/, (m, p1, p2) => p2 === "&" ? p1 : "");
48
+ return withoutTs.replace(/[?&]$/, "");
49
+ }
50
+ }, []);
51
+ const updateHistoryState = useCallback(() => {
52
+ setHistoryIndex(historyIndexRef.current);
53
+ setHistoryLength(historyRef.current.length);
54
+ }, []);
55
+ const updateUrlInput = useCallback((href) => {
56
+ try {
57
+ const currentUrlObj = new URL(href, window.location.href);
58
+ setUrlInput(currentUrlObj.pathname || "/");
59
+ }
60
+ catch (_a) {
61
+ setUrlInput(href);
62
+ }
63
+ }, []);
64
+ const pushHistoryEntry = useCallback((href) => {
65
+ const sliceUntil = Math.min(historyRef.current.length, historyIndexRef.current + 1);
66
+ historyRef.current = [...historyRef.current.slice(0, sliceUntil), href];
67
+ historyIndexRef.current = historyRef.current.length - 1;
68
+ updateHistoryState();
69
+ }, [updateHistoryState]);
70
+ useEffect(() => {
71
+ if (!previewUrl)
72
+ return;
73
+ // Skip iframe src update if we're syncing from a navigation message
74
+ // (to prevent reloading the iframe when it has already navigated internally)
75
+ if (!suppressIframeSrcUpdateRef.current) {
76
+ // Keep iframe src in sync with external previewUrl when it changes meaningfully
77
+ setIframeSrc((prev) => {
78
+ const prevNorm = prev ? normalizeUrl(prev) : "";
79
+ const nextNorm = normalizeUrl(previewUrl);
80
+ return prevNorm === nextNorm ? prev || previewUrl : previewUrl;
81
+ });
82
+ }
83
+ else {
84
+ // Reset the flag after skipping the update
85
+ suppressIframeSrcUpdateRef.current = false;
86
+ }
87
+ updateUrlInput(previewUrl);
88
+ if (historyRef.current.length === 0) {
89
+ historyRef.current = [previewUrl];
90
+ historyIndexRef.current = 0;
91
+ updateHistoryState();
92
+ suppressHistoryPushRef.current = false;
93
+ return;
94
+ }
95
+ if (suppressHistoryPushRef.current) {
96
+ suppressHistoryPushRef.current = false;
97
+ return;
98
+ }
99
+ const current = historyRef.current[historyIndexRef.current];
100
+ if (normalizeUrl(current) === normalizeUrl(previewUrl))
101
+ return;
102
+ pushHistoryEntry(normalizeUrl(previewUrl));
103
+ }, [
104
+ previewUrl,
105
+ updateUrlInput,
106
+ pushHistoryEntry,
107
+ updateHistoryState,
108
+ normalizeUrl,
109
+ ]);
110
+ /**
111
+ * Force-reload the current iframe without mutating previewUrl.
112
+ * Tries same-origin reload first; falls back to cache-busting the src.
113
+ */
114
+ const reloadIframe = () => {
115
+ var _a;
116
+ if (!previewUrl)
117
+ return;
118
+ try {
119
+ const base = ((_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.src) || iframeSrc || previewUrl;
120
+ const url = new URL(base, window.location.href);
121
+ url.searchParams.set("_ts", Date.now().toString());
122
+ setIframeSrc(url.toString());
123
+ }
124
+ catch (_b) {
125
+ try {
126
+ const url = new URL(previewUrl, window.location.href);
127
+ url.searchParams.set("_ts", Date.now().toString());
128
+ setIframeSrc(url.toString());
129
+ }
130
+ catch (_c) {
131
+ setIframeSrc(previewUrl);
132
+ }
133
+ }
134
+ };
135
+ const getFrameWindow = () => {
136
+ const iframe = iframeRef.current;
137
+ if (!iframe)
138
+ return null;
139
+ try {
140
+ return iframe.contentWindow;
141
+ }
142
+ catch (_a) {
143
+ return null;
144
+ }
145
+ };
146
+ const getFrameHref = useCallback(() => {
147
+ var _a, _b;
148
+ const iframe = iframeRef.current;
149
+ if (!iframe)
150
+ return null;
151
+ try {
152
+ const href = (_b = (_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href;
153
+ if (href && !isIgnorableUrl(href))
154
+ return href;
155
+ }
156
+ catch (_c) {
157
+ // Ignore cross-origin errors; fall back to iframe.src below.
158
+ }
159
+ try {
160
+ // For cross-origin iframes, we can't read location.href, so use iframe.src
161
+ // But iframe.src might not reflect client-side navigation, so we also check
162
+ // if we have a more recent previewUrl
163
+ const src = iframe.src;
164
+ if (src && !isIgnorableUrl(src)) {
165
+ // If previewUrl is more recent and different, prefer it
166
+ if (previewUrl && normalizeUrl(previewUrl) !== normalizeUrl(src)) {
167
+ return previewUrl;
168
+ }
169
+ return src;
170
+ }
171
+ // Fallback to previewUrl if iframe.src is not available
172
+ if (previewUrl && !isIgnorableUrl(previewUrl)) {
173
+ return previewUrl;
174
+ }
175
+ return null;
176
+ }
177
+ catch (_d) {
178
+ // Final fallback to previewUrl
179
+ if (previewUrl && !isIgnorableUrl(previewUrl)) {
180
+ return previewUrl;
181
+ }
182
+ return null;
183
+ }
184
+ }, [previewUrl, normalizeUrl, isIgnorableUrl]);
185
+ /**
186
+ * Synchronize state from the iframe's current location (when accessible).
187
+ */
188
+ const syncFromIframe = useCallback(() => {
189
+ const currentHref = getFrameHref();
190
+ if (!currentHref || isIgnorableUrl(currentHref)) {
191
+ // If we can't get the href, at least try to update from previewUrl
192
+ if (previewUrl && !isIgnorableUrl(previewUrl)) {
193
+ updateUrlInput(previewUrl);
194
+ }
195
+ return;
196
+ }
197
+ try {
198
+ const currentUrlObj = new URL(currentHref, window.location.href);
199
+ const pathOnly = currentUrlObj.pathname || "/";
200
+ setUrlInput(pathOnly);
201
+ }
202
+ catch (_a) {
203
+ // If URL parsing fails, try to extract path from the string
204
+ try {
205
+ const pathMatch = currentHref.match(/\/\/[^\/]+(\/.*?)(?:\?|$)/);
206
+ if (pathMatch && pathMatch[1]) {
207
+ setUrlInput(pathMatch[1]);
208
+ }
209
+ else {
210
+ setUrlInput(currentHref);
211
+ }
212
+ }
213
+ catch (_b) {
214
+ setUrlInput(currentHref);
215
+ }
216
+ }
217
+ const normalizedCurrent = normalizeUrl(currentHref);
218
+ if (!suppressHistoryPushRef.current) {
219
+ const currentFlowLength = historyRef.current.length;
220
+ const sliceUntil = Math.min(currentFlowLength, historyIndexRef.current + 1);
221
+ historyRef.current = [
222
+ ...historyRef.current.slice(0, sliceUntil),
223
+ normalizedCurrent,
224
+ ];
225
+ historyIndexRef.current = historyRef.current.length - 1;
226
+ setHistoryIndex(historyIndexRef.current);
227
+ setHistoryLength(historyRef.current.length);
228
+ }
229
+ // Only propagate meaningful navigations (ignore cache-busting changes)
230
+ if (normalizeUrl(previewUrl) !== normalizedCurrent) {
231
+ setPreviewUrl(normalizedCurrent);
232
+ }
233
+ }, [
234
+ previewUrl,
235
+ normalizeUrl,
236
+ updateUrlInput,
237
+ setPreviewUrl,
238
+ getFrameHref,
239
+ isIgnorableUrl,
240
+ ]);
241
+ /**
242
+ * Install navigation listeners in the iframe (same-origin only) so SPA-style
243
+ * navigations update our UI without a full reload.
244
+ */
245
+ const attachIframeNavigationSync = () => {
246
+ const win = getFrameWindow();
247
+ if (!win)
248
+ return;
249
+ // Same-origin guard: any property access on cross-origin windows throws.
250
+ // If we can't read location.href, bail out and avoid installing listeners.
251
+ try {
252
+ void win.location.href;
253
+ }
254
+ catch (_a) {
255
+ return;
256
+ }
257
+ const notify = () => {
258
+ // Delay slightly to allow location to settle
259
+ setTimeout(() => syncFromIframe(), 0);
260
+ };
261
+ if (win.__saNavSyncInstalled)
262
+ return;
263
+ win.addEventListener("hashchange", notify);
264
+ win.addEventListener("popstate", notify);
265
+ try {
266
+ const wrapHistoryMethod = (method) => {
267
+ const original = win.history[method];
268
+ if (typeof original !== "function")
269
+ return;
270
+ win.history[method] = ((...args) => {
271
+ const result = original.apply(win.history, args);
272
+ win.dispatchEvent(new Event("sa-locationchange"));
273
+ return result;
274
+ });
275
+ };
276
+ wrapHistoryMethod("pushState");
277
+ wrapHistoryMethod("replaceState");
278
+ win.addEventListener("sa-locationchange", notify);
279
+ }
280
+ catch (_b) {
281
+ // If we cannot patch history, rely on popstate/hashchange only
282
+ }
283
+ win.__saNavSyncInstalled = true;
284
+ };
285
+ // Trigger iframe reload when external signal changes
286
+ useEffect(() => {
287
+ if (typeof reloadSignal === "number") {
288
+ reloadIframe();
289
+ }
290
+ // eslint-disable-next-line react-hooks/exhaustive-deps
291
+ }, [reloadSignal]);
292
+ // Listen for navigation messages from the iframe
293
+ useEffect(() => {
294
+ const handleMessage = (event) => {
295
+ var _a, _b;
296
+ // Only accept messages from the same origin or trusted origins
297
+ if (event.origin !== window.location.origin &&
298
+ !event.origin.endsWith("sampleapp.love")) {
299
+ return;
300
+ }
301
+ // Handle navigation updates from the iframe
302
+ if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === "NAVIGATION_UPDATE" && ((_b = event.data) === null || _b === void 0 ? void 0 : _b.url)) {
303
+ const newUrl = event.data.url;
304
+ const normalized = normalizeUrl(newUrl);
305
+ // Update the URL display
306
+ updateUrlInput(newUrl);
307
+ // Update history without triggering iframe src change
308
+ const current = historyRef.current[historyIndexRef.current];
309
+ if (normalizeUrl(current) !== normalized) {
310
+ suppressHistoryPushRef.current = true;
311
+ pushHistoryEntry(normalized);
312
+ }
313
+ // Update previewUrl so syncFromIframe() can use the correct value
314
+ // Use a flag to prevent the useEffect from updating iframe src (which would cause reload)
315
+ if (normalizeUrl(previewUrl) !== normalized) {
316
+ suppressIframeSrcUpdateRef.current = true;
317
+ setPreviewUrl(normalized);
318
+ }
319
+ }
320
+ };
321
+ window.addEventListener("message", handleMessage);
322
+ return () => {
323
+ window.removeEventListener("message", handleMessage);
324
+ };
325
+ }, [
326
+ normalizeUrl,
327
+ updateUrlInput,
328
+ pushHistoryEntry,
329
+ previewUrl,
330
+ setPreviewUrl,
331
+ ]);
332
+ /**
333
+ * Navigation logic
334
+ */
335
+ const handleNavigate = (url) => {
336
+ try {
337
+ let processedUrl;
338
+ const trimmedUrl = url.trim();
339
+ // If the input is blank, navigate to root path
340
+ if (trimmedUrl === "" ||
341
+ trimmedUrl.includes("https://") ||
342
+ trimmedUrl.includes("http://") ||
343
+ trimmedUrl.includes("localhost")) {
344
+ const baseUrl = containerEndpoint || "";
345
+ try {
346
+ const baseUrlObj = new URL(baseUrl);
347
+ processedUrl = `${baseUrlObj.origin}/`;
348
+ }
349
+ catch (error) {
350
+ console.error("Error creating URL with base:", baseUrl, error);
351
+ setError(`Invalid base URL: ${baseUrl}`);
352
+ setIsLoading(false);
353
+ return;
354
+ }
355
+ }
356
+ // If it starts with /api/browser, use as is
357
+ else if (trimmedUrl.startsWith("/api/browser")) {
358
+ processedUrl = trimmedUrl;
359
+ }
360
+ // If it's a relative path (starts with /)
361
+ else if (trimmedUrl.startsWith("/")) {
362
+ const baseUrl = containerEndpoint || "";
363
+ try {
364
+ const baseUrlObj = new URL(baseUrl);
365
+ processedUrl = `${baseUrlObj.origin}${trimmedUrl}`;
366
+ }
367
+ catch (error) {
368
+ console.error("Error creating URL with base:", baseUrl, error);
369
+ setError(`Invalid base URL: ${baseUrl}`);
370
+ setIsLoading(false);
371
+ return;
372
+ }
373
+ }
374
+ // Otherwise, treat as external URL
375
+ else {
376
+ const baseUrl = containerEndpoint || "";
377
+ try {
378
+ const baseUrlObj = new URL(baseUrl);
379
+ processedUrl = `${baseUrlObj.origin}/`;
380
+ }
381
+ catch (error) {
382
+ console.error("Error creating URL with base:", baseUrl, error);
383
+ setError(`Invalid base URL: ${baseUrl}`);
384
+ setIsLoading(false);
385
+ return;
386
+ }
387
+ }
388
+ setIsLoading(true);
389
+ setError(null);
390
+ const newHistory = historyRef.current.slice(0, historyIndex + 1);
391
+ newHistory.push(processedUrl);
392
+ historyRef.current = newHistory;
393
+ setHistoryIndex(newHistory.length - 1);
394
+ // Check if we're navigating to the same URL (ignoring cache-busting)
395
+ const compareAgainst = iframeSrc || previewUrl;
396
+ const isSameUrl = normalizeUrl(processedUrl) === normalizeUrl(compareAgainst);
397
+ // Update the URL input display right away
398
+ setUrlInput(getDisplayUrl(processedUrl));
399
+ // Force a refresh if navigating to the same URL
400
+ if (isSameUrl) {
401
+ setIsLoading(true);
402
+ setError(null);
403
+ reloadIframe();
404
+ }
405
+ else {
406
+ // Normal navigation for different URLs
407
+ setPreviewUrl(processedUrl);
408
+ }
409
+ // Set a timeout to reset loading state if it takes too long
410
+ setTimeout(() => {
411
+ if (isLoading) {
412
+ setIsLoading(false);
413
+ }
414
+ }, 10000);
415
+ }
416
+ catch (error) {
417
+ console.error("Navigation error:", error);
418
+ setError(`Invalid URL: ${error.message || "Unknown error"}`);
419
+ setIsLoading(false);
420
+ }
421
+ };
422
+ const handleBack = () => {
423
+ var _a, _b;
424
+ setIsLoading(true);
425
+ // Try to use iframe's own history for same-origin navigations
426
+ try {
427
+ (_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.history.back();
428
+ // onLoad will sync state when navigation completes
429
+ return;
430
+ }
431
+ catch (_c) {
432
+ // Fallback to our own tracked history
433
+ }
434
+ if (historyIndex > 0) {
435
+ const newIndex = historyIndex - 1;
436
+ setHistoryIndex(newIndex);
437
+ const previousUrl = historyRef.current[newIndex];
438
+ setPreviewUrl(previousUrl);
439
+ setUrlInput(getDisplayUrl(previousUrl));
440
+ }
441
+ else {
442
+ setIsLoading(false);
443
+ }
444
+ };
445
+ const handleForward = () => {
446
+ var _a, _b;
447
+ setIsLoading(true);
448
+ // Try to use iframe's own history for same-origin navigations
449
+ try {
450
+ (_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.history.forward();
451
+ // onLoad will sync state when navigation completes
452
+ return;
453
+ }
454
+ catch (_c) {
455
+ // Fallback to our own tracked history
456
+ }
457
+ if (historyIndex < historyRef.current.length - 1) {
458
+ const newIndex = historyIndex + 1;
459
+ setHistoryIndex(newIndex);
460
+ const nextUrl = historyRef.current[newIndex];
461
+ setPreviewUrl(nextUrl);
462
+ setUrlInput(getDisplayUrl(nextUrl));
463
+ }
464
+ else {
465
+ setIsLoading(false);
466
+ }
467
+ };
468
+ const handleRefresh = () => {
469
+ if (previewUrl) {
470
+ setIsLoading(true);
471
+ setError(null);
472
+ reloadIframe();
473
+ }
474
+ };
475
+ const handleUrlChange = (e) => {
476
+ if (e.key === "Enter") {
477
+ handleNavigate(urlInput);
478
+ }
479
+ };
480
+ // Render the iframe directly with built-in event handlers
481
+ const renderIframe = () => {
482
+ if (!previewUrl && !iframeSrc)
483
+ return null;
484
+ return (React.createElement("iframe", { ref: iframeRef, src: iframeSrc || previewUrl, className: cn("h-full w-full border-none", outerContainerClassName), sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads allow-top-navigation", allow: "fullscreen; payment; geolocation; microphone; camera", onLoad: () => {
485
+ setIsLoading(false);
486
+ setError(null);
487
+ attachIframeNavigationSync();
488
+ syncFromIframe();
489
+ }, onError: () => {
490
+ console.error("Iframe failed to load");
491
+ setIsLoading(false);
492
+ setError("Failed to load page");
493
+ } }));
494
+ };
495
+ return (React.createElement("div", { className: "flex h-full w-full flex-col" },
496
+ !useVm && !isGuardian && (React.createElement("div", { className: "bg-white dark:bg-black border-b border-zinc-200 dark:border-zinc-800 flex-shrink-0" },
497
+ React.createElement("div", { className: "flex items-center gap-1.5 px-2 py-1.5 h-[2.38rem]" },
498
+ React.createElement("div", { className: "flex gap-0.5" },
499
+ React.createElement("button", { onClick: handleBack, disabled: historyIndex === 0, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Back" },
500
+ React.createElement(ChevronLeft, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
501
+ React.createElement("button", { onClick: handleForward, disabled: historyIndex === historyLength - 1, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded disabled:opacity-30 disabled:hover:bg-transparent transition-colors", title: "Forward" },
502
+ React.createElement(ChevronRight, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
503
+ React.createElement("button", { onClick: handleRefresh, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "Refresh" },
504
+ React.createElement(RefreshCw, { size: 14, className: `text-zinc-600 dark:text-zinc-400 ${isLoading ? "animate-spin" : ""}` }))),
505
+ React.createElement("div", { className: "flex-1 flex items-center gap-1.5 px-2 py-1 bg-zinc-50 dark:bg-zinc-950 rounded text-zinc-600 dark:text-zinc-400 focus-within:bg-white dark:focus-within:bg-zinc-900 transition-colors" },
506
+ error ? (React.createElement(AlertTriangle, { size: 12, className: "text-red-500 flex-shrink-0" })) : (React.createElement(Lock, { size: 12, className: "text-zinc-400 dark:text-zinc-600 flex-shrink-0" })),
507
+ React.createElement("input", { type: "text",
508
+ // value={urlInput}
509
+ value: "/", onChange: (e) => setUrlInput(e.target.value), onKeyDown: handleUrlChange, className: "flex-1 text-xs border-none outline-none bg-transparent text-zinc-700 dark:text-zinc-300 placeholder:text-zinc-400 dark:placeholder:text-zinc-600", placeholder: "Enter URL" }),
510
+ isGuardian && (React.createElement("button", { onClick: () => setIsBrowserMaximized(!isBrowserMaximized), className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: isBrowserMaximized
511
+ ? "Exit fullscreen"
512
+ : "Fullscreen preview" }, isBrowserMaximized ? (React.createElement(Minimize2, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })) : (React.createElement(Maximize2, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })))),
513
+ React.createElement("button", { onClick: () => {
514
+ window.open(previewUrl, "_blank", "noopener,noreferrer");
515
+ }, className: "ml-1 p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "Open in new tab", disabled: !urlInput || !!error, tabIndex: -1, type: "button" },
516
+ React.createElement(ExternalLink, { size: 14, className: "text-zinc-600 dark:text-zinc-400" })),
517
+ React.createElement(DropdownMenu, null,
518
+ React.createElement(DropdownMenuTrigger, { asChild: true },
519
+ React.createElement("button", { className: "ml-1 p-1 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded transition-colors", title: "More options", type: "button" },
520
+ React.createElement(MoreVertical, { size: 14, className: "text-zinc-600 dark:text-zinc-400" }))),
521
+ React.createElement(DropdownMenuContent, { align: "end", className: "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800" },
522
+ React.createElement(DropdownMenuItem, { onClick: () => {
523
+ // Restart functionality - can be implemented if needed
524
+ console.log("Restart app");
525
+ }, className: "cursor-pointer text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800" }, "Restart App"))))))),
526
+ error && (React.createElement("div", { className: "p-4 bg-red-50 border-b border-red-100 text-red-700 text-sm dark:bg-red-950 dark:border-red-800 dark:text-red-200" },
527
+ React.createElement("div", { className: "flex items-center gap-2" },
528
+ React.createElement(AlertTriangle, { size: 16 }),
529
+ React.createElement("span", null, error)))),
530
+ React.createElement("div", { className: "flex-1 min-h-0 relative" },
531
+ renderIframe(),
532
+ isLoading && (React.createElement("div", { className: "absolute inset-0 bg-zinc-100 dark:bg-zinc-800 bg-opacity-50 flex items-center justify-center z-10" },
533
+ React.createElement("div", { className: "text-sm text-zinc-600 bg-white dark:bg-black p-3 rounded-lg shadow-md" },
534
+ "Loading ",
535
+ urlInput,
536
+ "..."))),
537
+ children)));
538
+ }