@intranefr/superbackend 1.6.5 → 1.6.7

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 (53) hide show
  1. package/.env.example +4 -0
  2. package/README.md +18 -0
  3. package/package.json +6 -1
  4. package/public/js/admin-superdemos.js +396 -0
  5. package/public/sdk/superdemos.iife.js +614 -0
  6. package/public/superdemos-qa.html +324 -0
  7. package/sdk/superdemos/browser/src/index.js +719 -0
  8. package/src/cli/agent-chat.js +369 -0
  9. package/src/cli/agent-list.js +42 -0
  10. package/src/controllers/adminAgentsChat.controller.js +172 -0
  11. package/src/controllers/adminSuperDemos.controller.js +382 -0
  12. package/src/controllers/superDemosPublic.controller.js +126 -0
  13. package/src/middleware.js +108 -19
  14. package/src/models/BlogAutomationLock.js +4 -4
  15. package/src/models/BlogPost.js +16 -16
  16. package/src/models/CacheEntry.js +17 -6
  17. package/src/models/JsonConfig.js +2 -4
  18. package/src/models/RateLimitMetricBucket.js +10 -5
  19. package/src/models/SuperDemo.js +38 -0
  20. package/src/models/SuperDemoProject.js +32 -0
  21. package/src/models/SuperDemoStep.js +27 -0
  22. package/src/routes/adminAgents.routes.js +10 -0
  23. package/src/routes/adminMarkdowns.routes.js +3 -0
  24. package/src/routes/adminSuperDemos.routes.js +31 -0
  25. package/src/routes/superDemos.routes.js +9 -0
  26. package/src/services/auditLogger.js +75 -37
  27. package/src/services/email.service.js +18 -3
  28. package/src/services/llm.service.js +1 -0
  29. package/src/services/plugins.service.js +50 -16
  30. package/src/services/superDemosAuthoringSessions.service.js +132 -0
  31. package/src/services/superDemosWs.service.js +164 -0
  32. package/src/services/terminalsWs.service.js +35 -3
  33. package/src/utils/rbac/rightsRegistry.js +2 -0
  34. package/views/admin-agents.ejs +261 -11
  35. package/views/admin-dashboard.ejs +78 -8
  36. package/views/admin-superdemos.ejs +335 -0
  37. package/views/admin-terminals.ejs +462 -34
  38. package/views/partials/admin/agents-chat.ejs +80 -0
  39. package/views/partials/dashboard/nav-items.ejs +1 -0
  40. package/views/partials/dashboard/tab-bar.ejs +6 -0
  41. package/cookies.txt +0 -6
  42. package/cookies1.txt +0 -6
  43. package/cookies2.txt +0 -6
  44. package/cookies3.txt +0 -6
  45. package/cookies4.txt +0 -5
  46. package/cookies_old.txt +0 -5
  47. package/cookies_old_test.txt +0 -6
  48. package/cookies_super.txt +0 -5
  49. package/cookies_super_test.txt +0 -6
  50. package/cookies_test.txt +0 -6
  51. package/test-access.js +0 -63
  52. package/test-iframe-fix.html +0 -63
  53. package/test-iframe.html +0 -14
