@replohq/sandbox-runtime 1.16.2 → 1.18.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.js +26 -0
- package/DocumentReadyBeacon.js +5 -1
- package/_vendor/replo-utils/lib/historyKeys.d.ts +2 -0
- package/_vendor/replo-utils/lib/historyKeys.mjs +18 -0
- package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +43 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +3 -541
- package/_vendor/schemas/previewHealthIframeRpc.d.ts +159 -0
- package/_vendor/schemas/previewHealthIframeRpc.mjs +123 -0
- package/_vendor/schemas/selectModeIframeRpc.d.ts +149 -3
- package/_vendor/schemas/selectModeIframeRpc.mjs +72 -2
- package/buildMetadata.js +3 -3
- package/chunk-KRA4LUIR.mjs +541 -0
- package/nextErrorFeed.d.ts +30 -0
- package/nextErrorFeed.js +64 -0
- package/package.json +5 -12
- package/previewHealth.d.ts +25 -0
- package/previewHealth.js +431 -0
- package/sandbox-runtime.js +0 -2
- package/select-mode/EditorSelection.js +5 -1
- package/select-mode/fast-refresh-relay.js +82 -3
- package/select-mode/fiber.d.ts +6 -0
- package/select-mode/fiber.js +33 -0
- package/select-mode/hooks/useParentMessaging.js +10 -3
- package/select-mode/hooks/useSelectionHover.js +2 -1
- package/select-mode/hooks/useSelectionInteractions.js +10 -0
- package/select-mode/iframeRpcChannel.d.ts +46 -2
- package/select-mode/iframeRpcChannel.js +7 -0
- package/select-mode/section-canvas/SectionCanvas.d.ts +7 -0
- package/select-mode/section-canvas/SectionCanvas.js +599 -0
- package/select-mode/section-canvas/optimisticSectionMove.d.ts +39 -0
- package/select-mode/section-canvas/optimisticSectionMove.js +172 -0
- package/select-mode/section-canvas/sectionCanvasControls.d.ts +3 -0
- package/select-mode/section-canvas/sectionCanvasControls.js +8 -0
- package/select-mode/section-canvas/sectionCanvasStore.d.ts +71 -0
- package/select-mode/section-canvas/sectionCanvasStore.js +275 -0
- package/select-mode/section-canvas/sectionElements.d.ts +16 -0
- package/select-mode/section-canvas/sectionElements.js +94 -0
- package/select-mode/section-canvas/sectionGhost.d.ts +5 -0
- package/select-mode/section-canvas/sectionGhost.js +23 -0
- package/select-mode/symbolicate.d.ts +2 -0
- package/select-mode/symbolicate.js +18 -1
- package/NextErrorReporter.d.ts +0 -1
- package/NextErrorReporter.js +0 -131
- package/_vendor/schemas/componentPreview.d.ts +0 -43
- package/_vendor/schemas/componentPreview.mjs +0 -79
- package/_vendor/schemas/generated/componentPreview.d.ts +0 -5
- package/_vendor/schemas/generated/componentPreview.mjs +0 -1
- package/_vendor/schemas/loaderKeys.d.ts +0 -69
- package/next/component-preview-client.d.ts +0 -8
- package/next/component-preview-client.js +0 -227
- package/next/component-preview-href.d.ts +0 -5
- package/next/component-preview-href.js +0 -13
- package/next/component-preview-props.d.ts +0 -8
- package/next/component-preview-props.js +0 -35
- package/next/component-preview.d.ts +0 -11
- package/next/component-preview.js +0 -83
package/previewHealth.js
ADDED
|
@@ -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
|
+
};
|
package/sandbox-runtime.js
CHANGED
|
@@ -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, {}),
|
|
@@ -8,6 +8,8 @@ import { useSelectionHover } from "./hooks/useSelectionHover";
|
|
|
8
8
|
import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
|
|
9
9
|
import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
10
10
|
import { PREVIEW_RENDERED_EVENT } from "./previewRendered";
|
|
11
|
+
import { SectionCanvas } from "./section-canvas/SectionCanvas";
|
|
12
|
+
import { sectionCanvasStore } from "./section-canvas/sectionCanvasStore";
|
|
11
13
|
import { SelectionFrames } from "./SelectionFrames";
|
|
12
14
|
import {
|
|
13
15
|
useDisplayMode,
|
|
@@ -90,6 +92,7 @@ function EditorSelection() {
|
|
|
90
92
|
onComplete: () => {
|
|
91
93
|
reapplyOverrides({ skipElement: store.getState().editingElement });
|
|
92
94
|
watchRender();
|
|
95
|
+
sectionCanvasStore.getState().actions.handlePreviewRefresh();
|
|
93
96
|
}
|
|
94
97
|
});
|
|
95
98
|
window.addEventListener(PREVIEW_RENDERED_EVENT, watchRender);
|
|
@@ -151,7 +154,8 @@ function EditorSelection() {
|
|
|
151
154
|
[data-select-mode-inline-editor][contenteditable="true"],
|
|
152
155
|
[data-select-mode-inline-editor][contenteditable="true"] * { cursor: text !important; }
|
|
153
156
|
` }),
|
|
154
|
-
/* @__PURE__ */ jsx(SelectionFrames, {})
|
|
157
|
+
/* @__PURE__ */ jsx(SelectionFrames, {}),
|
|
158
|
+
/* @__PURE__ */ jsx(SectionCanvas, {})
|
|
155
159
|
] });
|
|
156
160
|
}
|
|
157
161
|
export {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
2
|
+
import { SECTION_CANVAS_ATTRIBUTE } from "./section-canvas/sectionCanvasControls";
|
|
2
3
|
import { invalidateSymbolicationCache } from "./symbolicate";
|
|
4
|
+
const SERVER_REFRESH_HEADER = "next-hmr-refresh";
|
|
5
|
+
const SERVER_REFRESH_COMMIT_TIMEOUT_MS = 2e3;
|
|
3
6
|
let hasInstalledFastRefreshRelay = false;
|
|
4
7
|
let onFastRefreshComplete = null;
|
|
5
8
|
let lastLatencyMs = null;
|
|
@@ -11,6 +14,7 @@ function installFastRefreshRelay({
|
|
|
11
14
|
return;
|
|
12
15
|
}
|
|
13
16
|
hasInstalledFastRefreshRelay = true;
|
|
17
|
+
installServerRefreshRelay();
|
|
14
18
|
const originalCallback = window.__NEXT_HMR_LATENCY_CB;
|
|
15
19
|
window.__NEXT_HMR_LATENCY_CB = (latencyMs) => {
|
|
16
20
|
const isRepeat = latencyMs === lastLatencyMs;
|
|
@@ -19,12 +23,87 @@ function installFastRefreshRelay({
|
|
|
19
23
|
originalCallback?.(latencyMs);
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
getWebsiteBuilderRpc()?.fastRefreshComplete(void 0);
|
|
24
|
-
onFastRefreshComplete?.();
|
|
26
|
+
reportRefreshComplete();
|
|
25
27
|
originalCallback?.(latencyMs);
|
|
26
28
|
};
|
|
27
29
|
}
|
|
30
|
+
function reportRefreshComplete() {
|
|
31
|
+
invalidateSymbolicationCache();
|
|
32
|
+
getWebsiteBuilderRpc()?.fastRefreshComplete(void 0);
|
|
33
|
+
onFastRefreshComplete?.();
|
|
34
|
+
}
|
|
35
|
+
function isServerRefreshRequest(input, init) {
|
|
36
|
+
try {
|
|
37
|
+
return new Headers(init?.headers).has(SERVER_REFRESH_HEADER) || input instanceof Request && input.headers.has(SERVER_REFRESH_HEADER);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function nextReactCommit() {
|
|
43
|
+
const hook = Reflect.get(
|
|
44
|
+
window,
|
|
45
|
+
"__REACT_DEVTOOLS_GLOBAL_HOOK__"
|
|
46
|
+
);
|
|
47
|
+
if (!hook || typeof hook.onCommitFiberRoot !== "function") {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const original = hook.onCommitFiberRoot;
|
|
52
|
+
hook.onCommitFiberRoot = function relayCommit(...args) {
|
|
53
|
+
hook.onCommitFiberRoot = original;
|
|
54
|
+
resolve();
|
|
55
|
+
return original?.apply(this, args);
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function nextPageMutation() {
|
|
60
|
+
let observer = null;
|
|
61
|
+
const promise = new Promise((resolve) => {
|
|
62
|
+
observer = new MutationObserver((records) => {
|
|
63
|
+
const isPageChange = records.some((record) => {
|
|
64
|
+
const element = record.target instanceof Element ? record.target : record.target.parentElement;
|
|
65
|
+
return element !== null && document.body.contains(element) && !element.closest(`[${SECTION_CANVAS_ATTRIBUTE}]`);
|
|
66
|
+
});
|
|
67
|
+
if (isPageChange) {
|
|
68
|
+
observer?.disconnect();
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
observer.observe(document.documentElement, {
|
|
73
|
+
childList: true,
|
|
74
|
+
subtree: true,
|
|
75
|
+
characterData: true
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
return { promise, cancel: () => observer?.disconnect() };
|
|
79
|
+
}
|
|
80
|
+
function installServerRefreshRelay() {
|
|
81
|
+
const originalFetch = window.fetch;
|
|
82
|
+
window.fetch = function relayServerRefresh(input, init) {
|
|
83
|
+
const response = originalFetch.call(this, input, init);
|
|
84
|
+
if (isServerRefreshRequest(input, init)) {
|
|
85
|
+
void response.then((received) => received.clone().arrayBuffer()).then(
|
|
86
|
+
async () => {
|
|
87
|
+
const commit = nextReactCommit();
|
|
88
|
+
const mutation = commit ? null : nextPageMutation();
|
|
89
|
+
let timeout;
|
|
90
|
+
await Promise.race([
|
|
91
|
+
commit ?? mutation?.promise,
|
|
92
|
+
new Promise((resolve) => {
|
|
93
|
+
timeout = setTimeout(resolve, SERVER_REFRESH_COMMIT_TIMEOUT_MS);
|
|
94
|
+
})
|
|
95
|
+
]);
|
|
96
|
+
mutation?.cancel();
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
reportRefreshComplete();
|
|
99
|
+
},
|
|
100
|
+
() => {
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return response;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
28
107
|
export {
|
|
29
108
|
installFastRefreshRelay
|
|
30
109
|
};
|
package/select-mode/fiber.d.ts
CHANGED
|
@@ -48,4 +48,10 @@ export declare function getFiberChain(element: HTMLElement): {
|
|
|
48
48
|
componentName: string | undefined;
|
|
49
49
|
props: Record<string, unknown> | undefined;
|
|
50
50
|
}[];
|
|
51
|
+
/**
|
|
52
|
+
* JSX call sites from an element outward (its own, then each owner's), still compiled. Skips
|
|
53
|
+
* `resolveCurrentFiber`'s tree scan: a call site is the same in both fiber generations, and this
|
|
54
|
+
* runs for every element near the top of the page.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getElementSourceChain(element: HTMLElement, maxEntries?: number): DebugSource[];
|
|
51
57
|
export {};
|
package/select-mode/fiber.js
CHANGED
|
@@ -493,9 +493,42 @@ function getFiberChain(element) {
|
|
|
493
493
|
}
|
|
494
494
|
return fiberChain;
|
|
495
495
|
}
|
|
496
|
+
function getElementSourceChain(element, maxEntries = 16) {
|
|
497
|
+
const chain = [];
|
|
498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
499
|
+
const seenOwners = /* @__PURE__ */ new Set();
|
|
500
|
+
const push = (source) => {
|
|
501
|
+
if (!source) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const key = `${source.fileName}:${source.lineNumber}:${source.columnNumber ?? ""}`;
|
|
505
|
+
if (!seen.has(key)) {
|
|
506
|
+
seen.add(key);
|
|
507
|
+
chain.push(source);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
let fiber = getFiberFromElement(element);
|
|
511
|
+
let depth = 0;
|
|
512
|
+
while (fiber && depth < MAX_FIBER_CHAIN_DEPTH && chain.length < maxEntries) {
|
|
513
|
+
push(parseDebugStackSource(fiber._debugStack));
|
|
514
|
+
let owner = fiber._debugOwner;
|
|
515
|
+
while (owner && !seenOwners.has(owner) && chain.length < maxEntries) {
|
|
516
|
+
seenOwners.add(owner);
|
|
517
|
+
if (!("name" in owner)) {
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
push(parseDebugStackSource(owner.debugStack));
|
|
521
|
+
owner = owner.owner;
|
|
522
|
+
}
|
|
523
|
+
fiber = fiber.return;
|
|
524
|
+
depth += 1;
|
|
525
|
+
}
|
|
526
|
+
return chain;
|
|
527
|
+
}
|
|
496
528
|
export {
|
|
497
529
|
findDebugSource,
|
|
498
530
|
getComponentName,
|
|
531
|
+
getElementSourceChain,
|
|
499
532
|
getFiberChain,
|
|
500
533
|
getFiberFromElement,
|
|
501
534
|
getRepetitionHostNodes,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useEffect, useMemo } from "react";
|
|
2
2
|
import { useIframeRpcChannel } from "../../_vendor/replo-utils/react/use-iframe-rpc-channel.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
iframeRuntimeBuildMetadataSchema,
|
|
5
|
+
SECTION_CANVAS_CAPABILITY
|
|
6
|
+
} from "../../_vendor/schemas/selectModeIframeRpc.mjs";
|
|
4
7
|
import * as z from "zod";
|
|
5
8
|
import { BUILD_METADATA } from "../../buildMetadata";
|
|
6
9
|
import {
|
|
@@ -9,6 +12,7 @@ import {
|
|
|
9
12
|
setSelectModeParentHandlers
|
|
10
13
|
} from "../iframeRpcChannel";
|
|
11
14
|
const SDK_BUILD_METADATA_SPECIFIER = "@replohq/sdk/lib/buildMetadata";
|
|
15
|
+
const RUNTIME_CAPABILITIES = [SECTION_CANVAS_CAPABILITY];
|
|
12
16
|
const sdkBuildMetadataModuleSchema = z.object({
|
|
13
17
|
BUILD_METADATA: iframeRuntimeBuildMetadataSchema
|
|
14
18
|
});
|
|
@@ -18,7 +22,10 @@ async function announceSdkBuildMetadata() {
|
|
|
18
22
|
await import(SDK_BUILD_METADATA_SPECIFIER)
|
|
19
23
|
);
|
|
20
24
|
if (sdkBuildMetadata.success) {
|
|
21
|
-
getWebsiteBuilderRpc()?.iframeReady(
|
|
25
|
+
getWebsiteBuilderRpc()?.iframeReady({
|
|
26
|
+
...sdkBuildMetadata.data.BUILD_METADATA,
|
|
27
|
+
capabilities: RUNTIME_CAPABILITIES
|
|
28
|
+
});
|
|
22
29
|
}
|
|
23
30
|
} catch {
|
|
24
31
|
}
|
|
@@ -78,7 +85,7 @@ function useParentMessaging({
|
|
|
78
85
|
}
|
|
79
86
|
});
|
|
80
87
|
const rpc = getWebsiteBuilderRpc();
|
|
81
|
-
rpc?.iframeReady(BUILD_METADATA);
|
|
88
|
+
rpc?.iframeReady({ ...BUILD_METADATA, capabilities: RUNTIME_CAPABILITIES });
|
|
82
89
|
void announceSdkBuildMetadata();
|
|
83
90
|
return () => {
|
|
84
91
|
setSelectModeParentHandlers(null);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
findContainingSection
|
|
7
7
|
} from "../interactions/sectionGeometry";
|
|
8
8
|
import { getElementBoundingRect } from "../interactions/selectionGeometry";
|
|
9
|
+
import { isSectionCanvasControl } from "../section-canvas/sectionCanvasControls";
|
|
9
10
|
import { useDisplayMode, useSelectModeStore } from "../selectModeStore";
|
|
10
11
|
function useSelectionHover() {
|
|
11
12
|
const store = useSelectModeStore();
|
|
@@ -54,7 +55,7 @@ function useSelectionHover() {
|
|
|
54
55
|
hoveredElement = hoveredElementStack[hoveredElementStack.length - 1] ?? null;
|
|
55
56
|
}
|
|
56
57
|
postHoveredElement(
|
|
57
|
-
hoveredElement ? getNearestSelectableElement(hoveredElement) : null
|
|
58
|
+
hoveredElement && !isSectionCanvasControl(hoveredElement) ? getNearestSelectableElement(hoveredElement) : null
|
|
58
59
|
);
|
|
59
60
|
};
|
|
60
61
|
const scheduleHoverSync = () => {
|