@sightspool/sdk 0.3.0 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +128 -329
- package/SECURITY.md +16 -0
- package/dist/index.cjs +230 -1225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -41
- package/dist/index.d.ts +20 -41
- package/dist/index.js +228 -757
- package/dist/index.js.map +1 -1
- package/dist/release.json +21 -0
- package/dist/sdk.global.js +2 -59
- package/dist/sdk.global.js.map +1 -1
- package/dist/source.json +11 -0
- package/package.json +14 -12
- package/site/CNAME +1 -0
- package/site/demo.html +6 -0
- package/site/demo.js +36 -0
- package/site/index.html +74 -0
- package/site/release.js +20 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/release.json +21 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js +3 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/sdk.global.js.map +1 -0
- package/site/releases/0.4.0/2acf3463db7571c459ca01a4516341891bb4c2ec6265962f494ca8b553cc0e9f/source.json +11 -0
- package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/release.json +21 -0
- package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/sdk.global.js +3 -0
- package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/sdk.global.js.map +1 -0
- package/site/releases/0.4.0/781ab339e13fe18b86b56cc9d84dd308bebf6482a885c8aae46a8b3b84801f6e/source.json +11 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/release.json +21 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js +3 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/sdk.global.js.map +1 -0
- package/site/releases/0.4.0/c21bee307a5c53e3ebb49d21f0f1ef2cce06a4483fa9824122bd6897ad6ca3ac/source.json +11 -0
- package/site/styles.css +69 -0
- package/site/trust.html +36 -0
- package/dist/chunk-4PADVIIO.js +0 -41
- package/dist/chunk-4PADVIIO.js.map +0 -1
- package/dist/probe-BNGMQQ5C.js +0 -113
- package/dist/probe-BNGMQQ5C.js.map +0 -1
- package/dist/prompt-GDFYC75D.js +0 -139
- package/dist/prompt-GDFYC75D.js.map +0 -1
- package/dist/survey-CICJQ5VI.js +0 -145
- package/dist/survey-CICJQ5VI.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## 0.4.1 — unpublished local candidate
|
|
2
|
+
|
|
3
|
+
- Replace popup interview delivery with one persistent corner panel.
|
|
4
|
+
- Keep open sessions mounted across minimize and recruitment cleanup.
|
|
5
|
+
- Restrict iframe permissions and validate parent message origin/source.
|
|
6
|
+
|
|
7
|
+
# Changelog
|
|
8
|
+
|
|
9
|
+
## 0.4.0 — prepared, unpublished
|
|
10
|
+
|
|
11
|
+
- Replace the public capture entry with research invitation delivery.
|
|
12
|
+
- Require an explicit all_visitors or signed_in audience; no fabricated visitor identity.
|
|
13
|
+
- Retain only local presence of identity, with no user IDs in offer payloads.
|
|
14
|
+
- Omit cookies and Referer from offer requests; pause, logout and cleanup reject late responses.
|
|
15
|
+
- Add versioned content-addressed browser files, SHA-384 integrity, source snapshots,
|
|
16
|
+
measured bundle sizes and manifest checks before publication.
|
|
17
|
+
- Add data/security documentation and an actual-SDK demo with simulated transport.
|
|
18
|
+
|
|
19
|
+
The app/backend handles participant consent, recording, transcription and analysis
|
|
20
|
+
separately. No independent security audit or new hosted acceptance is claimed.
|
|
21
|
+
Earlier capture API documentation is archived in docs/legacy.
|
package/README.md
CHANGED
|
@@ -1,384 +1,183 @@
|
|
|
1
1
|
# @sightspool/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
into your Sightspool workspace.
|
|
3
|
+
Embed Sightspool research in your product. The SDK invites website visitors and signed-in users into
|
|
4
|
+
owner-approved research during User Hours, then opens Sightspool for consent and
|
|
5
|
+
the interview. It does not capture page content, behavior, form values or analytics.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
them).
|
|
7
|
+
This is the **0.4.0 research API**, a deliberate change from the old capture SDK.
|
|
8
|
+
Install the exact version below and review upgrades before deploying them.
|
|
9
|
+
Old capture code and documentation are archived in `docs/legacy`.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
Read the [data and security guide](https://www.sightspool.com/sdk/data-security),
|
|
12
|
+
inspect the [public source](https://github.com/sightspool/sdk), or try the
|
|
13
|
+
[isolated SDK demo](https://www.sightspool.com/sdk/demo).
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
[llms.txt](https://sdk.sightspool.com/llms.txt) (the machine-readable install/CSP/config
|
|
19
|
-
doc for agents) · [npm](https://www.npmjs.com/package/@sightspool/sdk)
|
|
15
|
+
## npm
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
```sh
|
|
18
|
+
npm install --save-exact @sightspool/sdk@0.4.1
|
|
19
|
+
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
```ts
|
|
22
|
+
import Sightspool from '@sightspool/sdk'
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
Sightspool.init({ key: 'YOUR_GO_LIVE_WIDGET_UUID', audience: 'all_visitors' })
|
|
25
|
+
// Optional when a visitor signs in:
|
|
26
|
+
Sightspool.identify(currentUser.id)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// On logout (anonymous visitors remain eligible in all_visitors mode):
|
|
29
|
+
Sightspool.identify(null)
|
|
30
|
+
|
|
31
|
+
// When leaving the pages included in your research:
|
|
32
|
+
Sightspool.destroy()
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
import Sightspool from '@sightspool/sdk'
|
|
35
|
+
Choose an explicit `audience` matching the approved interview plan:
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
- `all_visitors`: anonymous website visitors and signed-in users. Initialize once on the relevant website pages. No identity or login is required; logout continues recruitment as a visitor.
|
|
38
|
+
- `signed_in`: only users with a real signed-in session. Initialize in your authenticated app shell and call `identify(actualUserId)` after authentication resolves. Until then, no request or invitation is made. `identify(null)` immediately removes invitations on logout.
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
There is no default audience. Missing or invalid audience configuration stays idle.
|
|
41
|
+
`identify` retains only whether an ID is present; the ID itself is not stored or
|
|
42
|
+
transmitted. Do not invent IDs for visitors. This client setting controls display;
|
|
43
|
+
it does not authenticate a participant or change the server-approved research cohort.
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
and **plan** — the only required wiring, and it's what lets Sightspool rank by customer
|
|
42
|
-
(and, with a connected billing source, by MRR). Most apps already make an equivalent
|
|
43
|
-
call for their analytics/support tools.
|
|
45
|
+
## Script tag
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
For visitors, no authentication integration is needed:
|
|
46
48
|
|
|
47
49
|
```html
|
|
48
|
-
<script
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
data-sightspool-key="pk_live_…"
|
|
52
|
-
></script>
|
|
50
|
+
<script async src="https://www.sightspool.com/sdk.global.js"
|
|
51
|
+
data-sightspool-key="YOUR_GO_LIVE_WIDGET_UUID"
|
|
52
|
+
data-sightspool-audience="all_visitors"></script>
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
For signed-in-only research, listen for readiness before loading the SDK, then
|
|
56
|
+
synchronize your actual session:
|
|
57
57
|
|
|
58
58
|
```html
|
|
59
59
|
<script>
|
|
60
|
-
|
|
60
|
+
let sightspoolUserId = null;
|
|
61
|
+
function syncSightspoolUser(userId) {
|
|
62
|
+
sightspoolUserId = userId;
|
|
63
|
+
window.Sightspool?.identify(userId);
|
|
64
|
+
}
|
|
65
|
+
window.addEventListener('sightspool:ready', () => {
|
|
66
|
+
window.Sightspool.identify(sightspoolUserId);
|
|
67
|
+
});
|
|
68
|
+
// Call syncSightspoolUser(actualUser.id) when authentication resolves.
|
|
69
|
+
// Call syncSightspoolUser(null) on logout.
|
|
61
70
|
</script>
|
|
71
|
+
<script async src="https://www.sightspool.com/sdk.global.js"
|
|
72
|
+
data-sightspool-key="YOUR_GO_LIVE_WIDGET_UUID"
|
|
73
|
+
data-sightspool-audience="signed_in"></script>
|
|
62
74
|
```
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`init` options — comma-separate selector lists):
|
|
69
|
-
|
|
70
|
-
```html
|
|
71
|
-
<script
|
|
72
|
-
async
|
|
73
|
-
src="https://www.sightspool.com/sdk.global.js"
|
|
74
|
-
data-sightspool-key="pk_live_…"
|
|
75
|
-
data-sightspool-block=".billing-panel, [data-private]"
|
|
76
|
-
data-sightspool-redact=".customer-name"
|
|
77
|
-
data-sightspool-debug
|
|
78
|
-
data-sightspool-capture-localhost
|
|
79
|
-
></script>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Configuration
|
|
85
|
-
|
|
86
|
-
`init(config)` — all optional except `key`:
|
|
87
|
-
|
|
88
|
-
| option | type | default | purpose |
|
|
89
|
-
|---|---|---|---|
|
|
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://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
|
-
| `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. |
|
|
94
|
-
| `consent` | `boolean` | `true` | Start capturing immediately. Set `false` to stay paused until you call `Sightspool.consent(true)` (or `start()`) after obtaining consent. |
|
|
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. |
|
|
96
|
-
| `block` | `string[]` | `[]` | CSS selectors whose events are **dropped entirely** (the hard opt-out). Equivalent to putting `data-sightspool-ignore` on the element. |
|
|
97
|
-
| `captureOnLocalhost` | `boolean` | `false` | By default the SDK **no-ops on localhost** (`localhost`, `127.0.0.1`, `*.local`, `*.localhost`) so your `npm run dev` traffic never pollutes analytics. Set `true` to capture locally (e.g. to test the install). |
|
|
98
|
-
| `debug` | `boolean` | `false` | Log every capture decision to the console (`[sightspool] …`) so you can watch it work. |
|
|
99
|
-
|
|
100
|
-
Server-side config (allowed CORS origins, additional redaction rules) lives on the
|
|
101
|
-
Connections card and is enforced at ingest — the key alone can't post from an
|
|
102
|
-
un-allowlisted origin.
|
|
103
|
-
|
|
104
|
-
### Privacy controls at a glance
|
|
105
|
-
|
|
106
|
-
| You want to… | Use |
|
|
107
|
-
|---|---|
|
|
108
|
-
| Never capture a subtree (e.g. a billing panel) | `data-sightspool-ignore` on the element, or a `block` selector |
|
|
109
|
-
| Mask a field's text but still log the interaction | a `redact` selector (text → `‹redacted›`) |
|
|
110
|
-
| Wait for cookie-banner consent | init `{ consent: false }` then `Sightspool.consent(true)` |
|
|
111
|
-
| Keep dev traffic out of analytics | nothing — localhost is suppressed by default |
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Keys & environments
|
|
116
|
-
|
|
117
|
-
Your key is **publishable** — safe to ship in client JS (the Stripe `pk_` model). Two
|
|
118
|
-
prefixes, one per environment:
|
|
119
|
-
|
|
120
|
-
| prefix | use it for |
|
|
121
|
-
|---|---|
|
|
122
|
-
| `pk_test_…` | development / staging / preview deploys |
|
|
123
|
-
| `pk_live_…` | production |
|
|
124
|
-
|
|
125
|
-
The SDK treats both prefixes **identically** — there's no client-side special-casing; the
|
|
126
|
-
prefix tells *Sightspool* (at ingest) which environment a Signal belongs to, so test traffic
|
|
127
|
-
never mixes into production analytics. Issue both from **Connections → In-product SDK**.
|
|
128
|
-
|
|
129
|
-
> The SDK also **no-ops on localhost** by default (see `captureOnLocalhost`), so even a
|
|
130
|
-
> `pk_live_` key won't capture from `npm run dev`. Test keys are for *deployed* non-prod
|
|
131
|
-
> environments (staging, previews).
|
|
132
|
-
|
|
133
|
-
Keep the key in an environment variable rather than hardcoding it, and pick test vs live by
|
|
134
|
-
environment. The key is exposed to the browser, so use your framework's **client** env-var
|
|
135
|
-
prefix (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, …) — it's publishable, so that's expected:
|
|
136
|
-
|
|
137
|
-
```js
|
|
138
|
-
Sightspool.init({ key: process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY })
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# .env.development / preview
|
|
143
|
-
NEXT_PUBLIC_SIGHTSPOOL_KEY=pk_test_…
|
|
144
|
-
# .env.production
|
|
145
|
-
NEXT_PUBLIC_SIGHTSPOOL_KEY=pk_live_…
|
|
146
|
-
```
|
|
76
|
+
Go live supplies the correct key and endpoint. Local keys belong to the local
|
|
77
|
+
Sightspool database and cannot be paired with production. Script installs default
|
|
78
|
+
to the origin serving the bundle; `data-sightspool-endpoint` overrides that for a
|
|
79
|
+
CDN or self-hosted install. npm defaults to `https://www.sightspool.com`.
|
|
147
80
|
|
|
148
|
-
|
|
81
|
+
## API
|
|
149
82
|
|
|
150
|
-
|
|
83
|
+
| Method | Behavior |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `init({ key, audience, endpoint? })` | Start research for the explicit audience; UUID key required. Repeating the same configuration is idempotent. Changing it tears down the previous runtime. |
|
|
86
|
+
| `identify(userId)` | Enable signed-in eligibility; only its boolean presence stays locally. `null` clears it; signed-in-only invitations disappear, all-visitors recruitment continues. |
|
|
87
|
+
| `pause()` | Pause recruitment, abort the current request and remove the launcher. |
|
|
88
|
+
| `resume()` | Resume recruitment for the configured audience. Does not bypass approval or hours. |
|
|
89
|
+
| `destroy()` | Remove the launcher, listeners and polling. An already opened interview is not silently ended. |
|
|
90
|
+
| `getStatus()` | `not_initialized`, `signed_out`, `paused`, `checking`, `unavailable`, `available` or `error`. |
|
|
151
91
|
|
|
152
|
-
|
|
92
|
+
`pause` and `resume` are recruitment controls, not consent to record. Participants
|
|
93
|
+
still review the offer, eligibility and recording consent in Sightspool. Owners
|
|
94
|
+
approve the research setup separately. The backend remains authoritative for
|
|
95
|
+
availability, capacity, signed offers and interview admission.
|
|
153
96
|
|
|
154
|
-
|
|
155
|
-
layout — the tag auto-`init`s from `data-sightspool-key`:
|
|
97
|
+
## React / Next.js
|
|
156
98
|
|
|
157
|
-
|
|
158
|
-
// app/layout.tsx
|
|
159
|
-
import Script from 'next/script'
|
|
160
|
-
|
|
161
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
162
|
-
return (
|
|
163
|
-
<html lang="en">
|
|
164
|
-
<body>
|
|
165
|
-
{children}
|
|
166
|
-
<Script
|
|
167
|
-
src="https://www.sightspool.com/sdk.global.js"
|
|
168
|
-
data-sightspool-key={process.env.NEXT_PUBLIC_SIGHTSPOOL_KEY}
|
|
169
|
-
strategy="afterInteractive"
|
|
170
|
-
/>
|
|
171
|
-
</body>
|
|
172
|
-
</html>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Then `identify` the user once known (e.g. a client component after auth):
|
|
99
|
+
Use a client component on the pages included in your research. Identity is optional for all visitors:
|
|
178
100
|
|
|
179
101
|
```tsx
|
|
180
102
|
'use client'
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
window.Sightspool?.identify(user.id, { account: user.account, plan: user.plan })
|
|
183
|
-
}, [user])
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
> `data-sightspool-key` is inlined at build time, so `NEXT_PUBLIC_SIGHTSPOOL_KEY` must be set
|
|
187
|
-
> in the environment Next builds in. `strategy="afterInteractive"` keeps it off the critical
|
|
188
|
-
> path.
|
|
189
|
-
|
|
190
|
-
### React / Next.js — `@sightspool/react`
|
|
191
|
-
|
|
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>
|
|
216
|
-
|
|
217
|
-
```tsx
|
|
218
103
|
import { useEffect } from 'react'
|
|
219
104
|
import Sightspool from '@sightspool/sdk'
|
|
220
105
|
|
|
221
|
-
export function
|
|
222
|
-
useEffect(() => {
|
|
223
|
-
|
|
106
|
+
export function Research({ userId = null }: { userId?: string | null }) {
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
Sightspool.init({ key: 'YOUR_GO_LIVE_WIDGET_UUID', audience: 'all_visitors' })
|
|
109
|
+
Sightspool.identify(userId)
|
|
110
|
+
return () => Sightspool.destroy()
|
|
111
|
+
}, [userId])
|
|
224
112
|
return null
|
|
225
113
|
}
|
|
226
114
|
```
|
|
227
|
-
</details>
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Content-Security-Policy
|
|
232
115
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
from the app it ingests to), so it's one host in two directives:
|
|
116
|
+
Optional `@sightspool/react` 0.2.0 provides `SightspoolProvider` and research hooks.
|
|
117
|
+
It requires SDK 0.4.x; see `packages/react/README.md`. Use one instance per document.
|
|
236
118
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
script-src https://www.sightspool.com;
|
|
241
|
-
connect-src https://www.sightspool.com;
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**npm / bundler install** — the SDK is bundled into your own first-party JS, so no
|
|
245
|
-
`script-src` host is needed; only the ingest origin:
|
|
246
|
-
|
|
247
|
-
```
|
|
248
|
-
connect-src https://www.sightspool.com;
|
|
249
|
-
```
|
|
119
|
+
## Privacy and verification
|
|
250
120
|
|
|
251
|
-
|
|
252
|
-
|
|
121
|
+
The offer request contains only the public workspace key, a random per-workspace
|
|
122
|
+
browser-session device token, and the operation name. No user ID, traits, auth
|
|
123
|
+
secrets, DOM text or page URL is included. Requests use `credentials: 'omit'`.
|
|
124
|
+
Offer requests explicitly suppress the Referer header; Origin and ordinary network metadata still reach the service.
|
|
125
|
+
No microphone or recording starts on initialization or launcher display.
|
|
253
126
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
`<style>`. Under a strict `style-src` without `'unsafe-inline'`, those styles may not apply
|
|
259
|
-
— the prompt stays **fully functional but unstyled** (the SDK never throws into your page).
|
|
260
|
-
Add `'unsafe-inline'` to `style-src` if you want it styled.
|
|
127
|
+
Polling occurs at most once per 15-second interval while visible and eligible for the configured audience,
|
|
128
|
+
plus explicit identity/resume/visibility changes. Only one request is in flight;
|
|
129
|
+
requests time out after 10 seconds. Identity changes, pause, hidden tabs and destruction invalidate stale responses.
|
|
130
|
+
Logout removes signed-in-only invitations; all-visitors mode refreshes as a visitor.
|
|
261
131
|
|
|
262
|
-
|
|
132
|
+
A connection receipt proves that the SDK contacted the configured server. It does
|
|
133
|
+
not certify your app's complete login/logout flow or a successful interview. Test
|
|
134
|
+
anonymous visits, login, navigation, logout, slow loading and unmounting in both modes. Use Go live
|
|
135
|
+
for server connection checks. A closed User Hours window can connect without an
|
|
136
|
+
invitation. Do not fabricate approval or weaken an origin/CSP policy to make it show.
|
|
263
137
|
|
|
264
|
-
##
|
|
138
|
+
## Verified script releases
|
|
265
139
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
that approval ("no proof **and** no approval, no act").
|
|
140
|
+
Go live generates a versioned, content-addressed script URL with SHA-384 integrity,
|
|
141
|
+
`crossorigin="anonymous"` and `referrerpolicy="no-referrer"`. Use those values together.
|
|
142
|
+
The unversioned script examples above are a convenience URL whose contents can change.
|
|
143
|
+
To pin a reviewed release, use Go live or the manifest linked from the data and security guide.
|
|
271
144
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
145
|
+
For self-hosting, copy the reviewed bundle and preserve its integrity value. Set
|
|
146
|
+
`data-sightspool-endpoint` to the workspace Sightspool origin explicitly, because
|
|
147
|
+
script auto-init otherwise defaults to the bundle host. Keep your own CSP and nonce
|
|
148
|
+
requirements; allow only the required script host and Sightspool connection origin.
|
|
276
149
|
|
|
277
|
-
|
|
278
|
-
Sightspool.init({ key: "pk_live_…" }); // serving is on by default
|
|
279
|
-
Sightspool.init({ key: "pk_live_…", interventions: false }); // capture only
|
|
280
|
-
```
|
|
150
|
+
## Maintainers
|
|
281
151
|
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
152
|
+
```sh
|
|
153
|
+
pnpm install --frozen-lockfile
|
|
154
|
+
pnpm -r --include-workspace-root build
|
|
155
|
+
pnpm -r --include-workspace-root test
|
|
156
|
+
npm publish --dry-run
|
|
287
157
|
```
|
|
288
158
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
302
|
-
## What it captures
|
|
303
|
-
|
|
304
|
-
- **Passively, no wiring** — route/screen sequence, clicks, **dead-clicks** and
|
|
305
|
-
**rage-clicks**, client-side errors and failed requests, and a rolling trail of the
|
|
306
|
-
last meaningful events.
|
|
307
|
-
- **Stated intent** — typed queries in search / filter / command-palette / empty-state
|
|
308
|
-
inputs, especially **zero-result** searches (the highest-signal intent, no question).
|
|
309
|
-
- **The one-tap ask** (at a session boundary or after detected friction, rate-limited
|
|
310
|
-
and fatigue-aware): *"Were you able to do what you came here to do today?"* → on "Not
|
|
311
|
-
really," a short shortlist of likely goals + an always-present "Something else."
|
|
312
|
-
|
|
313
|
-
Each capture emits one **Signal** (`intent + path + account + effort`). Intent and
|
|
314
|
-
effort are *constructed* server-side with calibrated confidence — the SDK ships the
|
|
315
|
-
raw trace and the answer; it never guesses.
|
|
316
|
-
|
|
317
|
-
---
|
|
159
|
+
The npm and browser builds share `src/research.ts`. The Sightspool app copies
|
|
160
|
+
`sdk.global.js` from this package and serves `research-widget.js` as a byte-identical
|
|
161
|
+
alias for recent internal snippets. Neither build imports the former capture engine.
|
|
162
|
+
See `docs/research-sdk-transition.md` for release sequencing. Apache-2.0.
|
|
318
163
|
|
|
319
|
-
##
|
|
164
|
+
## Embedded interview panel (0.4.1, local candidate)
|
|
320
165
|
|
|
321
|
-
|
|
166
|
+
An available invitation opens one bottom-right panel on the product page. Consent,
|
|
167
|
+
waiting, supported founder audio or Sightspool text conversation, and completion
|
|
168
|
+
remain inside its isolated Sightspool iframe. Minimize or Escape hides the panel;
|
|
169
|
+
Return to interview reopens the same session. Minimize does not mute, end or withdraw.
|
|
170
|
+
Use the explicit in-panel controls to stop audio or delete interview evidence.
|
|
322
171
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
a keylog.
|
|
330
|
-
- **You control the rest.** Drop any subtree with `data-sightspool-ignore` or a `block`
|
|
331
|
-
selector; mask a field's text with a `redact` selector; gate everything behind
|
|
332
|
-
`Sightspool.consent(false)` until your cookie banner says otherwise.
|
|
333
|
-
- **Suppressed on localhost** so dev traffic never pollutes analytics (opt back in with
|
|
334
|
-
`captureOnLocalhost`).
|
|
172
|
+
Allow the configured Sightspool origin in your site's `frame-src` policy and
|
|
173
|
+
permit microphone delegation to that origin if founder audio is used. Preserve
|
|
174
|
+
other CSP and Permissions Policy restrictions; a host policy may intentionally
|
|
175
|
+
block audio. The iframe requests a microphone only after explicit participant action.
|
|
176
|
+
The frame is restricted to the workspace's saved product origin and exchanges only
|
|
177
|
+
a minimize message with its parent, never interview text or audio. There is no popup.
|
|
335
178
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
styled-broken by your CSS and adds ~nothing to your bundle until it's needed.
|
|
342
|
-
- **Processor posture.** Sightspool processes on your behalf; the prompt discloses that
|
|
343
|
-
diagnostic context is attached.
|
|
344
|
-
|
|
345
|
-
---
|
|
346
|
-
|
|
347
|
-
## API
|
|
348
|
-
|
|
349
|
-
```ts
|
|
350
|
-
Sightspool.init(config: SightspoolConfig): void
|
|
351
|
-
Sightspool.identify(userId: string, traits?: { account?: string; plan?: string }): void
|
|
352
|
-
Sightspool.consent(granted: boolean): void // runtime consent toggle (wire to your cookie banner)
|
|
353
|
-
Sightspool.start(): void // begin capture if init'd with { consent: false } (alias of consent(true))
|
|
354
|
-
Sightspool.stop(): void // pause capture and flush (alias of consent(false))
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
## Roadmap (not yet built)
|
|
360
|
-
|
|
361
|
-
Deliberately deferred from the v1 collect side. Most are **data-gated** — they need real
|
|
362
|
-
traffic to calibrate, so they wait for the first production installs.
|
|
363
|
-
|
|
364
|
-
- **Server-LLM State-B candidates** (`/api/sdk/candidates`). Today the prompt's candidate goals
|
|
365
|
-
are derived **locally** (recent search query + page label) — instant and free, but shallow. A
|
|
366
|
-
server endpoint would generate sharper candidates in the app's own feature vocabulary, at the
|
|
367
|
-
cost of a per-prompt round-trip; it must fall back to the local/generic ask within a tight
|
|
368
|
-
latency budget.
|
|
369
|
-
- **Adaptive micro-interview** on high-value / high-MRR friction — a short, session-grounded,
|
|
370
|
-
agent-authored follow-up beyond the one-tap default, under the same fatigue caps.
|
|
371
|
-
- **Reliable zero-result detection.** v1 harvests the typed query; flagging it as *zero-result*
|
|
372
|
-
(the highest-signal intent) is best-effort and needs per-app empty-state hints.
|
|
373
|
-
- **Server-to-server signed (HMAC) ingest** — for non-browser / backend Signal sources (the
|
|
374
|
-
publishable-key + origin-allowlist posture is browser-only).
|
|
375
|
-
- **Mobile / native SDK** — web-first for now.
|
|
376
|
-
|
|
377
|
-
## Develop
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
pnpm install
|
|
381
|
-
pnpm build # tsup → dist/ (ESM + CJS + types + dist/sdk.global.js)
|
|
382
|
-
pnpm type-check
|
|
383
|
-
pnpm test # node --test over the pure cores
|
|
384
|
-
```
|
|
179
|
+
Once opened, the panel stays mounted across visibility changes, pause, identity
|
|
180
|
+
changes and SDK destroy/remount, so recruitment cleanup cannot silently end a call.
|
|
181
|
+
Destroy stops further recruitment. The active panel remains reachable until the
|
|
182
|
+
page is left; a full page navigation may interrupt audio. Minimize preserves the
|
|
183
|
+
session only within the current document.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
Report suspected vulnerabilities in the Sightspool SDK privately to
|
|
4
|
+
security@sightspool.com. Include the package version, affected feature, impact and
|
|
5
|
+
a minimal reproduction without customer data or secrets. Do not open a public
|
|
6
|
+
issue containing exploit details or private information.
|
|
7
|
+
|
|
8
|
+
The existing disclosure policy is https://www.sightspool.com/.well-known/security.txt.
|
|
9
|
+
It covers the public SDK packages and Sightspool service, not testing a customer's
|
|
10
|
+
website without their authorization. There is no paid bounty or new response-time
|
|
11
|
+
commitment implied here.
|
|
12
|
+
|
|
13
|
+
Research SDK 0.4.0 is prepared locally and has not had an independent security audit.
|
|
14
|
+
Automated tests and release integrity checks are not a security certification.
|
|
15
|
+
The legacy capture API is retired; it is not the supported research installation.
|
|
16
|
+
For data access/deletion requests, contact privacy@sightspool.com.
|