@snapcap/native
Unofficial TypeScript SDK for Snapchat Web. Runs Snap's own bundle and WASM modules inside an isolated Node vm.Context — no Playwright, no Frida, no rooted phone.
@snapcap/native is an unofficial TypeScript SDK for Snapchat Web that runs Snap's own JavaScript bundle and WASM modules inside an isolated Node vm.Context. It exposes a browser-free, typed client for authentication, friends, messaging, persistence, throttling, network observability, and multi-account automation — without Playwright, Puppeteer, Selenium, Frida, emulators, or rooted phones.
import { SnapcapClient, FileDataStore } from "@snapcap/native";
const client = new SnapcapClient({
dataStore: new FileDataStore("./auth.json"),
credentials: {
username: process.env.SNAP_USER!,
password: process.env.SNAP_PASS!,
},
browser: {
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
},
});
await client.authenticate();
const friends = await client.friends.list();
console.log(friends.map((f) => f.username));This is research and developer tooling, not affiliated with Snap Inc. Read Safety & risk before pointing this at production accounts.
Where to go next
Get started
Install, authenticate, and make your first call in under five minutes.
Architecture
Layered overview — Sandbox, bundle, WASM, shims, transport.
Safety & risk
ToS posture, account risk, what this project will not be used for.
API reference
Every public method on SnapcapClient and the per-domain managers.
Why this is different
Most Snapchat-automation projects fall into one of these buckets. @snapcap/native deliberately doesn't.
| Approach | Shape | Why this is different |
|---|---|---|
| Playwright / Puppeteer / Selenium | Full Chromium per account | We don't drive a browser at all. Per-account cost is a vm.Context instead of a Chromium process. |
| Frida / rooted Android / iOS | Native Snap binary + instrumentation | Snap's mobile risk engine is aggressive. The web target is durable; the mobile target wasn't. |
| Old "private API" wrappers | Hand-rolled protobuf calls | We run Snap's own bundle and WASM, not a reimplementation. When Snap rebuilds, the shapes shift but behaviour is the same. |
| Snap Kit / Login Kit | Official, sanctioned | Too narrow — no friend graph, no message send, no story posting. |
| Userscripts / browser extensions | One Chrome tab | Single-account, single-tab, no headless, no multi-tenant. |
Concretely, @snapcap/native:
- Runs in native Node (≥ 22 or Bun ≥ 1.3) — no headless browser at runtime.
- Loads Snap's web JS + WASM into a per-instance
vm.Contextwith a happy-dom Window projected on top. - Calls Snap's own WASM for kameleon attestation, the chat session, and Fidelius E2E. We drive the primitives, not reimplement them.
- Exposes an idiomatic TypeScript surface with per-domain managers (
client.friends,client.messaging, …). - Multi-tenant in-process. Every
SnapcapClientowns its own sandbox, DataStore, and bundle bring-up caches. - Pluggable persistence.
localStorage,sessionStorage,IndexedDB, and the cookie jar all route through a smallDataStoreinterface — file, memory, or your own backend.
What this is not
- Not a wrapper around an emulator, Frida, or a rooted phone — those approaches were tried and don't survive Snap's mobile risk engine.
- Not Snap's official Login Kit or Snap Kit — those don't expose friend management or story posting.
- Not Playwright-driven. There is zero headless browser at runtime.
- Not affiliated with Snap Inc. Unofficial, MIT-licensed, research tooling.
Status
The source of truth for what's wired today. See the capability matrix in the README for the complete table; the headline groups:
Working
SnapcapClientconstruction,authenticate(),logout(),refreshAuthToken(),isAuthenticated().client.setStatus(...)/client.getStatus()— globalPresent/Away/AwaitingReactivateslot.client.friends—list/search/getUsers/snapshot/refresh/sendRequest/acceptRequest/rejectRequest/block/unblock.client.friends.on(...)— typed graph events with offline replay.client.messaging.listConversations()/fetchEncryptedMessages()— direct gRPC-Web, no decrypt cost.client.messaging.on("message", cb)— live decrypted inbound (cached history pump on first subscribe).client.messaging.sendText(convId, text)— outbound text DM via Snap's own bundle send path.client.messaging.setTyping(...)/setViewing(...)/setRead(...)— outbound presence via the bundle's convMgr + presence-slice dual path.- Fidelius identity mint + register, per-instance and isolated.
- Per-instance Sandbox (multi-tenant in one process) — verified by
scripts/test-isolation.ts. DataStore-backed sandbox persistence (cookies,local_*,session_*,indexdb_*).- Opt-in HTTP throttling (per-instance + shared gates).
- Structured network observability (
setLogger,SNAP_NETLOG=1).
Experimental
- Live-message push — the persistent duplex WS receives frames, but the bundle's inbound delegate hook is inconsistent across runs. Tracked by
tests/api/messaging-multi-account.test.ts. client.messaging.sendImage(...)/sendSnap(...)— scaffolded through the bundle session; compile and bring up cleanly, not wire-tested.client.stories.post(media)— same shape assendImage; not wire-tested.
Planned
client.messaging.on("typing" | "viewing" | "read", cb)— declared on the bus, the bundle's delegate slots are not yet mapped.- Per-instance proxy / outbound IP rotation (TODO —
BrowserContext.httpAgentreserved for an undiciDispatcher).
Architecture, in one diagram
SnapcapClient (public TypeScript SDK)
└─ Sandbox (per-instance vm.Context)
├─ happy-dom Window projection
├─ Patched webpack runtime + chat bundle JS
├─ Snap's WASM modules
│ · kameleon attestation
│ · chat session worker
│ · Fidelius E2E (~12 MB + 814 KB)
├─ Browser API shims
│ fetch / XHR / WebSocket / Storage / IndexedDB / cookies
└─ DataStore-backed persistence
└─ Native transport (Node fetch + tough-cookie jar — bypasses sandbox)
└─ gRPC-Web → web.snapchat.com / accounts.snapchat.com / aws.duplex.snapchat.comSee Architecture overview for a paragraph on each layer, or jump straight into the internals chapters for the long-form story.