runable
This commit is contained in:
112
ingestion/sdk/web/README.md
Normal file
112
ingestion/sdk/web/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# CDP Web SDK
|
||||
|
||||
Single-file TypeScript tracker for browsers. No build step, no dependencies.
|
||||
|
||||
## Install
|
||||
|
||||
Copy [`cdp.ts`](./cdp.ts) into your app. A common spot for Next.js:
|
||||
|
||||
```
|
||||
your-app/
|
||||
└── lib/
|
||||
└── cdp.ts ← paste here
|
||||
```
|
||||
|
||||
## Init (Next.js App Router)
|
||||
|
||||
`app/layout.tsx`:
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { cdp } from '@/lib/cdp';
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
cdp.init({
|
||||
writeKey: process.env.NEXT_PUBLIC_CDP_WRITE_KEY!,
|
||||
endpoint: process.env.NEXT_PUBLIC_CDP_ENDPOINT ?? 'http://localhost:3049',
|
||||
autoPage: true, // fire `page` on every SPA route change
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <html><body>{children}</body></html>;
|
||||
}
|
||||
```
|
||||
|
||||
`.env.local`:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_CDP_WRITE_KEY=cdp_dev_writekey_1234567890
|
||||
NEXT_PUBLIC_CDP_ENDPOINT=http://localhost:3049
|
||||
```
|
||||
|
||||
(The dev key above is the one seeded by `infra/migrations/000002_seed_dev.up.sql`.)
|
||||
|
||||
## Use
|
||||
|
||||
```tsx
|
||||
import { cdp } from '@/lib/cdp';
|
||||
|
||||
// On login
|
||||
cdp.identify(user.id, { email: user.email, plan: user.plan });
|
||||
|
||||
// On a meaningful action
|
||||
cdp.track('Checkout Completed', { revenue: 199, currency: 'USD' });
|
||||
|
||||
// Manual page call (skip if autoPage is on)
|
||||
cdp.page('Pricing');
|
||||
|
||||
// On logout
|
||||
cdp.reset();
|
||||
```
|
||||
|
||||
## Vite / Create React App
|
||||
|
||||
Identical — `cdp.init(...)` in your root component / `main.tsx`.
|
||||
|
||||
## What gets sent
|
||||
|
||||
Every call POSTs to `${endpoint}/v1/<type>` with this envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "track",
|
||||
"messageId": "uuid-v4",
|
||||
"anonymousId": "uuid-v4 from localStorage",
|
||||
"userId": "from identify()",
|
||||
"sentAt": "2026-05-25T03:14:15Z",
|
||||
"context": { "library_name": "cdp-web", "user_agent": "..." },
|
||||
"event": "Checkout Completed",
|
||||
"properties": { "revenue": 199, "currency": "USD" }
|
||||
}
|
||||
```
|
||||
|
||||
Header: `Authorization: Basic base64(<writeKey>:)`.
|
||||
|
||||
Payload is Segment-compatible: if you ever swap endpoints to Segment the same
|
||||
code works.
|
||||
|
||||
## Things to know
|
||||
|
||||
- `anonymousId` is generated once and persisted in `localStorage` under
|
||||
`cdp_anon`. It survives across sessions.
|
||||
- `userId` is persisted in `cdp_uid` until you call `cdp.reset()`.
|
||||
- `fetch` uses `keepalive: true` so events fire even when the page is
|
||||
unloading. No `sendBeacon` because we need the `Authorization` header.
|
||||
- For SSR (Next.js Server Components, Remix loaders) skip the SDK — fire
|
||||
events from your API route or a server-side function instead.
|
||||
|
||||
## CORS
|
||||
|
||||
The ingest service serves `Access-Control-Allow-Origin: *` so any origin
|
||||
works in dev. Lock this down for production (configure a reverse proxy or
|
||||
patch `internal/middleware/middleware.go`).
|
||||
|
||||
## Production checklist
|
||||
|
||||
- [ ] Issue a per-workspace write key from the console (don't ship the dev key)
|
||||
- [ ] Restrict CORS to known origins
|
||||
- [ ] Front the ingest service with HTTPS (browser refuses mixed content)
|
||||
- [ ] Set `NEXT_PUBLIC_CDP_ENDPOINT` to the public URL
|
||||
Reference in New Issue
Block a user