@replohq/sandbox-runtime 1.17.0 → 1.18.1

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 (38) hide show
  1. package/ClientErrorBoundary.js +26 -0
  2. package/DocumentReadyBeacon.js +5 -1
  3. package/_vendor/schemas/iframeRpcChannel.mjs +3 -541
  4. package/_vendor/schemas/previewHealthIframeRpc.d.ts +159 -0
  5. package/_vendor/schemas/previewHealthIframeRpc.mjs +123 -0
  6. package/_vendor/schemas/selectMode.d.ts +1 -1
  7. package/_vendor/schemas/selectModeIframeRpc.d.ts +5 -5
  8. package/buildMetadata.js +3 -3
  9. package/capturePage.js +5 -1
  10. package/chunk-KRA4LUIR.mjs +541 -0
  11. package/nextErrorFeed.d.ts +30 -0
  12. package/nextErrorFeed.js +64 -0
  13. package/package.json +5 -12
  14. package/previewHealth.d.ts +25 -0
  15. package/previewHealth.js +431 -0
  16. package/sandbox-runtime.js +0 -2
  17. package/select-mode/hooks/useSelectionHover.js +5 -0
  18. package/select-mode/iframeRpcChannel.d.ts +1 -1
  19. package/select-mode/section-canvas/SectionCanvas.js +80 -124
  20. package/select-mode/section-canvas/sectionCanvasStore.d.ts +0 -2
  21. package/select-mode/section-canvas/sectionCanvasStore.js +0 -15
  22. package/NextErrorReporter.d.ts +0 -1
  23. package/NextErrorReporter.js +0 -131
  24. package/_vendor/schemas/componentPreview.d.ts +0 -43
  25. package/_vendor/schemas/componentPreview.mjs +0 -79
  26. package/_vendor/schemas/generated/componentPreview.d.ts +0 -5
  27. package/_vendor/schemas/generated/componentPreview.mjs +0 -1
  28. package/_vendor/schemas/loaderKeys.d.ts +0 -69
  29. package/next/component-preview-client.d.ts +0 -8
  30. package/next/component-preview-client.js +0 -227
  31. package/next/component-preview-href.d.ts +0 -5
  32. package/next/component-preview-href.js +0 -13
  33. package/next/component-preview-props.d.ts +0 -8
  34. package/next/component-preview-props.js +0 -35
  35. package/next/component-preview.d.ts +0 -11
  36. package/next/component-preview.js +0 -83
  37. package/select-mode/section-canvas/sectionGhost.d.ts +0 -5
  38. package/select-mode/section-canvas/sectionGhost.js +0 -23
