@sightspool/sdk 0.3.0 → 0.4.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +111 -333
  3. package/SECURITY.md +16 -0
  4. package/dist/index.cjs +159 -1226
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +20 -41
  7. package/dist/index.d.ts +20 -41
  8. package/dist/index.js +160 -761
  9. package/dist/index.js.map +1 -1
  10. package/dist/release.json +21 -0
  11. package/dist/sdk.global.js +2 -59
  12. package/dist/sdk.global.js.map +1 -1
  13. package/dist/source.json +11 -0
  14. package/package.json +14 -12
  15. package/site/CNAME +1 -0
  16. package/site/demo.html +6 -0
  17. package/site/demo.js +27 -0
  18. package/site/index.html +74 -0
  19. package/site/release.js +20 -0
  20. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/release.json +21 -0
  21. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js +3 -0
  22. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js.map +1 -0
  23. package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/source.json +11 -0
  24. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/release.json +21 -0
  25. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js +3 -0
  26. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js.map +1 -0
  27. package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/source.json +11 -0
  28. package/site/styles.css +69 -0
  29. package/site/trust.html +36 -0
  30. package/dist/chunk-4PADVIIO.js +0 -41
  31. package/dist/chunk-4PADVIIO.js.map +0 -1
  32. package/dist/probe-BNGMQQ5C.js +0 -113
  33. package/dist/probe-BNGMQQ5C.js.map +0 -1
  34. package/dist/prompt-GDFYC75D.js +0 -139
  35. package/dist/prompt-GDFYC75D.js.map +0 -1
  36. package/dist/survey-CICJQ5VI.js +0 -145
  37. package/dist/survey-CICJQ5VI.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -2,1283 +2,216 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __esm = (fn, res) => function __init() {
8
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ // src/research.ts
6
+ var slot = /* @__PURE__ */ Symbol.for("sightspool.research.runtime.v1");
7
+ var host = () => typeof window === "undefined" ? null : window;
8
+ var current = () => {
9
+ var _a;
10
+ return (_a = host()) == null ? void 0 : _a[slot];
9
11
  };
10
- var __export = (target, all) => {
11
- for (var name in all)
12
- __defProp(target, name, { get: all[name], enumerable: true });
13
- };
14
-
15
- // src/privacy.ts
16
- function redactText(input) {
17
- if (!input) return "";
18
- try {
19
- return String(input).replace(EMAIL_RE, "\u2039email\u203A").replace(LONG_DIGITS_RE, "\u2039num\u203A").replace(/\s+/g, " ").trim().slice(0, MAX_LABEL_LEN);
20
- } catch (e) {
21
- return "";
22
- }
23
- }
24
- function safeClosest(el, selector) {
25
- try {
26
- return typeof el.closest === "function" && el.closest(selector) != null;
27
- } catch (e) {
28
- return false;
29
- }
30
- }
31
- function isIgnoredElement(el, blockSelectors = []) {
32
- if (!el || typeof el.closest !== "function") return false;
33
- if (safeClosest(el, `[${IGNORE_ATTR}]`)) return true;
34
- for (const sel of blockSelectors) {
35
- if (sel && safeClosest(el, sel)) return true;
36
- }
37
- return false;
38
- }
39
- function matchesRedactSelector(el, redactSelectors = []) {
40
- if (!el || typeof el.closest !== "function") return false;
41
- for (const sel of redactSelectors) {
42
- if (sel && safeClosest(el, sel)) return true;
43
- }
44
- return false;
45
- }
46
- var EMAIL_RE, LONG_DIGITS_RE, MAX_LABEL_LEN, ATTACH_DISCLOSURE, REDACTED, IGNORE_ATTR;
47
- var init_privacy = __esm({
48
- "src/privacy.ts"() {
49
- EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
50
- LONG_DIGITS_RE = /\d{6,}/g;
51
- MAX_LABEL_LEN = 280;
52
- ATTACH_DISCLOSURE = "Session and account attached automatically.";
53
- REDACTED = "\u2039redacted\u203A";
54
- IGNORE_ATTR = "data-sightspool-ignore";
55
- }
56
- });
57
-
58
- // src/prompt.ts
59
- var prompt_exports = {};
60
- __export(prompt_exports, {
61
- showPrompt: () => showPrompt
62
- });
63
- function showPrompt(opts) {
64
- return new Promise((resolve) => {
65
- if (typeof document === "undefined") {
66
- resolve({ answer: "dismissed" });
67
- return;
68
- }
69
- let settled = false;
70
- const host = document.createElement("div");
71
- const shadow = host.attachShadow({ mode: "open" });
72
- function finish(result) {
73
- if (settled) return;
74
- settled = true;
75
- try {
76
- host.remove();
77
- } catch (e) {
78
- }
79
- resolve(result);
80
- }
81
- const style = document.createElement("style");
82
- style.textContent = STYLE;
83
- shadow.appendChild(style);
84
- const wrap = document.createElement("div");
85
- wrap.className = "wrap";
86
- shadow.appendChild(wrap);
87
- function render(node) {
88
- wrap.innerHTML = "";
89
- const card = document.createElement("div");
90
- card.className = "card";
91
- card.style.position = "relative";
92
- const x = document.createElement("button");
93
- x.className = "x";
94
- x.textContent = "\xD7";
95
- x.setAttribute("aria-label", "Dismiss");
96
- x.onclick = () => finish({ answer: "dismissed" });
97
- card.appendChild(x);
98
- card.appendChild(node);
99
- const disc = document.createElement("p");
100
- disc.className = "disc";
101
- disc.textContent = ATTACH_DISCLOSURE;
102
- card.appendChild(disc);
103
- wrap.appendChild(card);
104
- }
105
- function stateA() {
106
- const node = document.createElement("div");
107
- const q = document.createElement("p");
108
- q.className = "q";
109
- q.textContent = "Were you able to do what you came here to do today?";
110
- node.appendChild(q);
111
- const row = document.createElement("div");
112
- row.className = "row";
113
- const yes = document.createElement("button");
114
- yes.className = "primary";
115
- yes.textContent = "Yes";
116
- yes.onclick = () => finish({ answer: "yes" });
117
- const no = document.createElement("button");
118
- no.textContent = "Not really";
119
- no.onclick = () => stateB();
120
- row.append(yes, no);
121
- node.appendChild(row);
122
- render(node);
123
- }
124
- function stateB() {
125
- const node = document.createElement("div");
126
- const q = document.createElement("p");
127
- q.className = "q";
128
- q.textContent = "What were you trying to do?";
129
- node.appendChild(q);
130
- for (const c of opts.candidates.slice(0, 4)) {
131
- const b = document.createElement("button");
132
- b.className = "cand";
133
- b.textContent = c;
134
- b.onclick = () => finish({ answer: "not_really", intent: c });
135
- node.appendChild(b);
136
- }
137
- const se = document.createElement("button");
138
- se.className = "cand";
139
- se.textContent = "Something else\u2026";
140
- se.onclick = () => stateText();
141
- node.appendChild(se);
142
- render(node);
143
- }
144
- function stateText() {
145
- const node = document.createElement("div");
146
- const q = document.createElement("p");
147
- q.className = "q";
148
- q.textContent = "What were you trying to do?";
149
- node.appendChild(q);
150
- const input = document.createElement("input");
151
- input.type = "text";
152
- input.placeholder = "In your own words\u2026";
153
- node.appendChild(input);
154
- const row = document.createElement("div");
155
- row.className = "row";
156
- const submit = document.createElement("button");
157
- submit.className = "primary";
158
- submit.textContent = "Send";
159
- submit.onclick = () => finish({ answer: "not_really", verbatim: input.value.trim() || void 0 });
160
- row.appendChild(submit);
161
- node.appendChild(row);
162
- render(node);
163
- try {
164
- input.focus();
165
- } catch (e) {
166
- }
167
- }
168
- try {
169
- document.body.appendChild(host);
170
- stateA();
171
- } catch (e) {
172
- finish({ answer: "dismissed" });
173
- }
174
- });
175
- }
176
- var STYLE;
177
- var init_prompt = __esm({
178
- "src/prompt.ts"() {
179
- init_privacy();
180
- STYLE = `
181
- :host { all: initial; }
182
- .wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
183
- width: 320px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
184
- .card { background: #fff; color: #18181b; border: 1px solid #e4e4e7; border-radius: 12px;
185
- box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
186
- .q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; }
187
- .row { display: flex; gap: 8px; flex-wrap: wrap; }
188
- button { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;
189
- border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }
190
- button:hover { background: #f4f4f5; }
191
- button.primary { background: #18181b; color: #fff; border-color: #18181b; }
192
- .cand { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
193
- .disc { font-size: 11px; color: #71717a; margin: 12px 0 0; }
194
- .x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
195
- color: #a1a1aa; padding: 2px 6px; }
196
- input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;
197
- border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }
198
- `;
199
- }
200
- });
201
-
202
- // src/probe.ts
203
- var probe_exports = {};
204
- __export(probe_exports, {
205
- showProbe: () => showProbe
206
- });
207
- function findSlot(slot) {
208
- try {
209
- return document.querySelector(`[data-sightspool-slot="${slot}"]`);
210
- } catch (e) {
211
- return null;
212
- }
213
- }
214
- function showProbe(config) {
215
- return new Promise((resolve) => {
216
- if (typeof document === "undefined") {
217
- resolve({ dismissed: true, mounted: false });
218
- return;
219
- }
220
- const anchor = config.slot ? findSlot(config.slot) : null;
221
- if (config.slot && !anchor) {
222
- resolve({ mounted: false });
223
- return;
224
- }
225
- let settled = false;
226
- const inline = !!anchor;
227
- const host = document.createElement(inline ? "span" : "div");
228
- const shadow = host.attachShadow({ mode: "open" });
229
- function teardown() {
230
- try {
231
- host.remove();
232
- } catch (e) {
233
- }
234
- }
235
- function finish(result, disclose) {
236
- if (settled) return;
237
- settled = true;
238
- resolve(result);
239
- if (disclose) {
240
- disclosure();
241
- setTimeout(teardown, DISCLOSURE_MS);
242
- } else {
243
- teardown();
244
- }
245
- }
246
- const style = document.createElement("style");
247
- style.textContent = inline ? INLINE_STYLE : FLOATING_STYLE;
248
- shadow.appendChild(style);
249
- const wrap = document.createElement(inline ? "span" : "div");
250
- if (!inline) wrap.className = "wrap";
251
- shadow.appendChild(wrap);
252
- function container() {
253
- wrap.innerHTML = "";
254
- if (inline) return wrap;
255
- const c = document.createElement("div");
256
- c.className = "card";
257
- wrap.appendChild(c);
258
- return c;
259
- }
260
- function probe() {
261
- const c = container();
262
- if (!inline) {
263
- const x = document.createElement("button");
264
- x.className = "x";
265
- x.textContent = "\xD7";
266
- x.setAttribute("aria-label", "Dismiss");
267
- x.onclick = () => finish({ dismissed: true, mounted: true }, false);
268
- c.appendChild(x);
269
- }
270
- const b = document.createElement("button");
271
- b.className = "probe";
272
- b.textContent = config.label;
273
- b.onclick = () => finish({ voted: true, mounted: true }, true);
274
- c.appendChild(b);
275
- }
276
- function disclosure() {
277
- const c = container();
278
- const p = document.createElement("p");
279
- p.className = "done";
280
- p.textContent = config.disclosure;
281
- c.appendChild(p);
282
- }
283
- try {
284
- (anchor != null ? anchor : document.body).appendChild(host);
285
- probe();
286
- } catch (e) {
287
- finish({ dismissed: true, mounted: false }, false);
288
- }
289
- });
290
- }
291
- var FLOATING_STYLE, INLINE_STYLE, DISCLOSURE_MS;
292
- var init_probe = __esm({
293
- "src/probe.ts"() {
294
- FLOATING_STYLE = `
295
- :host { all: initial; }
296
- .wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
297
- width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
298
- .card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
299
- border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
300
- .probe { font: inherit; font-size: 13px; cursor: pointer; display: block; width: 100%;
301
- text-align: left; border: 1px dashed #a1a1aa; border-radius: 8px; padding: 10px 12px;
302
- background: #fafafa; color: #3f3f46; }
303
- .probe:hover { border-color: #18181b; color: #18181b; }
304
- .x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
305
- color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
306
- .done { font-size: 13px; color: #18181b; margin: 0; line-height: 1.4; }
307
- `;
308
- INLINE_STYLE = `
309
- :host { all: initial; display: inline-block; max-width: 100%; }
310
- .probe { font: inherit; font-family: system-ui, -apple-system, sans-serif; font-size: 13px;
311
- cursor: pointer; display: inline-block; text-align: left; border: 1px dashed #a1a1aa;
312
- border-radius: 8px; padding: 8px 12px; background: transparent; color: #52525b; }
313
- .probe:hover { border-color: #18181b; color: #18181b; }
314
- .done { font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: #52525b;
315
- margin: 0; line-height: 1.4; border: 1px dashed #d4d4d8; border-radius: 8px; padding: 8px 12px; }
316
- `;
317
- DISCLOSURE_MS = 3200;
318
- }
319
- });
320
-
321
- // src/survey.ts
322
- var survey_exports = {};
323
- __export(survey_exports, {
324
- showSurvey: () => showSurvey
325
- });
326
- function showSurvey(config) {
327
- return new Promise((resolve) => {
328
- if (typeof document === "undefined") {
329
- resolve({ dismissed: true });
12
+ var eligible = (r) => r.audience === "all_visitors" || r.identified;
13
+ function remove(r) {
14
+ var _a;
15
+ (_a = r.button) == null ? void 0 : _a.remove();
16
+ r.button = null;
17
+ r.offer = null;
18
+ }
19
+ function invalidate(r) {
20
+ var _a;
21
+ r.generation += 1;
22
+ (_a = r.pending) == null ? void 0 : _a.abort();
23
+ r.pending = null;
24
+ remove(r);
25
+ }
26
+ function status(r, value) {
27
+ r.status = value;
28
+ }
29
+ async function check(r) {
30
+ if (r.disposed || r.paused || !eligible(r) || r.pending || document.visibilityState !== "visible") return;
31
+ const generation = r.generation;
32
+ const request = new AbortController();
33
+ r.pending = request;
34
+ const timeout = window.setTimeout(() => request.abort(), 1e4);
35
+ if (!r.button) status(r, "checking");
36
+ try {
37
+ const response = await fetch(r.endpoint + "/widget-offer", {
38
+ method: "POST",
39
+ credentials: "omit",
40
+ cache: "no-store",
41
+ referrerPolicy: "no-referrer",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify({ operation: "offer", key: r.key, device: r.device }),
44
+ signal: request.signal
45
+ });
46
+ if (!response.ok) throw Error("offer unavailable");
47
+ const result = await response.json();
48
+ if (r.disposed || generation !== r.generation || r.paused || !eligible(r)) return;
49
+ if (result.available !== true || typeof result.offer !== "string" || !result.offer) {
50
+ remove(r);
51
+ status(r, "unavailable");
330
52
  return;
331
53
  }
332
- let settled = false;
333
- const host = document.createElement("div");
334
- const shadow = host.attachShadow({ mode: "open" });
335
- function teardown() {
54
+ r.offer = result.offer;
55
+ status(r, "available");
56
+ if (r.button) return;
57
+ const button = document.createElement("button");
58
+ button.type = "button";
59
+ button.textContent = "Talk to the founder \xB7 5 min";
60
+ button.setAttribute("aria-label", "Sightspool: join a five-minute user interview");
61
+ button.style.cssText = "position:fixed;bottom:20px;right:20px;z-index:2147483000;padding:14px 18px;border:0;border-radius:16px;background:#171717;color:white;font:500 14px system-ui;box-shadow:0 8px 30px #0003;cursor:pointer;max-width:calc(100vw - 40px)";
62
+ button.onclick = () => {
336
63
  try {
337
- host.remove();
338
- } catch (e) {
339
- }
340
- }
341
- function finish(result, thank) {
342
- if (settled) return;
343
- settled = true;
344
- resolve(result);
345
- if (thank) {
346
- thankYou();
347
- setTimeout(teardown, THANKS_MS);
348
- } else {
349
- teardown();
350
- }
351
- }
352
- const style = document.createElement("style");
353
- style.textContent = STYLE2;
354
- shadow.appendChild(style);
355
- const wrap = document.createElement("div");
356
- wrap.className = "wrap";
357
- shadow.appendChild(wrap);
358
- function card() {
359
- wrap.innerHTML = "";
360
- const c = document.createElement("div");
361
- c.className = "card";
362
- wrap.appendChild(c);
363
- return c;
364
- }
365
- function withClose(c) {
366
- const x = document.createElement("button");
367
- x.className = "x";
368
- x.textContent = "\xD7";
369
- x.setAttribute("aria-label", "Dismiss");
370
- x.onclick = () => finish({ dismissed: true }, false);
371
- c.appendChild(x);
372
- }
373
- function question() {
374
- var _a2, _b, _c;
375
- const c = card();
376
- withClose(c);
377
- const q = document.createElement("p");
378
- q.className = "q";
379
- q.textContent = config.question;
380
- c.appendChild(q);
381
- for (const opt of ((_a2 = config.options) != null ? _a2 : []).slice(0, 6)) {
382
- const b = document.createElement("button");
383
- b.className = "opt";
384
- b.textContent = opt;
385
- b.onclick = () => finish({ choice: opt }, true);
386
- c.appendChild(b);
387
- }
388
- if (config.allow_text) {
389
- const se = document.createElement("button");
390
- se.className = "opt";
391
- se.textContent = ((_c = (_b = config.options) == null ? void 0 : _b.length) != null ? _c : 0) > 0 ? "Something else\u2026" : "Answer\u2026";
392
- se.onclick = freeText;
393
- c.appendChild(se);
394
- }
395
- }
396
- function freeText() {
397
- const c = card();
398
- withClose(c);
399
- const q = document.createElement("p");
400
- q.className = "q";
401
- q.textContent = config.question;
402
- c.appendChild(q);
403
- const input = document.createElement("input");
404
- input.type = "text";
405
- input.placeholder = "In your own words\u2026";
406
- c.appendChild(input);
407
- const row = document.createElement("div");
408
- row.className = "row";
409
- const send = document.createElement("button");
410
- send.className = "primary";
411
- send.textContent = "Send";
412
- const submit = () => {
413
- const text = input.value.trim();
414
- if (!text) {
415
- finish({ dismissed: true }, false);
64
+ if (r.disposed || r.paused || !eligible(r) || !r.offer) return;
65
+ if (r.popup && !r.popup.closed) {
66
+ r.popup.focus();
416
67
  return;
417
68
  }
418
- finish({ text }, true);
419
- };
420
- send.onclick = submit;
421
- input.onkeydown = (e) => {
422
- if (e.key === "Enter") submit();
423
- };
424
- row.appendChild(send);
425
- c.appendChild(row);
426
- try {
427
- input.focus();
69
+ const hash = new URLSearchParams({ offer: r.offer, device: r.device });
70
+ r.popup = window.open(r.endpoint + "/interview-widget#" + hash, "sightspool-interview", "popup,width=520,height=760");
428
71
  } catch (e) {
429
72
  }
73
+ };
74
+ r.button = button;
75
+ document.body.appendChild(button);
76
+ } catch (e) {
77
+ if (!r.disposed && generation === r.generation) {
78
+ remove(r);
79
+ status(r, "error");
430
80
  }
431
- function thankYou() {
432
- const c = card();
433
- const p = document.createElement("p");
434
- p.className = "done";
435
- p.textContent = "Thanks \u2014 that helps.";
436
- c.appendChild(p);
437
- }
438
- try {
439
- document.body.appendChild(host);
440
- question();
441
- } catch (e) {
442
- finish({ dismissed: true }, false);
443
- }
444
- });
445
- }
446
- var STYLE2, THANKS_MS;
447
- var init_survey = __esm({
448
- "src/survey.ts"() {
449
- STYLE2 = `
450
- :host { all: initial; }
451
- .wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
452
- width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
453
- .card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
454
- border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
455
- .q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; padding-right: 16px; }
456
- .row { display: flex; gap: 8px; flex-wrap: wrap; }
457
- button { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;
458
- border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }
459
- button:hover { background: #f4f4f5; }
460
- button.primary { background: #18181b; color: #fff; border-color: #18181b; }
461
- .opt { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
462
- .x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
463
- color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
464
- .done { font-size: 13px; color: #18181b; margin: 0; }
465
- input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;
466
- border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }
467
- `;
468
- THANKS_MS = 1100;
81
+ } finally {
82
+ window.clearTimeout(timeout);
83
+ if (r.pending === request) r.pending = null;
469
84
  }
470
- });
471
-
472
- // src/capture.ts
473
- init_privacy();
474
- var TRAIL_MAX = 30;
475
- var DEAD_CLICK_MS = 700;
476
- var RAGE_WINDOW_MS = 1e3;
477
- var RAGE_COUNT = 3;
478
- function hasDom() {
479
- return typeof window !== "undefined" && typeof document !== "undefined";
480
85
  }
481
- function createCapture(opts) {
482
- var _a2, _b;
483
- const redactSelectors = (_a2 = opts.redact) != null ? _a2 : [];
484
- const blockSelectors = (_b = opts.block) != null ? _b : [];
485
- let buf = [];
486
- let lastError;
487
- let started = false;
488
- const subs = [];
489
- const cleanups = [];
490
- let recentClicks = [];
491
- function notify() {
492
- for (const cb of subs) {
493
- try {
494
- cb();
495
- } catch (e) {
496
- }
497
- }
498
- }
499
- function push(type, label) {
500
- try {
501
- buf.push({ t: Date.now(), type, label: label ? redactText(label) : void 0 });
502
- if (buf.length > TRAIL_MAX) buf = buf.slice(-TRAIL_MAX);
503
- if (opts.debug) console.debug("[sightspool] event", type, label);
504
- notify();
505
- } catch (e) {
506
- }
507
- }
508
- function currentRoute() {
509
- try {
510
- return location.pathname + location.search;
511
- } catch (e) {
512
- return "";
513
- }
514
- }
515
- function currentLabel() {
516
- try {
517
- return document.title || "";
518
- } catch (e) {
519
- return "";
520
- }
521
- }
522
- function onClick(ev) {
523
- var _a3;
524
- try {
525
- const target = ev.target;
526
- if (isIgnoredElement(target, blockSelectors)) return;
527
- const text = target && (((_a3 = target.getAttribute) == null ? void 0 : _a3.call(target, "aria-label")) || target.textContent) || "";
528
- const label = matchesRedactSelector(target, redactSelectors) ? REDACTED : text.trim().slice(0, 60);
529
- const t = Date.now();
530
- recentClicks = recentClicks.filter((ts) => t - ts <= RAGE_WINDOW_MS);
531
- recentClicks.push(t);
532
- const isRage = recentClicks.length >= RAGE_COUNT;
533
- push(isRage ? "rage_click" : "click", label);
534
- const routeBefore = currentRoute();
535
- let mutated = false;
536
- let observer = null;
537
- try {
538
- observer = new MutationObserver(() => {
539
- mutated = true;
540
- });
541
- observer.observe(document.body, { childList: true, subtree: true, attributes: true });
542
- } catch (e) {
543
- }
544
- window.setTimeout(() => {
545
- try {
546
- observer == null ? void 0 : observer.disconnect();
547
- if (!mutated && currentRoute() === routeBefore && label) {
548
- push("dead_click", label);
549
- }
550
- } catch (e) {
551
- }
552
- }, DEAD_CLICK_MS);
553
- } catch (e) {
554
- }
555
- }
556
- function onError(ev) {
557
- try {
558
- lastError = String(ev.message || "error");
559
- push("error", lastError);
560
- } catch (e) {
561
- }
562
- }
563
- function onRejection(ev) {
564
- try {
565
- const reason = ev.reason;
566
- lastError = reason instanceof Error ? reason.message : String(reason);
567
- push("error", lastError);
568
- } catch (e) {
569
- }
570
- }
571
- function installRouteTracking() {
572
- try {
573
- const fire = () => push("route", currentRoute());
574
- const origPush = history.pushState;
575
- const origReplace = history.replaceState;
576
- history.pushState = function(...args) {
577
- const r = origPush.apply(this, args);
578
- fire();
579
- return r;
580
- };
581
- history.replaceState = function(...args) {
582
- const r = origReplace.apply(this, args);
583
- fire();
584
- return r;
585
- };
586
- window.addEventListener("popstate", fire);
587
- cleanups.push(() => {
588
- history.pushState = origPush;
589
- history.replaceState = origReplace;
590
- window.removeEventListener("popstate", fire);
591
- });
592
- push("route", currentRoute());
593
- } catch (e) {
594
- }
595
- }
596
- function installFetchTracking() {
597
- try {
598
- if (typeof window.fetch !== "function") return;
599
- const orig = window.fetch.bind(window);
600
- window.fetch = async (...args) => {
601
- try {
602
- const res = await orig(...args);
603
- if (!res.ok) push("request_error", `HTTP ${res.status}`);
604
- return res;
605
- } catch (err) {
606
- push("request_error", err instanceof Error ? err.message : "network error");
607
- throw err;
608
- }
609
- };
610
- cleanups.push(() => {
611
- window.fetch = orig;
612
- });
613
- } catch (e) {
614
- }
615
- }
616
- return {
617
- start() {
618
- if (started || !hasDom()) return;
619
- started = true;
620
- try {
621
- document.addEventListener("click", onClick, true);
622
- window.addEventListener("error", onError);
623
- window.addEventListener("unhandledrejection", onRejection);
624
- cleanups.push(() => {
625
- document.removeEventListener("click", onClick, true);
626
- window.removeEventListener("error", onError);
627
- window.removeEventListener("unhandledrejection", onRejection);
628
- });
629
- installRouteTracking();
630
- installFetchTracking();
631
- } catch (e) {
632
- }
633
- },
634
- stop() {
635
- started = false;
636
- for (const c of cleanups.splice(0)) {
637
- try {
638
- c();
639
- } catch (e) {
640
- }
641
- }
642
- },
643
- trail() {
644
- return buf.slice();
645
- },
646
- route: currentRoute,
647
- label: currentLabel,
648
- error() {
649
- return lastError;
650
- },
651
- onEvent(cb) {
652
- subs.push(cb);
653
- },
654
- recordSearch(query, zeroResult) {
655
- const q = query.trim();
656
- if (!q) return;
657
- push(zeroResult ? "zero_result" : "search", q);
658
- }
659
- };
660
- }
661
-
662
- // src/harvest.ts
663
- init_privacy();
664
- var SEARCH_HINT_RE = /search|filter|find|query|command|lookup/i;
665
- var DEBOUNCE_MS = 800;
666
- function looksLikeSearch(el) {
86
+ function init(config) {
667
87
  try {
668
- const input = el;
669
- if (input.type === "search") return true;
670
- const role = el.getAttribute("role") || "";
671
- if (role === "searchbox" || role === "combobox") return true;
672
- const hay = [
673
- input.name,
674
- input.placeholder,
675
- el.getAttribute("aria-label"),
676
- el.id,
677
- el.className
678
- ].filter(Boolean).join(" ");
679
- return SEARCH_HINT_RE.test(hay);
680
- } catch (e) {
681
- return false;
682
- }
683
- }
684
- function createHarvest(capture, opts) {
685
- var _a2, _b;
686
- let hint;
687
- let timer = null;
688
- let started = false;
689
- const redactSelectors = (_a2 = opts.redact) != null ? _a2 : [];
690
- const blockSelectors = (_b = opts.block) != null ? _b : [];
691
- function onInput(ev) {
88
+ const browser = host();
89
+ if (!browser || !config || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(config.key)) return;
90
+ if (config.audience !== "all_visitors" && config.audience !== "signed_in") return;
91
+ const url = new URL(config.endpoint || "https://www.sightspool.com");
92
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) return;
93
+ if (url.username || url.password) return;
94
+ const previous = current();
95
+ if (previous && previous.key === config.key && previous.endpoint === url.origin && previous.audience === config.audience) return;
96
+ if (previous) destroy();
97
+ let device = "";
692
98
  try {
693
- const target = ev.target;
694
- if (!target || target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") return;
695
- if (!looksLikeSearch(target)) return;
696
- if (isIgnoredElement(target, blockSelectors) || matchesRedactSelector(target, redactSelectors))
697
- return;
698
- const value = (target.value || "").trim();
699
- if (value.length < 2) return;
700
- if (timer !== null) window.clearTimeout(timer);
701
- timer = window.setTimeout(() => {
702
- hint = value;
703
- capture.recordSearch(value);
704
- if (opts.debug) console.debug("[sightspool] harvested intent", value);
705
- }, DEBOUNCE_MS);
99
+ device = sessionStorage.getItem("sightspool-widget-device:" + config.key) || "";
706
100
  } catch (e) {
707
101
  }
708
- }
709
- return {
710
- start() {
711
- if (started || typeof document === "undefined") return;
712
- started = true;
713
- try {
714
- document.addEventListener("input", onInput, true);
715
- } catch (e) {
716
- }
717
- },
718
- stop() {
719
- started = false;
102
+ if (!/^ss_fcd_[A-Za-z0-9_-]{43}$/.test(device)) {
103
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
104
+ device = "ss_fcd_" + btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
720
105
  try {
721
- document.removeEventListener("input", onInput, true);
722
- if (timer !== null) window.clearTimeout(timer);
106
+ sessionStorage.setItem("sightspool-widget-device:" + config.key, device);
723
107
  } catch (e) {
724
108
  }
725
- },
726
- lastIntentHint() {
727
- return hint;
728
- }
729
- };
730
- }
731
-
732
- // src/egress.ts
733
- var FLUSH_BATCH = 10;
734
- var FLUSH_INTERVAL_MS = 15e3;
735
- function createEgress(opts) {
736
- let queue = [];
737
- let identity;
738
- let interval = null;
739
- let started = false;
740
- const url = `${opts.endpoint.replace(/\/+$/, "")}/api/sdk/ingest`;
741
- function send(signals) {
742
- if (signals.length === 0) return;
743
- const body = { key: opts.key, identity, signals };
744
- const json = JSON.stringify(body);
745
- try {
746
- if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
747
- const blob = new Blob([json], { type: "text/plain" });
748
- const ok = navigator.sendBeacon(url, blob);
749
- if (ok) {
750
- if (opts.debug) console.debug("[sightspool] beacon", signals.length);
751
- return;
752
- }
753
- }
754
- void fetch(url, {
755
- method: "POST",
756
- headers: { "Content-Type": "text/plain" },
757
- body: json,
758
- keepalive: true,
759
- mode: "cors",
760
- credentials: "omit"
761
- }).catch(() => {
762
- });
763
- } catch (e) {
764
- }
765
- }
766
- function onVisibility() {
767
- try {
768
- if (document.visibilityState === "hidden") flush();
769
- } catch (e) {
770
109
  }
771
- }
772
- function flush() {
773
- if (queue.length === 0) return;
774
- const batch = queue;
775
- queue = [];
776
- send(batch);
777
- }
778
- return {
779
- start() {
780
- if (started || typeof window === "undefined") return;
781
- started = true;
782
- try {
783
- document.addEventListener("visibilitychange", onVisibility);
784
- window.addEventListener("pagehide", flush);
785
- interval = window.setInterval(flush, FLUSH_INTERVAL_MS);
786
- } catch (e) {
787
- }
788
- },
789
- stop() {
790
- started = false;
110
+ const r = {
111
+ key: config.key,
112
+ endpoint: url.origin,
113
+ audience: config.audience,
114
+ identified: false,
115
+ paused: false,
116
+ disposed: false,
117
+ generation: 0,
118
+ device,
119
+ offer: null,
120
+ button: null,
121
+ popup: null,
122
+ pending: null,
123
+ timer: 0,
124
+ visibility: () => {
125
+ },
126
+ status: "signed_out"
127
+ };
128
+ browser[slot] = r;
129
+ r.visibility = () => {
791
130
  try {
792
- document.removeEventListener("visibilitychange", onVisibility);
793
- window.removeEventListener("pagehide", flush);
794
- if (interval !== null) window.clearInterval(interval);
131
+ if (document.visibilityState === "visible") void check(r);
132
+ else {
133
+ invalidate(r);
134
+ status(r, r.paused ? "paused" : eligible(r) ? "unavailable" : "signed_out");
135
+ }
795
136
  } catch (e) {
796
137
  }
797
- flush();
798
- },
799
- enqueue(signal) {
800
- queue.push(signal);
801
- if (queue.length >= FLUSH_BATCH) flush();
802
- },
803
- flush,
804
- setIdentity(next) {
805
- identity = next;
806
- }
807
- };
808
- }
809
-
810
- // src/triggers.ts
811
- var RAGE_CLICK_COUNT = 3;
812
- var RAGE_WINDOW_MS2 = 1e3;
813
- var FRICTION_RETRY_COUNT = 3;
814
- var FRICTION_WINDOW_MS = 8e3;
815
- function detectFriction(trail, now, windowMs = FRICTION_WINDOW_MS) {
816
- var _a2;
817
- const recent = trail.filter((e) => now - e.t <= windowMs);
818
- if (recent.some((e) => e.type === "error" || e.type === "request_error")) return "error";
819
- const clicks = recent.filter((e) => e.type === "click" || e.type === "rage_click");
820
- for (let i = 0; i < clicks.length; i++) {
821
- const burst = clicks.filter(
822
- (e) => e.t >= clicks[i].t && e.t - clicks[i].t <= RAGE_WINDOW_MS2
823
- );
824
- if (burst.length >= RAGE_CLICK_COUNT) return "rage";
825
- }
826
- if (recent.some((e) => e.type === "dead_click")) return "dead_end";
827
- const byLabel = /* @__PURE__ */ new Map();
828
- for (const e of clicks) {
829
- if (!e.label) continue;
830
- const n = ((_a2 = byLabel.get(e.label)) != null ? _a2 : 0) + 1;
831
- byLabel.set(e.label, n);
832
- if (n >= FRICTION_RETRY_COUNT) return "retry";
833
- }
834
- return null;
835
- }
836
- var DEFAULT_CAPS = {
837
- maxPromptsPerSession: 1,
838
- cooldownMs: 5 * 6e4
839
- };
840
- function canPrompt(state, now, caps = DEFAULT_CAPS) {
841
- if (state.promptsShown >= caps.maxPromptsPerSession) return false;
842
- if (state.lastPromptAt !== null && now - state.lastPromptAt < caps.cooldownMs) return false;
843
- return true;
844
- }
845
-
846
- // src/intervention.ts
847
- var RENDERABLE = /* @__PURE__ */ new Set(["survey", "demand_probe"]);
848
- function str(v, max) {
849
- if (typeof v !== "string") return void 0;
850
- const t = v.trim();
851
- return t ? t.slice(0, max) : void 0;
852
- }
853
- function buildServeContext(route, identity) {
854
- const ctx = {};
855
- const r = str(route, 500);
856
- if (r) ctx.route = r;
857
- const account = str(identity == null ? void 0 : identity.account, 200);
858
- if (account) ctx.account = account;
859
- const plan = str(identity == null ? void 0 : identity.plan, 80);
860
- if (plan) ctx.plan = plan;
861
- return ctx;
862
- }
863
- function normalizeServed(raw) {
864
- if (!raw || typeof raw !== "object") return null;
865
- const r = raw;
866
- const id = str(r.id, 64);
867
- const type = str(r.type, 40);
868
- if (!id || !type || !RENDERABLE.has(type)) return null;
869
- const cfg = r.config && typeof r.config === "object" ? r.config : {};
870
- if (type === "demand_probe") {
871
- const label = str(cfg.label, 200);
872
- const disclosure = str(cfg.disclosure, 300);
873
- if (!label || !disclosure) return null;
874
- const rawSlot = str(cfg.slot, 64);
875
- const slot = rawSlot && /^[a-z0-9][a-z0-9_-]*$/i.test(rawSlot) ? rawSlot : void 0;
876
- return { id, type, config: { label, disclosure, ...slot ? { slot } : {} } };
877
- }
878
- const question = str(cfg.question, 500);
879
- if (!question) return null;
880
- const options = Array.isArray(cfg.options) ? cfg.options.map((o) => str(o, 200)).filter((o) => !!o).slice(0, 6) : void 0;
881
- const config = {
882
- question,
883
- ...options && options.length ? { options } : {},
884
- allow_text: cfg.allow_text === true
885
- };
886
- return { id, type: "survey", config };
887
- }
888
- function pickRespondentKey(userId, persisted, sessionRef) {
889
- const uid = str(userId, 200);
890
- if (uid) return uid;
891
- const p = str(persisted, 200);
892
- if (p) return p;
893
- return sessionRef;
894
- }
895
- var RK_STORAGE_KEY = "sightspool_rk";
896
- function getRespondentKey(identity, sessionRef) {
897
- const uid = str(identity == null ? void 0 : identity.userId, 200);
898
- if (uid) return uid;
899
- try {
900
- let v = localStorage.getItem(RK_STORAGE_KEY);
901
- if (!v) {
902
- v = "anon-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
903
- localStorage.setItem(RK_STORAGE_KEY, v);
904
- }
905
- return pickRespondentKey(void 0, v, sessionRef);
906
- } catch (e) {
907
- return sessionRef;
908
- }
909
- }
910
- function serveUrl(endpoint, path) {
911
- return `${endpoint.replace(/\/+$/, "")}/api/sdk/${path}`;
912
- }
913
- async function fetchIntervention(opts) {
914
- var _a2;
915
- try {
916
- if (typeof fetch !== "function") return null;
917
- const res = await fetch(serveUrl(opts.endpoint, "serve"), {
918
- method: "POST",
919
- headers: { "Content-Type": "text/plain" },
920
- body: JSON.stringify({
921
- key: opts.key,
922
- context: opts.ctx,
923
- respondent_key: opts.respondentKey
924
- }),
925
- mode: "cors",
926
- credentials: "omit"
927
- });
928
- if (!res.ok) return null;
929
- const data = await res.json();
930
- const served = normalizeServed(data == null ? void 0 : data.intervention);
931
- if (opts.debug) console.debug("[sightspool] serve", (_a2 = served == null ? void 0 : served.id) != null ? _a2 : "(none)");
932
- return served;
933
- } catch (e) {
934
- return null;
935
- }
936
- }
937
- function submitResponse(opts) {
938
- try {
939
- const url = serveUrl(opts.endpoint, "respond");
940
- const json = JSON.stringify({
941
- key: opts.key,
942
- intervention_id: opts.interventionId,
943
- response: opts.response,
944
- respondent_key: opts.respondentKey
945
- });
946
- if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
947
- const blob = new Blob([json], { type: "text/plain" });
948
- if (navigator.sendBeacon(url, blob)) return;
949
- }
950
- if (typeof fetch === "function") {
951
- void fetch(url, {
952
- method: "POST",
953
- headers: { "Content-Type": "text/plain" },
954
- body: json,
955
- keepalive: true,
956
- mode: "cors",
957
- credentials: "omit"
958
- }).catch(() => {
959
- });
960
- }
961
- } catch (e) {
962
- }
963
- }
964
-
965
- // src/env.ts
966
- var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", ""]);
967
- function isLocalhost(hostname) {
968
- const h = (hostname != null ? hostname : "").trim().toLowerCase();
969
- if (LOCAL_HOSTS.has(h)) return true;
970
- return h.endsWith(".local") || h.endsWith(".localhost");
971
- }
972
-
973
- // src/index.ts
974
- var DEFAULT_ENDPOINT = "https://www.sightspool.com";
975
- var FRICTION_EMIT_COOLDOWN_MS = 3e4;
976
- var EVENT_DEBOUNCE_MS = 600;
977
- var SERVE_SETTLE_MS = 2500;
978
- var MAX_INTERVENTIONS_PER_SESSION = 1;
979
- function currentHostname() {
980
- try {
981
- return typeof location !== "undefined" ? location.hostname : "";
138
+ };
139
+ document.addEventListener("visibilitychange", r.visibility);
140
+ r.timer = window.setInterval(() => {
141
+ void check(r);
142
+ }, 15e3);
143
+ if (eligible(r)) void check(r);
982
144
  } catch (e) {
983
- return "";
984
145
  }
985
146
  }
986
- var ctrl = null;
987
- function uuid() {
147
+ function identify(userId) {
988
148
  try {
989
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
990
- return crypto.randomUUID();
149
+ const r = current();
150
+ if (!r) return;
151
+ const identified = typeof userId === "string" && userId.trim().length > 0;
152
+ if (!identified && !r.identified) {
153
+ if (!r.paused && eligible(r)) void check(r);
154
+ return;
991
155
  }
156
+ invalidate(r);
157
+ r.identified = identified;
158
+ status(r, r.paused ? "paused" : eligible(r) ? "unavailable" : "signed_out");
159
+ if (eligible(r) && !r.paused) void check(r);
992
160
  } catch (e) {
993
161
  }
994
- return "s-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
995
162
  }
996
- function deriveCandidates(c) {
997
- const out = [];
998
- const hint = c.harvest.lastIntentHint();
999
- if (hint) out.push(`Searching for \u201C${hint}\u201D`);
1000
- const label = c.capture.label();
1001
- if (label) out.push(`Something on \u201C${label.slice(0, 40)}\u201D`);
1002
- return out;
1003
- }
1004
- function buildSignal(c, trigger, extra = {}) {
1005
- const err = c.capture.error();
1006
- return {
1007
- captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1008
- route: c.capture.route(),
1009
- client_label: c.capture.label(),
1010
- trail: c.capture.trail(),
1011
- session_ref: c.sessionRef,
1012
- trigger,
1013
- intent_hint: c.harvest.lastIntentHint(),
1014
- error: err,
1015
- outcome: err || trigger === "friction" ? "failed" : "unknown",
1016
- ...extra
1017
- };
1018
- }
1019
- async function maybePrompt(c, trigger) {
1020
- var _a2;
1021
- const now = Date.now();
1022
- if (canPrompt(c.fatigue, now, DEFAULT_CAPS)) {
1023
- c.fatigue.promptsShown += 1;
1024
- c.fatigue.lastPromptAt = now;
1025
- try {
1026
- const { showPrompt: showPrompt2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
1027
- const result = await showPrompt2({ candidates: deriveCandidates(c) });
1028
- c.egress.enqueue(
1029
- buildSignal(c, trigger, {
1030
- answer: result.answer,
1031
- verbatim: result.verbatim,
1032
- intent_hint: (_a2 = result.intent) != null ? _a2 : c.harvest.lastIntentHint(),
1033
- outcome: result.answer === "yes" ? "achieved" : "failed"
1034
- })
1035
- );
1036
- } catch (e) {
1037
- c.egress.enqueue(buildSignal(c, trigger));
1038
- }
1039
- } else {
1040
- c.egress.enqueue(buildSignal(c, trigger));
1041
- }
1042
- }
1043
- async function maybeServeIntervention(c) {
1044
- if (!c.running || !c.interventionsEnabled || c.suppressed) return;
1045
- if (c.surveyOnScreen) return;
1046
- if (c.interventionsShown >= MAX_INTERVENTIONS_PER_SESSION) return;
1047
- const now = Date.now();
1048
- if (c.fatigue.lastPromptAt !== null && now - c.fatigue.lastPromptAt < DEFAULT_CAPS.cooldownMs)
1049
- return;
1050
- const respondentKey = getRespondentKey(c.identity, c.sessionRef);
1051
- const served = await fetchIntervention({
1052
- endpoint: c.config.endpoint,
1053
- key: c.config.key,
1054
- ctx: buildServeContext(c.capture.route(), c.identity),
1055
- respondentKey,
1056
- debug: c.config.debug
1057
- });
1058
- if (!served || !c.running) return;
1059
- const prevShown = c.interventionsShown;
1060
- const prevPromptAt = c.fatigue.lastPromptAt;
1061
- c.surveyOnScreen = true;
1062
- c.interventionsShown += 1;
1063
- c.fatigue.lastPromptAt = Date.now();
163
+ function pause() {
1064
164
  try {
1065
- if (served.type === "demand_probe") {
1066
- const { showProbe: showProbe2 } = await Promise.resolve().then(() => (init_probe(), probe_exports));
1067
- const result = await showProbe2(served.config);
1068
- if (!result.mounted) {
1069
- c.interventionsShown = prevShown;
1070
- c.fatigue.lastPromptAt = prevPromptAt;
1071
- } else if (result.voted) {
1072
- submitResponse({
1073
- endpoint: c.config.endpoint,
1074
- key: c.config.key,
1075
- interventionId: served.id,
1076
- response: { choice: "vote" },
1077
- respondentKey
1078
- });
1079
- }
1080
- } else {
1081
- const { showSurvey: showSurvey2 } = await Promise.resolve().then(() => (init_survey(), survey_exports));
1082
- const result = await showSurvey2(served.config);
1083
- if (!result.dismissed && (result.choice || result.text)) {
1084
- submitResponse({
1085
- endpoint: c.config.endpoint,
1086
- key: c.config.key,
1087
- interventionId: served.id,
1088
- response: { choice: result.choice, text: result.text },
1089
- respondentKey
1090
- });
1091
- }
165
+ const r = current();
166
+ if (r) {
167
+ r.paused = true;
168
+ invalidate(r);
169
+ status(r, "paused");
1092
170
  }
1093
171
  } catch (e) {
1094
- } finally {
1095
- c.surveyOnScreen = false;
1096
172
  }
1097
173
  }
1098
- function onCaptureEvent() {
1099
- const c = ctrl;
1100
- if (!c || !c.running) return;
174
+ function resume() {
1101
175
  try {
1102
- const route = c.capture.route();
1103
- if (route && route !== c.lastServeRoute) {
1104
- c.lastServeRoute = route;
1105
- void maybeServeIntervention(c);
176
+ const r = current();
177
+ if (r) {
178
+ r.paused = false;
179
+ if (!eligible(r)) status(r, "signed_out");
180
+ void check(r);
1106
181
  }
1107
182
  } catch (e) {
1108
183
  }
1109
- if (c.eventTimer !== null) return;
1110
- c.eventTimer = (typeof window !== "undefined" ? window.setTimeout : setTimeout)(() => {
1111
- c.eventTimer = null;
1112
- try {
1113
- const now = Date.now();
1114
- if (now - c.lastFrictionEmitAt < FRICTION_EMIT_COOLDOWN_MS) return;
1115
- const kind = detectFriction(c.capture.trail(), now);
1116
- if (!kind) return;
1117
- c.lastFrictionEmitAt = now;
1118
- void maybePrompt(c, "friction");
1119
- } catch (e) {
1120
- }
1121
- }, EVENT_DEBOUNCE_MS);
1122
184
  }
1123
- function onExitIntent(ev) {
1124
- const c = ctrl;
1125
- if (!c || !c.running || !c.config.boundaryAsk) return;
185
+ function destroy() {
1126
186
  try {
1127
- if (ev.clientY <= 0) void maybePrompt(c, "boundary");
187
+ const r = current();
188
+ if (!r) return;
189
+ r.disposed = true;
190
+ invalidate(r);
191
+ window.clearInterval(r.timer);
192
+ document.removeEventListener("visibilitychange", r.visibility);
193
+ delete host()[slot];
1128
194
  } catch (e) {
1129
195
  }
1130
196
  }
1131
- function startRuntime(c) {
1132
- if (c.running) return;
1133
- if (c.suppressed) {
1134
- if (c.config.debug)
1135
- console.debug(
1136
- "[sightspool] capture suppressed on localhost (set captureOnLocalhost:true to override)"
1137
- );
1138
- return;
1139
- }
1140
- c.running = true;
1141
- c.capture.start();
1142
- c.harvest.start();
1143
- c.egress.start();
1144
- c.capture.onEvent(onCaptureEvent);
197
+ function getStatus() {
198
+ var _a, _b;
1145
199
  try {
1146
- document.addEventListener("mouseout", onExitIntent);
200
+ return (_b = (_a = current()) == null ? void 0 : _a.status) != null ? _b : "not_initialized";
1147
201
  } catch (e) {
202
+ return "error";
1148
203
  }
1149
- if (c.interventionsEnabled) {
1150
- c.lastServeRoute = (() => {
1151
- try {
1152
- return c.capture.route();
1153
- } catch (e) {
1154
- return null;
1155
- }
1156
- })();
1157
- (typeof window !== "undefined" ? window.setTimeout : setTimeout)(() => {
1158
- void maybeServeIntervention(c);
1159
- }, SERVE_SETTLE_MS);
1160
- }
1161
- if (c.config.debug) console.debug("[sightspool] started", c.sessionRef);
1162
- }
1163
- function init(config) {
1164
- try {
1165
- if (!config || !config.key) {
1166
- console.warn("[sightspool] init: a `key` is required");
1167
- return;
1168
- }
1169
- if (ctrl) return;
1170
- const endpoint = config.endpoint || DEFAULT_ENDPOINT;
1171
- const privacyOpts = { redact: config.redact, block: config.block };
1172
- const capture = createCapture({ debug: !!config.debug, ...privacyOpts });
1173
- const harvest = createHarvest(capture, { debug: !!config.debug, ...privacyOpts });
1174
- const egress = createEgress({ key: config.key, endpoint, debug: !!config.debug });
1175
- ctrl = {
1176
- config: {
1177
- ...config,
1178
- key: config.key,
1179
- endpoint,
1180
- boundaryAsk: config.boundaryAsk !== false,
1181
- debug: !!config.debug
1182
- },
1183
- capture,
1184
- harvest,
1185
- egress,
1186
- identity: {},
1187
- sessionRef: uuid(),
1188
- fatigue: { promptsShown: 0, lastPromptAt: null },
1189
- lastFrictionEmitAt: 0,
1190
- eventTimer: null,
1191
- running: false,
1192
- suppressed: isLocalhost(currentHostname()) && config.captureOnLocalhost !== true,
1193
- interventionsEnabled: config.interventions !== false,
1194
- lastServeRoute: null,
1195
- surveyOnScreen: false,
1196
- interventionsShown: 0
1197
- };
1198
- if (config.consent !== false) startRuntime(ctrl);
1199
- } catch (err) {
1200
- try {
1201
- console.warn("[sightspool] init failed", err);
1202
- } catch (e) {
1203
- }
1204
- }
1205
- }
1206
- function identify(userId, traits) {
1207
- try {
1208
- if (!ctrl) return;
1209
- ctrl.identity = { userId, account: traits == null ? void 0 : traits.account, plan: traits == null ? void 0 : traits.plan };
1210
- ctrl.egress.setIdentity(ctrl.identity);
1211
- } catch (e) {
1212
- }
1213
- }
1214
- function start() {
1215
- try {
1216
- if (ctrl) startRuntime(ctrl);
1217
- } catch (e) {
1218
- }
1219
- }
1220
- function consent(granted) {
1221
- try {
1222
- if (!ctrl) return;
1223
- if (granted) startRuntime(ctrl);
1224
- else stop();
1225
- } catch (e) {
1226
- }
1227
- }
1228
- function stop() {
1229
- try {
1230
- if (!ctrl) return;
1231
- ctrl.running = false;
1232
- ctrl.capture.stop();
1233
- ctrl.harvest.stop();
1234
- ctrl.egress.stop();
1235
- try {
1236
- document.removeEventListener("mouseout", onExitIntent);
1237
- } catch (e) {
1238
- }
1239
- } catch (e) {
1240
- }
1241
- }
1242
- var api = { init, identify, start, stop, consent };
1243
- var src_default = api;
1244
- var _a;
1245
- try {
1246
- if (typeof document !== "undefined") {
1247
- const el = (_a = document.currentScript) != null ? _a : document.querySelector("script[data-sightspool-key]");
1248
- const key = el == null ? void 0 : el.getAttribute("data-sightspool-key");
1249
- if (key) {
1250
- let endpoint = (el == null ? void 0 : el.getAttribute("data-sightspool-endpoint")) || void 0;
1251
- if (!endpoint && (el == null ? void 0 : el.src)) {
1252
- try {
1253
- endpoint = new URL(el.src).origin;
1254
- } catch (e) {
1255
- }
1256
- }
1257
- const list = (attr) => {
1258
- const raw = el == null ? void 0 : el.getAttribute(attr);
1259
- if (!raw) return void 0;
1260
- const out = raw.split(",").map((s) => s.trim()).filter(Boolean);
1261
- return out.length ? out : void 0;
1262
- };
1263
- init({
1264
- key,
1265
- endpoint,
1266
- redact: list("data-sightspool-redact"),
1267
- block: list("data-sightspool-block"),
1268
- captureOnLocalhost: (el == null ? void 0 : el.hasAttribute("data-sightspool-capture-localhost")) || void 0,
1269
- interventions: (el == null ? void 0 : el.hasAttribute("data-sightspool-no-interventions")) ? false : void 0,
1270
- debug: (el == null ? void 0 : el.hasAttribute("data-sightspool-debug")) || void 0
1271
- });
1272
- }
1273
- }
1274
- } catch (e) {
1275
204
  }
1276
205
 
1277
- exports.consent = consent;
206
+ // src/index.ts
207
+ var src_default = { init, identify, pause, resume, destroy, getStatus };
208
+
1278
209
  exports.default = src_default;
210
+ exports.destroy = destroy;
211
+ exports.getStatus = getStatus;
1279
212
  exports.identify = identify;
1280
213
  exports.init = init;
1281
- exports.start = start;
1282
- exports.stop = stop;
214
+ exports.pause = pause;
215
+ exports.resume = resume;
1283
216
  //# sourceMappingURL=index.cjs.map
1284
217
  //# sourceMappingURL=index.cjs.map