@replohq/sandbox-runtime 1.19.2 → 1.19.4

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.
@@ -1,30 +1,50 @@
1
1
  "use client";
2
2
  import { useEffect } from "react";
3
+ import {
4
+ EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID,
5
+ EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION,
6
+ parentToSandboxExternalNavigationMethodConfig,
7
+ sandboxToParentExternalNavigationMethodConfig
8
+ } from "./_vendor/schemas/externalNavigationIframeRpc.mjs";
9
+ import {
10
+ createIframeRpcChannel,
11
+ createIframeRpcRemote
12
+ } from "./_vendor/schemas/iframeRpcChannel.mjs";
3
13
  import { displayModeStore } from "./select-mode/displayModeStore";
4
- const EXTERNAL_NAVIGATION_HANDLER_KEY = "__reploExternalNavigationHandler";
5
14
  function blockNavigation(event) {
6
15
  event.preventDefault();
7
16
  event.stopPropagation();
8
17
  event.stopImmediatePropagation();
9
18
  }
10
19
  function isNavigationLike(value) {
11
- if (typeof value !== "object" || value === null) {
12
- return false;
13
- }
14
- return typeof Reflect.get(value, "addEventListener") === "function" && typeof Reflect.get(value, "removeEventListener") === "function";
20
+ return typeof value === "object" && value !== null && "addEventListener" in value && typeof value.addEventListener === "function" && "removeEventListener" in value && typeof value.removeEventListener === "function";
15
21
  }
16
22
  function getWindowNavigation() {
17
- const navigation = Reflect.get(window, "navigation");
18
- return isNavigationLike(navigation) ? navigation : null;
23
+ return "navigation" in window && isNavigationLike(window.navigation) ? window.navigation : null;
19
24
  }