@@ -0,0 +1,614 @@
1
+ var SuperDemos = (() => {
2
+ // sdk/superdemos/browser/src/index.js
3
+ (function() {
4
+ function toStr(v) {
5
+ return v === void 0 || v === null ? "" : String(v);
6
+ }
7
+ function normalizeApiUrl(apiUrl) {
8
+ const u = toStr(apiUrl).trim();
9
+ if (!u) return "";
10
+ return u.replace(/\/$/, "");
11
+ }
12
+ function buildHeaders(apiKey) {
13
+ const headers = {};
14
+ const key = toStr(apiKey).trim();
15
+ if (key) headers["x-project-key"] = key;
16
+ return headers;
17
+ }
18
+ async function fetchJson(url, headers) {
19
+ const res = await fetch(url, { headers: headers || {} });
20
+ const text = await res.text();
21
+ let data = null;
22
+ try {
23
+ data = text ? JSON.parse(text) : null;
24
+ } catch {
25
+ data = null;
26
+ }
27
+ if (!res.ok) {
28
+ const msg = data && data.error ? data.error : "Request failed";
29
+ const err = new Error(msg);
30
+ err.status = res.status;
31
+ throw err;
32
+ }
33
+ return data;
34
+ }
35
+ function getQuery() {
36
+ try {
37
+ return new URL(window.location.href).searchParams;
38
+ } catch {
39
+ return new URLSearchParams();
40
+ }
41
+ }
42
+ function wsUrlFromHttp(apiUrl) {
43
+ const u = new URL(apiUrl);
44
+ u.protocol = u.protocol === "https:" ? "wss:" : "ws:";
45
+ return u.toString().replace(/\/$/, "");
46
+ }
47
+ function randomId(prefix) {
48
+ const buf = new Uint8Array(16);
49
+ if (window.crypto && window.crypto.getRandomValues) {
50
+ window.crypto.getRandomValues(buf);
51
+ } else {
52
+ for (let i = 0; i < buf.length; i += 1) buf[i] = Math.floor(Math.random() * 256);
53
+ }
54
+ let out = "";
55
+ for (const b of buf) out += b.toString(16).padStart(2, "0");
56
+ return prefix + "_" + out;
57
+ }
58
+ function getAnonId() {
59
+ const key = "superdemos.anonId";
60
+ try {
61
+ const existing = localStorage.getItem(key);
62
+ if (existing) return existing;
63
+ const v = randomId("anon");
64
+ localStorage.setItem(key, v);
65
+ return v;
66
+ } catch {
67
+ return randomId("anon");
68
+ }
69
+ }
70
+ function parseForceFlag(value) {
71
+ const v = String(value || "").trim().toLowerCase();
72
+ return v === "1" || v === "true" || v === "yes" || v === "force";
73
+ }
74
+ function ensureStyles() {
75
+ if (document.getElementById("superdemos-styles")) return;
76
+ const style = document.createElement("style");
77
+ style.id = "superdemos-styles";
78
+ style.textContent = `
79
+ .sd-highlight { position: absolute; z-index: 2147483000; pointer-events: none; border: 2px solid rgba(168,85,247,0.9); background: rgba(168,85,247,0.08); border-radius: 6px; }
80
+ .sd-bubble { position: absolute; z-index: 2147483001; max-width: 320px; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; font-size: 13px; line-height: 1.35; color: #111827; background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.18); padding: 10px 12px; }
81
+ .sd-bubble .sd-actions { margin-top: 8px; display: flex; gap: 8px; justify-content: flex-end; }
82
+ .sd-bubble button { cursor: pointer; border: 0; border-radius: 8px; padding: 7px 10px; font-size: 12px; }
83
+ .sd-bubble .sd-next { background: #7c3aed; color: white; }
84
+ .sd-bubble .sd-close { background: #f3f4f6; color: #111827; }
85
+ .sd-inspector-tip { position: fixed; z-index: 2147483002; bottom: 12px; right: 12px; background: rgba(17,24,39,0.92); color: #fff; padding: 8px 10px; border-radius: 8px; font-size: 12px; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
86
+ `;
87
+ document.head.appendChild(style);
88
+ }
89
+ function presetCss(preset) {
90
+ const p = String(preset || "default").trim().toLowerCase();
91
+ if (p === "glass-dark") {
92
+ return `
93
+ .sd-bubble { background: rgba(17,24,39,0.92) !important; color: #f9fafb !important; border-color: rgba(255,255,255,0.22) !important; backdrop-filter: blur(8px); }
94
+ .sd-bubble .sd-close { background: rgba(255,255,255,0.16) !important; color: #fff !important; }
95
+ .sd-bubble .sd-next { background: #22c55e !important; color: #06260f !important; }
96
+ .sd-highlight { border-color: rgba(34,197,94,0.95) !important; background: rgba(34,197,94,0.14) !important; }
97
+ `;
98
+ }
99
+ if (p === "high-contrast") {
100
+ return `
101
+ .sd-bubble { background: #000 !important; color: #fff !important; border-color: #fff !important; box-shadow: 0 0 0 2px #fff !important; }
102
+ .sd-bubble .sd-close { background: #fff !important; color: #000 !important; }
103
+ .sd-bubble .sd-next { background: #ffeb00 !important; color: #000 !important; }
104
+ .sd-highlight { border-color: #ffeb00 !important; background: rgba(255,235,0,0.2) !important; }
105
+ `;
106
+ }
107
+ if (p === "soft-purple") {
108
+ return `
109
+ .sd-bubble { background: #faf5ff !important; color: #3b0764 !important; border-color: #d8b4fe !important; }
110
+ .sd-bubble .sd-close { background: #f3e8ff !important; color: #581c87 !important; }
111
+ .sd-bubble .sd-next { background: #a855f7 !important; color: #fff !important; }
112
+ .sd-highlight { border-color: #a855f7 !important; background: rgba(168,85,247,0.14) !important; }
113
+ `;
114
+ }
115
+ return "";
116
+ }
117
+ function applyProjectStyles(project) {
118
+ ensureStyles();
119
+ const existing = document.getElementById("superdemos-styles-override");
120
+ if (existing) existing.remove();
121
+ const css = `${presetCss(project && project.stylePreset)}
122
+ ${toStr(project && project.styleOverrides)}`;
123
+ if (!css.trim()) return;
124
+ const style = document.createElement("style");
125
+ style.id = "superdemos-styles-override";
126
+ style.textContent = css;
127
+ document.head.appendChild(style);
128
+ }
129
+ function rectFor(el) {
130
+ const r = el.getBoundingClientRect();
131
+ return { x: r.left + window.scrollX, y: r.top + window.scrollY, w: r.width, h: r.height };
132
+ }
133
+ function isProbablyUniqueSelector(selector) {
134
+ try {
135
+ return document.querySelectorAll(selector).length === 1;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+ function buildSelector(el) {
141
+ if (!el || el.nodeType !== 1) return "";
142
+ const stableAttrs = ["data-testid", "data-test", "data-cy", "aria-label", "name"];
143
+ for (const attr of stableAttrs) {
144
+ const v = el.getAttribute(attr);
145
+ if (v) {
146
+ const candidate = `[${attr}="${CSS.escape(v)}"]`;
147
+ if (isProbablyUniqueSelector(candidate)) return candidate;
148
+ }
149
+ }
150
+ const id = el.getAttribute("id");
151
+ if (id) {
152
+ const candidate = `#${CSS.escape(id)}`;
153
+ if (isProbablyUniqueSelector(candidate)) return candidate;
154
+ }
155
+ const parts = [];
156
+ let cur = el;
157
+ let depth = 0;
158
+ while (cur && cur.nodeType === 1 && depth < 6) {
159
+ const tag = cur.tagName.toLowerCase();
160
+ let part = tag;
161
+ const cls = toStr(cur.getAttribute("class")).trim();
162
+ if (cls) {
163
+ const classList = cls.split(/\s+/).map((c) => c.trim()).filter(Boolean).slice(0, 2).map((c) => `.${CSS.escape(c)}`).join("");
164
+ if (classList) part += classList;
165
+ }
166
+ const parent = cur.parentElement;
167
+ if (parent) {
168
+ const siblings = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
169
+ if (siblings.length > 1) {
170
+ const idx = Array.from(parent.children).indexOf(cur) + 1;
171
+ part += `:nth-child(${idx})`;
172
+ }
173
+ }
174
+ parts.unshift(part);
175
+ const candidate = parts.join(" > ");
176
+ if (isProbablyUniqueSelector(candidate)) return candidate;
177
+ cur = cur.parentElement;
178
+ depth += 1;
179
+ }
180
+ return parts.join(" > ");
181
+ }
182
+ function truncateText(s, max) {
183
+ const str = toStr(s).trim().replace(/\s+/g, " ");
184
+ if (str.length <= max) return str;
185
+ return str.slice(0, max - 1) + "\u2026";
186
+ }
187
+ function hintsFor(el) {
188
+ const out = {
189
+ tag: el.tagName.toLowerCase()
190
+ };
191
+ const text = truncateText(el.textContent, 80);
192
+ if (text) out.text = text;
193
+ const attrs = {};
194
+ ["data-testid", "data-test", "data-cy", "aria-label", "name", "type", "role", "placeholder"].forEach((k) => {
195
+ const v = el.getAttribute(k);
196
+ if (v) attrs[k] = v;
197
+ });
198
+ if (Object.keys(attrs).length) out.attrs = attrs;
199
+ return out;
200
+ }
201
+ function createHighlight() {
202
+ const el = document.createElement("div");
203
+ el.className = "sd-highlight";
204
+ el.style.display = "none";
205
+ document.body.appendChild(el);
206
+ return el;
207
+ }
208
+ function positionBox(boxEl, rect) {
209
+ boxEl.style.left = rect.x + "px";
210
+ boxEl.style.top = rect.y + "px";
211
+ boxEl.style.width = Math.max(0, rect.w) + "px";
212
+ boxEl.style.height = Math.max(0, rect.h) + "px";
213
+ }
214
+ function createBubble() {
215
+ const el = document.createElement("div");
216
+ el.className = "sd-bubble";
217
+ el.style.display = "none";
218
+ document.body.appendChild(el);
219
+ return el;
220
+ }
221
+ function positionBubble(bubbleEl, targetRect, placement) {
222
+ const pad = 10;
223
+ const w = bubbleEl.offsetWidth;
224
+ const h = bubbleEl.offsetHeight;
225
+ let x = targetRect.x;
226
+ let y = targetRect.y;
227
+ const p = toStr(placement).toLowerCase();
228
+ if (p === "top") {
229
+ x = targetRect.x;
230
+ y = targetRect.y - h - pad;
231
+ } else if (p === "bottom") {
232
+ x = targetRect.x;
233
+ y = targetRect.y + targetRect.h + pad;
234
+ } else if (p === "left") {
235
+ x = targetRect.x - w - pad;
236
+ y = targetRect.y;
237
+ } else if (p === "right") {
238
+ x = targetRect.x + targetRect.w + pad;
239
+ y = targetRect.y;
240
+ } else {
241
+ y = targetRect.y + targetRect.h + pad;
242
+ }
243
+ const maxX = window.scrollX + window.innerWidth - w - pad;
244
+ const maxY = window.scrollY + window.innerHeight - h - pad;
245
+ x = Math.max(window.scrollX + pad, Math.min(x, maxX));
246
+ y = Math.max(window.scrollY + pad, Math.min(y, maxY));
247
+ bubbleEl.style.left = x + "px";
248
+ bubbleEl.style.top = y + "px";
249
+ }
250
+ async function waitForSelector(selector, timeoutMs) {
251
+ const timeout = Number(timeoutMs) > 0 ? Number(timeoutMs) : 8e3;
252
+ const start = Date.now();
253
+ while (Date.now() - start < timeout) {
254
+ const el = document.querySelector(selector);
255
+ if (el) return el;
256
+ await new Promise((r) => setTimeout(r, 100));
257
+ }
258
+ return null;
259
+ }
260
+ function safeScrollIntoView(el) {
261
+ try {
262
+ el.scrollIntoView({ block: "center", inline: "nearest", behavior: "smooth" });
263
+ } catch {
264
+ try {
265
+ el.scrollIntoView(true);
266
+ } catch {
267
+ }
268
+ }
269
+ }
270
+ function setSeen(projectId, demoId, version, anonId) {
271
+ const key = `superdemos.seen.${projectId}.${demoId}.v${version}.${anonId}`;
272
+ try {
273
+ localStorage.setItem(key, "1");
274
+ } catch {
275
+ }
276
+ }
277
+ function hasSeen(projectId, demoId, version, anonId) {
278
+ const key = `superdemos.seen.${projectId}.${demoId}.v${version}.${anonId}`;
279
+ try {
280
+ return localStorage.getItem(key) === "1";
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+ async function playSteps(def) {
286
+ ensureStyles();
287
+ const highlight = createHighlight();
288
+ const bubble = createBubble();
289
+ let cleanupTargetListener = null;
290
+ function cleanup() {
291
+ try {
292
+ highlight.remove();
293
+ } catch {
294
+ }
295
+ try {
296
+ bubble.remove();
297
+ } catch {
298
+ }
299
+ try {
300
+ if (cleanupTargetListener) cleanupTargetListener();
301
+ } catch {
302
+ }
303
+ cleanupTargetListener = null;
304
+ }
305
+ function showBubbleOn(el, step, onNext) {
306
+ const rect = rectFor(el);
307
+ highlight.style.display = "block";
308
+ positionBox(highlight, rect);
309
+ bubble.innerHTML = "";
310
+ const msg = document.createElement("div");
311
+ msg.textContent = toStr(step.message);
312
+ bubble.appendChild(msg);
313
+ const actions = document.createElement("div");
314
+ actions.className = "sd-actions";
315
+ const closeBtn = document.createElement("button");
316
+ closeBtn.className = "sd-close";
317
+ closeBtn.textContent = "Close";
318
+ closeBtn.addEventListener("click", () => cleanup());
319
+ actions.appendChild(closeBtn);
320
+ const nextBtn = document.createElement("button");
321
+ nextBtn.className = "sd-next";
322
+ nextBtn.textContent = "Next";
323
+ nextBtn.addEventListener("click", () => onNext());
324
+ const advType = toStr(step.advance && step.advance.type).trim() || "manualNext";
325
+ if (advType === "manualNext") {
326
+ actions.appendChild(nextBtn);
327
+ }
328
+ bubble.appendChild(actions);
329
+ bubble.style.display = "block";
330
+ const placement = toStr(step.placement || "auto");
331
+ positionBubble(bubble, rect, placement);
332
+ if (advType === "clickTarget") {
333
+ const handler = () => {
334
+ onNext();
335
+ };
336
+ el.addEventListener("click", handler, { once: true, capture: true });
337
+ cleanupTargetListener = () => {
338
+ try {
339
+ el.removeEventListener("click", handler, { capture: true });
340
+ } catch {
341
+ }
342
+ };
343
+ }
344
+ if (advType === "delayMs") {
345
+ const ms = Number(step.advance && step.advance.ms) > 0 ? Number(step.advance.ms) : 1200;
346
+ setTimeout(() => onNext(), ms);
347
+ }
348
+ }
349
+ const steps = Array.isArray(def.steps) ? def.steps : [];
350
+ let idx = 0;
351
+ return new Promise((resolve) => {
352
+ async function runNext() {
353
+ if (idx >= steps.length) {
354
+ cleanup();
355
+ resolve({ completed: true });
356
+ return;
357
+ }
358
+ const step = steps[idx];
359
+ idx += 1;
360
+ const selector = toStr(step.selector).trim();
361
+ if (!selector) {
362
+ runNext();
363
+ return;
364
+ }
365
+ const timeoutMs = Number(step.waitFor && step.waitFor.timeoutMs) || 8e3;
366
+ const el = await waitForSelector(selector, timeoutMs);
367
+ if (!el) {
368
+ runNext();
369
+ return;
370
+ }
371
+ safeScrollIntoView(el);
372
+ showBubbleOn(el, step, runNext);
373
+ }
374
+ runNext();
375
+ });
376
+ }
377
+ function createInspectorOverlay({ onHover, onSelect, onExit }) {
378
+ ensureStyles();
379
+ const highlight = createHighlight();
380
+ const tip = document.createElement("div");
381
+ tip.className = "sd-inspector-tip";
382
+ tip.textContent = "SuperDemos authoring: hover to preview, click to select. Press Esc to exit.";
383
+ document.body.appendChild(tip);
384
+ let lastHoverEl = null;
385
+ function updateHighlight(el) {
386
+ if (!el) {
387
+ highlight.style.display = "none";
388
+ return;
389
+ }
390
+ const r = rectFor(el);
391
+ highlight.style.display = "block";
392
+ positionBox(highlight, r);
393
+ }
394
+ function isOverlayEl(el) {
395
+ if (!el) return false;
396
+ if (el.classList && (el.classList.contains("sd-highlight") || el.classList.contains("sd-bubble") || el.classList.contains("sd-inspector-tip")))
397
+ return true;
398
+ return false;
399
+ }
400
+ function onMouseMove(e) {
401
+ const el = e.target;
402
+ if (!el || isOverlayEl(el)) return;
403
+ if (el === lastHoverEl) return;
404
+ lastHoverEl = el;
405
+ updateHighlight(el);
406
+ if (onHover) onHover(el);
407
+ }
408
+ function onClick(e) {
409
+ const el = e.target;
410
+ if (!el || isOverlayEl(el)) return;
411
+ e.preventDefault();
412
+ e.stopPropagation();
413
+ updateHighlight(el);
414
+ if (onSelect) onSelect(el);
415
+ }
416
+ function onKeyDown(e) {
417
+ if (e.key === "Escape") {
418
+ cleanup();
419
+ if (onExit) onExit();
420
+ }
421
+ }
422
+ function cleanup() {
423
+ try {
424
+ window.removeEventListener("mousemove", onMouseMove, true);
425
+ } catch {
426
+ }
427
+ try {
428
+ window.removeEventListener("click", onClick, true);
429
+ } catch {
430
+ }
431
+ try {
432
+ window.removeEventListener("keydown", onKeyDown, true);
433
+ } catch {
434
+ }
435
+ try {
436
+ highlight.remove();
437
+ } catch {
438
+ }
439
+ try {
440
+ tip.remove();
441
+ } catch {
442
+ }
443
+ }
444
+ window.addEventListener("mousemove", onMouseMove, true);
445
+ window.addEventListener("click", onClick, true);
446
+ window.addEventListener("keydown", onKeyDown, true);
447
+ return { cleanup };
448
+ }
449
+ async function startAuthorMode({ apiUrl, sessionId, token }) {
450
+ if (!apiUrl) throw new Error("apiUrl is required for author mode");
451
+ if (!sessionId || !token) throw new Error("Missing sd_session/sd_token");
452
+ ensureStyles();
453
+ const wsBase = wsUrlFromHttp(apiUrl);
454
+ const wsUrl = `${wsBase}/api/superdemos/ws?sessionId=${encodeURIComponent(sessionId)}&role=sdk&token=${encodeURIComponent(
455
+ token
456
+ )}`;
457
+ const bubble = createBubble();
458
+ let inspector = null;
459
+ function clearPreview() {
460
+ bubble.style.display = "none";
461
+ bubble.innerHTML = "";
462
+ }
463
+ function showPreview(selector, message, placement) {
464
+ clearPreview();
465
+ let target;
466
+ try {
467
+ target = document.querySelector(selector);
468
+ } catch {
469
+ target = null;
470
+ }
471
+ if (!target) return;
472
+ const rect = rectFor(target);
473
+ bubble.textContent = toStr(message);
474
+ bubble.style.display = "block";
475
+ positionBubble(bubble, rect, placement || "auto");
476
+ }
477
+ const ws = new WebSocket(wsUrl);
478
+ ws.addEventListener("open", () => {
479
+ console.log("[SuperDemos] author WS connected");
480
+ try {
481
+ ws.send(
482
+ JSON.stringify({
483
+ type: "location",
484
+ location: {
485
+ url: window.location.href,
486
+ title: document.title || ""
487
+ }
488
+ })
489
+ );
490
+ } catch {
491
+ }
492
+ });
493
+ ws.addEventListener("message", (evt) => {
494
+ let msg;
495
+ try {
496
+ msg = JSON.parse(String(evt.data || ""));
497
+ } catch {
498
+ return;
499
+ }
500
+ if (msg.type === "preview_bubble") {
501
+ showPreview(toStr(msg.selector), toStr(msg.message), toStr(msg.placement || "auto"));
502
+ }
503
+ if (msg.type === "clear_preview") {
504
+ clearPreview();
505
+ }
506
+ });
507
+ ws.addEventListener("close", () => {
508
+ console.log("[SuperDemos] author WS disconnected");
509
+ try {
510
+ if (inspector) inspector.cleanup();
511
+ } catch {
512
+ }
513
+ try {
514
+ bubble.remove();
515
+ } catch {
516
+ }
517
+ });
518
+ inspector = createInspectorOverlay({
519
+ onHover(el) {
520
+ const selector = buildSelector(el);
521
+ if (!selector) return;
522
+ ws.send(
523
+ JSON.stringify({
524
+ type: "hover",
525
+ element: {
526
+ selector,
527
+ rect: rectFor(el),
528
+ hints: hintsFor(el)
529
+ }
530
+ })
531
+ );
532
+ },
533
+ onSelect(el) {
534
+ const selector = buildSelector(el);
535
+ if (!selector) return;
536
+ ws.send(
537
+ JSON.stringify({
538
+ type: "select",
539
+ element: {
540
+ selector,
541
+ rect: rectFor(el),
542
+ hints: hintsFor(el)
543
+ }
544
+ })
545
+ );
546
+ },
547
+ onExit() {
548
+ try {
549
+ ws.close();
550
+ } catch {
551
+ }
552
+ }
553
+ });
554
+ return { ws, inspector };
555
+ }
556
+ const state = {
557
+ initialized: false,
558
+ projectId: null,
559
+ apiKey: null,
560
+ apiUrl: "",
561
+ mode: "live"
562
+ };
563
+ async function init(options) {
564
+ const opts = options || {};
565
+ const projectId = toStr(opts.projectId).trim();
566
+ if (!projectId) throw new Error("projectId is required");
567
+ const apiUrl = normalizeApiUrl(opts.apiUrl);
568
+ if (!apiUrl) throw new Error("apiUrl is required");
569
+ state.projectId = projectId;
570
+ state.apiKey = opts.apiKey;
571
+ state.apiUrl = apiUrl;
572
+ state.mode = toStr(opts.mode || "live").trim().toLowerCase() === "author" ? "author" : "live";
573
+ const q = getQuery();
574
+ const sessionId = toStr(q.get("sd_session")).trim();
575
+ const token = toStr(q.get("sd_token")).trim();
576
+ const authorFlag = toStr(q.get("sd_author")).trim();
577
+ const forceFlag = parseForceFlag(opts.force) || parseForceFlag(q.get("sd_force"));
578
+ if ((authorFlag === "1" || authorFlag === "true" || sessionId && token) && sessionId && token) {
579
+ state.mode = "author";
580
+ await startAuthorMode({ apiUrl, sessionId, token });
581
+ state.initialized = true;
582
+ return { mode: "author", sessionId };
583
+ }
584
+ const anonId = getAnonId();
585
+ const listUrl = apiUrl + "/api/superdemos/projects/" + encodeURIComponent(projectId) + "/demos/published?url=" + encodeURIComponent(window.location.href);
586
+ const list = await fetchJson(listUrl, buildHeaders(opts.apiKey));
587
+ applyProjectStyles(list && list.project ? list.project : null);
588
+ const demos = list && Array.isArray(list.demos) ? list.demos : [];
589
+ const first = demos[0] || null;
590
+ if (!first) {
591
+ state.initialized = true;
592
+ return { mode: "live", played: false, reason: "no_demos" };
593
+ }
594
+ if (!forceFlag && hasSeen(projectId, first.demoId, first.publishedVersion || 0, anonId)) {
595
+ state.initialized = true;
596
+ return { mode: "live", played: false, reason: "already_seen", demoId: first.demoId };
597
+ }
598
+ const defUrl = apiUrl + "/api/superdemos/demos/" + encodeURIComponent(first.demoId) + "/definition";
599
+ const def = await fetchJson(defUrl, buildHeaders(opts.apiKey));
600
+ await playSteps(def);
601
+ if (!forceFlag) {
602
+ setSeen(projectId, first.demoId, first.publishedVersion || 0, anonId);
603
+ }
604
+ state.initialized = true;
605
+ return { mode: "live", played: true, demoId: first.demoId };
606
+ }
607
+ const SuperDemos = {
608
+ init,
609
+ _state: state
610
+ };
611
+ window.SuperDemos = SuperDemos;
612
+ window.superDemos = SuperDemos;
613
+ })();
614
+ })();