@replohq/sandbox-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ClientErrorBoundary.d.ts +10 -0
- package/ClientErrorBoundary.js +61 -0
- package/ClientErrorBoundary.js.map +7 -0
- package/DocumentReadyBeacon.d.ts +1 -0
- package/DocumentReadyBeacon.js +9 -0
- package/DocumentReadyBeacon.js.map +7 -0
- package/LICENSE +11 -0
- package/LinkInterceptor.d.ts +1 -0
- package/LinkInterceptor.js +128 -0
- package/LinkInterceptor.js.map +7 -0
- package/NextErrorReporter.d.ts +1 -0
- package/NextErrorReporter.js +92 -0
- package/NextErrorReporter.js.map +7 -0
- package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
- package/_vendor/replo-utils/lib/errors.d.ts +83 -0
- package/_vendor/replo-utils/lib/fonts.d.ts +49 -0
- package/_vendor/replo-utils/lib/fonts.mjs +221 -0
- package/_vendor/replo-utils/lib/geometry.d.ts +18 -0
- package/_vendor/replo-utils/lib/geometry.mjs +9 -0
- package/_vendor/replo-utils/lib/json.d.ts +6 -0
- package/_vendor/replo-utils/lib/log.d.ts +11 -0
- package/_vendor/replo-utils/lib/misc.d.ts +145 -0
- package/_vendor/replo-utils/lib/misc.mjs +2 -0
- package/_vendor/replo-utils/lib/object.d.ts +31 -0
- package/_vendor/replo-utils/lib/parse.d.ts +15 -0
- package/_vendor/replo-utils/lib/posthogReplay.d.ts +43 -0
- package/_vendor/replo-utils/lib/posthogReplay.mjs +121 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.d.ts +14 -0
- package/_vendor/replo-utils/react/use-iframe-rpc-channel.mjs +14 -0
- package/_vendor/schemas/designLibrary.d.ts +361 -0
- package/_vendor/schemas/designLibrary.mjs +126 -0
- package/_vendor/schemas/errors.d.ts +459 -0
- package/_vendor/schemas/generated/errors.d.ts +61 -0
- package/_vendor/schemas/generated/pageAnnotations.d.ts +48 -0
- package/_vendor/schemas/generated/pageAnnotations.mjs +1 -0
- package/_vendor/schemas/generated/selectMode.d.ts +119 -0
- package/_vendor/schemas/generated/selectMode.mjs +1 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +100 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.mjs +1 -0
- package/_vendor/schemas/iframeRpcChannel.d.ts +82 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +520 -0
- package/_vendor/schemas/pageAnnotations.d.ts +134 -0
- package/_vendor/schemas/pageAnnotations.mjs +61 -0
- package/_vendor/schemas/savedStyles.d.ts +140 -0
- package/_vendor/schemas/selectMode.d.ts +522 -0
- package/_vendor/schemas/selectMode.mjs +3 -0
- package/_vendor/schemas/selectModeIframeRpc.d.ts +900 -0
- package/_vendor/schemas/selectModeIframeRpc.mjs +223 -0
- package/annotations/AnnotationLayerMount.d.ts +1 -0
- package/annotations/AnnotationLayerMount.js +13 -0
- package/annotations/AnnotationLayerMount.js.map +7 -0
- package/annotations/annotationController.d.ts +1 -0
- package/annotations/annotationController.js +384 -0
- package/annotations/annotationController.js.map +7 -0
- package/annotations/bridgeGate.d.ts +7 -0
- package/annotations/bridgeGate.js +22 -0
- package/annotations/bridgeGate.js.map +7 -0
- package/chunk-3UFG7XTQ.mjs +205 -0
- package/chunk-FNTGY7I5.mjs +68 -0
- package/chunk-N5EI2MOZ.mjs +1535 -0
- package/design-library-preview/DesignLibraryPreview.d.ts +1 -0
- package/design-library-preview/DesignLibraryPreview.js +93 -0
- package/design-library-preview/DesignLibraryPreview.js.map +7 -0
- package/package.json +41 -0
- package/posthog-relay.d.ts +11 -0
- package/posthog-relay.js +72 -0
- package/posthog-relay.js.map +7 -0
- package/sandbox-runtime.d.ts +1 -0
- package/sandbox-runtime.js +29 -0
- package/sandbox-runtime.js.map +7 -0
- package/select-mode/EditorSelection.d.ts +24 -0
- package/select-mode/EditorSelection.js +95 -0
- package/select-mode/EditorSelection.js.map +7 -0
- package/select-mode/annotationFrames.d.ts +33 -0
- package/select-mode/annotationFrames.js +119 -0
- package/select-mode/annotationFrames.js.map +7 -0
- package/select-mode/descriptor/elementDescriptor.d.ts +26 -0
- package/select-mode/descriptor/elementDescriptor.js +201 -0
- package/select-mode/descriptor/elementDescriptor.js.map +7 -0
- package/select-mode/descriptor/styleExtraction.d.ts +3 -0
- package/select-mode/descriptor/styleExtraction.js +271 -0
- package/select-mode/descriptor/styleExtraction.js.map +7 -0
- package/select-mode/displayModeStore.d.ts +10 -0
- package/select-mode/displayModeStore.js +47 -0
- package/select-mode/displayModeStore.js.map +7 -0
- package/select-mode/fast-refresh-relay.d.ts +3 -0
- package/select-mode/fast-refresh-relay.js +33 -0
- package/select-mode/fast-refresh-relay.js.map +7 -0
- package/select-mode/fiber.d.ts +49 -0
- package/select-mode/fiber.js +306 -0
- package/select-mode/fiber.js.map +7 -0
- package/select-mode/hooks/useCrosshairCursor.d.ts +12 -0
- package/select-mode/hooks/useCrosshairCursor.js +20 -0
- package/select-mode/hooks/useCrosshairCursor.js.map +7 -0
- package/select-mode/hooks/useEditModeStateMachine.d.ts +16 -0
- package/select-mode/hooks/useEditModeStateMachine.js +235 -0
- package/select-mode/hooks/useEditModeStateMachine.js.map +7 -0
- package/select-mode/hooks/useOptimisticOverrides.d.ts +17 -0
- package/select-mode/hooks/useOptimisticOverrides.js +283 -0
- package/select-mode/hooks/useOptimisticOverrides.js.map +7 -0
- package/select-mode/hooks/useParentMessaging.d.ts +36 -0
- package/select-mode/hooks/useParentMessaging.js +74 -0
- package/select-mode/hooks/useParentMessaging.js.map +7 -0
- package/select-mode/hooks/useSelectModeController.d.ts +8 -0
- package/select-mode/hooks/useSelectModeController.js +46 -0
- package/select-mode/hooks/useSelectModeController.js.map +7 -0
- package/select-mode/hooks/useSelectionHover.d.ts +22 -0
- package/select-mode/hooks/useSelectionHover.js +110 -0
- package/select-mode/hooks/useSelectionHover.js.map +7 -0
- package/select-mode/hooks/useSelectionInteractions.d.ts +37 -0
- package/select-mode/hooks/useSelectionInteractions.js +353 -0
- package/select-mode/hooks/useSelectionInteractions.js.map +7 -0
- package/select-mode/hooks/useSelectionScrollSync.d.ts +28 -0
- package/select-mode/hooks/useSelectionScrollSync.js +70 -0
- package/select-mode/hooks/useSelectionScrollSync.js.map +7 -0
- package/select-mode/hooks/useSiblingResolution.d.ts +6 -0
- package/select-mode/hooks/useSiblingResolution.js +59 -0
- package/select-mode/hooks/useSiblingResolution.js.map +7 -0
- package/select-mode/iframeRpcChannel.d.ts +356 -0
- package/select-mode/iframeRpcChannel.js +92 -0
- package/select-mode/iframeRpcChannel.js.map +7 -0
- package/select-mode/inlineTextMarks.d.ts +6 -0
- package/select-mode/inlineTextMarks.js +25 -0
- package/select-mode/inlineTextMarks.js.map +7 -0
- package/select-mode/interactions/selectionGeometry.d.ts +105 -0
- package/select-mode/interactions/selectionGeometry.js +192 -0
- package/select-mode/interactions/selectionGeometry.js.map +7 -0
- package/select-mode/selectModeSession.d.ts +21 -0
- package/select-mode/selectModeSession.js +7 -0
- package/select-mode/selectModeSession.js.map +7 -0
- package/select-mode/selectionFrame.d.ts +81 -0
- package/select-mode/selectionFrame.js +132 -0
- package/select-mode/selectionFrame.js.map +7 -0
- package/select-mode/symbolicate.d.ts +2 -0
- package/select-mode/symbolicate.js +200 -0
- package/select-mode/symbolicate.js.map +7 -0
- package/select-mode/tiptapInlineEditor.d.ts +23 -0
- package/select-mode/tiptapInlineEditor.js +120 -0
- package/select-mode/tiptapInlineEditor.js.map +7 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* NOTE (Max, 2026-06-10): Client-side error boundary. On a caught render error it
|
|
4
|
+
* (1) reports the error to the parent editor via postMessage (so it can get reported to Sentry)
|
|
5
|
+
* and (2) renders a neutral cover instead of going blank, so the preview never flashes the broken
|
|
6
|
+
* page while the agent fixes it. The editor decides what to show on top (loading
|
|
7
|
+
* vs. an actionable error screen); the embedder reloads the iframe once the error
|
|
8
|
+
* clears.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ClientErrorBoundary({ children }: React.PropsWithChildren): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
class ErrorBoundary extends React.Component {
|
|
5
|
+
state = {
|
|
6
|
+
didCatch: false
|
|
7
|
+
};
|
|
8
|
+
static getDerivedStateFromError() {
|
|
9
|
+
return { didCatch: true };
|
|
10
|
+
}
|
|
11
|
+
componentDidCatch(error, info) {
|
|
12
|
+
this.props.onError?.(error, info);
|
|
13
|
+
}
|
|
14
|
+
render() {
|
|
15
|
+
if (this.state.didCatch) {
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
style: {
|
|
20
|
+
position: "fixed",
|
|
21
|
+
inset: 0,
|
|
22
|
+
zIndex: 2147483646,
|
|
23
|
+
background: "#ffffff"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return this.props.children;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function ClientErrorBoundary({ children }) {
|
|
32
|
+
const handleError = React.useCallback(
|
|
33
|
+
(error, errorInfo) => {
|
|
34
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
35
|
+
window.parent.postMessage(
|
|
36
|
+
{
|
|
37
|
+
// TODO (Max, 2026-06-11, REPL-27851): When adding Sentry to miniapps, I'll ensure we listen to this message
|
|
38
|
+
// and send it to Sentry. I'm not a fan of having 2 separate post messages for reporting errors (the 'error' post message in NextErrorReporter, and this one).
|
|
39
|
+
// But there's no way for the NextErrorReporter to get the actual error object, so we'll
|
|
40
|
+
// need a separate postMessage (this one, with an updated name).
|
|
41
|
+
type: "replo-editor:client-error",
|
|
42
|
+
payload: {
|
|
43
|
+
pageRoute: window.location.pathname,
|
|
44
|
+
error: {
|
|
45
|
+
message: errorObj.message,
|
|
46
|
+
stack: errorObj.stack,
|
|
47
|
+
componentStack: errorInfo.componentStack
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"*"
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { onError: handleError, children });
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
ClientErrorBoundary
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=ClientErrorBoundary.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../ClientErrorBoundary.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\n\ninterface ErrorBoundaryState {\n didCatch: boolean;\n}\n\ntype ErrorBoundaryProps = React.PropsWithChildren<{\n onError?: (error: unknown, info: React.ErrorInfo) => void;\n}>;\n\nclass ErrorBoundary extends React.Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n state: ErrorBoundaryState = {\n didCatch: false,\n };\n\n static getDerivedStateFromError(): ErrorBoundaryState {\n return { didCatch: true };\n }\n\n componentDidCatch(error: unknown, info: React.ErrorInfo) {\n this.props.onError?.(error, info);\n }\n\n render() {\n if (this.state.didCatch) {\n /**\n * NOTE (Max, 2026-06-10): When there's any error that hits this Error boundary,\n * we render this neutral cover.\n * getDerivedStateFromError() runs in the render phase, before commit, so this cover\n * is what hits the DOM immediately when the error is caught.\n * The editor lays its own loading / error UI on top (driven by NextErrorReporter's\n * status postMessage).\n * This div has a plain light neutral color, close enough to the editor's\n * loading animation. If we returned null, it'd show a cover with the site's default color (e.g. black),\n * which makes the transition less smooth between the sandbox runtime showing this cover, and the iframe embedder\n * receiving the post message telling it to show some loading animation.\n *\n * I'm really unsatisfied with this approach: there's no apparent flicker only because the editor's loading animation\n * when receiving the post message has the same color as this neutral cover... Alternative would be for\n * this error boundary to show the loading animation itself (instead of the embedder) but that's even more weird...\n */\n return (\n <div\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483646,\n background: \"#ffffff\",\n }}\n />\n );\n }\n return this.props.children;\n }\n}\n\n/**\n * NOTE (Max, 2026-06-10): Client-side error boundary. On a caught render error it\n * (1) reports the error to the parent editor via postMessage (so it can get reported to Sentry)\n * and (2) renders a neutral cover instead of going blank, so the preview never flashes the broken\n * page while the agent fixes it. The editor decides what to show on top (loading\n * vs. an actionable error screen); the embedder reloads the iframe once the error\n * clears.\n */\nexport function ClientErrorBoundary({ children }: React.PropsWithChildren) {\n const handleError = React.useCallback(\n (error: unknown, errorInfo: React.ErrorInfo) => {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n window.parent.postMessage(\n {\n // TODO (Max, 2026-06-11, REPL-27851): When adding Sentry to miniapps, I'll ensure we listen to this message\n // and send it to Sentry. I'm not a fan of having 2 separate post messages for reporting errors (the 'error' post message in NextErrorReporter, and this one).\n // But there's no way for the NextErrorReporter to get the actual error object, so we'll\n // need a separate postMessage (this one, with an updated name).\n type: \"replo-editor:client-error\",\n payload: {\n pageRoute: window.location.pathname,\n error: {\n message: errorObj.message,\n stack: errorObj.stack,\n componentStack: errorInfo.componentStack,\n },\n },\n },\n \"*\",\n );\n },\n [],\n );\n\n return <ErrorBoundary onError={handleError}>{children}</ErrorBoundary>;\n}\n"],
|
|
5
|
+
"mappings": ";AA+CQ;AA7CR,YAAY,WAAW;AAUvB,MAAM,sBAAsB,MAAM,UAGhC;AAAA,EACA,QAA4B;AAAA,IAC1B,UAAU;AAAA,EACZ;AAAA,EAEA,OAAO,2BAA+C;AACpD,WAAO,EAAE,UAAU,KAAK;AAAA,EAC1B;AAAA,EAEA,kBAAkB,OAAgB,MAAuB;AACvD,SAAK,MAAM,UAAU,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,SAAS;AACP,QAAI,KAAK,MAAM,UAAU;AAiBvB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL,UAAU;AAAA,YACV,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,YAAY;AAAA,UACd;AAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAUO,SAAS,oBAAoB,EAAE,SAAS,GAA4B;AACzE,QAAM,cAAc,MAAM;AAAA,IACxB,CAAC,OAAgB,cAA+B;AAC9C,YAAM,WACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,aAAO,OAAO;AAAA,QACZ;AAAA;AAAA;AAAA;AAAA;AAAA,UAKE,MAAM;AAAA,UACN,SAAS;AAAA,YACP,WAAW,OAAO,SAAS;AAAA,YAC3B,OAAO;AAAA,cACL,SAAS,SAAS;AAAA,cAClB,OAAO,SAAS;AAAA,cAChB,gBAAgB,UAAU;AAAA,YAC5B;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,SAAO,oBAAC,iBAAc,SAAS,aAAc,UAAS;AACxD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function DocumentReadyBeacon(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
const BEACON_SCRIPT = `if (window.parent !== window) { window.parent.postMessage({ type: "replo:preview:status", status: "ok", phase: "document", route: window.location.pathname }, "*"); }`;
|
|
3
|
+
function DocumentReadyBeacon() {
|
|
4
|
+
return /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: BEACON_SCRIPT } });
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
DocumentReadyBeacon
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=DocumentReadyBeacon.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../DocumentReadyBeacon.tsx"],
|
|
4
|
+
"sourcesContent": ["// Posts at HTML parse time, pre-hydration; error shells and retry pages are different documents without this script, so receiving it proves the real site's document arrived.\n// phase: \"document\" lets the editor distinguish this from the hydration-time reporter \u2014 document arrival must not disarm its JS-boot watchdog.\nconst BEACON_SCRIPT = `if (window.parent !== window) { window.parent.postMessage({ type: \"replo:preview:status\", status: \"ok\", phase: \"document\", route: window.location.pathname }, \"*\"); }`;\n\nexport function DocumentReadyBeacon() {\n return <script dangerouslySetInnerHTML={{ __html: BEACON_SCRIPT }} />;\n}\n"],
|
|
5
|
+
"mappings": "AAKS;AAHT,MAAM,gBAAgB;AAEf,SAAS,sBAAsB;AACpC,SAAO,oBAAC,YAAO,yBAAyB,EAAE,QAAQ,cAAc,GAAG;AACrE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Replo Sandbox Runtime — proprietary software of Replo. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software is licensed, not sold. Subject to the Replo Terms of Service
|
|
4
|
+
(https://replo.app/terms), Replo grants you a limited, non-exclusive,
|
|
5
|
+
non-transferable, revocable license to install and use this software solely to
|
|
6
|
+
build, preview, and operate sites on the Replo platform. You may not
|
|
7
|
+
redistribute, resell, sublicense, or use this software to build or operate any
|
|
8
|
+
product or service independent of or competing with Replo.
|
|
9
|
+
|
|
10
|
+
Full terms: https://replo.app/terms
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function LinkInterceptor(): null;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { displayModeStore } from "./select-mode/displayModeStore";
|
|
4
|
+
function blockNavigation(event) {
|
|
5
|
+
event.preventDefault();
|
|
6
|
+
event.stopPropagation();
|
|
7
|
+
event.stopImmediatePropagation();
|
|
8
|
+
}
|
|
9
|
+
function isNavigationLike(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return typeof Reflect.get(value, "addEventListener") === "function" && typeof Reflect.get(value, "removeEventListener") === "function";
|
|
14
|
+
}
|
|
15
|
+
function getWindowNavigation() {
|
|
16
|
+
const navigation = Reflect.get(window, "navigation");
|
|
17
|
+
return isNavigationLike(navigation) ? navigation : null;
|
|
18
|
+
}
|
|
19
|
+
function shouldInterceptNavigationForAgentPreview() {
|
|
20
|
+
if (typeof window === "undefined") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (window.parent === window) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return new URLSearchParams(window.location.search).get("__reploAgentPreview") === "1";
|
|
27
|
+
}
|
|
28
|
+
function urlLeavesSandbox(rawUrl) {
|
|
29
|
+
try {
|
|
30
|
+
const resolved = typeof rawUrl === "string" ? new URL(rawUrl, window.location.href) : new URL(rawUrl.href, window.location.href);
|
|
31
|
+
return resolved.origin !== window.location.origin;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function LinkInterceptor() {
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!shouldInterceptNavigationForAgentPreview()) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const originalWindowOpen = window.open;
|
|
42
|
+
const originalPushState = history.pushState;
|
|
43
|
+
const originalReplaceState = history.replaceState;
|
|
44
|
+
const navigation = getWindowNavigation();
|
|
45
|
+
window.open = function(url, target, features) {
|
|
46
|
+
if (url !== void 0 && url !== "" && urlLeavesSandbox(url) === true) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return originalWindowOpen.call(window, url, target, features);
|
|
50
|
+
};
|
|
51
|
+
history.pushState = function(state, unused, url) {
|
|
52
|
+
if (url && urlLeavesSandbox(url) === true) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
return originalPushState.call(history, state, unused, url);
|
|
56
|
+
};
|
|
57
|
+
history.replaceState = function(state, unused, url) {
|
|
58
|
+
if (url && urlLeavesSandbox(url) === true) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
return originalReplaceState.call(history, state, unused, url);
|
|
62
|
+
};
|
|
63
|
+
const handleNavigate = (event) => {
|
|
64
|
+
const destinationUrl = event.destination?.url;
|
|
65
|
+
if (destinationUrl === void 0 || destinationUrl === "" || urlLeavesSandbox(destinationUrl) !== true) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
event.preventDefault?.();
|
|
69
|
+
};
|
|
70
|
+
const handleClick = (event) => {
|
|
71
|
+
if (displayModeStore.getSnapshot() === "selection") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const target = event.target;
|
|
75
|
+
if (!(target instanceof Element)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const link = target.closest("a[href]");
|
|
79
|
+
if (!(link instanceof HTMLAnchorElement)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const href = link.getAttribute("href");
|
|
83
|
+
if (!href) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (href.startsWith("#")) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const linkTarget = link.getAttribute("target");
|
|
90
|
+
if (linkTarget && linkTarget !== "_self") {
|
|
91
|
+
blockNavigation(event);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (urlLeavesSandbox(href) === true) {
|
|
95
|
+
blockNavigation(event);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
let route;
|
|
100
|
+
try {
|
|
101
|
+
route = new URL(href, window.location.href).pathname;
|
|
102
|
+
} catch {
|
|
103
|
+
route = href;
|
|
104
|
+
}
|
|
105
|
+
window.parent.postMessage(
|
|
106
|
+
{
|
|
107
|
+
type: "replo:navigate",
|
|
108
|
+
route
|
|
109
|
+
},
|
|
110
|
+
"*"
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
navigation?.addEventListener("navigate", handleNavigate);
|
|
114
|
+
document.addEventListener("click", handleClick, true);
|
|
115
|
+
return () => {
|
|
116
|
+
window.open = originalWindowOpen;
|
|
117
|
+
history.pushState = originalPushState;
|
|
118
|
+
history.replaceState = originalReplaceState;
|
|
119
|
+
navigation?.removeEventListener("navigate", handleNavigate);
|
|
120
|
+
document.removeEventListener("click", handleClick, true);
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
LinkInterceptor as default
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=LinkInterceptor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../LinkInterceptor.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { useEffect } from \"react\";\n\nimport { displayModeStore } from \"./select-mode/displayModeStore\";\n\ntype NavigationNavigateEvent = {\n destination?: { url?: string };\n preventDefault?: () => void;\n};\n\ntype NavigationLike = {\n addEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n removeEventListener: (\n type: \"navigate\",\n listener: (event: NavigationNavigateEvent) => void,\n ) => void;\n};\n\nfunction blockNavigation(event: MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n}\n\nfunction isNavigationLike(value: unknown): value is NavigationLike {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n typeof Reflect.get(value, \"addEventListener\") === \"function\" &&\n typeof Reflect.get(value, \"removeEventListener\") === \"function\"\n );\n}\n\nfunction getWindowNavigation() {\n const navigation = Reflect.get(window, \"navigation\");\n return isNavigationLike(navigation) ? navigation : null;\n}\n\nfunction shouldInterceptNavigationForAgentPreview() {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n if (window.parent === window) {\n return false;\n }\n\n return (\n new URLSearchParams(window.location.search).get(\"__reploAgentPreview\") ===\n \"1\"\n );\n}\n\nfunction urlLeavesSandbox(rawUrl: string | URL): boolean | null {\n try {\n const resolved =\n typeof rawUrl === \"string\"\n ? new URL(rawUrl, window.location.href)\n : new URL(rawUrl.href, window.location.href);\n return resolved.origin !== window.location.origin;\n } catch {\n return null;\n }\n}\n\nexport default function LinkInterceptor() {\n // eslint-disable-next-line replo/no-use-effect -- Browser navigation APIs must be patched at client runtime and restored when the interceptor unmounts.\n useEffect(() => {\n if (!shouldInterceptNavigationForAgentPreview()) {\n return;\n }\n\n const originalWindowOpen = window.open;\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n const navigation = getWindowNavigation();\n\n window.open = function (\n url?: string | URL,\n target?: string,\n features?: string,\n ) {\n if (url !== undefined && url !== \"\" && urlLeavesSandbox(url) === true) {\n return null;\n }\n\n return originalWindowOpen.call(window, url, target, features);\n };\n\n history.pushState = function (\n state: Parameters<History[\"pushState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n return originalPushState.call(history, state, unused, url);\n };\n\n history.replaceState = function (\n state: Parameters<History[\"replaceState\"]>[0],\n unused: string,\n url?: string | URL | null,\n ) {\n if (url && urlLeavesSandbox(url) === true) {\n return;\n }\n\n return originalReplaceState.call(history, state, unused, url);\n };\n\n const handleNavigate = (event: NavigationNavigateEvent) => {\n const destinationUrl = event.destination?.url;\n if (\n destinationUrl === undefined ||\n destinationUrl === \"\" ||\n urlLeavesSandbox(destinationUrl) !== true\n ) {\n return;\n }\n\n event.preventDefault?.();\n };\n\n const handleClick = (event: MouseEvent) => {\n // While select mode is active a link click must select the element, not\n // navigate. `useSelectionInteractions` prevents the default nav, but this\n // sibling capture listener would still post `replo:navigate` and move the\n // preview off the page \u2014 `stopPropagation` there can't suppress a\n // listener on the same node, so we bail out here explicitly.\n if (displayModeStore.getSnapshot() === \"selection\") {\n return;\n }\n\n const target = event.target;\n\n if (!(target instanceof Element)) {\n return;\n }\n\n const link = target.closest(\"a[href]\");\n\n if (!(link instanceof HTMLAnchorElement)) {\n return;\n }\n\n const href = link.getAttribute(\"href\");\n if (!href) {\n return;\n }\n\n if (href.startsWith(\"#\")) {\n return;\n }\n\n const linkTarget = link.getAttribute(\"target\");\n if (linkTarget && linkTarget !== \"_self\") {\n blockNavigation(event);\n return;\n }\n\n if (urlLeavesSandbox(href) === true) {\n blockNavigation(event);\n return;\n }\n\n event.preventDefault();\n\n let route: string;\n try {\n route = new URL(href, window.location.href).pathname;\n } catch {\n route = href;\n }\n\n window.parent.postMessage(\n {\n type: \"replo:navigate\",\n route,\n },\n \"*\",\n );\n };\n\n navigation?.addEventListener(\"navigate\", handleNavigate);\n document.addEventListener(\"click\", handleClick, true);\n\n return () => {\n window.open = originalWindowOpen;\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n navigation?.removeEventListener(\"navigate\", handleNavigate);\n document.removeEventListener(\"click\", handleClick, true);\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function NextErrorReporter(): null;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const RED_CHROME_SELECTOR = '[data-next-badge][data-error="true"], [data-nextjs-dialog]';
|
|
4
|
+
const BLOCKING_ERROR_SELECTOR = "[data-nextjs-dialog]";
|
|
5
|
+
const STATUS_LATCH_MS = 500;
|
|
6
|
+
function NextErrorReporter() {
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
let lastStatus = null;
|
|
9
|
+
let shadowObserver = null;
|
|
10
|
+
let observedRoot = null;
|
|
11
|
+
let pendingPost = null;
|
|
12
|
+
const postNow = (status) => {
|
|
13
|
+
if (status === lastStatus) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
lastStatus = status;
|
|
17
|
+
window.parent.postMessage(
|
|
18
|
+
{
|
|
19
|
+
type: "replo:preview:status",
|
|
20
|
+
status,
|
|
21
|
+
route: window.location.pathname
|
|
22
|
+
},
|
|
23
|
+
"*"
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
const post = (status) => {
|
|
27
|
+
if (pendingPost) {
|
|
28
|
+
if (pendingPost.status === status) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
window.clearTimeout(pendingPost.timeoutId);
|
|
32
|
+
pendingPost = null;
|
|
33
|
+
}
|
|
34
|
+
if (status === lastStatus) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (lastStatus === null && status === "ok") {
|
|
38
|
+
postNow("ok");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const timeoutId = window.setTimeout(() => {
|
|
42
|
+
pendingPost = null;
|
|
43
|
+
postNow(status);
|
|
44
|
+
}, STATUS_LATCH_MS);
|
|
45
|
+
pendingPost = { status, timeoutId };
|
|
46
|
+
};
|
|
47
|
+
const syncOverlayState = () => {
|
|
48
|
+
const portal = document.getElementsByTagName("nextjs-portal")[0];
|
|
49
|
+
if (!(portal instanceof HTMLElement)) {
|
|
50
|
+
post("ok");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const root = portal.shadowRoot;
|
|
54
|
+
if (root && root !== observedRoot) {
|
|
55
|
+
shadowObserver?.disconnect();
|
|
56
|
+
shadowObserver = new MutationObserver(syncOverlayState);
|
|
57
|
+
shadowObserver.observe(root, {
|
|
58
|
+
childList: true,
|
|
59
|
+
subtree: true,
|
|
60
|
+
attributes: true,
|
|
61
|
+
attributeFilter: ["data-error"]
|
|
62
|
+
});
|
|
63
|
+
observedRoot = root;
|
|
64
|
+
}
|
|
65
|
+
portal.style.display = portal.shadowRoot?.querySelector(
|
|
66
|
+
RED_CHROME_SELECTOR
|
|
67
|
+
) ? "none" : "";
|
|
68
|
+
const hasBlockingError = Boolean(
|
|
69
|
+
portal.shadowRoot?.querySelector(BLOCKING_ERROR_SELECTOR)
|
|
70
|
+
);
|
|
71
|
+
post(hasBlockingError ? "error" : "ok");
|
|
72
|
+
};
|
|
73
|
+
const lightObserver = new MutationObserver(syncOverlayState);
|
|
74
|
+
lightObserver.observe(document.documentElement, {
|
|
75
|
+
childList: true,
|
|
76
|
+
subtree: true
|
|
77
|
+
});
|
|
78
|
+
syncOverlayState();
|
|
79
|
+
return () => {
|
|
80
|
+
lightObserver.disconnect();
|
|
81
|
+
shadowObserver?.disconnect();
|
|
82
|
+
if (pendingPost) {
|
|
83
|
+
window.clearTimeout(pendingPost.timeoutId);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
NextErrorReporter
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=NextErrorReporter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../NextErrorReporter.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * NOTE (Max, 2026-06-10): This component sends post messages to its embedder (website-builder miniapp)\n * so that it can render a loading / error UI if there's an error on this page.\n *\n * Ideally, we wouldn't need any update to the sandbox runtime to achieve this: the miniapp would just read the\n * iframe's content and detect whenever there's a nextjs error overlay.\n * But the preview runs cross-origin from the editor, so the editor can't read the iframe's\n * DOM (browser security).\n *\n * This component has observers that detect when the nextjs error overlay appears or\n * disappears, and sends a post message with a status:\n * - `status`: \"error\" while Next's dev overlay opens its blocking error dialog\n * (a real page break \u2014 see the selectors below for why the badge's issue\n * count is deliberately NOT used: it also counts recovered loader misses)\n * - `status`: \"ok\" otherwise. Cleared by Fast Refresh on a fix, so it's also the\n * recovery signal (agent fixed the error, so it's the error -> ok transition)\n *\n * It also hides Next's dev error overlay (`<nextjs-portal>`) at the source the\n * instant an error appears, so the red bubble never paints \u2014 the MutationObserver\n * callback runs as a microtask before paint. `data-error` stays queryable while\n * hidden, so detection is unaffected;\n *\n * The overlay lives in `<nextjs-portal>`'s shadow root, which Next can recreate,\n * so we re-attach the shadow observer whenever it hands us a new root. No polling.\n *\n * This must be mounted OUTSIDE the error boundary (see ReploProvider) so it keeps\n * reporting while the boundary is caught (and showing its neutral cover).\n *\n * I'm unsatisfied with this approach: observing for the nextjs-portal is brittle and ugly.\n * Ideally, the ClientErrorBoundary, which hooks into the render cycle, in componentDidCatch, would\n * just report a post message to the miniapp. I prototyped that, the error detection works\n * great (ok -> error transition).\n * But the error -> ok transition doesn't work for Server components (it only does for Client\n * components). Here's a slack thread with me <> Gabe on it:\n * https://replohq.slack.com/archives/C03AACVP08Y/p1781154057832689?thread_ts=1781029224.564489&cid=C03AACVP08Y.\n */\n// NOTE (Max, 2026-08-04): <nextjs-portal> surfaces two very different things,\n// and only one of them means the page is actually broken:\n// - the dev-tools badge (`[data-next-badge][data-error=\"true\"]`) lights for\n// EVERY collected issue, including a server-side loader error that React\n// already recovered from by streaming the LoaderErrorBoundary fallback. That\n// is a graceful, recoverable miss (product unpublished, deleted collection,\n// \u2026) \u2014 the page renders fine \u2014 and it must NOT be reported as a page error.\n// - the error overlay dialog (`[data-nextjs-dialog]`) only mounts when Next\n// auto-opens the overlay for a genuinely blocking error: the app shell's\n// error boundary catching an uncaught render crash, an unrecoverable SSR\n// error (missing root tags), or a build error. Next never auto-opens the\n// dialog for a recovered error. Verified against next@16.1.7 (the version\n// the platform SDK packages and most templates pin) and next@15.3.8: the sole auto-open\n// is AppDevOverlayErrorBoundary's componentDidCatch -> openErrorOverlay,\n// i.e. the shell boundary, which a nested LoaderErrorBoundary never reaches\n// (a caught loader miss only increments the badge count). The badge count\n// opens the dialog only on an explicit user click.\n// So the open dialog \u2014 not the badge's issue count \u2014 is the fatal-vs-recoverable\n// discriminator. Keying off the badge reported recoverable loader misses as\n// full-page errors, blanking a preview that had actually rendered its fallback.\n//\n// We still *hide* the whole portal whenever Next paints red chrome (the errored\n// badge OR the dialog) so no dev overlay bleeds into the preview \u2014 a recovered\n// loader miss shouldn't flash a red dot either. Only the blocking dialog is\n// reported upward as a page error.\nconst RED_CHROME_SELECTOR =\n '[data-next-badge][data-error=\"true\"], [data-nextjs-dialog]';\nconst BLOCKING_ERROR_SELECTOR = \"[data-nextjs-dialog]\";\n\n// HMR portal churn flips the overlay for milliseconds in both directions; only a state that persists this long is real.\nconst STATUS_LATCH_MS = 500;\n\ntype PreviewStatus = \"ok\" | \"error\";\n\nexport function NextErrorReporter() {\n // eslint-disable-next-line replo/no-use-effect -- Observes Next's dev error overlay (a shadow-DOM node outside React's tree) and relays its state to the cross-origin embedder. No React alternative applies.\n React.useEffect(() => {\n let lastStatus: PreviewStatus | null = null;\n let shadowObserver: MutationObserver | null = null;\n let observedRoot: ShadowRoot | null = null;\n let pendingPost: { status: PreviewStatus; timeoutId: number } | null = null;\n\n const postNow = (status: PreviewStatus) => {\n if (status === lastStatus) {\n return;\n }\n lastStatus = status;\n window.parent.postMessage(\n {\n type: \"replo:preview:status\",\n status,\n route: window.location.pathname,\n },\n \"*\",\n );\n };\n\n // Latch every transition (the opposite observation cancels a pending one) \u2014 a false error <-> ok flip makes the editor reload the preview. Only the initial \"ok\" posts immediately, as the readiness signal.\n const post = (status: PreviewStatus) => {\n if (pendingPost) {\n if (pendingPost.status === status) {\n return;\n }\n window.clearTimeout(pendingPost.timeoutId);\n pendingPost = null;\n }\n if (status === lastStatus) {\n return;\n }\n if (lastStatus === null && status === \"ok\") {\n postNow(\"ok\");\n return;\n }\n const timeoutId = window.setTimeout(() => {\n pendingPost = null;\n postNow(status);\n }, STATUS_LATCH_MS);\n pendingPost = { status, timeoutId };\n };\n\n // NOTE (Max, 2026-06-10): Reads the dev overlay's current error state and\n // propagates it \u2014 re-attach the shadow observer to the live root, hide the\n // portal while erroring, and post ok/error up to the editor. Runs on init and\n // on every observed DOM mutation.\n const syncOverlayState = () => {\n const portal = document.getElementsByTagName(\"nextjs-portal\")[0];\n if (!(portal instanceof HTMLElement)) {\n post(\"ok\");\n return;\n }\n // NOTE (Max, 2026-06-10): Re-attach whenever Next hands us a fresh shadow\n // root, else we'd keep watching a detached one after the overlay is\n // recreated.\n const root = portal.shadowRoot;\n if (root && root !== observedRoot) {\n shadowObserver?.disconnect();\n shadowObserver = new MutationObserver(syncOverlayState);\n shadowObserver.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-error\"],\n });\n observedRoot = root;\n }\n // NOTE (Max, 2026-06-10): Hide the dev overlay at the source so the red\n // bubble never paints \u2014 for any red chrome, including a recovered miss's\n // errored badge.\n portal.style.display = portal.shadowRoot?.querySelector(\n RED_CHROME_SELECTOR,\n )\n ? \"none\"\n : \"\";\n // Only the blocking overlay dialog means the page actually broke; the\n // badge's issue count also covers recovered loader misses (see selectors).\n const hasBlockingError = Boolean(\n portal.shadowRoot?.querySelector(BLOCKING_ERROR_SELECTOR),\n );\n post(hasBlockingError ? \"error\" : \"ok\");\n };\n\n // NOTE (Max, 2026-06-10): Catches the portal being added/removed in the light\n // DOM; the re-attached shadow observer catches in-overlay changes.\n const lightObserver = new MutationObserver(syncOverlayState);\n lightObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n syncOverlayState();\n\n return () => {\n lightObserver.disconnect();\n shadowObserver?.disconnect();\n if (pendingPost) {\n window.clearTimeout(pendingPost.timeoutId);\n }\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,YAAY,WAAW;AA+DvB,MAAM,sBACJ;AACF,MAAM,0BAA0B;AAGhC,MAAM,kBAAkB;AAIjB,SAAS,oBAAoB;AAElC,QAAM,UAAU,MAAM;AACpB,QAAI,aAAmC;AACvC,QAAI,iBAA0C;AAC9C,QAAI,eAAkC;AACtC,QAAI,cAAmE;AAEvE,UAAM,UAAU,CAAC,WAA0B;AACzC,UAAI,WAAW,YAAY;AACzB;AAAA,MACF;AACA,mBAAa;AACb,aAAO,OAAO;AAAA,QACZ;AAAA,UACE,MAAM;AAAA,UACN;AAAA,UACA,OAAO,OAAO,SAAS;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,OAAO,CAAC,WAA0B;AACtC,UAAI,aAAa;AACf,YAAI,YAAY,WAAW,QAAQ;AACjC;AAAA,QACF;AACA,eAAO,aAAa,YAAY,SAAS;AACzC,sBAAc;AAAA,MAChB;AACA,UAAI,WAAW,YAAY;AACzB;AAAA,MACF;AACA,UAAI,eAAe,QAAQ,WAAW,MAAM;AAC1C,gBAAQ,IAAI;AACZ;AAAA,MACF;AACA,YAAM,YAAY,OAAO,WAAW,MAAM;AACxC,sBAAc;AACd,gBAAQ,MAAM;AAAA,MAChB,GAAG,eAAe;AAClB,oBAAc,EAAE,QAAQ,UAAU;AAAA,IACpC;AAMA,UAAM,mBAAmB,MAAM;AAC7B,YAAM,SAAS,SAAS,qBAAqB,eAAe,EAAE,CAAC;AAC/D,UAAI,EAAE,kBAAkB,cAAc;AACpC,aAAK,IAAI;AACT;AAAA,MACF;AAIA,YAAM,OAAO,OAAO;AACpB,UAAI,QAAQ,SAAS,cAAc;AACjC,wBAAgB,WAAW;AAC3B,yBAAiB,IAAI,iBAAiB,gBAAgB;AACtD,uBAAe,QAAQ,MAAM;AAAA,UAC3B,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,iBAAiB,CAAC,YAAY;AAAA,QAChC,CAAC;AACD,uBAAe;AAAA,MACjB;AAIA,aAAO,MAAM,UAAU,OAAO,YAAY;AAAA,QACxC;AAAA,MACF,IACI,SACA;AAGJ,YAAM,mBAAmB;AAAA,QACvB,OAAO,YAAY,cAAc,uBAAuB;AAAA,MAC1D;AACA,WAAK,mBAAmB,UAAU,IAAI;AAAA,IACxC;AAIA,UAAM,gBAAgB,IAAI,iBAAiB,gBAAgB;AAC3D,kBAAc,QAAQ,SAAS,iBAAiB;AAAA,MAC9C,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AACD,qBAAiB;AAEjB,WAAO,MAAM;AACX,oBAAc,WAAW;AACzB,sBAAgB,WAAW;AAC3B,UAAI,aAAa;AACf,eAAO,aAAa,YAAY,SAAS;AAAA,MAC3C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Crypto helpers shared across runtime targets (Cloudflare Workers, Node
|
|
3
|
+
* 19+, browsers). Built on `crypto.subtle` so the same implementation runs
|
|
4
|
+
* inside Workers without a Node-only `node:crypto` import.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
7
|
+
export declare function base64UrlToBytes(input: string): Uint8Array;
|
|
8
|
+
export declare function randomBase64Url(byteLength: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Prefer this over the global `crypto.randomUUID()`, which is secure-context-only
|
|
11
|
+
* and therefore undefined over plain `http://`. `uuid`'s v4 is backed by
|
|
12
|
+
* `crypto.getRandomValues`, which carries no such restriction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function randomUUID(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the SHA-256 of `input` as a 64-char lowercase hex string.
|
|
17
|
+
*
|
|
18
|
+
* Used for storing opaque secrets (e.g. public API tokens) where we want
|
|
19
|
+
* constant-length hex output suitable for a `VARCHAR(64)` column with a
|
|
20
|
+
* unique index.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sha256Hex(input: string): Promise<string>;
|
|
23
|
+
export declare function constantTimeEqual({ left, right, }: {
|
|
24
|
+
left: Uint8Array;
|
|
25
|
+
right: Uint8Array;
|
|
26
|
+
}): boolean;
|
|
27
|
+
export declare function getHmacSha256Hex({ rawBody, signingKey, }: {
|
|
28
|
+
rawBody: string;
|
|
29
|
+
signingKey: string;
|
|
30
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
type ValueOfType<ObjectType> = ObjectType[keyof ObjectType];
|
|
2
|
+
/**
|
|
3
|
+
* Mostly taken from
|
|
4
|
+
* https://gist.githubusercontent.com/scokmen/f813c904ef79022e84ab2409574d1b45/raw/cd8709a2fccb005bb53e9bfb2461e07d40b4e8d8/HttpStatusCode.ts
|
|
5
|
+
*/
|
|
6
|
+
export declare const HTTP_STATUS_CODES: {
|
|
7
|
+
readonly CONTINUE: 100;
|
|
8
|
+
readonly SWITCHING_PROTOCOLS: 101;
|
|
9
|
+
readonly PROCESSING: 102;
|
|
10
|
+
readonly OK: 200;
|
|
11
|
+
readonly CREATED: 201;
|
|
12
|
+
readonly ACCEPTED: 202;
|
|
13
|
+
readonly NON_AUTHORITATIVE_INFORMATION: 203;
|
|
14
|
+
readonly NO_CONTENT: 204;
|
|
15
|
+
readonly RESET_CONTENT: 205;
|
|
16
|
+
readonly PARTIAL_CONTENT: 206;
|
|
17
|
+
readonly MULTI_STATUS: 207;
|
|
18
|
+
readonly ALREADY_REPORTED: 208;
|
|
19
|
+
readonly IM_USED: 226;
|
|
20
|
+
readonly MULTIPLE_CHOICES: 300;
|
|
21
|
+
readonly MOVED_PERMANENTLY: 301;
|
|
22
|
+
readonly FOUND: 302;
|
|
23
|
+
readonly SEE_OTHER: 303;
|
|
24
|
+
readonly NOT_MODIFIED: 304;
|
|
25
|
+
readonly USE_PROXY: 305;
|
|
26
|
+
readonly SWITCH_PROXY: 306;
|
|
27
|
+
readonly TEMPORARY_REDIRECT: 307;
|
|
28
|
+
readonly PERMANENT_REDIRECT: 308;
|
|
29
|
+
readonly BAD_REQUEST: 400;
|
|
30
|
+
readonly UNAUTHORIZED: 401;
|
|
31
|
+
readonly PAYMENT_REQUIRED: 402;
|
|
32
|
+
readonly FORBIDDEN: 403;
|
|
33
|
+
readonly NOT_FOUND: 404;
|
|
34
|
+
readonly METHOD_NOT_ALLOWED: 405;
|
|
35
|
+
readonly NOT_ACCEPTABLE: 406;
|
|
36
|
+
readonly PROXY_AUTHENTICATION_REQUIRED: 407;
|
|
37
|
+
readonly REQUEST_TIMEOUT: 408;
|
|
38
|
+
readonly CONFLICT: 409;
|
|
39
|
+
readonly GONE: 410;
|
|
40
|
+
readonly LENGTH_REQUIRED: 411;
|
|
41
|
+
readonly PRECONDITION_FAILED: 412;
|
|
42
|
+
readonly PAYLOAD_TOO_LARGE: 413;
|
|
43
|
+
readonly URI_TOO_LONG: 414;
|
|
44
|
+
readonly UNSUPPORTED_MEDIA_TYPE: 415;
|
|
45
|
+
readonly RANGE_NOT_SATISFIABLE: 416;
|
|
46
|
+
readonly EXPECTATION_FAILED: 417;
|
|
47
|
+
readonly I_AM_A_TEAPOT: 418;
|
|
48
|
+
readonly MISDIRECTED_REQUEST: 421;
|
|
49
|
+
readonly UNPROCESSABLE_ENTITY: 422;
|
|
50
|
+
readonly LOCKED: 423;
|
|
51
|
+
readonly FAILED_DEPENDENCY: 424;
|
|
52
|
+
readonly UPGRADE_REQUIRED: 426;
|
|
53
|
+
readonly PRECONDITION_REQUIRED: 428;
|
|
54
|
+
readonly TOO_MANY_REQUESTS: 429;
|
|
55
|
+
readonly REQUEST_HEADER_FIELDS_TOO_LARGE: 431;
|
|
56
|
+
readonly UNAVAILABLE_FOR_LEGAL_REASONS: 451;
|
|
57
|
+
readonly INTERNAL_SERVER_ERROR: 500;
|
|
58
|
+
readonly NOT_IMPLEMENTED: 501;
|
|
59
|
+
readonly BAD_GATEWAY: 502;
|
|
60
|
+
readonly SERVICE_UNAVAILABLE: 503;
|
|
61
|
+
readonly GATEWAY_TIMEOUT: 504;
|
|
62
|
+
readonly HTTP_VERSION_NOT_SUPPORTED: 505;
|
|
63
|
+
readonly VARIANT_ALSO_NEGOTIATES: 506;
|
|
64
|
+
readonly INSUFFICIENT_STORAGE: 507;
|
|
65
|
+
readonly LOOP_DETECTED: 508;
|
|
66
|
+
readonly NOT_EXTENDED: 510;
|
|
67
|
+
readonly NETWORK_AUTHENTICATION_REQUIRED: 511;
|
|
68
|
+
readonly UPSTREAM_WEBSERVER_ERROR: 520;
|
|
69
|
+
};
|
|
70
|
+
export type HttpStatusCode = ValueOfType<typeof HTTP_STATUS_CODES>;
|
|
71
|
+
/**
|
|
72
|
+
* Checks if an error message indicates a network error (e.g., connectivity issues,
|
|
73
|
+
* request timeouts, DNS failures). These errors typically don't need to be tracked
|
|
74
|
+
* in error monitoring systems like Sentry since they're user-side connectivity issues.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isNetworkErrorMessage(message: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Extracts a human-readable message from an unknown caught value. If the value
|
|
79
|
+
* is an Error, returns its message; otherwise falls back to the provided
|
|
80
|
+
* fallback string or `String(error)`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function getErrorMessage(error: unknown, fallback?: string): string;
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugified family name, matching how `fontNameToCssVariable` derives a CSS var
|
|
3
|
+
* (e.g. "DM Sans" → "dm-sans"), so a `--font-<slug>` var round-trips back to a
|
|
4
|
+
* catalog family.
|
|
5
|
+
*/
|
|
6
|
+
export declare function slugifyFontFamily(family: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* "DM Sans" → "--font-dm-sans". The CSS variable a `next/font` import exposes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function fontNameToCssVariable(fontName: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a family in single quotes for `font-family` when it contains whitespace
|
|
13
|
+
* (e.g. `Open Sans` → `'Open Sans'`); single-word families pass through.
|
|
14
|
+
*/
|
|
15
|
+
export declare function quoteFontFamily(family: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* "DM Sans" → "dmSans". The JS const name for a `next/font` declaration.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fontNameToVariableName(fontName: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Google Fonts CSS2 stylesheet URL for a family at the given weights (defaulting
|
|
22
|
+
* to 400), with `display=swap`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildGoogleFontsCssUrl(family: string, weights?: number[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* "Brand Sans-Bold.woff2" → { family: "Brand Sans Bold", mimeType: "font/woff2" }.
|
|
27
|
+
* The family is derived from the filename since Bedrock assets don't store one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function describeFontFile(fileName: string): {
|
|
30
|
+
family: string;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The `format(...)` keyword for an @font-face src, from a font file extension
|
|
35
|
+
* (Bedrock's `find` returns `fileExtension`, not a MIME type).
|
|
36
|
+
*/
|
|
37
|
+
export declare function fontFormatFromExtension(extension: string): string;
|
|
38
|
+
/** The `format(...)` keyword for an @font-face src, from a font MIME type. */
|
|
39
|
+
export declare function fontFormatFromMimeType(mimeType: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* The @font-face rule that makes a custom family renderable from its asset URL.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildFontFaceCss({ family, faceUrl, format, weight, }: {
|
|
44
|
+
family: string;
|
|
45
|
+
faceUrl: string;
|
|
46
|
+
format: string;
|
|
47
|
+
weight?: number;
|
|
48
|
+
}): string;
|
|
49
|
+
export declare const escapeFontFamilyName: (fontFamily: string) => string;
|