@saasquatch/squatch-js 2.8.3-3 → 2.8.3-30

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 (46) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/babelregister.js +0 -1
  3. package/coverage/base.css +224 -0
  4. package/coverage/block-navigation.js +87 -0
  5. package/coverage/clover.xml +996 -0
  6. package/coverage/coverage-final.json +26 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/prettify.css +1 -0
  9. package/coverage/prettify.js +2 -0
  10. package/coverage/sort-arrow-sprite.png +0 -0
  11. package/coverage/sorter.js +210 -0
  12. package/demo/perf-benchmark.ts +363 -0
  13. package/demo/perf-compare.html +870 -0
  14. package/demo/perf-deploy/vercel.json +17 -0
  15. package/demo/perf-frame.html +417 -0
  16. package/demo/perf-server.ts +131 -0
  17. package/dist/ErrorTemplate-DUNm11h9.js +124 -0
  18. package/dist/ErrorTemplate-DUNm11h9.js.map +1 -0
  19. package/dist/ErrorTemplate-DumOlC5f.cjs +109 -0
  20. package/dist/ErrorTemplate-DumOlC5f.cjs.map +1 -0
  21. package/dist/SkeletonTemplate-B3Bk4NFu.cjs +243 -0
  22. package/dist/SkeletonTemplate-B3Bk4NFu.cjs.map +1 -0
  23. package/dist/SkeletonTemplate-Day_0iMM.js +246 -0
  24. package/dist/SkeletonTemplate-Day_0iMM.js.map +1 -0
  25. package/dist/squatch.cjs.js +33 -2523
  26. package/dist/squatch.cjs.js.map +1 -1
  27. package/dist/squatch.esm.js +928 -1956
  28. package/dist/squatch.esm.js.map +1 -1
  29. package/dist/squatch.js +227 -2373
  30. package/dist/squatch.js.map +1 -1
  31. package/dist/squatch.min.js +4 -5
  32. package/dist/types.d.ts +134 -1
  33. package/dist/utils/cookieUtils.d.ts +1 -0
  34. package/dist/utils/logger.d.ts +23 -0
  35. package/dist/widgets/EmbedWidget.d.ts +1 -1
  36. package/dist/widgets/ErrorTemplate.d.ts +9 -0
  37. package/dist/widgets/PopupWidget.d.ts +3 -5
  38. package/dist/widgets/SkeletonTemplate.d.ts +1 -4
  39. package/dist/widgets/Widget.d.ts +29 -2
  40. package/dist/widgets/declarative/DeclarativeWidget.d.ts +9 -1
  41. package/dist/widgets/declarative/DeclarativeWidgets.d.ts +0 -6
  42. package/package.json +11 -15
  43. package/vite-env.d.ts +2 -1
  44. package/vite.config.ts +17 -0
  45. package/babel.config.js +0 -8
  46. package/jest.config.ts +0 -200
