@superblocksteam/library 2.0.139-next.1 → 2.0.140-next.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/dist/lib/index.js +221 -0
- package/dist/lib/index.js.map +1 -1
- package/package.json +6 -6
package/dist/lib/index.js
CHANGED
|
@@ -26,6 +26,222 @@ import { useHotkeys } from "react-hotkeys-hook";
|
|
|
26
26
|
import ReactDOM from "react-dom";
|
|
27
27
|
import equal from "@superblocksteam/fast-deep-equal/es6/index.js";
|
|
28
28
|
import { compile, middleware, prefixer, serialize, stringify } from "stylis";
|
|
29
|
+
//#region src/lib/tracing/dynamic-import-errors.ts
|
|
30
|
+
/**
|
|
31
|
+
* Diagnostics for transient dynamic `import()` failures in the rendered app.
|
|
32
|
+
*
|
|
33
|
+
* These surface as `TypeError: Failed to fetch dynamically imported module: …`
|
|
34
|
+
* and are otherwise invisible: they are unhandled promise rejections that no
|
|
35
|
+
* existing handler records, and the shared fetch/XHR instrumentation ignores
|
|
36
|
+
* static assets. Without this reporter the only signal is a user complaint
|
|
37
|
+
* (see APPS-1428 / APPS-3097).
|
|
38
|
+
*
|
|
39
|
+
* The reporter emits one structured `console.error`, which the library's
|
|
40
|
+
* `LibraryEarlyConsoleBuffer` ships to OpenTelemetry (service
|
|
41
|
+
* `superblocks-ui-framework`) at ERROR severity with the diagnostic fields as
|
|
42
|
+
* log attributes. It is installed early (before bootstrap) so failures during
|
|
43
|
+
* initial load / generation are buffered and flushed once the OTel logger is
|
|
44
|
+
* initialized.
|
|
45
|
+
*
|
|
46
|
+
* The core failure event is emitted synchronously so it survives an imminent
|
|
47
|
+
* page unload. On top of that it re-fetches the failed module URL (see
|
|
48
|
+
* `probeModuleUrl`) and emits a separate, correlated `dynamic_import_probe`
|
|
49
|
+
* event with the HTTP status the browser's `import()` rejection hides — the
|
|
50
|
+
* field that distinguishes a missing chunk (404, stale build) from an
|
|
51
|
+
* unreachable dev server (502/error, disconnected/torn-down edit session) from
|
|
52
|
+
* a serving-config issue (200 with wrong content).
|
|
53
|
+
*/
|
|
54
|
+
const DYNAMIC_IMPORT_ERROR_PATTERNS = [
|
|
55
|
+
"Failed to fetch dynamically imported module",
|
|
56
|
+
"error loading dynamically imported module",
|
|
57
|
+
"Importing a module script failed"
|
|
58
|
+
];
|
|
59
|
+
const DEFAULT_DEDUP_WINDOW_MS = 5e3;
|
|
60
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 3e3;
|
|
61
|
+
function isDynamicImportError(message) {
|
|
62
|
+
if (!message) return false;
|
|
63
|
+
return DYNAMIC_IMPORT_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
|
|
64
|
+
}
|
|
65
|
+
function extractModuleUrl(message) {
|
|
66
|
+
return message.match(/dynamically imported module:\s*(\S+)/i)?.[1];
|
|
67
|
+
}
|
|
68
|
+
function extractVersionToken(moduleUrl) {
|
|
69
|
+
if (!moduleUrl) return void 0;
|
|
70
|
+
try {
|
|
71
|
+
return new URL(moduleUrl).searchParams.get("t") ?? void 0;
|
|
72
|
+
} catch {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function detectMode(href) {
|
|
77
|
+
try {
|
|
78
|
+
const { hostname } = new URL(href);
|
|
79
|
+
if (hostname === "localhost" || hostname.startsWith("127.")) return "local";
|
|
80
|
+
if (hostname.includes(".edit.")) return "edit";
|
|
81
|
+
return "deployed";
|
|
82
|
+
} catch {
|
|
83
|
+
return "deployed";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function safePathname(href) {
|
|
87
|
+
try {
|
|
88
|
+
return new URL(href).pathname;
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether the customer app shell has rendered, computed the same way as
|
|
95
|
+
* `edit-mode/error-capture`: the edit-mode global if set, otherwise the
|
|
96
|
+
* `#sb-provider-root` element (rendered by `<App>` in every mode). The DOM
|
|
97
|
+
* check keeps this meaningful in deployed mode, where the edit-mode global is
|
|
98
|
+
* never set.
|
|
99
|
+
*/
|
|
100
|
+
function detectAppRendered(win) {
|
|
101
|
+
if (win.__SB_PROVIDER_RENDERED_EDIT_MODE__ === true) return true;
|
|
102
|
+
return !!win.document?.getElementById?.("sb-provider-root");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Re-fetch the module URL that `import()` failed on, to capture the HTTP
|
|
106
|
+
* outcome the browser's rejection message hides. This is the field that
|
|
107
|
+
* separates the leading APPS-1428 hypotheses:
|
|
108
|
+
*
|
|
109
|
+
* - `404` — the chunk is genuinely gone: stale build / version skew (the tab's
|
|
110
|
+
* module graph references a hash the server no longer has).
|
|
111
|
+
* - `502`/`503`/`504`, or a rejected fetch recorded as `error` — the origin is
|
|
112
|
+
* unreachable: in edit mode this is the per-session dev server behind the
|
|
113
|
+
* `*.edit.superblocks.com` proxy being disconnected / torn down (inactivity)
|
|
114
|
+
* / restarting. Note `navigator.onLine` stays `true` here, so the probe is
|
|
115
|
+
* the only way to see it.
|
|
116
|
+
* - `200` — the asset is served but was unusable (content-type / SPA-fallback
|
|
117
|
+
* HTML), i.e. a serving-config problem, not a missing asset.
|
|
118
|
+
*
|
|
119
|
+
* Uses GET (not HEAD): dev servers such as Vite may not route HEAD for module
|
|
120
|
+
* URLs and would misreport an existing chunk as 404. `no-store` bypasses the
|
|
121
|
+
* cache so we observe the real current state. Bounded by an abort timeout and
|
|
122
|
+
* never throws.
|
|
123
|
+
*/
|
|
124
|
+
async function probeModuleUrl(url, fetchImpl, timeoutMs) {
|
|
125
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
126
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
127
|
+
try {
|
|
128
|
+
const res = await fetchImpl(url, {
|
|
129
|
+
method: "GET",
|
|
130
|
+
cache: "no-store",
|
|
131
|
+
signal: controller?.signal
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
status: res.status,
|
|
135
|
+
ok: res.ok
|
|
136
|
+
};
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return { error: e instanceof Error ? `${e.name}: ${e.message}` : String(e) };
|
|
139
|
+
} finally {
|
|
140
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function buildDynamicImportFailureAttributes(input) {
|
|
144
|
+
const failedModuleUrl = extractModuleUrl(input.message);
|
|
145
|
+
return {
|
|
146
|
+
event: "dynamic_import_failure",
|
|
147
|
+
"dynamic_import.failed_module_url": failedModuleUrl,
|
|
148
|
+
"dynamic_import.version_token": extractVersionToken(failedModuleUrl),
|
|
149
|
+
"dynamic_import.mode": detectMode(input.href),
|
|
150
|
+
"dynamic_import.route": safePathname(input.href),
|
|
151
|
+
"dynamic_import.app_rendered": input.appRendered,
|
|
152
|
+
"dynamic_import.online": input.online,
|
|
153
|
+
"dynamic_import.error.name": input.errorName,
|
|
154
|
+
"dynamic_import.error.message": input.message
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function buildModuleProbeAttributes(failedModuleUrl, mode, probe) {
|
|
158
|
+
return {
|
|
159
|
+
event: "dynamic_import_probe",
|
|
160
|
+
"dynamic_import.failed_module_url": failedModuleUrl,
|
|
161
|
+
"dynamic_import.mode": mode,
|
|
162
|
+
"dynamic_import.probe.status": probe.status,
|
|
163
|
+
"dynamic_import.probe.ok": probe.ok,
|
|
164
|
+
"dynamic_import.probe.error": probe.error
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function defaultReport(message, attributes) {
|
|
168
|
+
console.error(`[dynamic-import-failure] ${message}`, consoleLogAttributes(attributes));
|
|
169
|
+
}
|
|
170
|
+
function createDynamicImportErrorReporter(options = {}) {
|
|
171
|
+
const report = options.report ?? defaultReport;
|
|
172
|
+
const now = options.now ?? (() => Date.now());
|
|
173
|
+
const dedupWindowMs = options.dedupWindowMs ?? DEFAULT_DEDUP_WINDOW_MS;
|
|
174
|
+
const probeEnabled = options.probe ?? true;
|
|
175
|
+
const probeTimeoutMs = options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
176
|
+
const getWindow = options.getWindow ?? (() => window);
|
|
177
|
+
const lastReportedAt = /* @__PURE__ */ new Map();
|
|
178
|
+
return {
|
|
179
|
+
handle(message, errorName) {
|
|
180
|
+
if (!isDynamicImportError(message)) return false;
|
|
181
|
+
const win = getWindow();
|
|
182
|
+
if (!win) return false;
|
|
183
|
+
const dedupKey = extractModuleUrl(message) ?? message;
|
|
184
|
+
const timestamp = now();
|
|
185
|
+
for (const [key, reportedAt] of lastReportedAt) if (timestamp - reportedAt >= dedupWindowMs) lastReportedAt.delete(key);
|
|
186
|
+
const previous = lastReportedAt.get(dedupKey);
|
|
187
|
+
if (previous !== void 0 && timestamp - previous < dedupWindowMs) return false;
|
|
188
|
+
lastReportedAt.set(dedupKey, timestamp);
|
|
189
|
+
const href = win.location.href;
|
|
190
|
+
report(message, buildDynamicImportFailureAttributes({
|
|
191
|
+
message,
|
|
192
|
+
errorName,
|
|
193
|
+
href,
|
|
194
|
+
online: win.navigator.onLine,
|
|
195
|
+
appRendered: detectAppRendered(win)
|
|
196
|
+
}));
|
|
197
|
+
const failedModuleUrl = extractModuleUrl(message);
|
|
198
|
+
const fetchImpl = options.fetchImpl ?? (typeof win.fetch === "function" ? win.fetch.bind(win) : void 0);
|
|
199
|
+
if (probeEnabled && failedModuleUrl && fetchImpl) (async () => {
|
|
200
|
+
try {
|
|
201
|
+
const probe = await probeModuleUrl(failedModuleUrl, fetchImpl, probeTimeoutMs);
|
|
202
|
+
report(message, buildModuleProbeAttributes(failedModuleUrl, detectMode(href), probe));
|
|
203
|
+
} catch {}
|
|
204
|
+
})();
|
|
205
|
+
return true;
|
|
206
|
+
},
|
|
207
|
+
dedupSize() {
|
|
208
|
+
return lastReportedAt.size;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Install global listeners that report transient dynamic-import failures.
|
|
214
|
+
* Safe to call in non-browser contexts (no-op). Returns an uninstall function.
|
|
215
|
+
*/
|
|
216
|
+
function installDynamicImportErrorReporter(options = {}) {
|
|
217
|
+
const win = (options.getWindow ?? (() => typeof window === "undefined" ? void 0 : window))();
|
|
218
|
+
if (!win) return () => {};
|
|
219
|
+
const reporter = createDynamicImportErrorReporter({
|
|
220
|
+
...options,
|
|
221
|
+
getWindow: () => win
|
|
222
|
+
});
|
|
223
|
+
const onRejection = (event) => {
|
|
224
|
+
try {
|
|
225
|
+
const reason = event?.reason;
|
|
226
|
+
const message = typeof reason === "string" ? reason : reason?.message;
|
|
227
|
+
const name = typeof reason === "string" ? void 0 : reason?.name;
|
|
228
|
+
reporter.handle(message, name);
|
|
229
|
+
} catch {}
|
|
230
|
+
};
|
|
231
|
+
const onError = (event) => {
|
|
232
|
+
try {
|
|
233
|
+
const message = event?.error?.message || event?.message;
|
|
234
|
+
reporter.handle(message, event?.error?.name);
|
|
235
|
+
} catch {}
|
|
236
|
+
};
|
|
237
|
+
win.addEventListener("unhandledrejection", onRejection);
|
|
238
|
+
win.addEventListener("error", onError);
|
|
239
|
+
return () => {
|
|
240
|
+
win.removeEventListener("unhandledrejection", onRejection);
|
|
241
|
+
win.removeEventListener("error", onError);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
29
245
|
//#region src/lib/internal-details/sb-wrapper.tsx
|
|
30
246
|
/**
|
|
31
247
|
* Register a component to be used with the framework.
|
|
@@ -7466,6 +7682,11 @@ function useEmitEmbedEvent() {
|
|
|
7466
7682
|
//#endregion
|
|
7467
7683
|
//#region src/lib/index.ts
|
|
7468
7684
|
LibraryEarlyConsoleBuffer.getInstance().patchEarly();
|
|
7685
|
+
try {
|
|
7686
|
+
installDynamicImportErrorReporter();
|
|
7687
|
+
} catch (err) {
|
|
7688
|
+
console.error("[internal] Failed to install dynamic import reporter", err);
|
|
7689
|
+
}
|
|
7469
7690
|
registerHtmlElements();
|
|
7470
7691
|
//#endregion
|
|
7471
7692
|
export { App, PageNotFound, Prop, Property, PropsCategory, RouteLoadError, Section, Superblocks, SbProvider as SuperblocksProvider, createElement, createTypedExecuteApi, embedStore, executeApi, getAppMode, logoutIntegrations, queryClient, registerComponent, tailwindStylesCategory, useApi, useApiData, useApiStateful, useEmbedEvent, useEmbedProperties, useEmitEmbedEvent, useSuperblocksDataTags, useSuperblocksGroups, useSuperblocksProfiles, useSuperblocksUser, useTypedApi, useTypedApiData };
|