@replohq/sandbox-runtime 1.7.13 → 1.9.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/PageCaptureResponder.d.ts +1 -0
- package/PageCaptureResponder.js +12 -0
- package/_vendor/replo-utils/lib/crypto.mjs +1 -0
- package/_vendor/replo-utils/lib/math.mjs +1 -1
- package/_vendor/replo-utils/lib/pageCapture.d.ts +34 -0
- package/_vendor/replo-utils/lib/pageCapture.mjs +52 -0
- package/_vendor/schemas/errors.d.ts +1 -0
- package/_vendor/schemas/generated/selectMode.d.ts +25 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +1 -1
- package/_vendor/schemas/selectMode.d.ts +101 -2
- package/_vendor/schemas/selectMode.mjs +1 -1
- package/_vendor/schemas/selectModeIframeRpc.d.ts +148 -4
- package/_vendor/schemas/selectModeIframeRpc.mjs +1 -1
- package/_vendor/schemas/selectModeImage.d.ts +9 -0
- package/_vendor/schemas/selectModeImage.mjs +24 -0
- package/buildMetadata.js +3 -3
- package/capturePage.d.ts +11 -0
- package/capturePage.js +261 -0
- package/chunk-LIPH4O4F.mjs +67 -0
- package/{chunk-4T7WSMC3.mjs → chunk-XWNBKE63.mjs} +25 -1
- package/package.json +5 -4
- package/sandbox-runtime.js +2 -0
- package/select-mode/descriptor/elementDescriptor.js +2 -0
- package/select-mode/hooks/useOptimisticOverrides.js +63 -10
- package/select-mode/hooks/useSelectionInteractions.js +1 -10
- package/select-mode/iframeRpcChannel.d.ts +42 -0
- package/select-mode/imageOverridePreview.d.ts +6 -0
- package/select-mode/imageOverridePreview.js +77 -0
- package/select-mode/imageSources.d.ts +3 -0
- package/select-mode/imageSources.js +68 -0
- package/startPageCaptureResponder.d.ts +32 -0
- package/startPageCaptureResponder.js +87 -0
- package/chunk-SGKQ5HGT.mjs +0 -7
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getImageEditSnapshot } from "../_vendor/schemas/selectModeImage.mjs";
|
|
2
|
+
import { getImageDescriptor, getPictureSources } from "./imageSources";
|
|
3
|
+
function createImagePreview(image) {
|
|
4
|
+
const picture = document.createElement("picture");
|
|
5
|
+
picture.hidden = true;
|
|
6
|
+
picture.dataset.reploImageResolver = "true";
|
|
7
|
+
const resolver = document.createElement("img");
|
|
8
|
+
resolver.alt = "";
|
|
9
|
+
const style = document.createElement("style");
|
|
10
|
+
const token = crypto.randomUUID();
|
|
11
|
+
image.dataset.reploImagePreview = token;
|
|
12
|
+
document.head.append(style);
|
|
13
|
+
let configuration = "";
|
|
14
|
+
let disposed = false;
|
|
15
|
+
function paint() {
|
|
16
|
+
if (disposed) return;
|
|
17
|
+
const url = resolver.currentSrc || resolver.src;
|
|
18
|
+
if (!url) return;
|
|
19
|
+
const rule = `[data-replo-image-preview="${token}"] { content: url(${JSON.stringify(url)}) !important; }`;
|
|
20
|
+
if (style.textContent !== rule) style.textContent = rule;
|
|
21
|
+
}
|
|
22
|
+
resolver.addEventListener("load", paint);
|
|
23
|
+
return {
|
|
24
|
+
render(config) {
|
|
25
|
+
const next = JSON.stringify(config);
|
|
26
|
+
if (configuration !== next) {
|
|
27
|
+
configuration = next;
|
|
28
|
+
const originals = getPictureSources(image);
|
|
29
|
+
const sources = config.sources.map((source, index) => {
|
|
30
|
+
const original = originals.find((candidate) => {
|
|
31
|
+
return candidate.media.trim() === source.media && candidate.type === source.type;
|
|
32
|
+
}) ?? originals[index];
|
|
33
|
+
const node = document.createElement("source");
|
|
34
|
+
node.media = source.media;
|
|
35
|
+
node.type = resolveMetadata(source.type, original?.type);
|
|
36
|
+
node.srcset = resolveMetadata(source.srcSet, original?.srcset);
|
|
37
|
+
node.sizes = resolveMetadata(source.sizes, original?.sizes);
|
|
38
|
+
return node;
|
|
39
|
+
});
|
|
40
|
+
resolver.setAttribute(
|
|
41
|
+
"src",
|
|
42
|
+
resolveMetadata(config.src, image.getAttribute("src"))
|
|
43
|
+
);
|
|
44
|
+
resolver.setAttribute(
|
|
45
|
+
"srcset",
|
|
46
|
+
resolveMetadata(config.srcSet, image.getAttribute("srcset"))
|
|
47
|
+
);
|
|
48
|
+
resolver.setAttribute(
|
|
49
|
+
"sizes",
|
|
50
|
+
resolveMetadata(config.sizes, image.sizes)
|
|
51
|
+
);
|
|
52
|
+
picture.replaceChildren(...sources, resolver);
|
|
53
|
+
if (!picture.isConnected) document.body.append(picture);
|
|
54
|
+
}
|
|
55
|
+
paint();
|
|
56
|
+
},
|
|
57
|
+
isPersisted(config) {
|
|
58
|
+
return JSON.stringify(
|
|
59
|
+
getImageEditSnapshot(getImageDescriptor(image), image.baseURI)
|
|
60
|
+
) === JSON.stringify(getImageEditSnapshot(config, image.baseURI));
|
|
61
|
+
},
|
|
62
|
+
dispose() {
|
|
63
|
+
disposed = true;
|
|
64
|
+
resolver.removeEventListener("load", paint);
|
|
65
|
+
picture.remove();
|
|
66
|
+
style.remove();
|
|
67
|
+
if (image.dataset.reploImagePreview === token)
|
|
68
|
+
delete image.dataset.reploImagePreview;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function resolveMetadata(value, original) {
|
|
73
|
+
return value === "[image metadata omitted]" ? original ?? "" : value;
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
createImagePreview
|
|
77
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function getPictureSources(image) {
|
|
2
|
+
const parent = image.parentElement;
|
|
3
|
+
if (parent?.tagName !== "PICTURE") {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
return Array.from(parent.children).filter(
|
|
7
|
+
(child) => {
|
|
8
|
+
return child instanceof HTMLSourceElement && Boolean(
|
|
9
|
+
child.compareDocumentPosition(image) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const MAX_IMAGE_METADATA_LENGTH = 2048;
|
|
15
|
+
const MAX_PICTURE_SOURCES = 16;
|
|
16
|
+
function boundedImageMetadata(value) {
|
|
17
|
+
if (value.length > MAX_IMAGE_METADATA_LENGTH || /data:/i.test(value)) {
|
|
18
|
+
return "[image metadata omitted]";
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function getPreviewUrl({
|
|
23
|
+
src,
|
|
24
|
+
srcSet,
|
|
25
|
+
baseURI
|
|
26
|
+
}) {
|
|
27
|
+
if (src.length > MAX_IMAGE_METADATA_LENGTH || srcSet.length > MAX_IMAGE_METADATA_LENGTH)
|
|
28
|
+
return null;
|
|
29
|
+
const candidate = src || srcSet.trim().split(/\s+/)[0]?.replace(/,+$/, "");
|
|
30
|
+
if (!candidate) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(candidate, baseURI);
|
|
35
|
+
return ["http:", "https:"].includes(url.protocol) ? url.href : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function getImageDescriptor(element) {
|
|
41
|
+
if (!(element instanceof HTMLImageElement)) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
const src = element.getAttribute("src") ?? "";
|
|
45
|
+
const srcSet = element.getAttribute("srcset") ?? "";
|
|
46
|
+
return {
|
|
47
|
+
src: boundedImageMetadata(src),
|
|
48
|
+
srcSet: boundedImageMetadata(srcSet),
|
|
49
|
+
sizes: boundedImageMetadata(element.sizes),
|
|
50
|
+
currentSrc: boundedImageMetadata(element.currentSrc),
|
|
51
|
+
previewUrl: getPreviewUrl({ src, srcSet, baseURI: element.baseURI }),
|
|
52
|
+
sources: getPictureSources(element).filter((source) => source.media.length <= MAX_IMAGE_METADATA_LENGTH).slice(0, MAX_PICTURE_SOURCES).map((source) => ({
|
|
53
|
+
srcSet: boundedImageMetadata(source.srcset),
|
|
54
|
+
sizes: boundedImageMetadata(source.sizes),
|
|
55
|
+
media: source.media.trim(),
|
|
56
|
+
type: boundedImageMetadata(source.type),
|
|
57
|
+
previewUrl: getPreviewUrl({
|
|
58
|
+
src: "",
|
|
59
|
+
srcSet: source.srcset,
|
|
60
|
+
baseURI: source.baseURI
|
|
61
|
+
})
|
|
62
|
+
}))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
getImageDescriptor,
|
|
67
|
+
getPictureSources
|
|
68
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PageCaptureResponseMessage } from "./_vendor/replo-utils/lib/pageCapture";
|
|
2
|
+
interface PageCaptureMessageListener {
|
|
3
|
+
(event: {
|
|
4
|
+
data: unknown;
|
|
5
|
+
source: unknown;
|
|
6
|
+
origin: string;
|
|
7
|
+
}): void;
|
|
8
|
+
}
|
|
9
|
+
/** The slice of `window` the responder touches, so specs can pass a fake. */
|
|
10
|
+
export interface PageCaptureWindow {
|
|
11
|
+
readonly innerWidth: number;
|
|
12
|
+
readonly parent: {
|
|
13
|
+
postMessage(message: PageCaptureResponseMessage, targetOrigin: string, transfer: Transferable[]): void;
|
|
14
|
+
};
|
|
15
|
+
readonly document: {
|
|
16
|
+
readonly fonts?: {
|
|
17
|
+
readonly ready: Promise<unknown>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
requestIdleCallback?(callback: () => void, options: {
|
|
21
|
+
timeout: number;
|
|
22
|
+
}): number;
|
|
23
|
+
addEventListener(type: "message", listener: PageCaptureMessageListener): void;
|
|
24
|
+
removeEventListener(type: "message", listener: PageCaptureMessageListener): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Answers Site Builder's page-capture requests with a JPEG of this page's
|
|
28
|
+
* first screen. Requests run one at a time so concurrent captures never
|
|
29
|
+
* fight over the page's temporary video posters.
|
|
30
|
+
*/
|
|
31
|
+
export declare function startPageCaptureResponder(targetWindow: PageCaptureWindow): () => void;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPageCaptureRequestMessage,
|
|
3
|
+
PAGE_CAPTURE_RESPONSE
|
|
4
|
+
} from "./_vendor/replo-utils/lib/pageCapture.mjs";
|
|
5
|
+
import { isTrustedViewportSnapshotRequester } from "./_vendor/replo-utils/lib/viewportSnapshot.mjs";
|
|
6
|
+
const IDLE_TIMEOUT_MS = 1e3;
|
|
7
|
+
function waitForIdle(targetWindow) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
if (targetWindow.requestIdleCallback) {
|
|
10
|
+
targetWindow.requestIdleCallback(
|
|
11
|
+
() => {
|
|
12
|
+
resolve();
|
|
13
|
+
},
|
|
14
|
+
{ timeout: IDLE_TIMEOUT_MS }
|
|
15
|
+
);
|
|
16
|
+
} else {
|
|
17
|
+
setTimeout(resolve, 0);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function respondToRequest({
|
|
22
|
+
targetWindow,
|
|
23
|
+
request
|
|
24
|
+
}) {
|
|
25
|
+
const failure = (error) => {
|
|
26
|
+
return {
|
|
27
|
+
response: {
|
|
28
|
+
type: PAGE_CAPTURE_RESPONSE,
|
|
29
|
+
requestId: request.requestId,
|
|
30
|
+
jpeg: null,
|
|
31
|
+
error
|
|
32
|
+
},
|
|
33
|
+
transfer: []
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
if (targetWindow.innerWidth !== request.width) {
|
|
37
|
+
return failure("layout-width-mismatch");
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const startedAt = performance.now();
|
|
41
|
+
await waitForIdle(targetWindow);
|
|
42
|
+
await targetWindow.document.fonts?.ready;
|
|
43
|
+
const { capturePage } = await import("./capturePage");
|
|
44
|
+
const blob = await capturePage({
|
|
45
|
+
width: request.width,
|
|
46
|
+
height: request.height
|
|
47
|
+
});
|
|
48
|
+
const jpeg = await blob.arrayBuffer();
|
|
49
|
+
return {
|
|
50
|
+
response: {
|
|
51
|
+
type: PAGE_CAPTURE_RESPONSE,
|
|
52
|
+
requestId: request.requestId,
|
|
53
|
+
jpeg,
|
|
54
|
+
ms: Math.round(performance.now() - startedAt)
|
|
55
|
+
},
|
|
56
|
+
transfer: [jpeg]
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function startPageCaptureResponder(targetWindow) {
|
|
63
|
+
let queue = Promise.resolve();
|
|
64
|
+
const onMessage = (event) => {
|
|
65
|
+
if (event.source !== targetWindow.parent || !isPageCaptureRequestMessage(event.data) || !isTrustedViewportSnapshotRequester(event.origin)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const request = event.data;
|
|
69
|
+
const requesterOrigin = event.origin;
|
|
70
|
+
queue = queue.then(async () => {
|
|
71
|
+
const { response, transfer } = await respondToRequest({
|
|
72
|
+
targetWindow,
|
|
73
|
+
request
|
|
74
|
+
});
|
|
75
|
+
targetWindow.parent.postMessage(response, requesterOrigin, transfer);
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
console.warn("[replo] page capture reply failed", error);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
targetWindow.addEventListener("message", onMessage);
|
|
81
|
+
return () => {
|
|
82
|
+
targetWindow.removeEventListener("message", onMessage);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
startPageCaptureResponder
|
|
87
|
+
};
|