@@ -0,0 +1,17 @@
1
+ {
2
+ "rewrites": [
3
+ { "source": "/api/:path*", "destination": "https://staging.referralsaasquatch.com/api/:path*" },
4
+ { "source": "/a/:path*", "destination": "https://staging.referralsaasquatch.com/a/:path*" }
5
+ ],
6
+ "headers": [
7
+ {
8
+ "source": "/(.*)",
9
+ "headers": [
10
+ { "key": "Access-Control-Allow-Origin", "value": "*" },
11
+ { "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" },
12
+ { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization, X-SaaSquatch-Referrer" }
13
+ ]
14
+ }
15
+ ],
16
+ "cleanUrls": true
17
+ }
@@ -0,0 +1,417 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Perf Frame</title>
7
+ <style>
8
+ body { margin: 0; padding: 0; font-family: system-ui, sans-serif; }
9
+ squatch-embed, impact-embed { display: block; width: 100%; }
10
+ #status { position: fixed; top: 0; left: 0; right: 0; background: rgba(0,0,0,0.7); color: #0f0; font-size: 11px; padding: 4px 8px; z-index: 99999; font-family: monospace; white-space: pre; }
11
+ </style>
12
+ <script>
13
+ // ── Instrumentation globals (runs before loader) ─────────────────
14
+ window.__perfT0 = performance.now();
15
+ window.__perfMetrics = null;
16
+ window.__perfMarks = {};
17
+
18
+ window.__perfMark = function (name) {
19
+ window.__perfMarks[name] = performance.now();
20
+ window.__perfUpdateStatus();
21
+ };
22
+
23
+ window.__perfElapsed = function (name) {
24
+ var v = window.__perfMarks[name];
25
+ return v ? (v - window.__perfT0).toFixed(1) + "ms" : "…";
26
+ };
27
+
28
+ window.__perfUpdateStatus = function () {
29
+ var el = document.getElementById("status");
30
+ if (!el) return;
31
+ el.textContent =
32
+ "SDK load: " + window.__perfElapsed("sdk-script-loaded") +
33
+ " ready: " + window.__perfElapsed("sdk-ready") +
34
+ " frame: " + window.__perfElapsed("frame-created") +
35
+ " paint: " + window.__perfElapsed("meaningful-paint") +
36
+ " stable: " + window.__perfElapsed("stable") +
37
+ " unstyled: " + window.__perfElapsed(window.__perfMarks["skeleton-start"] ? "skeleton-start" : "unstyled-end");
38
+ };
39
+
40
+ window.__perfReport = function () {
41
+ var t0 = window.__perfT0;
42
+ var marks = window.__perfMarks;
43
+ function delta(name) {
44
+ var v = marks[name];
45
+ return (v && v > t0) ? v - t0 : 0;
46
+ }
47
+ var sdkUrl = new URLSearchParams(window.location.search).get("sdk") || "";
48
+ var bundleSize = 0;
49
+ try {
50
+ var entries = performance.getEntriesByType("resource");
51
+ for (var i = 0; i < entries.length; i++) {
52
+ if (entries[i].name && entries[i].name.indexOf("squatch-js") > -1) {
53
+ bundleSize = entries[i].transferSize || entries[i].encodedBodySize || 0;
54
+ break;
55
+ }
56
+ }
57
+ } catch (e) {}
58
+
59
+ var networkTime = 0;
60
+ try {
61
+ var resEntries = performance.getEntriesByType("resource");
62
+ for (var j = 0; j < resEntries.length; j++) {
63
+ var e = resEntries[j];
64
+ if (e.responseEnd > 0 && e.startTime >= 0) {
65
+ networkTime += e.responseEnd - e.startTime;
66
+ }
67
+ }
68
+ } catch (e) {}
69
+
70
+ var out = {
71
+ sdkUrl: sdkUrl,
72
+ sdkScriptLoad: delta("sdk-script-loaded"),
73
+ sdkReady: delta("sdk-ready"),
74
+ frameCreated: delta("frame-created"),
75
+ meaningfulPaint: delta("meaningful-paint"),
76
+ stable: delta("stable"),
77
+ timeUnstyled: marks["skeleton-start"]
78
+ ? delta("skeleton-start")
79
+ : delta("unstyled-end"),
80
+ bundleSizeBytes: bundleSize,
81
+ networkTime: networkTime,
82
+ raw: marks
83
+ };
84
+ window.__perfMetrics = out;
85
+ try {
86
+ window.parent.postMessage({ type: "perf-metrics", metrics: out }, "*");
87
+ } catch (e) {}
88
+ console.log("[perf-frame] metrics:", JSON.stringify(out, null, 2));
89
+ };
90
+ </script>
91
+ <script>
92
+ // ── SDK Loader (exact production pattern, dynamic URL) ──────────
93
+ var __sdkUrl = new URLSearchParams(window.location.search).get("sdk");
94
+ if (__sdkUrl) {
95
+ !(function (a, b) {
96
+ a("squatch", __sdkUrl, b);
97
+ })(function (a, b, c) {
98
+ var d, e, f;
99
+ ((c["_" + a] = {}),
100
+ (c[a] = {}),
101
+ (c[a].ready = function (b) {
102
+ c["_" + a].ready = c["_" + a].ready || [];
103
+ c["_" + a].ready.push(b);
104
+ }),
105
+ (e = document.createElement("script")),
106
+ (e.async = 1),
107
+ (e.src = b),
108
+ (e.onload = function () {
109
+ window.__perfMark("sdk-script-loaded");
110
+ }),
111
+ (e.onerror = function () {
112
+ window.__perfMark("sdk-script-error");
113
+ window.__perfReport();
114
+ }),
115
+ (f = document.getElementsByTagName("script")[0]),
116
+ f.parentNode.insertBefore(e, f));
117
+ }, this);
118
+
119
+ window.__perfMark("sdk-load-start");
120
+
121
+ window.squatch.ready(function () {
122
+ window.__perfMark("sdk-ready");
123
+ });
124
+ }
125
+ </script>
126
+ <script>
127
+ // ── Widget configuration ────────────────────────────────────────
128
+ // Read config from URL params (set by perf-compare.html), with defaults
129
+ var __params = new URLSearchParams(window.location.search);
130
+ var __jwt = __params.get("jwt") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IklSTVhzWXk2WVlxcTQ2OTQzN21HOEVSUXQ4UW9LRkJhRzEifQ.eyJ1c2VyIjp7ImlkIjoiMGM2YmUwZTcxMGViMDk1OTU2ODMxYTkyOTUxODVlMmU5N2VmMTQzMDhjN2UzNjFmYjBlYzY0NTE0NjUwY2NhOCIsImFjY291bnRJZCI6IjBjNmJlMGU3MTBlYjA5NTk1NjgzMWE5Mjk1MTg1ZTJlOTdlZjE0MzA4YzdlMzYxZmIwZWM2NDUxNDY1MGNjYTgiLCJlbWFpbCI6ImRlcmVrLnNpZW1lbnMrbm93OTJAaW1wYWN0LmNvbSJ9fQ.KOORz1aOIVRPR6PdF3Ss0D1AW19EYTX97WnA6fOVXaI";
131
+ var __domain = __params.get("domain") || window.location.origin;
132
+ var __tenant = __params.get("tenant") || "ac52kfybp1tkr";
133
+ var __widget = __params.get("widget") || "p/47253/w/referrerWidget";
134
+ var __cfg = { domain: __domain };
135
+
136
+ window.squatchTenant = __tenant;
137
+ // v2 globals
138
+ window.squatchToken = __jwt;
139
+ window.squatchConfig = __cfg;
140
+ // @next globals (takes precedence when both are set)
141
+ window.impactToken = __jwt;
142
+ window.impactConfig = __cfg;
143
+ </script>
144
+ <script>
145
+ // ── Dynamic widget element ──────────────────────────────────────
146
+ // v2 only registers <squatch-embed>, @next registers both
147
+ // <squatch-embed> and <impact-embed>. Use <squatch-embed> for v2,
148
+ // <impact-embed> for @next.
149
+ (function () {
150
+ var sdk = new URLSearchParams(window.location.search).get("sdk") || "";
151
+ var isV2 = sdk.indexOf("@2") > -1;
152
+ var tag = isV2 ? "squatch-embed" : "impact-embed";
153
+ var el = document.createElement(tag);
154
+ el.setAttribute("widget", __widget);
155
+ document.addEventListener("DOMContentLoaded", function () {
156
+ document.body.appendChild(el);
157
+ // connectedCallback clears shadow root innerHTML (removing :host style)
158
+ // then asynchronously appends the skeleton. Watch for the skeleton and
159
+ // re-inject the :host style so it fills full width.
160
+ var injectStyle = setInterval(function () {
161
+ var sr = el.shadowRoot;
162
+ if (!sr) return;
163
+ // Wait until the skeleton is actually in the shadow root
164
+ var skel = sr.getElementById("loading-skeleton");
165
+ if (!skel) return;
166
+ if (!sr.querySelector("#perf-host-fix")) {
167
+ var s = document.createElement("style");
168
+ s.id = "perf-host-fix";
169
+ s.textContent = ":host { display: block; width: 100%; }";
170
+ sr.prepend(s);
171
+ }
172
+ clearInterval(injectStyle);
173
+ }, 5);
174
+ setTimeout(function () { clearInterval(injectStyle); }, 5000);
175
+ });
176
+ })();
177
+ </script>
178
+ <script>
179
+ // ── DOM observers for widget lifecycle metrics ───────────────────
180
+ (function () {
181
+ var stableTimer = null;
182
+ var STABLE_DELAY = 500;
183
+
184
+ function resetStableTimer() {
185
+ if (stableTimer) clearTimeout(stableTimer);
186
+ stableTimer = setTimeout(function () {
187
+ window.__perfMark("stable");
188
+ window.__perfReport();
189
+ }, STABLE_DELAY);
190
+ }
191
+
192
+ // Fallback: report after 15s even if nothing happens
193
+ setTimeout(function () {
194
+ if (!window.__perfMarks["stable"]) {
195
+ window.__perfMark("stable");
196
+ }
197
+ window.__perfReport();
198
+ }, 15000);
199
+
200
+ var frameDetected = false;
201
+
202
+ function observeWidgetIframe(iframe) {
203
+ // Track whether the iframe has been resized at least once
204
+ var hasResized = false;
205
+
206
+ // Watch height/style attribute changes for stable timer
207
+ var resizeObs = new MutationObserver(function (mutations) {
208
+ for (var i = 0; i < mutations.length; i++) {
209
+ var attr = mutations[i].attributeName;
210
+ if (attr === "height" || attr === "style") {
211
+ hasResized = true;
212
+ resetStableTimer();
213
+ }
214
+ }
215
+ });
216
+ resizeObs.observe(iframe, { attributes: true, attributeFilter: ["height", "style"] });
217
+ // Don't start stable timer here — wait for first actual mutation
218
+
219
+ // Helper: check if any real CSS rules have been applied in the iframe document
220
+ function hasStylesApplied(doc) {
221
+ try {
222
+ var sheets = doc.styleSheets;
223
+ if (!sheets || sheets.length === 0) return false;
224
+ for (var i = 0; i < sheets.length; i++) {
225
+ try {
226
+ if (sheets[i].cssRules && sheets[i].cssRules.length > 0) return true;
227
+ } catch (e) { return true; }
228
+ }
229
+ return false;
230
+ } catch (e) { return false; }
231
+ }
232
+
233
+ // Detect "time unstyled" (FOUC): time between first visible content and styles applied.
234
+ // V2 doesn't use visibility:hidden, so content shows before CSS loads = FOUC.
235
+ // @next uses html{visibility:hidden} for mint-components, hiding until styled = no FOUC.
236
+ (function detectFOUC() {
237
+
238
+ // Helper: check if content is hidden (skeleton/visibility:hidden)
239
+ function isContentHidden(doc) {
240
+ try {
241
+ var vis = doc.documentElement ? getComputedStyle(doc.documentElement).visibility : "visible";
242
+ return vis === "hidden";
243
+ } catch (e) { return false; }
244
+ }
245
+
246
+ // Helper: check if a skeleton overlay is present
247
+ function hasSkeleton(doc) {
248
+ try { return !!doc.getElementById("sq-preload"); } catch (e) { return false; }
249
+ }
250
+
251
+ // Immediately mark unstyled-start as frame-created time
252
+ // (content is visible from the moment frame is created for V2)
253
+ // We'll adjust this if we discover content was actually hidden.
254
+ var started = false;
255
+ var adjustedStart = false;
256
+
257
+ var pollCount = 0;
258
+ function poll() {
259
+ if (window.__perfMarks["unstyled-end"]) return;
260
+ try {
261
+ var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
262
+ if (!doc || !doc.body || doc.body.children.length === 0) { retryPoll(); return; }
263
+
264
+ var hidden = isContentHidden(doc);
265
+ var skeleton = hasSkeleton(doc);
266
+ var styled = hasStylesApplied(doc);
267
+
268
+ if (!started) {
269
+ if (hidden || skeleton) {
270
+ // Content is hidden or has skeleton — this is @next behavior
271
+ // No visible FOUC is happening; wait for styles
272
+ if (styled) {
273
+ // Styles loaded while hidden — no FOUC at all
274
+ window.__perfMark("unstyled-start");
275
+ window.__perfMark("unstyled-end");
276
+ return;
277
+ }
278
+ retryPoll();
279
+ return;
280
+ }
281
+
282
+ // Content is visible and not behind a skeleton
283
+ if (styled) {
284
+ // Styles already applied — no FOUC
285
+ window.__perfMark("unstyled-start");
286
+ window.__perfMark("unstyled-end");
287
+ return;
288
+ }
289
+
290
+ // FOUC: visible + no styles + no skeleton
291
+ // Backdate unstyled-start to frame-created time (that's when
292
+ // content first became visible)
293
+ started = true;
294
+ if (window.__perfMarks["frame-created"]) {
295
+ window.__perfMarks["unstyled-start"] = window.__perfMarks["frame-created"];
296
+ } else {
297
+ window.__perfMark("unstyled-start");
298
+ }
299
+ retryPoll();
300
+ } else {
301
+ // Waiting for styles to end the FOUC period
302
+ if (styled) {
303
+ window.__perfMark("unstyled-end");
304
+ return;
305
+ }
306
+ retryPoll();
307
+ }
308
+ } catch (e) {
309
+ retryPoll();
310
+ }
311
+ }
312
+ function retryPoll() {
313
+ pollCount++;
314
+ if (pollCount < 400) setTimeout(poll, 25);
315
+ }
316
+ setTimeout(poll, 10);
317
+ })();
318
+
319
+ // Watch for skeleton removal or visibility change (meaningful paint)
320
+ var skeletonRetries = 0;
321
+ function watchSkeleton() {
322
+ if (window.__perfMarks["meaningful-paint"]) return;
323
+ try {
324
+ var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
325
+ if (!doc) { retrySkeleton(); return; }
326
+
327
+ var skel = doc.getElementById("sq-preload");
328
+ if (!skel) {
329
+ var html = doc.documentElement;
330
+ if (html && doc.body && doc.body.children.length > 0) {
331
+ var vis = getComputedStyle(html).visibility;
332
+ // Wait until styled and either the iframe has been resized
333
+ // (V2 starts at default small height) OR the iframe already
334
+ // has a reasonable height (>300px — @next sets a near-final
335
+ // default height so waiting for resize is unnecessary).
336
+ var ready = doc.readyState === "complete";
337
+ var tallEnough = iframe.offsetHeight > 300 || parseInt(iframe.style.height, 10) > 300;
338
+ if (vis === "visible" && ready && hasStylesApplied(doc) && (hasResized || tallEnough)) {
339
+ window.__perfMark("meaningful-paint");
340
+ return;
341
+ }
342
+ }
343
+ retrySkeleton();
344
+ return;
345
+ }
346
+
347
+ // Skeleton found — poll for its removal
348
+ var pollId = setInterval(function () {
349
+ if (!doc.getElementById("sq-preload")) {
350
+ window.__perfMark("meaningful-paint");
351
+ clearInterval(pollId);
352
+ }
353
+ }, 100);
354
+ setTimeout(function () { clearInterval(pollId); }, 10000);
355
+ } catch (e) {
356
+ retrySkeleton();
357
+ }
358
+ }
359
+ function retrySkeleton() {
360
+ skeletonRetries++;
361
+ if (skeletonRetries < 50) setTimeout(watchSkeleton, 200);
362
+ }
363
+ setTimeout(watchSkeleton, 100);
364
+ }
365
+
366
+ // Poll shadow roots of declarative widgets for iframe creation
367
+ var checkShadow = setInterval(function () {
368
+ if (frameDetected) { clearInterval(checkShadow); return; }
369
+ var embeds = document.querySelectorAll("impact-embed, squatch-embed");
370
+ for (var k = 0; k < embeds.length; k++) {
371
+ var sr = embeds[k].shadowRoot;
372
+ if (sr) {
373
+ // Detect outer skeleton (#loading-skeleton in shadow root)
374
+ // This is inserted by connectedCallback BEFORE the iframe exists
375
+ if (!window.__perfMarks["skeleton-start"] && sr.getElementById("loading-skeleton")) {
376
+ window.__perfMark("skeleton-start");
377
+ }
378
+ var iframe = sr.querySelector("iframe");
379
+ if (iframe) {
380
+ frameDetected = true;
381
+ clearInterval(checkShadow);
382
+ window.__perfMark("frame-created");
383
+ observeWidgetIframe(iframe);
384
+ return;
385
+ }
386
+ }
387
+ }
388
+ }, 50);
389
+ setTimeout(function () { clearInterval(checkShadow); }, 15000);
390
+
391
+ // Also watch body for directly-appended iframes (non-declarative path)
392
+ document.addEventListener("DOMContentLoaded", function () {
393
+ var bodyObs = new MutationObserver(function (mutations) {
394
+ if (frameDetected) return;
395
+ for (var i = 0; i < mutations.length; i++) {
396
+ var added = mutations[i].addedNodes;
397
+ for (var j = 0; j < added.length; j++) {
398
+ var node = added[j];
399
+ if (node.nodeName === "IFRAME" || (node.querySelector && node.querySelector("iframe"))) {
400
+ frameDetected = true;
401
+ window.__perfMark("frame-created");
402
+ var iframe = node.nodeName === "IFRAME" ? node : node.querySelector("iframe");
403
+ if (iframe) observeWidgetIframe(iframe);
404
+ return;
405
+ }
406
+ }
407
+ }
408
+ });
409
+ bodyObs.observe(document.body, { childList: true, subtree: true });
410
+ });
411
+ })();
412
+ </script>
413
+ </head>
414
+ <body>
415
+ <div id="status"></div>
416
+ </body>
417
+ </html>
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Dev server for perf comparison UI.
3
+ * Serves static files from demo/ and proxies /api/* to staging (bypasses CORS).
4
+ *
5
+ * Usage:
6
+ * npx tsx demo/perf-server.ts
7
+ * npx tsx demo/perf-server.ts --port 5000
8
+ */
9
+ import { createServer, type IncomingMessage, type ServerResponse } from "http";
10
+ import { request as httpsRequest } from "https";
11
+ import { readFileSync, existsSync, statSync } from "fs";
12
+ import { join, extname, dirname } from "path";
13
+
14
+ const __scriptDir =
15
+ typeof __dirname !== "undefined"
16
+ ? __dirname
17
+ : dirname(new URL(import.meta.url).pathname);
18
+
19
+ const STAGING_HOST = "staging.referralsaasquatch.com";
20
+
21
+ const MIME: Record<string, string> = {
22
+ ".html": "text/html",
23
+ ".js": "application/javascript",
24
+ ".css": "text/css",
25
+ ".json": "application/json",
26
+ ".png": "image/png",
27
+ ".svg": "image/svg+xml",
28
+ };
29
+
30
+ function parsePort(): number {
31
+ const i = process.argv.indexOf("--port");
32
+ if (i !== -1 && process.argv[i + 1]) {
33
+ const p = parseInt(process.argv[i + 1], 10);
34
+ if (!isNaN(p) && p > 0) return p;
35
+ }
36
+ return 4001;
37
+ }
38
+
39
+ function proxy(req: IncomingMessage, res: ServerResponse, path: string) {
40
+ const chunks: Buffer[] = [];
41
+ req.on("data", (c) => chunks.push(c));
42
+ req.on("end", () => {
43
+ const body = Buffer.concat(chunks);
44
+ const headers: Record<string, string> = {};
45
+ for (const [k, v] of Object.entries(req.headers)) {
46
+ if (["host", "origin", "referer", "connection"].includes(k)) continue;
47
+ if (typeof v === "string") headers[k] = v;
48
+ }
49
+ headers["host"] = STAGING_HOST;
50
+
51
+ const proxyReq = httpsRequest(
52
+ { hostname: STAGING_HOST, port: 443, path, method: req.method, headers },
53
+ (proxyRes) => {
54
+ const rh: Record<string, string | string[]> = {};
55
+ for (const [k, v] of Object.entries(proxyRes.headers)) {
56
+ if (v) rh[k] = v;
57
+ }
58
+ rh["access-control-allow-origin"] = "*";
59
+ rh["access-control-allow-methods"] = "GET, POST, PUT, DELETE, OPTIONS";
60
+ rh["access-control-allow-headers"] =
61
+ "Content-Type, Authorization, X-SaaSquatch-Referrer";
62
+ res.writeHead(proxyRes.statusCode || 502, rh);
63
+ proxyRes.pipe(res);
64
+ },
65
+ );
66
+ proxyReq.on("error", (err) => {
67
+ res.writeHead(502, { "Content-Type": "text/plain" });
68
+ res.end("Proxy error: " + err.message);
69
+ });
70
+ if (body.length > 0) proxyReq.write(body);
71
+ proxyReq.end();
72
+ });
73
+ }
74
+
75
+ function serveStatic(res: ServerResponse, urlPath: string) {
76
+ let fp = join(__scriptDir, urlPath);
77
+ // Clean URLs: try .html extension
78
+ if (!existsSync(fp) || statSync(fp).isDirectory()) {
79
+ const withHtml = fp + ".html";
80
+ if (existsSync(withHtml)) fp = withHtml;
81
+ }
82
+ if (!existsSync(fp) || statSync(fp).isDirectory()) {
83
+ res.writeHead(404);
84
+ res.end("Not found");
85
+ return;
86
+ }
87
+ const ext = extname(fp);
88
+ res.writeHead(200, {
89
+ "Content-Type": MIME[ext] || "application/octet-stream",
90
+ });
91
+ res.end(readFileSync(fp));
92
+ }
93
+
94
+ const port = parsePort();
95
+
96
+ createServer((req, res) => {
97
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
98
+ const path = url.pathname + url.search;
99
+
100
+ // CORS preflight
101
+ if (
102
+ req.method === "OPTIONS" &&
103
+ (url.pathname.startsWith("/api/") || url.pathname.startsWith("/a/"))
104
+ ) {
105
+ res.writeHead(204, {
106
+ "access-control-allow-origin": "*",
107
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
108
+ "access-control-allow-headers":
109
+ "Content-Type, Authorization, X-SaaSquatch-Referrer",
110
+ "access-control-max-age": "86400",
111
+ });
112
+ res.end();
113
+ return;
114
+ }
115
+
116
+ // Proxy API and cookie requests
117
+ if (url.pathname.startsWith("/api/") || url.pathname.startsWith("/a/")) {
118
+ proxy(req, res, path);
119
+ return;
120
+ }
121
+
122
+ serveStatic(
123
+ res,
124
+ url.pathname === "/" ? "/perf-compare.html" : url.pathname,
125
+ );
126
+ }).listen(port, () => {
127
+ console.log(`\n Perf server: http://localhost:${port}/perf-compare`);
128
+ console.log(
129
+ ` API proxy: /api/* → https://${STAGING_HOST}/api/*\n`,
130
+ );
131
+ });
@@ -0,0 +1,124 @@
1
+ function c(s = {}) {
2
+ const {
3
+ rsCode: r,
4
+ apiErrorCode: i,
5
+ statusCode: a,
6
+ message: d,
7
+ mode: l = "modal",
8
+ style: n = ""
9
+ } = s, o = document.createElement("span"), t = (g) => (o.textContent = g, o.innerHTML), e = [];
10
+ a !== void 0 && e.push(`<dt>Status Code</dt><dd>${t(String(a))}</dd>`), i && e.push(`<dt>API Error Code</dt><dd>${t(i)}</dd>`), r && e.push(`<dt>RS Code</dt><dd>${t(r)}</dd>`), d && e.push(`<dt>Message</dt><dd>${t(d)}</dd>`);
11
+ const p = e.length > 0 ? `<dl class="error-details">${e.join(`
12
+ `)}</dl>` : "";
13
+ return `<!DOCTYPE html>
14
+ <!--[if IE 7]><html class="ie7 oldie" lang="en"><![endif]-->
15
+ <!--[if IE 8]><html class="ie8 oldie" lang="en"><![endif]-->
16
+ <!--[if gt IE 8]><!--><html lang="en"><!--<![endif]-->
17
+ <head>
18
+ <style>
19
+ ${n}
20
+ body {
21
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
22
+ line-height: 20px;
23
+ }
24
+ h3 {
25
+ text-align: center;
26
+ }
27
+ .errortitle {
28
+ margin: 0;
29
+ font-size: 14px;
30
+ }
31
+ .errorbody p {
32
+ text-align: center;
33
+ font-size: 12px;
34
+ margin: 0 0 10px;
35
+ }
36
+ h4 {
37
+ font-size: 17.5px;
38
+ text-align: center;
39
+ margin: 10px 0;
40
+ }
41
+ .embed {
42
+ width: 100%;
43
+ max-width: 500px;
44
+ margin-left: auto;
45
+ margin-right: auto;
46
+ }
47
+ .errorbody {
48
+ padding: 15px;
49
+ position: relative;
50
+ height: auto;
51
+ }
52
+ p {
53
+ text-align: center;
54
+ font-size: 12px;
55
+ }
56
+ .sadface {
57
+ padding: 10px;
58
+ }
59
+ .sadface img {
60
+ display: block;
61
+ margin: auto;
62
+ height: 100px;
63
+ }
64
+ .modal-disable-overlay {
65
+ display: none;
66
+ position: absolute;
67
+ top: 50px;
68
+ width: 100%;
69
+ height: 85%;
70
+ background: rgba(255, 255, 255, 0.4);
71
+ }
72
+
73
+ .right-align {
74
+ text-align: right;
75
+ }
76
+ .errtxt {
77
+ color: #CCC9C9;
78
+ }
79
+ .error-details {
80
+ margin-top: 16px;
81
+ padding: 12px;
82
+ background: #f8f8f8;
83
+ border-radius: 4px;
84
+ text-align: left;
85
+ font-size: 13px;
86
+ color: #666;
87
+ display: block;
88
+ overflow: visible;
89
+ }
90
+ .error-details dt {
91
+ display: block;
92
+ font-weight: 600;
93
+ color: #333;
94
+ margin-top: 8px;
95
+ }
96
+ .error-details dt:first-child {
97
+ margin-top: 0;
98
+ }
99
+ .error-details dd {
100
+ display: block;
101
+ margin: 4px 0 0 0;
102
+ word-break: break-word;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+
108
+ <div class="squatch-container ${l}" style="width:100%;background:#FFF;">
109
+ <div class="errorbody">
110
+ <div class="sadface"><img src="https://fast.ssqt.io/assets/images/whoops-error-image.png"></div>
111
+ <h4>Our referral program is temporarily unavailable.</h4>
112
+ <p>Please reload the page or check back later.</p>
113
+ <p>If the problem persists please contact our support team.</p>
114
+
115
+ ${p}
116
+ </div>
117
+ </div>
118
+ </body>
119
+ </html>`;
120
+ }
121
+ export {
122
+ c as getErrorTemplate
123
+ };
124
+ //# sourceMappingURL=ErrorTemplate-DUNm11h9.js.map