@@ -0,0 +1,431 @@
1
+ import retry from "async-retry";
2
+ import {
3
+ createIframeRpcChannel,
4
+ createIframeRpcRemote
5
+ } from "./_vendor/schemas/iframeRpcChannel.mjs";
6
+ import {
7
+ editorToPreviewHealthMethodConfig,
8
+ PREVIEW_HEALTH_IFRAME_RPC_CHANNEL_ID,
9
+ PREVIEW_HEALTH_IFRAME_RPC_CHANNEL_VERSION,
10
+ previewHealthToEditorMethodConfig
11
+ } from "./_vendor/schemas/previewHealthIframeRpc.mjs";
12
+ import { v4 as uuidv4 } from "uuid";
13
+ import * as z from "zod";
14
+ import { createStore } from "zustand/vanilla";
15
+ import { installNextErrorFeed } from "./nextErrorFeed";
16
+ const RECOVERY_SETTLE_MS = 300;
17
+ const MAX_DIAGNOSTICS_PER_DOCUMENT = 30;
18
+ const MAX_TEXT_LENGTH = 4e3;
19
+ const HMR_PATHS = ["/_next/hmr", "/_next/webpack-hmr"];
20
+ const HMR_RECONNECT_MIN_MS = 1e3;
21
+ const HMR_RECONNECT_MAX_MS = 1e4;
22
+ function truncate(value) {
23
+ return value.length > MAX_TEXT_LENGTH ? `${value.slice(0, MAX_TEXT_LENGTH)}\u2026` : value;
24
+ }
25
+ function stripAnsi(value) {
26
+ return value.replace(/\u001b\[[0-9;]*m/g, "");
27
+ }
28
+ const thrownErrorSchema = z.object({
29
+ message: z.string(),
30
+ name: z.string().nullable().catch(null),
31
+ stack: z.string().nullable().catch(null),
32
+ digest: z.string().nullable().catch(null)
33
+ });
34
+ const hmrBuildStatusSchema = z.object({
35
+ type: z.enum(["built", "sync"]),
36
+ errors: z.array(
37
+ z.union([
38
+ z.string(),
39
+ z.object({ message: z.string() }).transform(({ message }) => {
40
+ return message;
41
+ })
42
+ ])
43
+ )
44
+ });
45
+ class HmrSocketClosedError extends Error {
46
+ }
47
+ function toPreviewErrorDetail(value, componentStack) {
48
+ const thrown = thrownErrorSchema.safeParse(value);
49
+ const { message, name, stack, digest } = thrown.success ? thrown.data : { message: String(value), name: null, stack: null, digest: null };
50
+ return {
51
+ message: truncate(message),
52
+ name,
53
+ stack: stack === null ? null : truncate(stack),
54
+ componentStack: componentStack === null ? null : truncate(componentStack),
55
+ digest
56
+ };
57
+ }
58
+ function toMessageOnlyErrorDetail(message) {
59
+ return {
60
+ message,
61
+ name: null,
62
+ stack: null,
63
+ componentStack: null,
64
+ digest: null
65
+ };
66
+ }
67
+ function createEditorRemote() {
68
+ return createIframeRpcRemote(
69
+ createIframeRpcChannel({
70
+ subscriberWindow: window,
71
+ channelIdentifier: PREVIEW_HEALTH_IFRAME_RPC_CHANNEL_ID,
72
+ version: PREVIEW_HEALTH_IFRAME_RPC_CHANNEL_VERSION,
73
+ getTargetWindow: () => window.parent,
74
+ targetOrigin: "*",
75
+ isExpectedMessage: (event) => event.source === window.parent,
76
+ outgoingMethods: previewHealthToEditorMethodConfig,
77
+ incomingMethods: editorToPreviewHealthMethodConfig,
78
+ handlers: {}
79
+ })
80
+ );
81
+ }
82
+ function getHealthKey(health) {
83
+ return health.status === "ok" ? "ok" : `error:${health.source}:${health.error.message}`;
84
+ }
85
+ function createPreviewHealthStore() {
86
+ return createStore((set) => {
87
+ return {
88
+ boundaries: /* @__PURE__ */ new Map(),
89
+ rootFailure: null,
90
+ buildErrors: [],
91
+ actions: {
92
+ boundaryRendered: (boundaryId) => {
93
+ set((state) => {
94
+ return {
95
+ boundaries: new Map(state.boundaries).set(boundaryId, {
96
+ status: "rendered"
97
+ }),
98
+ rootFailure: null
99
+ };
100
+ });
101
+ },
102
+ boundaryCaughtError: (boundaryId, caught) => {
103
+ set((state) => {
104
+ return {
105
+ boundaries: new Map(state.boundaries).set(boundaryId, {
106
+ status: "failed",
107
+ failedAt: Date.now(),
108
+ ...caught
109
+ })
110
+ };
111
+ });
112
+ },
113
+ boundaryUnmounted: (boundaryId) => {
114
+ set((state) => {
115
+ if (!state.boundaries.has(boundaryId)) {
116
+ return state;
117
+ }
118
+ const boundaries = new Map(state.boundaries);
119
+ boundaries.delete(boundaryId);
120
+ return { boundaries };
121
+ });
122
+ },
123
+ rootCaughtError: (error) => {
124
+ set({ rootFailure: { error } });
125
+ },
126
+ buildErrorsChanged: (buildErrors) => {
127
+ set({ buildErrors });
128
+ }
129
+ }
130
+ };
131
+ });
132
+ }
133
+ function selectPreviewHealth(state) {
134
+ const firstBuildError = state.buildErrors[0];
135
+ if (firstBuildError !== void 0) {
136
+ return {
137
+ status: "error",
138
+ source: "build",
139
+ error: toMessageOnlyErrorDetail(firstBuildError)
140
+ };
141
+ }
142
+ let latestFailure = null;
143
+ let hasRenderedBoundary = false;
144
+ for (const health of state.boundaries.values()) {
145
+ if (health.status === "rendered") {
146
+ hasRenderedBoundary = true;
147
+ } else if (!latestFailure || health.failedAt >= latestFailure.failedAt) {
148
+ latestFailure = health;
149
+ }
150
+ }
151
+ if (latestFailure) {
152
+ return {
153
+ status: "error",
154
+ source: "boundary",
155
+ error: toPreviewErrorDetail(
156
+ latestFailure.error,
157
+ latestFailure.componentStack
158
+ )
159
+ };
160
+ }
161
+ if (state.rootFailure) {
162
+ return {
163
+ status: "error",
164
+ source: "root",
165
+ error: toPreviewErrorDetail(state.rootFailure.error, null)
166
+ };
167
+ }
168
+ return hasRenderedBoundary ? { status: "ok" } : null;
169
+ }
170
+ function createPreviewHealthMonitor() {
171
+ const store = createPreviewHealthStore();
172
+ const { actions } = store.getState();
173
+ const documentId = uuidv4();
174
+ const editor = createEditorRemote();
175
+ let seq = 0;
176
+ let lastPosted = null;
177
+ let pendingRecovery = null;
178
+ const pageFailureErrors = /* @__PURE__ */ new WeakSet();
179
+ const diagnosticKeys = /* @__PURE__ */ new Set();
180
+ const postStatus = (health) => {
181
+ lastPosted = health;
182
+ seq += 1;
183
+ editor.statusChanged({
184
+ route: window.location.pathname,
185
+ documentId,
186
+ seq,
187
+ health
188
+ });
189
+ };
190
+ const cancelPendingRecovery = () => {
191
+ if (pendingRecovery !== null) {
192
+ window.clearTimeout(pendingRecovery);
193
+ pendingRecovery = null;
194
+ }
195
+ };
196
+ const publish = (health) => {
197
+ if (health === null) {
198
+ return;
199
+ }
200
+ if (lastPosted && getHealthKey(lastPosted) === getHealthKey(health)) {
201
+ cancelPendingRecovery();
202
+ return;
203
+ }
204
+ if (health.status === "ok" && lastPosted?.status === "error") {
205
+ pendingRecovery ??= window.setTimeout(() => {
206
+ pendingRecovery = null;
207
+ const settled = selectPreviewHealth(store.getState());
208
+ if (settled?.status === "ok") {
209
+ postStatus(settled);
210
+ }
211
+ }, RECOVERY_SETTLE_MS);
212
+ return;
213
+ }
214
+ cancelPendingRecovery();
215
+ postStatus(health);
216
+ };
217
+ const recordDiagnostic = ({
218
+ origin,
219
+ impact,
220
+ error
221
+ }) => {
222
+ const firstStackFrame = error.stack?.split("\n")[1]?.trim() ?? "";
223
+ const key = `${origin}|${error.message}|${firstStackFrame}`;
224
+ if (diagnosticKeys.has(key) || diagnosticKeys.size >= MAX_DIAGNOSTICS_PER_DOCUMENT) {
225
+ return;
226
+ }
227
+ diagnosticKeys.add(key);
228
+ editor.diagnosticRecorded({
229
+ route: window.location.pathname,
230
+ documentId,
231
+ diagnostic: { origin, impact, error, at: Date.now() }
232
+ });
233
+ };
234
+ const rememberPageFailure = (value) => {
235
+ if (typeof value === "object" && value !== null) {
236
+ pageFailureErrors.add(value);
237
+ }
238
+ };
239
+ const recordPageFailureDiagnostics = (state, previous) => {
240
+ if (state.rootFailure && state.rootFailure !== previous.rootFailure) {
241
+ rememberPageFailure(state.rootFailure.error);
242
+ recordDiagnostic({
243
+ origin: "root",
244
+ impact: "page",
245
+ error: toPreviewErrorDetail(state.rootFailure.error, null)
246
+ });
247
+ }
248
+ for (const [boundaryId, health] of state.boundaries) {
249
+ const previousHealth = previous.boundaries.get(boundaryId);
250
+ if (health.status === "failed" && (previousHealth?.status !== "failed" || previousHealth.error !== health.error)) {
251
+ rememberPageFailure(health.error);
252
+ recordDiagnostic({
253
+ origin: "boundary",
254
+ impact: "page",
255
+ error: toPreviewErrorDetail(health.error, health.componentStack)
256
+ });
257
+ }
258
+ }
259
+ if (state.buildErrors !== previous.buildErrors) {
260
+ const previousBuildErrors = new Set(previous.buildErrors);
261
+ for (const message of state.buildErrors) {
262
+ if (!previousBuildErrors.has(message)) {
263
+ recordDiagnostic({
264
+ origin: "build",
265
+ impact: "page",
266
+ error: toMessageOnlyErrorDetail(message)
267
+ });
268
+ }
269
+ }
270
+ }
271
+ };
272
+ const unsubscribe = store.subscribe((state, previous) => {
273
+ recordPageFailureDiagnostics(state, previous);
274
+ publish(selectPreviewHealth(state));
275
+ });
276
+ const handleNextError = (event) => {
277
+ if (event.kind === "rootCaught") {
278
+ actions.rootCaughtError(event.error);
279
+ return;
280
+ }
281
+ if (pageFailureErrors.has(event.error)) {
282
+ return;
283
+ }
284
+ recordDiagnostic({
285
+ origin: event.kind === "rejection" ? "rejection" : event.origin,
286
+ impact: "unknown",
287
+ error: toPreviewErrorDetail(event.error, null)
288
+ });
289
+ };
290
+ if (process.env.NODE_ENV !== "production") {
291
+ installNextErrorFeed(handleNextError);
292
+ }
293
+ const overlayStyle = document.createElement("style");
294
+ overlayStyle.dataset.reploPreviewHealth = "";
295
+ overlayStyle.textContent = "nextjs-portal{display:none!important}";
296
+ document.head.append(overlayStyle);
297
+ const stopBuildStatus = subscribeToBuildStatus({
298
+ onBuildErrors: actions.buildErrorsChanged
299
+ });
300
+ return {
301
+ actions,
302
+ dispose() {
303
+ unsubscribe();
304
+ stopBuildStatus();
305
+ overlayStyle.remove();
306
+ cancelPendingRecovery();
307
+ if (window.__reploNextErrorFeed?.listener === handleNextError) {
308
+ window.__reploNextErrorFeed.listener = null;
309
+ }
310
+ }
311
+ };
312
+ }
313
+ function openSocket(url) {
314
+ return new Promise((resolve) => {
315
+ const socket = new window.WebSocket(url);
316
+ socket.addEventListener("open", () => {
317
+ resolve(socket);
318
+ });
319
+ socket.addEventListener("close", () => {
320
+ resolve(null);
321
+ });
322
+ });
323
+ }
324
+ function subscribeToBuildStatus({
325
+ onBuildErrors
326
+ }) {
327
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
328
+ const getUrl = (path) => {
329
+ return `${protocol}//${window.location.host}${path}`;
330
+ };
331
+ let isStopped = false;
332
+ let currentSocket = null;
333
+ const listen = (socket, path) => {
334
+ if (isStopped) {
335
+ socket.close();
336
+ return;
337
+ }
338
+ currentSocket = socket;
339
+ socket.addEventListener("message", (event) => {
340
+ const data = z.string().safeParse(event.data);
341
+ if (!data.success) {
342
+ return;
343
+ }
344
+ const status = hmrBuildStatusSchema.safeParse(JSON.parse(data.data));
345
+ if (status.success) {
346
+ onBuildErrors(
347
+ status.data.errors.map((message) => {
348
+ return truncate(stripAnsi(message));
349
+ })
350
+ );
351
+ }
352
+ });
353
+ socket.addEventListener("close", () => {
354
+ if (!isStopped) {
355
+ void reconnect(path);
356
+ }
357
+ });
358
+ };
359
+ const reconnect = async (path) => {
360
+ const socket = await retry(
361
+ async () => {
362
+ if (isStopped) {
363
+ return null;
364
+ }
365
+ const opened = await openSocket(getUrl(path));
366
+ if (opened === null) {
367
+ throw new HmrSocketClosedError(path);
368
+ }
369
+ return opened;
370
+ },
371
+ {
372
+ forever: true,
373
+ minTimeout: HMR_RECONNECT_MIN_MS,
374
+ maxTimeout: HMR_RECONNECT_MAX_MS
375
+ }
376
+ );
377
+ if (socket !== null) {
378
+ listen(socket, path);
379
+ }
380
+ };
381
+ const connect = async () => {
382
+ await retry(
383
+ async () => {
384
+ for (const path of HMR_PATHS) {
385
+ if (isStopped) {
386
+ return;
387
+ }
388
+ const socket = await openSocket(getUrl(path));
389
+ if (socket !== null) {
390
+ listen(socket, path);
391
+ return;
392
+ }
393
+ }
394
+ throw new HmrSocketClosedError(
395
+ "No HMR endpoint accepted the connection"
396
+ );
397
+ },
398
+ {
399
+ forever: true,
400
+ minTimeout: HMR_RECONNECT_MIN_MS,
401
+ maxTimeout: HMR_RECONNECT_MAX_MS
402
+ }
403
+ );
404
+ };
405
+ void connect();
406
+ return () => {
407
+ isStopped = true;
408
+ currentSocket?.close();
409
+ };
410
+ }
411
+ function getPreviewHealthMonitor() {
412
+ if (window.self === window.top) {
413
+ return null;
414
+ }
415
+ window.__reploPreviewHealthMonitor ??= createPreviewHealthMonitor();
416
+ return window.__reploPreviewHealthMonitor;
417
+ }
418
+ function getPreviewHealthActions() {
419
+ return getPreviewHealthMonitor()?.actions ?? null;
420
+ }
421
+ function resetPreviewHealthMonitorForTests() {
422
+ window.__reploPreviewHealthMonitor?.dispose();
423
+ delete window.__reploPreviewHealthMonitor;
424
+ }
425
+ if (typeof window !== "undefined" && process.env.NODE_ENV !== "production") {
426
+ getPreviewHealthMonitor();
427
+ }
428
+ export {
429
+ getPreviewHealthActions,
430
+ resetPreviewHealthMonitorForTests
431
+ };
@@ -7,7 +7,6 @@ import { DesignLibraryPreview } from "./design-library-preview/DesignLibraryPrev
7
7
  import { DocumentReadyBeacon } from "./DocumentReadyBeacon";
8
8
  import LinkInterceptor from "./LinkInterceptor";
9
9
  import { NavigationReceiver } from "./NavigationReceiver";
10
- import { NextErrorReporter } from "./NextErrorReporter";
11
10
  import { PageCaptureResponder } from "./PageCaptureResponder";
12
11
  import PostHogRelay from "./posthog-relay";
13
12
  import { EditorSelection } from "./select-mode/EditorSelection";
@@ -19,7 +18,6 @@ function SandboxRuntime({ children }) {
19
18
  const isDevServer = process.env.NODE_ENV === "development";
20
19
  return isDevServer ? /* @__PURE__ */ jsxs(Fragment, { children: [
21
20
  /* @__PURE__ */ jsx(DocumentReadyBeacon, {}),
22
- /* @__PURE__ */ jsx(NextErrorReporter, {}),
23
21
  /* @__PURE__ */ jsxs(ClientErrorBoundary, { resetKey: children, children: [
24
22
  /* @__PURE__ */ jsx(LinkInterceptor, {}),
25
23
  /* @__PURE__ */ jsx(NavigationReceiver, {}),
@@ -7,6 +7,7 @@ import {
7
7
  } from "../interactions/sectionGeometry";
8
8
  import { getElementBoundingRect } from "../interactions/selectionGeometry";
9
9
  import { isSectionCanvasControl } from "../section-canvas/sectionCanvasControls";
10
+ import { sectionCanvasStore } from "../section-canvas/sectionCanvasStore";
10
11
  import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
11
12
  function useSelectionHover() {
12
13
  const store = useSelectModeStore();
@@ -31,6 +32,10 @@ function useSelectionHover() {
31
32
  getWebsiteBuilderRpc()?.hoverElement({ rect: hoverRect });
32
33
  };
33
34
  const syncCurrentHover = (pointerClientPosition = store.getState().lastPointerClientPosition) => {
35
+ if (sectionCanvasStore.getState().drag) {
36
+ postHoveredElement(null);
37
+ return;
38
+ }
34
39
  const previewSection = store.getState().sectionPreviewElement;
35
40
  if (previewSection) {
36
41
  if (previewSection.isConnected) {
@@ -455,9 +455,9 @@ export declare function getSelectModeIframeRpcChannel(): import("../_vendor/sche
455
455
  inputSchema: import("zod").ZodObject<{
456
456
  feedback: import("zod").ZodNullable<import("zod").ZodObject<{
457
457
  status: import("zod").ZodEnum<{
458
+ failed: "failed";
458
459
  pending: "pending";
459
460
  unavailable: "unavailable";
460
- failed: "failed";
461
461
  }>;
462
462
  rect: import("zod").ZodObject<{
463
463
  x: import("zod").ZodNumber;