@sightspool/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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,7 +88,7 @@ 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
93
  | `interventions` | `boolean` | `true` | Show human-approved **surveys** served by your Sightspool workspace (see [Interventions](#interventions-surveys)). Set `false` to capture only. |
94
94
  | `consent` | `boolean` | `true` | Start capturing immediately. Set `false` to stay paused until you call `Sightspool.consent(true)` (or `start()`) after obtaining consent. |
@@ -164,7 +164,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
164
164
  <body>
165
165
  {children}
166
166
  <Script
167
- src="https://app.sightspool.com/sdk.global.js"
167
+ src="https://www.sightspool.com/sdk.global.js"
168
168
  data-sightspool-key={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY}
169
169
  strategy="afterInteractive"
170
170
  />
@@ -237,15 +237,15 @@ from the app it ingests to), so it's one host in two directives:
237
237
  **Script-tag install**
238
238
 
239
239
  ```
240
- script-src https://app.sightspool.com;
241
- connect-src https://app.sightspool.com;
240
+ script-src https://www.sightspool.com;
241
+ connect-src https://www.sightspool.com;
242
242
  ```
243
243
 
244
244
  **npm / bundler install** — the SDK is bundled into your own first-party JS, so no
245
245
  `script-src` host is needed; only the ingest origin:
246
246
 
247
247
  ```
248
- connect-src https://app.sightspool.com;
248
+ connect-src https://www.sightspool.com;
249
249
  ```
250
250
 
251
251
  If you pass a custom `endpoint`, use *that* origin in `connect-src`. The beacon goes via
@@ -281,7 +281,7 @@ Sightspool.init({ key: "pk_live_…", interventions: false }); // capture only
281
281
 
282
282
  ```html
283
283
  <!-- script-tag install: opt out with one attribute -->
284
- <script src="https://app.sightspool.com/sdk.global.js"
284
+ <script src="https://www.sightspool.com/sdk.global.js"
285
285
  data-sightspool-key="pk_live_…"
286
286
  data-sightspool-no-interventions></script>
287
287
  ```
package/dist/index.cjs CHANGED
@@ -199,6 +199,125 @@ input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; pad
199
199
  }
200
200
  });
201
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
+
202
321
  // src/survey.ts
203
322
  var survey_exports = {};
204
323
  __export(survey_exports, {
@@ -725,7 +844,7 @@ function canPrompt(state, now, caps = DEFAULT_CAPS) {
725
844
  }
726
845
 
727
846
  // src/intervention.ts
728
- var RENDERABLE = /* @__PURE__ */ new Set(["survey"]);
847
+ var RENDERABLE = /* @__PURE__ */ new Set(["survey", "demand_probe"]);
729
848
  function str(v, max) {
730
849
  if (typeof v !== "string") return void 0;
731
850
  const t = v.trim();
@@ -748,6 +867,14 @@ function normalizeServed(raw) {
748
867
  const type = str(r.type, 40);
749
868
  if (!id || !type || !RENDERABLE.has(type)) return null;
750
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
+ }
751
878
  const question = str(cfg.question, 500);
752
879
  if (!question) return null;
753
880
  const options = Array.isArray(cfg.options) ? cfg.options.map((o) => str(o, 200)).filter((o) => !!o).slice(0, 6) : void 0;
@@ -756,7 +883,7 @@ function normalizeServed(raw) {
756
883
  ...options && options.length ? { options } : {},
757
884
  allow_text: cfg.allow_text === true
758
885
  };
759
- return { id, type, config };
886
+ return { id, type: "survey", config };
760
887
  }
761
888
  function pickRespondentKey(userId, persisted, sessionRef) {
762
889
  const uid = str(userId, 200);
@@ -844,7 +971,7 @@ function isLocalhost(hostname) {
844
971
  }
845
972
 
846
973
  // src/index.ts
847
- var DEFAULT_ENDPOINT = "https://app.sightspool.com";
974
+ var DEFAULT_ENDPOINT = "https://www.sightspool.com";
848
975
  var FRICTION_EMIT_COOLDOWN_MS = 3e4;
849
976
  var EVENT_DEBOUNCE_MS = 600;
850
977
  var SERVE_SETTLE_MS = 2500;
@@ -929,20 +1056,39 @@ async function maybeServeIntervention(c) {
929
1056
  debug: c.config.debug
930
1057
  });
931
1058
  if (!served || !c.running) return;
1059
+ const prevShown = c.interventionsShown;
1060
+ const prevPromptAt = c.fatigue.lastPromptAt;
932
1061
  c.surveyOnScreen = true;
933
1062
  c.interventionsShown += 1;
934
1063
  c.fatigue.lastPromptAt = Date.now();
935
1064
  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
- });
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
+ }
946
1092
  }
947
1093
  } catch (e) {
948
1094
  } finally {