@sightspool/sdk 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,8 +14,8 @@ them).
14
14
  > **not** act on your surface (surveys/nudges/experiments are Wave 0005, and every one
15
15
  > is human-gated). Trigger sensitivity and intent inference calibrate with live traffic.
16
16
 
17
- **Docs:** [full reference + CSP & framework guides](https://sightspool.github.io/sdk/) ·
18
- [llms.txt](https://sightspool.github.io/sdk/llms.txt) (the machine-readable install/CSP/config
17
+ **Docs:** [full reference + CSP & framework guides](https://sdk.sightspool.com/) ·
18
+ [llms.txt](https://sdk.sightspool.com/llms.txt) (the machine-readable install/CSP/config
19
19
  doc for agents) · [npm](https://www.npmjs.com/package/@sightspool/sdk)
20
20
 
21
21
  ---
@@ -47,7 +47,7 @@ call for their analytics/support tools.
47
47
  ```html
48
48
  <script
49
49
  async
50
- src="https://app.sightspool.com/sdk.global.js"
50
+ src="https://www.sightspool.com/sdk.global.js"
51
51
  data-sightspool-key="pk_live_…"
52
52
  ></script>
53
53
  ```
@@ -70,7 +70,7 @@ The script tag also reads these optional attributes (the no-build equivalent of
70
70
  ```html
71
71
  <script
72
72
  async
73
- src="https://app.sightspool.com/sdk.global.js"
73
+ src="https://www.sightspool.com/sdk.global.js"
74
74
  data-sightspool-key="pk_live_…"
75
75
  data-sightspool-block=".billing-panel, [data-private]"
76
76
  data-sightspool-redact=".customer-name"
@@ -88,8 +88,9 @@ The script tag also reads these optional attributes (the no-build equivalent of
88
88
  | option | type | default | purpose |
89
89
  |---|---|---|---|
90
90
  | `key` | `string` | — | **required.** Your publishable key (`pk_test_…` / `pk_live_…`), from the Connections → In-product SDK card. Publishable — safe to ship in client JS. See [Keys & environments](#keys--environments). |
91
- | `endpoint` | `string` | the bundle's origin (script tag) / `https://app.sightspool.com` (npm) | Ingest base URL. The `<script>` install auto-resolves it to wherever `sdk.global.js` was served from (your app), so the key alone is enough; override for a CDN-hosted bundle or dev. |
91
+ | `endpoint` | `string` | the bundle's origin (script tag) / `https://www.sightspool.com` (npm) | Ingest base URL. The `<script>` install auto-resolves it to wherever `sdk.global.js` was served from (your app), so the key alone is enough; override for a CDN-hosted bundle or dev. |
92
92
  | `boundaryAsk` | `boolean` | `true` | Show the one-tap "did you do what you came to do?" ask at session boundaries. |
93
+ | `interventions` | `boolean` | `true` | Show human-approved **surveys** served by your Sightspool workspace (see [Interventions](#interventions-surveys)). Set `false` to capture only. |
93
94
  | `consent` | `boolean` | `true` | Start capturing immediately. Set `false` to stay paused until you call `Sightspool.consent(true)` (or `start()`) after obtaining consent. |
94
95
  | `redact` | `string[]` | `[]` | CSS selectors whose captured text is **masked** (replaced with `‹redacted›`) before anything leaves the page. The event is still recorded — only its label is masked. |
95
96
  | `block` | `string[]` | `[]` | CSS selectors whose events are **dropped entirely** (the hard opt-out). Equivalent to putting `data-sightspool-ignore` on the element. |
@@ -163,7 +164,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
163
164
  <body>
164
165
  {children}
165
166
  <Script
166
- src="https://app.sightspool.com/sdk.global.js"
167
+ src="https://www.sightspool.com/sdk.global.js"
167
168
  data-sightspool-key={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY}
168
169
  strategy="afterInteractive"
169
170
  />
@@ -186,29 +187,44 @@ useEffect(() => {
186
187
  > in the environment Next builds in. `strategy="afterInteractive"` keeps it off the critical
187
188
  > path.
188
189
 
189
- ### React (bundler install)
190
+ ### React / Next.js — `@sightspool/react`
190
191
 
191
- Install the package and call `init` once at app start (a top-level effect or your entry
192
- module):
192
+ For React apps, use the official bindings a declarative provider over the core SDK:
193
+
194
+ ```bash
195
+ npm install @sightspool/react @sightspool/sdk react
196
+ ```
197
+
198
+ ```tsx
199
+ import { SightspoolProvider } from '@sightspool/react'
200
+
201
+ <SightspoolProvider
202
+ apiKey={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY!}
203
+ identity={user && { userId: user.id, account: user.account, plan: user.plan }}
204
+ >
205
+ <App />
206
+ </SightspoolProvider>
207
+ ```
208
+
209
+ It ships a `"use client"` banner (drops straight into a Next.js App Router **server**
210
+ layout), re-fires `identify` whenever `identity` changes, and takes a reactive `consent`
211
+ prop for cookie banners. Full API:
212
+ [@sightspool/react](https://www.npmjs.com/package/@sightspool/react) ·
213
+ [packages/react](https://github.com/sightspool/sdk/tree/main/packages/react).
214
+
215
+ <details><summary>Or wire the core SDK by hand (no wrapper)</summary>
193
216
 
194
217
  ```tsx
195
218
  import { useEffect } from 'react'
196
219
  import Sightspool from '@sightspool/sdk'
197
220
 
198
221
  export function SightspoolBoot({ userId, account, plan }) {
199
- useEffect(() => {
200
- Sightspool.init({ key: import.meta.env.VITE_SIGHTSPOOL_KEY })
201
- }, [])
202
- useEffect(() => {
203
- if (userId) Sightspool.identify(userId, { account, plan })
204
- }, [userId, account, plan])
222
+ useEffect(() => { Sightspool.init({ key: import.meta.env.VITE_SIGHTSPOOL_KEY }) }, [])
223
+ useEffect(() => { if (userId) Sightspool.identify(userId, { account, plan }) }, [userId, account, plan])
205
224
  return null
206
225
  }
207
226
  ```
208
-
209
- > A first-class **`@sightspool/react`** wrapper (a `<SightspoolProvider>` + a `useSightspool`
210
- > hook over `init`/`identify`) is planned —
211
- > [issue #3](https://github.com/sightspool/sdk/issues/3).
227
+ </details>
212
228
 
213
229
  ---
214
230
 
@@ -221,15 +237,15 @@ from the app it ingests to), so it's one host in two directives:
221
237
  **Script-tag install**
222
238
 
223
239
  ```
224
- script-src https://app.sightspool.com;
225
- connect-src https://app.sightspool.com;
240
+ script-src https://www.sightspool.com;
241
+ connect-src https://www.sightspool.com;
226
242
  ```
227
243
 
228
244
  **npm / bundler install** — the SDK is bundled into your own first-party JS, so no
229
245
  `script-src` host is needed; only the ingest origin:
230
246
 
231
247
  ```
232
- connect-src https://app.sightspool.com;
248
+ connect-src https://www.sightspool.com;
233
249
  ```
234
250
 
235
251
  If you pass a custom `endpoint`, use *that* origin in `connect-src`. The beacon goes via
@@ -245,6 +261,44 @@ If you pass a custom `endpoint`, use *that* origin in `connect-src`. The beacon
245
261
 
246
262
  ---
247
263
 
264
+ ## Interventions (surveys)
265
+
266
+ The SDK is two-way. Besides *capturing*, it can show a **human-approved survey** at the
267
+ moment of friction — the "ask" side of Sightspool. You don't author these in code: your
268
+ team proposes a survey off a proven finding in the app, **a human approves it**, sets who
269
+ sees it (route / account / plan), and the SDK serves it. Nothing reaches a user without
270
+ that approval ("no proof **and** no approval, no act").
271
+
272
+ It's on by default — the same one or two install lines that capture also serve. When the
273
+ SDK loads (and on each route change) it asks your workspace *"anything to show this user
274
+ here?"*; if there's a matching approved survey, it renders a small card in a shadow root
275
+ (your CSS can't reach it; it leaks no styles), and posts the answer back.
276
+
277
+ ```js
278
+ Sightspool.init({ key: "pk_live_…" }); // serving is on by default
279
+ Sightspool.init({ key: "pk_live_…", interventions: false }); // capture only
280
+ ```
281
+
282
+ ```html
283
+ <!-- script-tag install: opt out with one attribute -->
284
+ <script src="https://www.sightspool.com/sdk.global.js"
285
+ data-sightspool-key="pk_live_…"
286
+ data-sightspool-no-interventions></script>
287
+ ```
288
+
289
+ **What shows, and how often** — all server-gated, so you stay in control:
290
+
291
+ - **Targeting** — only to the route / account / plan the approver chose.
292
+ - **De-dup** — a survey a user has answered never reappears (a per-user key is kept
293
+ locally; it's anonymous unless you've called `identify()`).
294
+ - **Won't pester** — at most one survey on screen, one per session, and it shares a
295
+ cooldown with the boundary ask so the two never stack back-to-back. The run also stops
296
+ itself once it hits the approver's response target.
297
+
298
+ The survey widget is **lazily loaded** — it splits into its own chunk and adds nothing to
299
+ your bundle until a survey is actually served. Today the SDK renders one-tap / short-text
300
+ surveys; richer types (incl. voice micro-research) render as they ship.
301
+
248
302
  ## What it captures
249
303
 
250
304
  - **Passively, no wiring** — route/screen sequence, clicks, **dead-clicks** and
package/dist/index.cjs CHANGED
@@ -199,6 +199,157 @@ input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; pad
199
199
  }
200
200
  });
201
201
 
202
+ // src/survey.ts
203
+ var survey_exports = {};
204
+ __export(survey_exports, {
205
+ showSurvey: () => showSurvey
206
+ });
207
+ function showSurvey(config) {
208
+ return new Promise((resolve) => {
209
+ if (typeof document === "undefined") {
210
+ resolve({ dismissed: true });
211
+ return;
212
+ }
213
+ let settled = false;
214
+ const host = document.createElement("div");
215
+ const shadow = host.attachShadow({ mode: "open" });
216
+ function teardown() {
217
+ try {
218
+ host.remove();
219
+ } catch (e) {
220
+ }
221
+ }
222
+ function finish(result, thank) {
223
+ if (settled) return;
224
+ settled = true;
225
+ resolve(result);
226
+ if (thank) {
227
+ thankYou();
228
+ setTimeout(teardown, THANKS_MS);
229
+ } else {
230
+ teardown();
231
+ }
232
+ }
233
+ const style = document.createElement("style");
234
+ style.textContent = STYLE2;
235
+ shadow.appendChild(style);
236
+ const wrap = document.createElement("div");
237
+ wrap.className = "wrap";
238
+ shadow.appendChild(wrap);
239
+ function card() {
240
+ wrap.innerHTML = "";
241
+ const c = document.createElement("div");
242
+ c.className = "card";
243
+ wrap.appendChild(c);
244
+ return c;
245
+ }
246
+ function withClose(c) {
247
+ const x = document.createElement("button");
248
+ x.className = "x";
249
+ x.textContent = "\xD7";
250
+ x.setAttribute("aria-label", "Dismiss");
251
+ x.onclick = () => finish({ dismissed: true }, false);
252
+ c.appendChild(x);
253
+ }
254
+ function question() {
255
+ var _a2, _b, _c;
256
+ const c = card();
257
+ withClose(c);
258
+ const q = document.createElement("p");
259
+ q.className = "q";
260
+ q.textContent = config.question;
261
+ c.appendChild(q);
262
+ for (const opt of ((_a2 = config.options) != null ? _a2 : []).slice(0, 6)) {
263
+ const b = document.createElement("button");
264
+ b.className = "opt";
265
+ b.textContent = opt;
266
+ b.onclick = () => finish({ choice: opt }, true);
267
+ c.appendChild(b);
268
+ }
269
+ if (config.allow_text) {
270
+ const se = document.createElement("button");
271
+ se.className = "opt";
272
+ se.textContent = ((_c = (_b = config.options) == null ? void 0 : _b.length) != null ? _c : 0) > 0 ? "Something else\u2026" : "Answer\u2026";
273
+ se.onclick = freeText;
274
+ c.appendChild(se);
275
+ }
276
+ }
277
+ function freeText() {
278
+ const c = card();
279
+ withClose(c);
280
+ const q = document.createElement("p");
281
+ q.className = "q";
282
+ q.textContent = config.question;
283
+ c.appendChild(q);
284
+ const input = document.createElement("input");
285
+ input.type = "text";
286
+ input.placeholder = "In your own words\u2026";
287
+ c.appendChild(input);
288
+ const row = document.createElement("div");
289
+ row.className = "row";
290
+ const send = document.createElement("button");
291
+ send.className = "primary";
292
+ send.textContent = "Send";
293
+ const submit = () => {
294
+ const text = input.value.trim();
295
+ if (!text) {
296
+ finish({ dismissed: true }, false);
297
+ return;
298
+ }
299
+ finish({ text }, true);
300
+ };
301
+ send.onclick = submit;
302
+ input.onkeydown = (e) => {
303
+ if (e.key === "Enter") submit();
304
+ };
305
+ row.appendChild(send);
306
+ c.appendChild(row);
307
+ try {
308
+ input.focus();
309
+ } catch (e) {
310
+ }
311
+ }
312
+ function thankYou() {
313
+ const c = card();
314
+ const p = document.createElement("p");
315
+ p.className = "done";
316
+ p.textContent = "Thanks \u2014 that helps.";
317
+ c.appendChild(p);
318
+ }
319
+ try {
320
+ document.body.appendChild(host);
321
+ question();
322
+ } catch (e) {
323
+ finish({ dismissed: true }, false);
324
+ }
325
+ });
326
+ }
327
+ var STYLE2, THANKS_MS;
328
+ var init_survey = __esm({
329
+ "src/survey.ts"() {
330
+ STYLE2 = `
331
+ :host { all: initial; }
332
+ .wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
333
+ width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
334
+ .card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
335
+ border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
336
+ .q { font-size: 14px; font-weight: 600; margin: 0 0 12px; line-height: 1.35; padding-right: 16px; }
337
+ .row { display: flex; gap: 8px; flex-wrap: wrap; }
338
+ button { font: inherit; font-size: 13px; cursor: pointer; border-radius: 8px; padding: 8px 12px;
339
+ border: 1px solid #e4e4e7; background: #fafafa; color: #18181b; }
340
+ button:hover { background: #f4f4f5; }
341
+ button.primary { background: #18181b; color: #fff; border-color: #18181b; }
342
+ .opt { display: block; width: 100%; text-align: left; margin-bottom: 6px; }
343
+ .x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
344
+ color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
345
+ .done { font-size: 13px; color: #18181b; margin: 0; }
346
+ input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; padding: 8px 10px;
347
+ border: 1px solid #e4e4e7; border-radius: 8px; margin-bottom: 8px; }
348
+ `;
349
+ THANKS_MS = 1100;
350
+ }
351
+ });
352
+
202
353
  // src/capture.ts
203
354
  init_privacy();
204
355
  var TRAIL_MAX = 30;
@@ -573,6 +724,117 @@ function canPrompt(state, now, caps = DEFAULT_CAPS) {
573
724
  return true;
574
725
  }
575
726
 
727
+ // src/intervention.ts
728
+ var RENDERABLE = /* @__PURE__ */ new Set(["survey"]);
729
+ function str(v, max) {
730
+ if (typeof v !== "string") return void 0;
731
+ const t = v.trim();
732
+ return t ? t.slice(0, max) : void 0;
733
+ }
734
+ function buildServeContext(route, identity) {
735
+ const ctx = {};
736
+ const r = str(route, 500);
737
+ if (r) ctx.route = r;
738
+ const account = str(identity == null ? void 0 : identity.account, 200);
739
+ if (account) ctx.account = account;
740
+ const plan = str(identity == null ? void 0 : identity.plan, 80);
741
+ if (plan) ctx.plan = plan;
742
+ return ctx;
743
+ }
744
+ function normalizeServed(raw) {
745
+ if (!raw || typeof raw !== "object") return null;
746
+ const r = raw;
747
+ const id = str(r.id, 64);
748
+ const type = str(r.type, 40);
749
+ if (!id || !type || !RENDERABLE.has(type)) return null;
750
+ const cfg = r.config && typeof r.config === "object" ? r.config : {};
751
+ const question = str(cfg.question, 500);
752
+ if (!question) return null;
753
+ const options = Array.isArray(cfg.options) ? cfg.options.map((o) => str(o, 200)).filter((o) => !!o).slice(0, 6) : void 0;
754
+ const config = {
755
+ question,
756
+ ...options && options.length ? { options } : {},
757
+ allow_text: cfg.allow_text === true
758
+ };
759
+ return { id, type, config };
760
+ }
761
+ function pickRespondentKey(userId, persisted, sessionRef) {
762
+ const uid = str(userId, 200);
763
+ if (uid) return uid;
764
+ const p = str(persisted, 200);
765
+ if (p) return p;
766
+ return sessionRef;
767
+ }
768
+ var RK_STORAGE_KEY = "sightspool_rk";
769
+ function getRespondentKey(identity, sessionRef) {
770
+ const uid = str(identity == null ? void 0 : identity.userId, 200);
771
+ if (uid) return uid;
772
+ try {
773
+ let v = localStorage.getItem(RK_STORAGE_KEY);
774
+ if (!v) {
775
+ v = "anon-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
776
+ localStorage.setItem(RK_STORAGE_KEY, v);
777
+ }
778
+ return pickRespondentKey(void 0, v, sessionRef);
779
+ } catch (e) {
780
+ return sessionRef;
781
+ }
782
+ }
783
+ function serveUrl(endpoint, path) {
784
+ return `${endpoint.replace(/\/+$/, "")}/api/sdk/${path}`;
785
+ }
786
+ async function fetchIntervention(opts) {
787
+ var _a2;
788
+ try {
789
+ if (typeof fetch !== "function") return null;
790
+ const res = await fetch(serveUrl(opts.endpoint, "serve"), {
791
+ method: "POST",
792
+ headers: { "Content-Type": "text/plain" },
793
+ body: JSON.stringify({
794
+ key: opts.key,
795
+ context: opts.ctx,
796
+ respondent_key: opts.respondentKey
797
+ }),
798
+ mode: "cors",
799
+ credentials: "omit"
800
+ });
801
+ if (!res.ok) return null;
802
+ const data = await res.json();
803
+ const served = normalizeServed(data == null ? void 0 : data.intervention);
804
+ if (opts.debug) console.debug("[sightspool] serve", (_a2 = served == null ? void 0 : served.id) != null ? _a2 : "(none)");
805
+ return served;
806
+ } catch (e) {
807
+ return null;
808
+ }
809
+ }
810
+ function submitResponse(opts) {
811
+ try {
812
+ const url = serveUrl(opts.endpoint, "respond");
813
+ const json = JSON.stringify({
814
+ key: opts.key,
815
+ intervention_id: opts.interventionId,
816
+ response: opts.response,
817
+ respondent_key: opts.respondentKey
818
+ });
819
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
820
+ const blob = new Blob([json], { type: "text/plain" });
821
+ if (navigator.sendBeacon(url, blob)) return;
822
+ }
823
+ if (typeof fetch === "function") {
824
+ void fetch(url, {
825
+ method: "POST",
826
+ headers: { "Content-Type": "text/plain" },
827
+ body: json,
828
+ keepalive: true,
829
+ mode: "cors",
830
+ credentials: "omit"
831
+ }).catch(() => {
832
+ });
833
+ }
834
+ } catch (e) {
835
+ }
836
+ }
837
+
576
838
  // src/env.ts
577
839
  var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", ""]);
578
840
  function isLocalhost(hostname) {
@@ -582,9 +844,11 @@ function isLocalhost(hostname) {
582
844
  }
583
845
 
584
846
  // src/index.ts
585
- var DEFAULT_ENDPOINT = "https://app.sightspool.com";
847
+ var DEFAULT_ENDPOINT = "https://www.sightspool.com";
586
848
  var FRICTION_EMIT_COOLDOWN_MS = 3e4;
587
849
  var EVENT_DEBOUNCE_MS = 600;
850
+ var SERVE_SETTLE_MS = 2500;
851
+ var MAX_INTERVENTIONS_PER_SESSION = 1;
588
852
  function currentHostname() {
589
853
  try {
590
854
  return typeof location !== "undefined" ? location.hostname : "";
@@ -649,9 +913,53 @@ async function maybePrompt(c, trigger) {
649
913
  c.egress.enqueue(buildSignal(c, trigger));
650
914
  }
651
915
  }
916
+ async function maybeServeIntervention(c) {
917
+ if (!c.running || !c.interventionsEnabled || c.suppressed) return;
918
+ if (c.surveyOnScreen) return;
919
+ if (c.interventionsShown >= MAX_INTERVENTIONS_PER_SESSION) return;
920
+ const now = Date.now();
921
+ if (c.fatigue.lastPromptAt !== null && now - c.fatigue.lastPromptAt < DEFAULT_CAPS.cooldownMs)
922
+ return;
923
+ const respondentKey = getRespondentKey(c.identity, c.sessionRef);
924
+ const served = await fetchIntervention({
925
+ endpoint: c.config.endpoint,
926
+ key: c.config.key,
927
+ ctx: buildServeContext(c.capture.route(), c.identity),
928
+ respondentKey,
929
+ debug: c.config.debug
930
+ });
931
+ if (!served || !c.running) return;
932
+ c.surveyOnScreen = true;
933
+ c.interventionsShown += 1;
934
+ c.fatigue.lastPromptAt = Date.now();
935
+ try {
936
+ const { showSurvey: showSurvey2 } = await Promise.resolve().then(() => (init_survey(), survey_exports));
937
+ const result = await showSurvey2(served.config);
938
+ if (!result.dismissed && (result.choice || result.text)) {
939
+ submitResponse({
940
+ endpoint: c.config.endpoint,
941
+ key: c.config.key,
942
+ interventionId: served.id,
943
+ response: { choice: result.choice, text: result.text },
944
+ respondentKey
945
+ });
946
+ }
947
+ } catch (e) {
948
+ } finally {
949
+ c.surveyOnScreen = false;
950
+ }
951
+ }
652
952
  function onCaptureEvent() {
653
953
  const c = ctrl;
654
954
  if (!c || !c.running) return;
955
+ try {
956
+ const route = c.capture.route();
957
+ if (route && route !== c.lastServeRoute) {
958
+ c.lastServeRoute = route;
959
+ void maybeServeIntervention(c);
960
+ }
961
+ } catch (e) {
962
+ }
655
963
  if (c.eventTimer !== null) return;
656
964
  c.eventTimer = (typeof window !== "undefined" ? window.setTimeout : setTimeout)(() => {
657
965
  c.eventTimer = null;
@@ -692,6 +1000,18 @@ function startRuntime(c) {
692
1000
  document.addEventListener("mouseout", onExitIntent);
693
1001
  } catch (e) {
694
1002
  }
1003
+ if (c.interventionsEnabled) {
1004
+ c.lastServeRoute = (() => {
1005
+ try {
1006
+ return c.capture.route();
1007
+ } catch (e) {
1008
+ return null;
1009
+ }
1010
+ })();
1011
+ (typeof window !== "undefined" ? window.setTimeout : setTimeout)(() => {
1012
+ void maybeServeIntervention(c);
1013
+ }, SERVE_SETTLE_MS);
1014
+ }
695
1015
  if (c.config.debug) console.debug("[sightspool] started", c.sessionRef);
696
1016
  }
697
1017
  function init(config) {
@@ -723,7 +1043,11 @@ function init(config) {
723
1043
  lastFrictionEmitAt: 0,
724
1044
  eventTimer: null,
725
1045
  running: false,
726
- suppressed: isLocalhost(currentHostname()) && config.captureOnLocalhost !== true
1046
+ suppressed: isLocalhost(currentHostname()) && config.captureOnLocalhost !== true,
1047
+ interventionsEnabled: config.interventions !== false,
1048
+ lastServeRoute: null,
1049
+ surveyOnScreen: false,
1050
+ interventionsShown: 0
727
1051
  };
728
1052
  if (config.consent !== false) startRuntime(ctrl);
729
1053
  } catch (err) {
@@ -796,6 +1120,7 @@ try {
796
1120
  redact: list("data-sightspool-redact"),
797
1121
  block: list("data-sightspool-block"),
798
1122
  captureOnLocalhost: (el == null ? void 0 : el.hasAttribute("data-sightspool-capture-localhost")) || void 0,
1123
+ interventions: (el == null ? void 0 : el.hasAttribute("data-sightspool-no-interventions")) ? false : void 0,
799
1124
  debug: (el == null ? void 0 : el.hasAttribute("data-sightspool-debug")) || void 0
800
1125
  });
801
1126
  }