20
- function shouldInterceptNavigationForAgentPreview() {
21
- if (typeof window === "undefined") {
22
- return false;
25
+ const LIVE_SHARED_PREVIEW_STORAGE_KEY = "__reploLiveSharedPreview";
26
+ function syncStoredLiveSharedPreview(isMarked) {
27
+ try {
28
+ if (isMarked) {
29
+ window.sessionStorage.setItem(LIVE_SHARED_PREVIEW_STORAGE_KEY, "1");
30
+ return true;
31
+ }
32
+ return window.sessionStorage.getItem(LIVE_SHARED_PREVIEW_STORAGE_KEY) === "1";
33
+ } catch {
34
+ return isMarked;
23
35
  }
24
- if (window.parent === window) {
25
- return false;
36
+ }
37
+ function getAgentPreviewMode() {
38
+ if (typeof window === "undefined" || window.parent === window) {
39
+ return null;
26
40
  }
27
- return new URLSearchParams(window.location.search).get("__reploAgentPreview") === "1";
41
+ const marker = new URLSearchParams(window.location.search).get(
42
+ "__reploAgentPreview"
43
+ );
44
+ if (marker === "1") {
45
+ return "editor";
46
+ }
47
+ return syncStoredLiveSharedPreview(marker === "live-shared-preview") ? "live-shared-preview" : null;
28
48
  }
29
49
  function resolveUrl(rawUrl) {
30
50
  try {
@@ -40,15 +60,81 @@ function urlLeavesSandbox(rawUrl) {
40
60
  const resolved = resolveUrl(rawUrl);
41
61
  return resolved === null ? null : resolved.origin !== window.location.origin;
42
62
  }
63
+ function isOutboundCheckout(rawUrl) {
64
+ const resolved = resolveUrl(rawUrl);
65
+ return resolved !== null && resolved.origin !== window.location.origin && (resolved.hostname === "checkout.stripe.com" || /\/cart\/(?:c\/|\d+:\d+)|\/checkouts\//u.test(resolved.pathname));
66
+ }
67
+ let parentRemote = null;
68
+ function getParentRemote() {
69
+ parentRemote ??= createIframeRpcRemote(
70
+ createIframeRpcChannel({
71
+ subscriberWindow: window,
72
+ channelIdentifier: EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID,
73
+ version: EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION,
74
+ getTargetWindow: () => window.parent,
75
+ targetOrigin: "*",
76
+ isExpectedMessage: (event) => event.source === window.parent,
77
+ outgoingMethods: sandboxToParentExternalNavigationMethodConfig,
78
+ incomingMethods: parentToSandboxExternalNavigationMethodConfig,
79
+ handlers: {}
80
+ })
81
+ );
82
+ return parentRemote;
83
+ }
84
+ function openInParent(rawUrl) {
85
+ const resolved = resolveUrl(rawUrl);
86
+ if (resolved?.protocol !== "http:" && resolved?.protocol !== "https:") {
87
+ return;
88
+ }
89
+ getParentRemote().openExternal({ url: resolved.toString() });
90
+ }
91
+ function addSharedPreviewCheckoutListeners(navigation) {
92
+ const handleNavigate = (event) => {
93
+ const destinationUrl = event.destination?.url;
94
+ if (!destinationUrl || !isOutboundCheckout(destinationUrl)) {
95
+ return;
96
+ }
97
+ event.preventDefault?.();
98
+ openInParent(destinationUrl);
99
+ };
100
+ const handleClick = (event) => {
101
+ const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
102
+ const href = link instanceof HTMLAnchorElement ? link.getAttribute("href") : null;
103
+ if (!href || !isOutboundCheckout(href)) {
104
+ return;
105
+ }
106
+ blockNavigation(event);
107
+ openInParent(href);
108
+ };
109
+ navigation?.addEventListener("navigate", handleNavigate);
110
+ document.addEventListener("click", handleClick, true);
111
+ return () => {
112
+ navigation?.removeEventListener("navigate", handleNavigate);
113
+ document.removeEventListener("click", handleClick, true);
114
+ };
115
+ }
43
116
  function LinkInterceptor() {
44
117
  useEffect(() => {
45
- if (!shouldInterceptNavigationForAgentPreview()) {
118
+ const mode = getAgentPreviewMode();
119
+ if (mode === null) {
46
120
  return;
47
121
  }
122
+ const host = window;
123
+ const navigation = getWindowNavigation();
124
+ host.__reploExternalNavigationHandler = (url) => {
125
+ openInParent(url);
126
+ return true;
127
+ };
128
+ if (mode === "live-shared-preview") {
129
+ const removeListeners = addSharedPreviewCheckoutListeners(navigation);
130
+ return () => {
131
+ delete host.__reploExternalNavigationHandler;
132
+ removeListeners();
133
+ };
134
+ }
48
135
  const originalWindowOpen = window.open;
49
136
  const originalPushState = history.pushState;
50
137
  const originalReplaceState = history.replaceState;
51
- const navigation = getWindowNavigation();
52
138
  const reportNavigated = (rawUrl) => {
53
139
  window.parent.postMessage(
54
140
  {
@@ -58,23 +144,6 @@ function LinkInterceptor() {
58
144
  "*"
59
145
  );
60
146
  };
61
- const openInParent = (rawUrl) => {
62
- const resolved = resolveUrl(rawUrl);
63
- if (!resolved) {
64
- return;
65
- }
66
- window.parent.postMessage(
67
- {
68
- type: "replo:open-external",
69
- url: resolved.toString()
70
- },
71
- "*"
72
- );
73
- };
74
- Reflect.set(window, EXTERNAL_NAVIGATION_HANDLER_KEY, (url) => {
75
- openInParent(url);
76
- return true;
77
- });
78
147
  window.open = function(url, target, features) {
79
148
  if (url !== void 0 && url !== "" && urlLeavesSandbox(url) === true) {
80
149
  openInParent(url);
@@ -156,7 +225,7 @@ function LinkInterceptor() {
156
225
  navigation?.addEventListener("navigate", handleNavigate);
157
226
  document.addEventListener("click", handleClick, true);
158
227
  return () => {
159
- Reflect.deleteProperty(window, EXTERNAL_NAVIGATION_HANDLER_KEY);
228
+ delete host.__reploExternalNavigationHandler;
160
229
  window.open = originalWindowOpen;
161
230
  history.pushState = originalPushState;
162
231
  history.replaceState = originalReplaceState;
@@ -1,4 +1,4 @@
1
1
  export { coerceNumberToString, deepCloneAndMergeReplacingArrays, errorMessage, exhaustiveSwitch, formatFileSize, getFromRecordOrNull, hasOwnProperty, invariant, isEmpty, isFunction_default as isFunction, isNotNullish, isNullish, isPrimitive, isString_default as isString, mergeReplacingArrays, mergeReplacingObjects, noop, preventDefault, sleep, sleepWithSignal, warning } from '../../../chunk-THOBCJJA.mjs';
2
- export { isNumber_default as isNumber } from '../../../chunk-SKVPFN7M.mjs';
3
2
  import '../../../chunk-4ITJNSXZ.mjs';
3
+ export { isNumber_default as isNumber } from '../../../chunk-SKVPFN7M.mjs';
4
4
  import '../../../chunk-XYS6G2ZC.mjs';
@@ -0,0 +1,17 @@
1
+ import * as z from "zod";
2
+ export declare const EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID: "replo:external-navigation-iframe-rpc";
3
+ export declare const EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION: 1;
4
+ export declare const sandboxToParentExternalNavigationMethodConfig: {
5
+ openExternal: {
6
+ inputSchema: z.ZodObject<{
7
+ url: z.ZodURL;
8
+ }, z.core.$strip>;
9
+ outputSchema: z.ZodVoid;
10
+ };
11
+ };
12
+ export declare const parentToSandboxExternalNavigationMethodConfig: {};
13
+ /** Opens what the frame hands up in a new tab. Null `frameOrigin` accepts any origin. */
14
+ export declare function createExternalNavigationParentChannel({ getFrameWindow, frameOrigin, }: {
15
+ getFrameWindow: () => Window | null;
16
+ frameOrigin: string | null;
17
+ }): import("./iframeRpcChannel").IframeRpcChannel<{}>;
@@ -0,0 +1,41 @@
1
+ import { createIframeRpcChannel } from '../../chunk-36IPO47A.mjs';
2
+ import '../../chunk-ZTT7SNYN.mjs';
3
+ import '../../chunk-THOBCJJA.mjs';
4
+ import '../../chunk-4ITJNSXZ.mjs';
5
+ import '../../chunk-SKVPFN7M.mjs';
6
+ import '../../chunk-XYS6G2ZC.mjs';
7
+ import * as z from 'zod';
8
+
9
+ var EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID = "replo:external-navigation-iframe-rpc";
10
+ var EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION = 1;
11
+ var sandboxToParentExternalNavigationMethodConfig = {
12
+ openExternal: {
13
+ inputSchema: z.object({ url: z.url({ protocol: /^https?$/ }) }),
14
+ outputSchema: z.void()
15
+ }
16
+ };
17
+ var parentToSandboxExternalNavigationMethodConfig = {};
18
+ function createExternalNavigationParentChannel({
19
+ getFrameWindow,
20
+ frameOrigin
21
+ }) {
22
+ return createIframeRpcChannel({
23
+ subscriberWindow: window,
24
+ channelIdentifier: EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID,
25
+ version: EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION,
26
+ getTargetWindow: getFrameWindow,
27
+ targetOrigin: "*",
28
+ isExpectedMessage: (event) => {
29
+ return event.source === getFrameWindow() && (frameOrigin === null || event.origin === frameOrigin);
30
+ },
31
+ outgoingMethods: parentToSandboxExternalNavigationMethodConfig,
32
+ incomingMethods: sandboxToParentExternalNavigationMethodConfig,
33
+ handlers: {
34
+ openExternal: ({ url: url2 }) => {
35
+ window.open(url2, "_blank", "noopener,noreferrer");
36
+ }
37
+ }
38
+ });
39
+ }
40
+
41
+ export { EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_ID, EXTERNAL_NAVIGATION_IFRAME_RPC_CHANNEL_VERSION, createExternalNavigationParentChannel, parentToSandboxExternalNavigationMethodConfig, sandboxToParentExternalNavigationMethodConfig };
@@ -1,6 +1,6 @@
1
1
  export { createIframeRpcChannel, createIframeRpcRemote, iframeRpcCallSchema } from '../../chunk-36IPO47A.mjs';
2
2
  import '../../chunk-ZTT7SNYN.mjs';
3
3
  import '../../chunk-THOBCJJA.mjs';
4
- import '../../chunk-SKVPFN7M.mjs';
5
4
  import '../../chunk-4ITJNSXZ.mjs';
5
+ import '../../chunk-SKVPFN7M.mjs';
6
6
  import '../../chunk-XYS6G2ZC.mjs';
@@ -1,8 +1,8 @@
1
1
  import { createIframeRpcChannel } from '../../chunk-36IPO47A.mjs';
2
2
  import '../../chunk-ZTT7SNYN.mjs';
3
3
  import '../../chunk-THOBCJJA.mjs';
4
- import '../../chunk-SKVPFN7M.mjs';
5
4
  import '../../chunk-4ITJNSXZ.mjs';
5
+ import '../../chunk-SKVPFN7M.mjs';
6
6
  import '../../chunk-XYS6G2ZC.mjs';
7
7
  import * as z from 'zod';
8
8
 
@@ -1,5 +1,5 @@
1
1
  export { SELECT_MODE_SELECTABLE_ATTRIBUTE, SELECT_MODE_SELECTABLE_PAYLOAD_ATTRIBUTE, displayModeSchema, getIframeOffset, isSelectedSection, readSelectablePayload, selectModeBoundingRectSchema, selectModeClickPositionSchema, selectModeDefinedStyleSchema, selectModeFiberChainEntrySchema, selectModeFormatCommandSchema, selectModeHoverRectSchema, selectModeImageSchema, selectModeImageScopeSchema, selectModeImageSourceSchema, selectModeInlineTextStatusSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionRectSchema, selectModeSelectionSchema, selectModeSelectionTypeSchema, selectModeSourceLocationSchema, selectModeStyleAtRuleSchema, selectModeStyleVariantSchema, selectModeTextMetricsSchema, selectModeTiptapActiveMarksSchema, selectModeVideoSchema, selectableDataPayloadSchema, selectableDataSchema, selectablePlanDataSchema, selectablePlanPayloadSchema, selectedElementSchema, transformPoint, transformRect, transformSelectableData, transformSelectedElement } from '../../chunk-QTGDSYSU.mjs';
2
2
  import '../../chunk-THOBCJJA.mjs';
3
- import '../../chunk-SKVPFN7M.mjs';
4
3
  import '../../chunk-4ITJNSXZ.mjs';
4
+ import '../../chunk-SKVPFN7M.mjs';
5
5
  import '../../chunk-XYS6G2ZC.mjs';
@@ -1,7 +1,7 @@
1
1
  import { selectModeBoundingRectSchema, selectModeTiptapActiveMarksSchema, selectModeSelectionRectSchema, selectableDataSchema, selectModeHoverRectSchema, selectModeFormatCommandSchema, selectModeOptimisticUpdatePayloadSchema, selectModeSelectionTypeSchema, displayModeSchema, selectModeClickPositionSchema, selectedElementSchema } from '../../chunk-QTGDSYSU.mjs';
2
2
  import '../../chunk-THOBCJJA.mjs';
3
- import '../../chunk-SKVPFN7M.mjs';
4
3
  import '../../chunk-4ITJNSXZ.mjs';
4
+ import '../../chunk-SKVPFN7M.mjs';
5
5
  import '../../chunk-XYS6G2ZC.mjs';
6
6
  import * as z from 'zod';
7
7
 
package/buildMetadata.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sandbox-runtime",
3
- "version": "1.19.2",
3
+ "version": "1.19.4",
4
4
  "branch": "HEAD",
5
- "commit": "cdc23a21d2f115a5a458a13a97942dac138e692d",
6
- "builtAt": "2026-10-24T17:26:05.661Z"
5
+ "commit": "3a362d942a24f8c7b0af6d26e3ccad003254d6c5",
6
+ "builtAt": "2026-10-25T05:11:58.524Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "1.19.2",
3
+ "version": "1.19.4",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sandbox-runtime",
6
- "version": "1.19.2",
6
+ "version": "1.19.4",
7
7
  "branch": "HEAD",
8
- "commit": "cdc23a21d2f115a5a458a13a97942dac138e692d",
9
- "builtAt": "2026-10-24T17:26:05.661Z"
8
+ "commit": "3a362d942a24f8c7b0af6d26e3ccad003254d6c5",
9
+ "builtAt": "2026-10-25T05:11:58.524Z"
10
10
  },
11
11
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",
@@ -116,6 +116,13 @@ function identifyElement({
116
116
  }
117
117
  return "image";
118
118
  }
119
+ if (element instanceof HTMLIFrameElement) {
120
+ if (element.hasAttribute("srcdoc")) {
121
+ return "iframe: inline HTML (srcdoc)";
122
+ }
123
+ const src = element.getAttribute("src");
124
+ return src ? `iframe: [${src.slice(0, 60)}]` : "iframe";
125
+ }
119
126
  if (/^h[1-6]$/.test(tagName)) {
120
127
  return trimmedText ? `heading(${tagName}): "${trimmedText.slice(0, 60)}"` : tagName;
121
128
  }
@@ -4,11 +4,7 @@ export declare const FRAME_SHIELD_CSS = "iframe { pointer-events: none !importan
4
4
  * Select mode shields iframes from the pointer so presses over an embed reach
5
5
  * this document instead of the embed's own (often cross-origin) one. The hit
6
6
  * element is then whatever sits behind the frame, so lift the shield for one
7
- * native hit test to find the frame the user actually pointed at. A frame
8
- * injected as raw HTML has no React source, so prefer its nearest
9
- * React-rendered ancestor, which the agent can edit. React 19 renders <body>
10
- * too, so stop below it: a widget script appends its frame straight to the
11
- * body, and selecting the frame beats selecting the whole page.
7
+ * native hit test to find the frame the user actually pointed at.
12
8
  */
13
9
  export declare function getSelectionTargetForEmbeddedFrameHit({ hitElement, clientX, clientY, }: {
14
10
  hitElement: Element;
@@ -1,4 +1,3 @@
1
- import { getFiberFromElement } from "../fiber";
2
1
  const FRAME_SHIELD_ATTRIBUTE = "data-select-mode-frame-shield";
3
2
  const FRAME_SHIELD_CSS = "iframe { pointer-events: none !important; }";
4
3
  function getSelectionTargetForEmbeddedFrameHit({
@@ -15,15 +14,7 @@ function getSelectionTargetForEmbeddedFrameHit({
15
14
  shield.disabled = true;
16
15
  const frame = document.elementFromPoint(clientX, clientY);
17
16
  shield.disabled = false;
18
- if (!(frame instanceof HTMLIFrameElement)) {
19
- return hitElement;
20
- }
21
- for (let candidate = frame; candidate && candidate !== document.body; candidate = candidate.parentElement) {
22
- if (getFiberFromElement(candidate)) {
23
- return candidate;
24
- }
25
- }
26
- return frame;
17
+ return frame instanceof HTMLIFrameElement ? frame : hitElement;
27
18
  }
28
19
  export {
29
20
  FRAME_SHIELD_ATTRIBUTE,