@snaplyagent/sdk
v2.23.0
Published
Consent-first screen capture SDK for the web — Snaply Agent.
Maintainers
Readme
@snaplyagent/sdk
The web SDK for Snaply Agent — consent-first screen capture for support teams. A support agent asks to see the screen, the person allows it, and only then is anything captured. Nothing is taken silently, and marked regions are covered before the image is encoded, so the real pixels never leave the browser.
Zero runtime dependencies. Ships ESM and CJS with TypeScript types.
Install
npm install @snaplyagent/sdkShips ESM and CJS with TypeScript types. Any bundler that resolves bare imports works — there is no build configuration to add.
Usage
Attach callbacks before init. A support request can arrive as soon as the device registers,
and anything attached afterwards would miss it.
import { Snaply } from "@snaplyagent/sdk";
// Anonymous is fine — identity is optional.
await Snaply.init({ key: "snap_live_…" });
// When the user signs in:
await Snaply.identify({ id: "usr_20481", name: "Maya K.", phone: "+14155550142" });
Snaply.on({
onRequestShown: () => {},
onAllowed: (capture) => console.log(capture.imageUrl),
onDenied: () => {},
onExpired: () => {},
// Live view stopped — by the user, the agent, or the call ending.
onLiveEnded: (session) => {},
// Remote control started or ended. The SDK shows its own prompt and banner; this is only a hook.
onRemoteControl: (active) => {},
onError: (error) => {},
});
// On sign-out:
await Snaply.reset();
// What the person reads out so support can find this browser:
Snaply.showSupportCode();Every callback is optional. init rejects with a SnaplyError — see Errors.
Environments
environment selects which Snaply deployment to talk to — they are separate backends (each
its own database), matching the dev/staging/live pipeline: "development" →
dev.snaplyagent.com, "staging" → staging.snaplyagent.com, "live" (default) →
snaplyagent.com. Use baseUrl for a self-hosted host.
await Snaply.init({ key: "…", environment: process.env.NODE_ENV === "production" ? "live" : "development" });Each environment has its own products and keys. (Within one environment, a product also has Test
vs Live keys — snap_test_… / snap_live_…, where test captures are excluded from quota/billing;
that's a separate axis.)
Redaction
Mark any element with the snaply-redact attribute and it is covered by a REDACTED box in every screenshot and live-view frame — painted into the image on the device before it uploads, so the support agent never sees the content. Password inputs are redacted automatically. Nothing else is — mark whatever you want hidden. What counts as sensitive is your call, not the SDK's, and the same rule holds on iOS and Android, so one marking pass covers every platform.
<input snaply-redact type="text" name="ssn" />
<div snaply-redact="Card on file">•••• 4242</div> <!-- attribute value overrides the label -->The box defaults to a red fill with a white REDACTED caption. Customize it globally at init, or per element:
Snaply.init({
key: "snap_live_…",
redaction: { color: "#14201A", label: "HIDDEN", labelColor: "#ffffff" },
});Per-element overrides: snaply-redact="Custom label", data-snaply-redact-color, data-snaply-redact-label-color. An empty label paints a plain box with no caption. While support has in-app control, redacted fields also stay locked (support can't read or type into them).
identify also takes an optional phone (E.164). Send it and the workspace's screen-pop API (POST /v1/calls/incoming) can resolve an incoming caller-ID to this user and pop them onto the agent's console — see the SDK reference.
Anonymous callers get a short support code (Snaply.supportCode(), e.g. 74-315) to read to the agent. Snaply.showSupportCode() presents it in a built-in corner card (same chrome as the consent prompt, follows the device's light/dark theme) with a Done button; if the device is identified — no code — it's a silent no-op. Use supportCode() instead if you'd rather render your own UI.
Consent model
init registers the device (the backend validates the API key and, for web/hybrid products, the request Origin against the product's domain allowlist — a mismatch returns 403 origin_mismatch), then opens a WebSocket for presence and consent push. When an agent requests a screenshot, the backend pushes a consent message; the SDK shows a bottom-right prompt card (copy adapts to the product's capture mode and follows the device's light/dark theme). Only after the user taps Allow does the SDK snapshot the viewport and upload it — nothing is captured silently. Views you mark are redacted before capture (see below).
Nothing is captured until the person allows it — that is the product, not a setting. The SDK rasterises your page only: it cannot see other tabs, other windows, or anything outside the browser, so no screen-share permission is requested and no browser picker appears. While a live session runs an on-screen indicator stays up, and the SDK stops sending the moment it can no longer prove that indicator is attached.
Errors
SnaplyError carries status and code: invalid_key (401), origin_mismatch / account_suspended / workspace_suspended / product_paused / seat_suspended / mode_not_allowed (403), device_offline / device_backgrounded (409), request_expired (410), quota_exceeded / device_limit (429).
device_limit is the one to expect during integration: it comes back from init() when the environment has already registered its maximum number of devices.
Vendor ID
There isn't one, and there will not be. Browsers expose no per-device identifier, and Snaply will
not derive one from a fingerprint — that is precisely the cross-site identifier browsers spend
their engineering budget preventing. vendorId is always null on the web, and the native SDKs'
collectVendorId flag has no effect here. Identity is the install-scoped UUID, so a new browser,
a cleared profile or a private window arrives as a new device.
Notes
- The key is not a secret. It identifies the workspace, ships in your public JavaScript, and is checked server-side against the product's allowed domains — a copy lifted from your bundle is useless from anywhere else.
- The device token is never persisted. Every page load registers afresh;
installId(a non-secret UUID inlocalStorage) is what keeps it the same device across reloads. - No runtime dependencies. The whole SDK is the tarball you install; nothing is fetched at run time, and nothing is loaded from a CDN.
Develop
npm install
npm test # vitest (paths + prompt UI, jsdom)
npm run build # tsup → dist (ESM + CJS + d.ts)demo/index.html is a live end-to-end demo against a local backend (snap_live_Xq81LdM4v7ZsHy3t, the seeded Acme Web key). Serve the repo root and open it, then request a capture from the agent console. Add ?anon to register anonymously and try the support-code popup.
Docs
Full documentation: https://snaplyagent.com/docs
