@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 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 };