feat: sdks and docs (#239)
* init * fix * update docs * bump: all sdks * rename types test
This commit is contained in:
committed by
GitHub
parent
790801b728
commit
83e223a496
104
apps/public/content/docs/(tracking)/adblockers.mdx
Normal file
104
apps/public/content/docs/(tracking)/adblockers.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: Avoid adblockers with proxy
|
||||
description: Learn why adblockers block analytics and how to avoid it by proxying events.
|
||||
---
|
||||
|
||||
In this article we need to talk about adblockers, why they exist, how they work, and how to avoid them.
|
||||
|
||||
Adblockers' main purpose was initially to block ads, but they have since started to block tracking scripts as well. This is primarily for privacy reasons, and while we respect that, there are legitimate use cases for understanding your visitors. OpenPanel is designed to be a privacy-friendly, cookieless analytics tool that doesn't track users across sites, but generic blocklists often catch all analytics tools indiscriminately.
|
||||
|
||||
The best way to avoid adblockers is to proxy events via your own domain name. Adblockers generally cannot block requests to your own domain (first-party requests) without breaking the functionality of the site itself.
|
||||
|
||||
## Built-in Support
|
||||
|
||||
Today, our Next.js SDK and WordPress plugin have built-in support for proxying:
|
||||
- **WordPress**: Does it automatically.
|
||||
- **Next.js**: Easy to setup with a route handler.
|
||||
|
||||
## Implementing Proxying for Any Framework
|
||||
|
||||
If you are not using Next.js or WordPress, you can implement proxying in any backend framework. The key is to set up an API endpoint on your domain (e.g., `api.domain.com` or `domain.com/api`) that forwards requests to OpenPanel.
|
||||
|
||||
Below is an example of how to set up a proxy using a [Hono](https://hono.dev/) server. This implementation mimics the logic used in our Next.js SDK.
|
||||
|
||||
> You can always see how our Next.js implementation looks like in our [repository](https://github.com/Openpanel-dev/openpanel/blob/main/packages/sdks/nextjs/createNextRouteHandler.ts).
|
||||
|
||||
### Hono Example
|
||||
|
||||
```typescript
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
// 1. Proxy the script file
|
||||
app.get('/op1.js', async (c) => {
|
||||
const scriptUrl = 'https://openpanel.dev/op1.js'
|
||||
try {
|
||||
const res = await fetch(scriptUrl)
|
||||
const text = await res.text()
|
||||
|
||||
c.header('Content-Type', 'text/javascript')
|
||||
// Optional caching for 24 hours
|
||||
c.header('Cache-Control', 'public, max-age=86400, stale-while-revalidate=86400')
|
||||
return c.body(text)
|
||||
} catch (e) {
|
||||
return c.json({ error: 'Failed to fetch script' }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
// 2. Proxy the track event
|
||||
app.post('/track', async (c) => {
|
||||
const body = await c.req.json()
|
||||
|
||||
// Forward the client's IP address (be sure to pick correct IP based on your infra)
|
||||
const ip = c.req.header('cf-connecting-ip') ??
|
||||
c.req.header('x-forwarded-for')?.split(',')[0]
|
||||
|
||||
const headers = new Headers()
|
||||
headers.set('Content-Type', 'application/json')
|
||||
headers.set('Origin', c.req.header('origin') ?? '')
|
||||
headers.set('User-Agent', c.req.header('user-agent') ?? '')
|
||||
headers.set('openpanel-client-id', c.req.header('openpanel-client-id') ?? '')
|
||||
|
||||
if (ip) {
|
||||
headers.set('openpanel-client-ip', ip)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('https://api.openpanel.dev/track', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
return c.json(await res.text(), res.status)
|
||||
} catch (e) {
|
||||
return c.json(e, 500)
|
||||
}
|
||||
})
|
||||
|
||||
export default app
|
||||
```
|
||||
|
||||
This script sets up two endpoints:
|
||||
1. `GET /op1.js`: Fetches the OpenPanel script and serves it from your domain.
|
||||
2. `POST /track`: Receives events from the frontend, adds necessary headers (User-Agent, Origin, Content-Type, openpanel-client-id, openpanel-client-ip), and forwards them to OpenPanel's API.
|
||||
|
||||
## Frontend Configuration
|
||||
|
||||
Once your proxy is running, you need to configure the OpenPanel script on your frontend to use your proxy endpoints instead of the default ones.
|
||||
|
||||
```html
|
||||
<script>
|
||||
window.op=window.op||function(){var n=[],o=new Proxy((function(){arguments.length>0&&n.push(Array.prototype.slice.call(arguments))}),{get:function(o,t){return"q"===t?n:function(){n.push([t].concat(Array.prototype.slice.call(arguments)))}}});return o}();
|
||||
window.op('init', {
|
||||
apiUrl: 'https://api.domain.com'
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
</script>
|
||||
<script src="https://api.domain.com/op1.js" defer async></script>
|
||||
```
|
||||
|
||||
By doing this, all requests are sent to your domain first, bypassing adblockers that look for third-party tracking domains.
|
||||
190
apps/public/content/docs/(tracking)/how-it-works.mdx
Normal file
190
apps/public/content/docs/(tracking)/how-it-works.mdx
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
title: How it works
|
||||
description: Understanding device IDs, session IDs, profile IDs, and event tracking
|
||||
---
|
||||
|
||||
## Device ID
|
||||
|
||||
A **device ID** is a unique identifier generated for each device/browser combination. It's calculated using a hash function that combines:
|
||||
|
||||
- **User Agent** (browser/client information)
|
||||
- **IP Address**
|
||||
- **Origin** (project ID)
|
||||
- **Salt** (a rotating secret key)
|
||||
|
||||
```typescript:packages/common/server/profileId.ts
|
||||
export function generateDeviceId({
|
||||
salt,
|
||||
ua,
|
||||
ip,
|
||||
origin,
|
||||
}: GenerateDeviceIdOptions) {
|
||||
return createHash(`${ua}:${ip}:${origin}:${salt}`, 16);
|
||||
}
|
||||
```
|
||||
|
||||
### Salt Rotation
|
||||
|
||||
The salt used for device ID generation rotates **daily at midnight** (UTC). This means:
|
||||
|
||||
- Device IDs remain consistent throughout a single day
|
||||
- Device IDs reset each day for privacy purposes
|
||||
- The system maintains both the current and previous day's salt to handle events that may arrive slightly after midnight
|
||||
|
||||
```typescript:apps/worker/src/jobs/cron.salt.ts
|
||||
// Salt rotation happens daily at midnight (pattern: '0 0 * * *')
|
||||
```
|
||||
|
||||
When the salt rotates, all device IDs change, effectively anonymizing tracking data on a daily basis while still allowing session continuity within a 24-hour period.
|
||||
|
||||
## Session ID
|
||||
|
||||
A **session** represents a continuous period of user activity. Sessions are used to group related events together and understand user behavior patterns.
|
||||
|
||||
### Session Duration
|
||||
|
||||
Sessions have a **30-minute timeout**. If no events are received for 30 minutes, the session automatically ends. Each new event resets this 30-minute timer.
|
||||
|
||||
```typescript:apps/worker/src/utils/session-handler.ts
|
||||
export const SESSION_TIMEOUT = 1000 * 60 * 30; // 30 minutes
|
||||
```
|
||||
|
||||
### Session Creation Rules
|
||||
|
||||
Sessions are **only created for client events**, not server events. This means:
|
||||
|
||||
- Events sent from browsers, mobile apps, or client-side SDKs will create sessions
|
||||
- Events sent from backend servers, scripts, or server-side SDKs will **not** create sessions
|
||||
- If you only track events from your backend, no sessions will be created
|
||||
|
||||
Additionally, sessions are **not created for events older than 15 minutes**. This prevents historical data imports from creating artificial sessions.
|
||||
|
||||
```typescript:apps/worker/src/jobs/events.incoming-event.ts
|
||||
// Sessions are not created if:
|
||||
// 1. The event is from a server (uaInfo.isServer === true)
|
||||
// 2. The timestamp is from the past (isTimestampFromThePast === true)
|
||||
if (uaInfo.isServer || isTimestampFromThePast) {
|
||||
// Event is attached to existing session or no session
|
||||
}
|
||||
```
|
||||
|
||||
## Profile ID
|
||||
|
||||
A **profile ID** is a persistent identifier for a user across multiple devices and sessions. It allows you to track the same user across different browsers, devices, and time periods.
|
||||
|
||||
### Profile ID Assignment
|
||||
|
||||
If a `profileId` is provided when tracking an event, it will be used to identify the user. However, **if no `profileId` is provided, it defaults to the `deviceId`**.
|
||||
|
||||
This means:
|
||||
- Anonymous users (without a profile ID) are tracked by their device ID
|
||||
- Once you identify a user (by providing a profile ID), all their events will be associated with that profile
|
||||
- The same user can be tracked across multiple devices by using the same profile ID
|
||||
|
||||
```typescript:packages/db/src/services/event.service.ts
|
||||
// If no profileId is provided, it defaults to deviceId
|
||||
if (!payload.profileId && payload.deviceId) {
|
||||
payload.profileId = payload.deviceId;
|
||||
}
|
||||
```
|
||||
|
||||
## Client Events vs Server Events
|
||||
|
||||
OpenPanel distinguishes between **client events** and **server events** based on the User-Agent header.
|
||||
|
||||
### Client Events
|
||||
|
||||
Client events are sent from:
|
||||
- Web browsers (Chrome, Firefox, Safari, etc.)
|
||||
- Mobile apps using client-side SDKs
|
||||
- Any client that sends a browser-like User-Agent
|
||||
|
||||
Client events:
|
||||
- Create sessions
|
||||
- Generate device IDs
|
||||
- Support full session tracking
|
||||
|
||||
### Server Events
|
||||
|
||||
Server events are detected when the User-Agent matches server patterns, such as:
|
||||
- `Go-http-client/1.0`
|
||||
- `node-fetch/1.0`
|
||||
- Other single-name/version patterns (e.g., `LibraryName/1.0`)
|
||||
|
||||
Server events:
|
||||
- Do **not** create sessions
|
||||
- Are attached to existing sessions if available
|
||||
- Are useful for backend tracking without session management
|
||||
|
||||
```typescript:packages/common/server/parser-user-agent.ts
|
||||
// Server events are detected by patterns like "Go-http-client/1.0"
|
||||
function isServer(res: UAParser.IResult) {
|
||||
if (SINGLE_NAME_VERSION_REGEX.test(res.ua)) {
|
||||
return true;
|
||||
}
|
||||
// ... additional checks
|
||||
}
|
||||
```
|
||||
|
||||
The distinction is made in the event processing pipeline:
|
||||
|
||||
```typescript:apps/worker/src/jobs/events.incoming-event.ts
|
||||
const uaInfo = parseUserAgent(userAgent, properties);
|
||||
|
||||
// Only client events create sessions
|
||||
if (uaInfo.isServer || isTimestampFromThePast) {
|
||||
// Server events or old events don't create new sessions
|
||||
}
|
||||
```
|
||||
|
||||
## Timestamps
|
||||
|
||||
Events can include custom timestamps to track when events actually occurred, rather than when they were received by the server.
|
||||
|
||||
### Setting Custom Timestamps
|
||||
|
||||
You can provide a custom timestamp using the `__timestamp` property in your event properties:
|
||||
|
||||
```javascript
|
||||
track('page_view', {
|
||||
__timestamp: '2024-01-15T10:30:00Z'
|
||||
});
|
||||
```
|
||||
|
||||
### Timestamp Validation
|
||||
|
||||
The system validates timestamps to prevent abuse and ensure data quality:
|
||||
|
||||
1. **Future timestamps**: If a timestamp is more than **1 minute in the future**, the server timestamp is used instead
|
||||
2. **Past timestamps**: If a timestamp is older than **15 minutes**, it's marked as `isTimestampFromThePast: true`
|
||||
|
||||
```typescript:apps/api/src/controllers/track.controller.ts
|
||||
// Timestamp validation logic
|
||||
const ONE_MINUTE_MS = 60 * 1000;
|
||||
const FIFTEEN_MINUTES_MS = 15 * ONE_MINUTE_MS;
|
||||
|
||||
// Future check: more than 1 minute ahead
|
||||
if (clientTimestampNumber > safeTimestamp + ONE_MINUTE_MS) {
|
||||
return { timestamp: safeTimestamp, isTimestampFromThePast: false };
|
||||
}
|
||||
|
||||
// Past check: older than 15 minutes
|
||||
const isTimestampFromThePast =
|
||||
clientTimestampNumber < safeTimestamp - FIFTEEN_MINUTES_MS;
|
||||
```
|
||||
|
||||
### Timestamp Impact on Sessions
|
||||
|
||||
**Important**: Events with timestamps older than 15 minutes (`isTimestampFromThePast: true`) will **not create new sessions**. This prevents historical data imports from creating artificial sessions in your analytics.
|
||||
|
||||
```typescript:apps/worker/src/jobs/events.incoming-event.ts
|
||||
// Events from the past don't create sessions
|
||||
if (uaInfo.isServer || isTimestampFromThePast) {
|
||||
// Attach to existing session or track without session
|
||||
}
|
||||
```
|
||||
|
||||
This ensures that:
|
||||
- Real-time tracking creates proper sessions
|
||||
- Historical data imports don't interfere with session analytics
|
||||
- Backdated events are still tracked but don't affect session metrics
|
||||
3
apps/public/content/docs/(tracking)/meta.json
Normal file
3
apps/public/content/docs/(tracking)/meta.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["sdks", "how-it-works", "..."]
|
||||
}
|
||||
364
apps/public/content/docs/(tracking)/revenue-tracking.mdx
Normal file
364
apps/public/content/docs/(tracking)/revenue-tracking.mdx
Normal file
@@ -0,0 +1,364 @@
|
||||
---
|
||||
title: Revenue tracking
|
||||
description: Learn how to easily track your revenue with OpenPanel and how to get it shown directly in your dashboard.
|
||||
---
|
||||
|
||||
import { FlowStep } from '@/components/flow-step';
|
||||
|
||||
Revenue tracking is a great way to get a better understanding of what your best revenue source is. On this page we'll break down how to get started.
|
||||
|
||||
Before we start, we need to know some fundamentals about how OpenPanel and your payment provider work and how we can link a payment to a visitor.
|
||||
|
||||
### Payment providers
|
||||
|
||||
Usually, you create your checkout from your backend, which then returns a payment link that your visitor will be redirected to. When creating the checkout link, you usually add additional fields such as metadata, customer information, or order details. We'll add the device ID information in this metadata field to be able to link your payment to a visitor.
|
||||
|
||||
### OpenPanel
|
||||
|
||||
OpenPanel is a cookieless analytics tool that identifies visitors using a `device_id`. To link a payment to a visitor, you need to capture their `device_id` before they complete checkout. This `device_id` will be stored in your payment provider's metadata, and when the payment webhook arrives, you'll use it to associate the revenue with the correct visitor.
|
||||
|
||||
## Some typical flows
|
||||
|
||||
- [Revenue tracking from your backend (not identified)](#revenue-tracking-from-your-backend-webhook)
|
||||
- [Revenue tracking from your backend (identified)](#revenue-tracking-from-your-backend-webhook-identified)
|
||||
- [Revenue tracking from your frontend](#revenue-tracking-from-your-frontend)
|
||||
- [Revenue tracking without linking it to a identity or device](#revenue-tracking-without-linking-it-to-an-identity-or-device)
|
||||
|
||||
### Revenue tracking from your backend (webhook)
|
||||
|
||||
This is the most common flow and most secure one. Your backend receives webhooks from your payment provider, and here is the best opportunity to do revenue tracking.
|
||||
|
||||
<FlowStep step={1} actor="Visitor" description="Visits your website" icon="visitor" />
|
||||
|
||||
<FlowStep step={2} actor="Visitor" description="Makes a purchase" icon="visitor" />
|
||||
|
||||
<FlowStep step={3} actor="Your website" description="Does a POST request to get the checkout URL" icon="website">
|
||||
When you create the checkout, you should first call `op.fetchDeviceId()`, which will return your visitor's current `deviceId`. Pass this to your checkout endpoint.
|
||||
|
||||
```javascript
|
||||
fetch('https://domain.com/api/checkout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
deviceId: await op.fetchDeviceId(), // ✅ since deviceId is here we can link the payment now
|
||||
// ... other checkout data
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Handle checkout response, e.g., redirect to payment link
|
||||
window.location.href = data.paymentUrl;
|
||||
})
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={4} actor="Your backend" description="Will generate and return the checkout URL" icon="backend">
|
||||
```javascript
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { deviceId, amount, currency } = await req.json();
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: currency,
|
||||
product_data: { name: 'Product Name' },
|
||||
unit_amount: amount * 100, // Convert to cents
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: 'payment',
|
||||
metadata: {
|
||||
deviceId: deviceId, // ✅ since deviceId is here we can link the payment now
|
||||
},
|
||||
success_url: 'https://domain.com/success',
|
||||
cancel_url: 'https://domain.com/cancel',
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
paymentUrl: session.url,
|
||||
});
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={5} actor="Visitor" description="Gets redirected to payment link" icon="visitor" />
|
||||
|
||||
<FlowStep step={6} actor="Visitor" description="Pays on your payment provider" icon="payment" />
|
||||
|
||||
<FlowStep step={7} actor="Your backend" description="Receives a webhook for a successful payment" icon="backend">
|
||||
```javascript
|
||||
export async function POST(req: Request) {
|
||||
const event = await req.json();
|
||||
|
||||
// Stripe sends events with type and data.object structure
|
||||
if (event.type === 'checkout.session.completed') {
|
||||
const session = event.data.object;
|
||||
const deviceId = session.metadata.deviceId;
|
||||
const amount = session.amount_total;
|
||||
|
||||
op.revenue(amount, { deviceId }); // ✅ since deviceId is here we can link the payment now
|
||||
}
|
||||
|
||||
return Response.json({ received: true });
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={8} actor="Visitor" description="Redirected to your website with payment confirmation" icon="success" isLast />
|
||||
|
||||
---
|
||||
|
||||
### Revenue tracking from your backend (webhook) - Identified users
|
||||
|
||||
If your visitors are identified (meaning you have called `identify` with a `profileId`), this process gets a bit easier. You don't need to pass the `deviceId` when creating your checkout, and you only need to provide the `profileId` (in backend) to the revenue call.
|
||||
|
||||
<FlowStep step={1} actor="Visitor" description="Visits your website" icon="visitor" />
|
||||
|
||||
<FlowStep step={2} actor="Your website" description="Identifies the visitor" icon="website">
|
||||
When a visitor logs in or is identified, call `op.identify()` with their unique `profileId`.
|
||||
|
||||
```javascript
|
||||
op.identify({
|
||||
profileId: 'user-123', // Unique identifier for this user
|
||||
email: 'user@example.com',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
});
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={3} actor="Visitor" description="Makes a purchase" icon="visitor" />
|
||||
|
||||
<FlowStep step={4} actor="Your website" description="Does a POST request to get the checkout URL" icon="website">
|
||||
Since the visitor is already identified, you don't need to fetch or pass the `deviceId`. Just send the checkout data.
|
||||
|
||||
```javascript
|
||||
fetch('https://domain.com/api/checkout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// ✅ No deviceId needed - user is already identified
|
||||
// ... other checkout data
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Handle checkout response, e.g., redirect to payment link
|
||||
window.location.href = data.paymentUrl;
|
||||
})
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={5} actor="Your backend" description="Will generate and return the checkout URL" icon="backend">
|
||||
Since the user is authenticated, you can get their `profileId` from the session and store it in metadata for easy retrieval in the webhook.
|
||||
|
||||
```javascript
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { amount, currency } = await req.json();
|
||||
|
||||
// Get profileId from authenticated session
|
||||
const profileId = req.session.userId; // or however you get the user ID
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: currency,
|
||||
product_data: { name: 'Product Name' },
|
||||
unit_amount: amount * 100, // Convert to cents
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: 'payment',
|
||||
metadata: {
|
||||
profileId: profileId, // ✅ Store profileId instead of deviceId
|
||||
},
|
||||
success_url: 'https://domain.com/success',
|
||||
cancel_url: 'https://domain.com/cancel',
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
paymentUrl: session.url,
|
||||
});
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={6} actor="Visitor" description="Gets redirected to payment link" icon="visitor" />
|
||||
|
||||
<FlowStep step={7} actor="Visitor" description="Pays on your payment provider" icon="payment" />
|
||||
|
||||
<FlowStep step={8} actor="Your backend" description="Receives a webhook for a successful payment" icon="backend">
|
||||
In the webhook handler, retrieve the `profileId` from the session metadata.
|
||||
|
||||
```javascript
|
||||
export async function POST(req: Request) {
|
||||
const event = await req.json();
|
||||
|
||||
// Stripe sends events with type and data.object structure
|
||||
if (event.type === 'checkout.session.completed') {
|
||||
const session = event.data.object;
|
||||
const profileId = session.metadata.profileId;
|
||||
const amount = session.amount_total;
|
||||
|
||||
op.revenue(amount, { profileId }); // ✅ Use profileId instead of deviceId
|
||||
}
|
||||
|
||||
return Response.json({ received: true });
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={9} actor="Visitor" description="Redirected to your website with payment confirmation" icon="success" isLast />
|
||||
|
||||
---
|
||||
|
||||
### Revenue tracking from your frontend
|
||||
|
||||
This flow tracks revenue directly from your frontend. Since the success page doesn't have access to the payment amount (payment happens on Stripe's side), we track revenue when checkout is initiated and then confirm it on the success page.
|
||||
|
||||
<FlowStep step={1} actor="Visitor" description="Visits your website" icon="visitor" />
|
||||
|
||||
<FlowStep step={2} actor="Visitor" description="Clicks to purchase" icon="visitor" />
|
||||
|
||||
<FlowStep step={3} actor="Your website" description="Track revenue when checkout is initiated" icon="website">
|
||||
When the visitor clicks the checkout button, track the revenue with the amount.
|
||||
|
||||
```javascript
|
||||
async function handleCheckout() {
|
||||
const amount = 2000; // Amount in cents
|
||||
|
||||
// Create a pending revenue (stored in sessionStorage)
|
||||
op.pendingRevenue(amount, {
|
||||
productId: '123',
|
||||
// ... other properties
|
||||
});
|
||||
|
||||
// Redirect to Stripe checkout
|
||||
window.location.href = 'https://checkout.stripe.com/...';
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
<FlowStep step={4} actor="Visitor" description="Gets redirected to payment link" icon="visitor" />
|
||||
|
||||
<FlowStep step={5} actor="Visitor" description="Pays on your payment provider" icon="payment" />
|
||||
|
||||
<FlowStep step={6} actor="Visitor" description="Redirected back to your success page" icon="visitor" />
|
||||
|
||||
<FlowStep step={7} actor="Your website" description="Confirm/flush the revenue on success page" icon="website" isLast>
|
||||
On your success page, flush all pending revenue events. This will send all pending revenues tracked during checkout and clear them from sessionStorage.
|
||||
|
||||
```javascript
|
||||
// Flush all pending revenues
|
||||
await op.flushRevenue();
|
||||
|
||||
// Or if you want to clear without sending (e.g., payment was cancelled)
|
||||
op.clearRevenue();
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
#### Pros:
|
||||
- Quick way to get going
|
||||
- No backend required
|
||||
- Can track revenue immediately when checkout starts
|
||||
|
||||
#### Cons:
|
||||
- Less accurate (visitor might not complete payment)
|
||||
- Less "secure" meaning anyone could post revenue data
|
||||
|
||||
---
|
||||
|
||||
### Revenue tracking without linking it to an identity or device
|
||||
|
||||
If you simply want to track revenue totals without linking payments to specific visitors or devices, you can call `op.revenue()` directly from your backend without providing a `deviceId` or `profileId`. This is the simplest approach and works well when you only need aggregate revenue data.
|
||||
|
||||
<FlowStep step={1} actor="Visitor" description="Makes a purchase" icon="visitor" />
|
||||
|
||||
<FlowStep step={2} actor="Visitor" description="Pays on your payment provider" icon="payment" />
|
||||
|
||||
<FlowStep step={3} actor="Your backend" description="Receives a webhook for a successful payment" icon="backend" isLast>
|
||||
Simply call `op.revenue()` with the amount. No `deviceId` or `profileId` is needed.
|
||||
|
||||
```javascript
|
||||
export async function POST(req: Request) {
|
||||
const event = await req.json();
|
||||
|
||||
// Stripe sends events with type and data.object structure
|
||||
if (event.type === 'checkout.session.completed') {
|
||||
const session = event.data.object;
|
||||
const amount = session.amount_total;
|
||||
|
||||
op.revenue(amount); // ✅ Simple revenue tracking without linking to a visitor
|
||||
}
|
||||
|
||||
return Response.json({ received: true });
|
||||
}
|
||||
```
|
||||
</FlowStep>
|
||||
|
||||
#### Pros:
|
||||
- Simplest implementation
|
||||
- No need to capture or pass device IDs
|
||||
- Works well for aggregate revenue tracking
|
||||
|
||||
#### Cons:
|
||||
- **You can't dive deeper into where this revenue came from.** For instance, you won't be able to see which source generates the best revenue, which campaigns are most profitable, or which visitors are your highest-value customers.
|
||||
- Revenue events won't be linked to specific user journeys or sessions
|
||||
|
||||
## Available methods
|
||||
|
||||
### Revenue
|
||||
|
||||
The revenue method will create a revenue event. It's important to know that this method will not work if your OpenPanel instance didn't receive a client secret (for security reasons). You can enable frontend revenue tracking within your project settings.
|
||||
|
||||
```javascript
|
||||
op.revenue(amount: number, properties: Record<string, unknown>): Promise<void>
|
||||
```
|
||||
|
||||
### Add a pending revenue
|
||||
|
||||
This method will create a pending revenue item and store it in sessionStorage. It will not be sent to OpenPanel until you call `flushRevenue()`. Pending revenues are automatically restored from sessionStorage when the SDK initializes.
|
||||
|
||||
```javascript
|
||||
op.pendingRevenue(amount: number, properties?: Record<string, unknown>): void
|
||||
```
|
||||
|
||||
### Send all pending revenues
|
||||
|
||||
This method will send all pending revenues to OpenPanel and then clear them from sessionStorage. Returns a Promise that resolves when all revenues have been sent.
|
||||
|
||||
```javascript
|
||||
await op.flushRevenue(): Promise<void>
|
||||
```
|
||||
|
||||
### Clear any pending revenue
|
||||
|
||||
This method will clear all pending revenues from memory and sessionStorage without sending them to OpenPanel. Useful if a payment was cancelled or you want to discard pending revenues.
|
||||
|
||||
```javascript
|
||||
op.clearRevenue(): void
|
||||
```
|
||||
|
||||
### Fetch your current users device id
|
||||
|
||||
```javascript
|
||||
op.fetchDeviceId(): Promise<string>
|
||||
```
|
||||
133
apps/public/content/docs/(tracking)/sdks/astro.mdx
Normal file
133
apps/public/content/docs/(tracking)/sdks/astro.mdx
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: Astro
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install @openpanel/astro
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
Add `OpenPanelComponent` to your root layout component.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { OpenPanelComponent } from '@openpanel/astro';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
trackScreenViews={true}
|
||||
// trackAttributes={true}
|
||||
// trackOutgoingLinks={true}
|
||||
// If you have a user id, you can pass it here to identify the user
|
||||
// profileId={'123'}
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
<WebSdkConfig />
|
||||
|
||||
##### Astro options
|
||||
|
||||
- `profileId` - If you have a user id, you can pass it here to identify the user
|
||||
- `cdnUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`)
|
||||
- `filter` - This is a function that will be called before tracking an event. If it returns false the event will not be tracked. [Read more](#filter)
|
||||
- `globalProperties` - This is an object of properties that will be sent with every event.
|
||||
|
||||
##### `filter`
|
||||
|
||||
This options needs to be a stringified function and cannot access any variables outside of the function.
|
||||
|
||||
```astro
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
filter={`
|
||||
function filter(event) {
|
||||
return event.name !== 'my_event';
|
||||
}
|
||||
`}
|
||||
/>
|
||||
```
|
||||
|
||||
To take advantage of typescript you can do the following. _Note `toString`_
|
||||
```ts
|
||||
import { type TrackHandlerPayload } from '@openpanel/astro';
|
||||
|
||||
const opFilter = ((event: TrackHandlerPayload) => {
|
||||
return event.type === 'track' && event.payload.name === 'my_event';
|
||||
}).toString();
|
||||
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
filter={opFilter}
|
||||
/>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Client-side Tracking
|
||||
|
||||
You can track events with the global op function or you can use data attributes.
|
||||
|
||||
```astro
|
||||
<button onclick="window.op('track', 'clicky')">Click me</button>
|
||||
<button data-track="clicky" data-prop1="prop1" data-prop2="prop2">Click me</button>
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user, you can use either the `identify` function or the `IdentifyComponent`.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { IdentifyComponent } from '@openpanel/astro';
|
||||
---
|
||||
|
||||
<IdentifyComponent
|
||||
profileId="123"
|
||||
firstName="Joe"
|
||||
lastName="Doe"
|
||||
email="joe@doe.com"
|
||||
properties={{
|
||||
tier: 'premium',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
You can set global properties that will be sent with every event using either the `setGlobalProperties` function or the `SetGlobalPropertiesComponent`.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { SetGlobalPropertiesComponent } from '@openpanel/astro';
|
||||
---
|
||||
|
||||
<SetGlobalPropertiesComponent
|
||||
properties={{
|
||||
app_version: '1.0.2',
|
||||
environment: 'production',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
80
apps/public/content/docs/(tracking)/sdks/express.mdx
Normal file
80
apps/public/content/docs/(tracking)/sdks/express.mdx
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Express
|
||||
description: The Express middleware is a basic wrapper around Javascript SDK. It provides a simple way to add the SDK to your Express application.
|
||||
---
|
||||
|
||||
import Link from 'next/link';
|
||||
import { DeviceIdWarning } from '@/components/device-id-warning';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm install @openpanel/express
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The default export of `@openpanel/express` is a function that returns an Express middleware. It will also append the Openpanel SDK to the `req` object.
|
||||
|
||||
You can access it via `req.op`.
|
||||
|
||||
```ts
|
||||
import express from 'express';
|
||||
|
||||
import createOpenpanelMiddleware from '@openpanel/express';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
createOpenpanelMiddleware({
|
||||
clientId: 'xxx',
|
||||
clientSecret: 'xxx',
|
||||
// trackRequest(url) {
|
||||
// return url.includes('/v1')
|
||||
// },
|
||||
// getProfileId(req) {
|
||||
// return req.user.id
|
||||
// }
|
||||
})
|
||||
);
|
||||
|
||||
app.get('/sign-up', (req, res) => {
|
||||
// track sign up events
|
||||
req.op.track('sign-up', {
|
||||
email: req.body.email,
|
||||
});
|
||||
res.send('Hello World');
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on http://localhost:3000');
|
||||
});
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
|
||||
#### Express options
|
||||
|
||||
- `trackRequest` - A function that returns `true` if the request should be tracked.
|
||||
- `getProfileId` - A function that returns the profile ID of the user making the request.
|
||||
|
||||
## Typescript
|
||||
|
||||
If `req.op` is not typed you can extend the `Request` interface.
|
||||
|
||||
```ts
|
||||
import { OpenPanel } from '@openpanel/express';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
export interface Request {
|
||||
op: OpenPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
143
apps/public/content/docs/(tracking)/sdks/index.mdx
Normal file
143
apps/public/content/docs/(tracking)/sdks/index.mdx
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: SDKs Overview
|
||||
description: Choose the right OpenPanel SDK for your project. We support web, mobile, server-side, and framework-specific integrations.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
OpenPanel provides SDKs for a wide range of platforms and frameworks, making it easy to integrate analytics into your application regardless of your tech stack.
|
||||
|
||||
## Quick Start
|
||||
|
||||
For most web projects, we recommend starting with one of these:
|
||||
|
||||
- **[Script Tag](/docs/sdks/script)** - The quickest way to get started, no build step required
|
||||
- **[Web SDK](/docs/sdks/web)** - For TypeScript support and more control
|
||||
- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js applications
|
||||
|
||||
## Web & Browser SDKs
|
||||
|
||||
### Simple Integration
|
||||
|
||||
- **[Script Tag](/docs/sdks/script)** - Add analytics with a simple `<script>` tag
|
||||
- ✅ No build step required
|
||||
- ✅ Works with any website
|
||||
- ✅ Automatic page view tracking
|
||||
|
||||
### JavaScript & TypeScript
|
||||
|
||||
- **[Web SDK](/docs/sdks/web)** - Full-featured browser SDK with TypeScript support
|
||||
- ✅ Type-safe API
|
||||
- ✅ Tree-shakeable
|
||||
- ✅ Modern browsers support
|
||||
|
||||
- **[JavaScript (Node / Generic)](/docs/sdks/javascript)** - Universal JavaScript SDK
|
||||
- ✅ Works in Node.js and browsers
|
||||
- ✅ Framework agnostic
|
||||
- ✅ Full API access
|
||||
|
||||
### Frontend Frameworks
|
||||
|
||||
- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js
|
||||
- ✅ Server and client side tracking
|
||||
- ✅ App Router support
|
||||
- ✅ Automatic route tracking
|
||||
|
||||
- **[Astro](/docs/sdks/astro)** - Astro framework integration
|
||||
- ✅ Static and SSR support
|
||||
- ✅ Component-based tracking
|
||||
- ✅ Island architecture compatible
|
||||
|
||||
- **[React](/docs/sdks/react)** - React integration
|
||||
- 📝 Use Script Tag or Web SDK for now
|
||||
|
||||
- **[Vue](/docs/sdks/vue)** - Vue.js integration
|
||||
- 📝 Use Script Tag or Web SDK for now
|
||||
|
||||
- **[Remix](/docs/sdks/remix)** - Remix framework integration
|
||||
- 📝 Use Script Tag or Web SDK for now
|
||||
|
||||
- **Svelte** - Svelte integration
|
||||
- 📝 Use [Script Tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now
|
||||
|
||||
- **Angular** - Angular integration
|
||||
- 📝 Use [Script Tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now
|
||||
|
||||
- **Solid.js** - Solid.js integration
|
||||
- 📝 Use [Script Tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now
|
||||
|
||||
- **Nuxt** - Nuxt.js integration
|
||||
- 📝 Use [Script Tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now
|
||||
|
||||
## Server-Side SDKs
|
||||
|
||||
- **[Node.js (Express)](/docs/sdks/express)** - Express.js middleware
|
||||
- ✅ Request tracking
|
||||
- ✅ Error tracking
|
||||
- ✅ Custom middleware support
|
||||
|
||||
- **[Python](/docs/sdks/python)** - Python SDK for server-side tracking
|
||||
- ✅ Thread-safe
|
||||
- ✅ Async support
|
||||
- ✅ Django and Flask compatible
|
||||
|
||||
## Mobile SDKs
|
||||
|
||||
- **[React Native](/docs/sdks/react-native)** - Cross-platform mobile analytics
|
||||
- ✅ iOS and Android support
|
||||
- ✅ Native performance
|
||||
- ✅ Offline support
|
||||
|
||||
- **[Swift](/docs/sdks/swift)** - Native iOS, macOS, tvOS, and watchOS SDK
|
||||
- ✅ Apple platform support
|
||||
- ✅ Swift Package Manager
|
||||
- ✅ Thread-safe API
|
||||
- ✅ Automatic lifecycle tracking
|
||||
|
||||
- **[Kotlin / Android](/docs/sdks/kotlin)** - Native Android SDK
|
||||
- ✅ Android support
|
||||
- ✅ System information collection
|
||||
- ✅ Thread-safe API
|
||||
- ⚠️ Not yet published to Maven
|
||||
|
||||
## Need Help Choosing?
|
||||
|
||||
<Callout>
|
||||
Not sure which SDK to use? Here are some recommendations:
|
||||
|
||||
- **Static website or HTML?** → Use [Script Tag](/docs/sdks/script)
|
||||
- **React app?** → Use [Web SDK](/docs/sdks/web) or [Script Tag](/docs/sdks/script)
|
||||
- **Next.js app?** → Use [Next.js SDK](/docs/sdks/nextjs)
|
||||
- **Server-side tracking?** → Use [Python](/docs/sdks/python) or [Node](/docs/sdks/javascript)
|
||||
- **Mobile app?** → Use [React Native](/docs/sdks/react-native), [Swift](/docs/sdks/swift), or [Kotlin](/docs/sdks/kotlin)
|
||||
</Callout>
|
||||
|
||||
## Core Features
|
||||
|
||||
All OpenPanel SDKs support these core features:
|
||||
|
||||
### Event Tracking
|
||||
Track custom events with properties to understand user behavior.
|
||||
|
||||
### User Identification
|
||||
Associate events with specific users and build user profiles.
|
||||
|
||||
### Global Properties
|
||||
Set properties that are automatically included with every event.
|
||||
|
||||
### Increment/Decrement
|
||||
Update numeric properties on user profiles.
|
||||
|
||||
### Privacy First
|
||||
All SDKs are built with privacy in mind - no cookies required, GDPR compliant.
|
||||
|
||||
## API Reference
|
||||
|
||||
For advanced integrations, check out our [API documentation](/docs/api/track) to track events directly via HTTP requests.
|
||||
|
||||
## Community & Support
|
||||
|
||||
- Join our [Discord community](https://go.openpanel.dev/discord)
|
||||
- Report issues on [GitHub](https://github.com/Openpanel-dev/openpanel/issues)
|
||||
- Email us at [hello@openpanel.dev](mailto:hello@openpanel.dev)
|
||||
|
||||
127
apps/public/content/docs/(tracking)/sdks/javascript.mdx
Normal file
127
apps/public/content/docs/(tracking)/sdks/javascript.mdx
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Javascript (Node / Generic)
|
||||
description: The OpenPanel Web SDK allows you to track user behavior on your website using a simple script tag. This guide provides instructions for installing and using the Web SDK in your project.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Step 1: Install
|
||||
|
||||
```bash
|
||||
npm install @openpanel/sdk
|
||||
```
|
||||
|
||||
### Step 2: Initialize
|
||||
|
||||
```js title="op.ts"
|
||||
import { OpenPanel } from '@openpanel/sdk';
|
||||
|
||||
const op = new OpenPanel({
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
clientSecret: 'YOUR_CLIENT_SECRET',
|
||||
});
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
|
||||
### Step 3: Usage
|
||||
|
||||
```js title="main.ts"
|
||||
import { op } from './op.js';
|
||||
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Tracking Events
|
||||
|
||||
You can track events with two different methods: by calling the `op.track( directly or by adding `data-track` attributes to your HTML elements.
|
||||
|
||||
```ts title="index.ts"
|
||||
import { op } from './op.ts';
|
||||
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user, call the `op.identify( method with a unique identifier.
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts';
|
||||
|
||||
op.identify({
|
||||
profileId: '123', // Required
|
||||
firstName: 'Joe',
|
||||
lastName: 'Doe',
|
||||
email: 'joe@doe.com',
|
||||
properties: {
|
||||
tier: 'premium',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.setGlobalProperties({
|
||||
app_version: '1.0.2',
|
||||
environment: 'production',
|
||||
});
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.increment({
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.decrement({
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.clear()
|
||||
```
|
||||
205
apps/public/content/docs/(tracking)/sdks/kotlin.mdx
Normal file
205
apps/public/content/docs/(tracking)/sdks/kotlin.mdx
Normal file
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: Kotlin / Android
|
||||
description: The OpenPanel Kotlin SDK allows you to track user behavior in your Kotlin and Android applications.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
|
||||
The OpenPanel Kotlin SDK allows you to track user behavior in your Kotlin applications. This guide provides instructions for installing and using the Kotlin SDK in your project.
|
||||
|
||||
## Installation
|
||||
|
||||
<Callout type="warn">
|
||||
This package is not yet published. So you cannot install it with `gradle` yet.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
### Step 1: Add Dependency
|
||||
|
||||
Add the OpenPanel SDK to your project's dependencies:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation 'dev.openpanel:openpanel:0.0.1'
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Initialize
|
||||
|
||||
First, import the SDK and initialize it with your client ID:
|
||||
|
||||
```kotlin
|
||||
import dev.openpanel.OpenPanel
|
||||
|
||||
val op = OpenPanel.create(
|
||||
context,
|
||||
OpenPanel.Options(
|
||||
clientId = "YOUR_CLIENT_ID",
|
||||
clientSecret = "YOUR_CLIENT_SECRET"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
#### `context`
|
||||
|
||||
- **Type**: `Context`
|
||||
- **Required**: Yes
|
||||
- **Description**: Android `Context` used for initializing the SDK.
|
||||
|
||||
#### `Options`
|
||||
|
||||
<CommonSdkConfig />
|
||||
|
||||
Additional Kotlin-specific options:
|
||||
|
||||
- `filter` - A function that will be called before tracking an event. If it returns false, the event will not be tracked
|
||||
- `disabled` - Set to `true` to disable all event tracking
|
||||
- `automaticTracking` - Set to `true` to automatically track app lifecycle events
|
||||
- `verbose` - Set to `true` to enable verbose logging
|
||||
|
||||
#### Filter Example
|
||||
|
||||
```kotlin
|
||||
val op = OpenPanel.create(
|
||||
context,
|
||||
OpenPanel.Options(
|
||||
clientId = "YOUR_CLIENT_ID",
|
||||
filter = { payload ->
|
||||
// Your custom filtering logic here
|
||||
true // or false to filter out the event
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Tracking Events
|
||||
|
||||
To track an event:
|
||||
|
||||
```kotlin
|
||||
op.track("button_clicked", mapOf("button_id" to "submit_form"))
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user:
|
||||
|
||||
```kotlin
|
||||
op.identify("user123", mapOf(
|
||||
"firstName" to "John",
|
||||
"lastName" to "Doe",
|
||||
"email" to "john@example.com",
|
||||
"customAttribute" to "value"
|
||||
))
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```kotlin
|
||||
op.setGlobalProperties(mapOf(
|
||||
"app_version" to "1.0.2",
|
||||
"environment" to "production"
|
||||
))
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property on a user profile:
|
||||
|
||||
```kotlin
|
||||
op.increment("user123", "login_count", 1)
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property on a user profile:
|
||||
|
||||
```kotlin
|
||||
op.decrement("user123", "credits", 5)
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
|
||||
```kotlin
|
||||
op.clear()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Event Filtering
|
||||
|
||||
You can set up custom event filtering:
|
||||
|
||||
```kotlin
|
||||
val op = OpenPanel.create(
|
||||
context,
|
||||
OpenPanel.Options(
|
||||
clientId = "YOUR_CLIENT_ID",
|
||||
filter = { payload ->
|
||||
// Your custom filtering logic here
|
||||
true // or false to filter out the event
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Disabling Tracking
|
||||
|
||||
You can temporarily disable tracking:
|
||||
|
||||
```kotlin
|
||||
val op = OpenPanel.create(
|
||||
context,
|
||||
OpenPanel.Options(
|
||||
clientId = "YOUR_CLIENT_ID",
|
||||
disabled = true
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Automatic Tracking
|
||||
|
||||
The SDK can automatically track app lifecycle events if `automaticTracking` is set to `true`. This will track "app_opened" and "app_closed" events:
|
||||
|
||||
```kotlin
|
||||
val op = OpenPanel.create(
|
||||
context,
|
||||
OpenPanel.Options(
|
||||
clientId = "YOUR_CLIENT_ID",
|
||||
automaticTracking = true
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### System Information
|
||||
|
||||
The SDK automatically gathers system information and adds it to the properties of every tracking event. This includes:
|
||||
|
||||
- OS details (e.g., `os`, `os_version`)
|
||||
- Device manufacturer, brand, and model (e.g., `manufacturer`, `brand`, `model`)
|
||||
- Screen resolution and DPI (e.g., `screen_width`, `screen_height`, `screen_dpi`)
|
||||
- App version (e.g., `app_version`, `app_build_number`)
|
||||
- Network details (e.g., `wifi`, `carrier`, `bluetooth_enabled`)
|
||||
|
||||
## Thread Safety
|
||||
|
||||
<Callout>
|
||||
The OpenPanel SDK is designed to be thread-safe. You can call its methods from any thread without additional synchronization.
|
||||
</Callout>
|
||||
|
||||
## Support
|
||||
|
||||
For any issues or feature requests, please file an issue on our [GitHub repository](https://github.com/Openpanel-dev/openpanel/issues).
|
||||
|
||||
20
apps/public/content/docs/(tracking)/sdks/meta.json
Normal file
20
apps/public/content/docs/(tracking)/sdks/meta.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "SDKs",
|
||||
"pages": [
|
||||
"script",
|
||||
"web",
|
||||
"javascript",
|
||||
"nextjs",
|
||||
"react",
|
||||
"vue",
|
||||
"astro",
|
||||
"remix",
|
||||
"express",
|
||||
"python",
|
||||
"react-native",
|
||||
"swift",
|
||||
"kotlin",
|
||||
"..."
|
||||
],
|
||||
"defaultOpen": false
|
||||
}
|
||||
294
apps/public/content/docs/(tracking)/sdks/nextjs.mdx
Normal file
294
apps/public/content/docs/(tracking)/sdks/nextjs.mdx
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
title: Next.js
|
||||
---
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
|
||||
import { DeviceIdWarning } from '@/components/device-id-warning';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
||||
|
||||
## Good to know
|
||||
|
||||
Keep in mind that all tracking here happens on the client!
|
||||
|
||||
Read more about server side tracking in the [Server Side Tracking](#track-server-events) section.
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install @openpanel/nextjs
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
Add `OpenPanelComponent` to your root layout component.
|
||||
|
||||
```tsx
|
||||
import { OpenPanelComponent } from '@openpanel/nextjs';
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
trackScreenViews={true}
|
||||
// trackAttributes={true}
|
||||
// trackOutgoingLinks={true}
|
||||
// If you have a user id, you can pass it here to identify the user
|
||||
// profileId={'123'}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
<WebSdkConfig />
|
||||
|
||||
##### NextJS options
|
||||
|
||||
- `profileId` - If you have a user id, you can pass it here to identify the user
|
||||
- `cdnUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`)
|
||||
- `filter` - This is a function that will be called before tracking an event. If it returns false the event will not be tracked. [Read more](#filter)
|
||||
- `globalProperties` - This is an object of properties that will be sent with every event.
|
||||
|
||||
##### `filter`
|
||||
|
||||
This options needs to be a stringified function and cannot access any variables outside of the function.
|
||||
|
||||
```tsx
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
filter={`
|
||||
function filter(event) {
|
||||
return event.name !== 'my_event';
|
||||
}
|
||||
`}
|
||||
/>
|
||||
```
|
||||
|
||||
To take advantage of typescript you can do the following. _Note `toString`_
|
||||
```tsx /.toString();/
|
||||
import { type TrackHandlerPayload } from '@openpanel/nextjs';
|
||||
|
||||
const opFilter = ((event: TrackHandlerPayload) => {
|
||||
return event.type === 'track' && event.payload.name === 'my_event';
|
||||
}).toString();
|
||||
|
||||
<OpenPanelComponent
|
||||
clientId="your-client-id"
|
||||
filter={opFilter}
|
||||
/>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Client components
|
||||
|
||||
For client components you can just use the `useOpenPanel` hook.
|
||||
|
||||
```tsx
|
||||
import { useOpenPanel } from '@openpanel/nextjs';
|
||||
|
||||
function YourComponent() {
|
||||
const op = useOpenPanel();
|
||||
|
||||
return <button type="button" onClick={() => op.track('my_event', { foo: 'bar' })}>Trigger event</button>
|
||||
}
|
||||
```
|
||||
|
||||
### Server components
|
||||
|
||||
Since you can't use hooks in server components, you need to create an instance of the SDK. This is exported from `@openpanel/nextjs`.
|
||||
|
||||
<Callout>Remember, your client secret is exposed here so do not use this on client side.</Callout>
|
||||
|
||||
```tsx title="utils/op.ts"
|
||||
import { OpenPanel } from '@openpanel/nextjs';
|
||||
|
||||
export const op = new OpenPanel({
|
||||
clientId: 'your-client-id',
|
||||
clientSecret: 'your-client-secret',
|
||||
});
|
||||
|
||||
// Now you can use `op` to track events
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
|
||||
Refer to the [Javascript SDK](/docs/sdks/javascript#usage) for usage instructions.
|
||||
|
||||
### Tracking Events
|
||||
|
||||
You can track events with two different methods: by calling the `op.track( directly or by adding `data-track` attributes to your HTML elements.
|
||||
|
||||
```ts title="index.ts"
|
||||
useOpenPanel().track('my_event', { foo: 'bar' });
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user, call the `op.identify( method with a unique identifier.
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().identify({
|
||||
profileId: '123', // Required
|
||||
firstName: 'Joe',
|
||||
lastName: 'Doe',
|
||||
email: 'joe@doe.com',
|
||||
properties: {
|
||||
tier: 'premium',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### For server components
|
||||
|
||||
For server components you can use the `IdentifyComponent` component which is exported from `@openpanel/nextjs`.
|
||||
|
||||
> This component is great if you have the user data available on the server side.
|
||||
|
||||
```tsx title="app/nested/layout.tsx"
|
||||
import { IdentifyComponent } from '@openpanel/nextjs';
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
return (
|
||||
<>
|
||||
<IdentifyComponent
|
||||
profileId={user.id}
|
||||
firstName={user.firstName}
|
||||
lastName={user.lastName}
|
||||
email={user.email}
|
||||
properties={{
|
||||
tier: 'premium',
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().setGlobalProperties({
|
||||
app_version: '1.0.2',
|
||||
environment: 'production',
|
||||
});
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().increment({
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().decrement({
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().clear()
|
||||
```
|
||||
|
||||
## Server side
|
||||
|
||||
If you want to track server-side events, you should create an instance of our Javascript SDK. It's exported from `@openpanel/nextjs`
|
||||
|
||||
<Callout>
|
||||
When using server events it's important that you use a secret to authenticate the request. This is to prevent unauthorized requests since we cannot use cors headers.
|
||||
|
||||
You can use the same clientId but you should pass the associated client secret to the SDK.
|
||||
|
||||
</Callout>
|
||||
|
||||
```typescript
|
||||
import { OpenpanelSdk } from '@openpanel/nextjs';
|
||||
|
||||
const opServer = new OpenpanelSdk({
|
||||
clientId: '{YOUR_CLIENT_ID}',
|
||||
clientSecret: '{YOUR_CLIENT_SECRET}',
|
||||
});
|
||||
|
||||
opServer.event('my_server_event', { ok: '✅' });
|
||||
|
||||
// Pass `profileId` to track events for a specific user
|
||||
opServer.event('my_server_event', { profileId: '123', ok: '✅' });
|
||||
```
|
||||
|
||||
### Serverless & Vercel
|
||||
|
||||
If you log events in a serverless environment like Vercel, you can use `waitUntil` to ensure the event is logged before the function is done.
|
||||
|
||||
Otherwise your function might close before the event is logged. Read more about it [here](https://vercel.com/docs/functions/functions-api-reference#waituntil).
|
||||
|
||||
```typescript
|
||||
import { waitUntil } from '@vercel/functions';
|
||||
import { opServer } from 'path/to/your-sdk-instance';
|
||||
|
||||
export function GET() {
|
||||
// Returns a response immediately while keeping the function alive
|
||||
waitUntil(opServer.event('my_server_event', { foo: 'bar' }));
|
||||
return new Response(`You're event has been logged!`);
|
||||
}
|
||||
```
|
||||
|
||||
### Proxy events
|
||||
|
||||
With `createNextRouteHandler` you can proxy your events through your server, this will ensure all events are tracked since there is a lot of adblockers that block requests to third party domains. You'll also need to either host our tracking script or you can use `createScriptHandler` function which proxies this as well.
|
||||
|
||||
```typescript title="/app/api/[...op]/route.ts"
|
||||
import { createNextRouteHandler, createScriptHandler } from '@openpanel/nextjs/server';
|
||||
|
||||
export const POST = createNextRouteHandler();
|
||||
export const GET = createScriptHandler()
|
||||
```
|
||||
|
||||
Remember to change the `apiUrl` and `cdnUrl` in the `OpenPanelComponent` to your own server.
|
||||
|
||||
```tsx
|
||||
<OpenPanelComponent
|
||||
apiUrl="/api/op" // [!code highlight]
|
||||
cdnUrl="/api/op/op1.js" // [!code highlight]
|
||||
clientId="your-client-id"
|
||||
trackScreenViews={true}
|
||||
/>
|
||||
```
|
||||
176
apps/public/content/docs/(tracking)/sdks/python.mdx
Normal file
176
apps/public/content/docs/(tracking)/sdks/python.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: Python
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
|
||||
The OpenPanel Python SDK allows you to track user behavior in your Python applications. This guide provides instructions for installing and using the Python SDK in your project.
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pip install openpanel
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
Import and initialize the OpenPanel SDK with your credentials:
|
||||
|
||||
```python
|
||||
from openpanel import OpenPanel
|
||||
|
||||
op = OpenPanel(
|
||||
client_id="YOUR_CLIENT_ID",
|
||||
client_secret="YOUR_CLIENT_SECRET"
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
|
||||
Additional Python-specific options:
|
||||
|
||||
- `filter` - A function that will be called before tracking an event. If it returns false the event will not be tracked
|
||||
- `disabled` - Set to `True` to disable all event tracking
|
||||
- `global_properties` - Dictionary of properties that will be sent with every event
|
||||
|
||||
#### Filter Function Example
|
||||
|
||||
```python
|
||||
def my_filter(event):
|
||||
# Skip events named 'my_event'
|
||||
return event.get('name') != 'my_event'
|
||||
|
||||
op = OpenPanel(
|
||||
client_id="YOUR_CLIENT_ID",
|
||||
client_secret="YOUR_CLIENT_SECRET",
|
||||
filter=my_filter
|
||||
)
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Tracking Events
|
||||
|
||||
To track an event, use the `track` method:
|
||||
|
||||
```python
|
||||
# Track a simple event
|
||||
op.track("button_clicked")
|
||||
|
||||
# Track with properties
|
||||
op.track("purchase_completed", {
|
||||
"product_id": "123",
|
||||
"price": 99.99,
|
||||
"currency": "USD"
|
||||
})
|
||||
|
||||
# Track for a specific user
|
||||
op.track("login_successful", {
|
||||
"method": "google"
|
||||
}, profile_id="user_123")
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user, use the `identify` method:
|
||||
|
||||
```python
|
||||
op.identify({
|
||||
"profile_id": "123", # Required
|
||||
"first_name": "Joe",
|
||||
"last_name": "Doe",
|
||||
"email": "joe@doe.com",
|
||||
"properties": {
|
||||
"tier": "premium",
|
||||
"company": "Acme Inc"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```python
|
||||
op.set_global_properties({
|
||||
"app_version": "1.0.2",
|
||||
"environment": "production",
|
||||
"deployment": "us-east-1"
|
||||
})
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property on a user profile:
|
||||
|
||||
```python
|
||||
op.increment({
|
||||
"profile_id": "1",
|
||||
"property": "visits",
|
||||
"value": 1 # optional, defaults to 1
|
||||
})
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property on a user profile:
|
||||
|
||||
```python
|
||||
op.decrement({
|
||||
"profile_id": "1",
|
||||
"property": "credits",
|
||||
"value": 1 # optional, defaults to 1
|
||||
})
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
|
||||
```python
|
||||
op.clear()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Thread Safety
|
||||
|
||||
<Callout>
|
||||
The OpenPanel SDK is thread-safe. You can safely use a single instance across multiple threads in your application.
|
||||
</Callout>
|
||||
|
||||
### Error Handling
|
||||
|
||||
The SDK includes built-in error handling and will not raise exceptions during normal operation. However, you can wrap SDK calls in try-except blocks for additional safety:
|
||||
|
||||
```python
|
||||
try:
|
||||
op.track("important_event", {"critical": True})
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to track event: {e}")
|
||||
```
|
||||
|
||||
### Disabling Tracking
|
||||
|
||||
You can temporarily disable all tracking:
|
||||
|
||||
```python
|
||||
# Disable during initialization
|
||||
op = OpenPanel(
|
||||
client_id="YOUR_CLIENT_ID",
|
||||
client_secret="YOUR_CLIENT_SECRET",
|
||||
disabled=True
|
||||
)
|
||||
|
||||
# Or disable after initialization
|
||||
op.disabled = True
|
||||
```
|
||||
115
apps/public/content/docs/(tracking)/sdks/react-native.mdx
Normal file
115
apps/public/content/docs/(tracking)/sdks/react-native.mdx
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: React Native
|
||||
---
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { DeviceIdWarning } from '@/components/device-id-warning';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Install dependencies
|
||||
|
||||
We're dependent on `expo-application` for `buildNumber`, `versionNumber` (and `referrer` on android) and `expo-constants` to get the `user-agent`.
|
||||
|
||||
```bash
|
||||
npm install @openpanel/react-native
|
||||
npx expo install expo-application expo-constants
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
On native we use a clientSecret to authenticate the app.
|
||||
|
||||
```typescript
|
||||
const op = new Openpanel({
|
||||
clientId: '{YOUR_CLIENT_ID}',
|
||||
clientSecret: '{YOUR_CLIENT_SECRET}',
|
||||
});
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Track event
|
||||
|
||||
```typescript
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
|
||||
### Navigation / Screen views
|
||||
|
||||
<Tabs items={['expo-router', 'react-navigation (simple)']}>
|
||||
<Tab value="expo-router">
|
||||
```typescript
|
||||
import { usePathname, useSegments } from 'expo-router';
|
||||
|
||||
const op = new Openpanel({ /* ... */ })
|
||||
|
||||
function RootLayout() {
|
||||
// ...
|
||||
const pathname = usePathname()
|
||||
// Segments is optional but can be nice to have if you
|
||||
// want to group routes together
|
||||
// pathname = /posts/123
|
||||
// segements = ['posts', '[id]']
|
||||
const segments = useSegments()
|
||||
|
||||
useEffect(() => {
|
||||
// Simple
|
||||
op.screenView(pathname)
|
||||
|
||||
// With extra data
|
||||
op.screenView(pathname, {
|
||||
// segments is optional but nice to have
|
||||
segments: segments.join('/'),
|
||||
// other optional data you want to send with the screen view
|
||||
})
|
||||
}, [pathname,segments])
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="react-navigation (simple)">
|
||||
```tsx
|
||||
import { createNavigationContainerRef } from '@react-navigation/native'
|
||||
import { Openpanel } from '@openpanel/react-native'
|
||||
|
||||
const op = new Openpanel({ /* ... */ })
|
||||
const navigationRef = createNavigationContainerRef()
|
||||
|
||||
export function NavigationRoot() {
|
||||
const handleNavigationStateChange = () => {
|
||||
const current = navigationRef.getCurrentRoute()
|
||||
if (current) {
|
||||
op.screenView(current.name, {
|
||||
params: current.params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
onReady={handleNavigationStateChange}
|
||||
onStateChange={handleNavigationStateChange}
|
||||
>
|
||||
<Stack.Navigator />
|
||||
</NavigationContainer>
|
||||
)
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For more information on how to use the SDK, check out the [Javascript SDK](/docs/sdks/javascript#usage).
|
||||
5
apps/public/content/docs/(tracking)/sdks/react.mdx
Normal file
5
apps/public/content/docs/(tracking)/sdks/react.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: React
|
||||
---
|
||||
|
||||
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated react sdk soon.
|
||||
5
apps/public/content/docs/(tracking)/sdks/remix.mdx
Normal file
5
apps/public/content/docs/(tracking)/sdks/remix.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Remix
|
||||
---
|
||||
|
||||
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated remix sdk soon.
|
||||
200
apps/public/content/docs/(tracking)/sdks/script.mdx
Normal file
200
apps/public/content/docs/(tracking)/sdks/script.mdx
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
title: Script Tag
|
||||
description: The OpenPanel Web SDK allows you to track user behavior on your website using a simple script tag. This guide provides instructions for installing and using the Web SDK in your project.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Just insert this snippet and replace `YOUR_CLIENT_ID` with your client id.
|
||||
|
||||
```html title="index.html" /clientId: 'YOUR_CLIENT_ID'/
|
||||
<script>
|
||||
window.op=window.op||function(){var n=[],o=new Proxy((function(){arguments.length>0&&n.push(Array.prototype.slice.call(arguments))}),{get:function(o,t){return"q"===t?n:function(){n.push([t].concat(Array.prototype.slice.call(arguments)))}}});return o}();
|
||||
window.op('init', {
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
</script>
|
||||
<script src="https://openpanel.dev/op1.js" defer async></script>
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
<WebSdkConfig />
|
||||
|
||||
## Usage
|
||||
|
||||
### Tracking Events
|
||||
|
||||
You can track events with two different methods: by calling the `window.op('track')` directly or by adding `data-track` attributes to your HTML elements.
|
||||
|
||||
```html title="index.html"
|
||||
<button type="button" onclick="window.op('track', 'my_event', { foo: 'bar' })">
|
||||
Track event
|
||||
</button>
|
||||
```
|
||||
|
||||
```html title="index.html"
|
||||
<button type="button" data-track="my_event" data-foo="bar">Track event</button>
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user, call the `window.op('identify')` method with a unique identifier.
|
||||
|
||||
```js title="main.js"
|
||||
window.op('identify', {
|
||||
profileId: '123', // Required
|
||||
firstName: 'Joe',
|
||||
lastName: 'Doe',
|
||||
email: 'joe@doe.com',
|
||||
properties: {
|
||||
tier: 'premium',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```js title="main.js"
|
||||
window.op('setGlobalProperties', {
|
||||
app_version: '1.0.2',
|
||||
environment: 'production',
|
||||
});
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to increment the property by. If not provided, the property will be incremented by 1.
|
||||
|
||||
```js title="main.js"
|
||||
window.op('increment', {
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property on a user profile.
|
||||
|
||||
- `value` is the amount to decrement the property by. If not provided, the property will be decremented by 1.
|
||||
|
||||
```js title="main.js"
|
||||
window.op('decrement', {
|
||||
profileId: '1',
|
||||
property: 'visits',
|
||||
value: 1 // optional
|
||||
});
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
|
||||
```js title="main.js"
|
||||
window.op('clear');
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Filtering events
|
||||
|
||||
You can filter out events by adding a `filter` property to the `init` method.
|
||||
|
||||
Below is an example of how to disable tracking for users who have a `disable_tracking` item in their local storage.
|
||||
|
||||
```js title="main.js"
|
||||
window.op('init', {
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
filter: () => localStorage.getItem('disable_tracking') === undefined,
|
||||
});
|
||||
```
|
||||
|
||||
### Using the Web SDK with NPM
|
||||
|
||||
<Steps>
|
||||
#### Step 1: Install the SDK
|
||||
|
||||
```bash
|
||||
npm install @openpanel/web
|
||||
```
|
||||
|
||||
#### Step 2: Initialize the SDK
|
||||
|
||||
```js title="op.js"
|
||||
import { OpenPanel } from '@openpanel/web';
|
||||
|
||||
const op = new OpenPanel({
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
```
|
||||
|
||||
#### Step 3: Use the SDK
|
||||
|
||||
```js title="main.js"
|
||||
import { op } from './op.js';
|
||||
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
</Steps>
|
||||
|
||||
### Typescript
|
||||
|
||||
Getting ts errors when using the SDK? You can add a custom type definition file to your project.
|
||||
|
||||
#### Simple
|
||||
|
||||
Just paste this code in any of your `.d.ts` files.
|
||||
|
||||
```ts title="op.d.ts"
|
||||
declare global {
|
||||
interface Window {
|
||||
op: {
|
||||
q?: string[][];
|
||||
(...args: [
|
||||
'init' | 'track' | 'identify' | 'setGlobalProperties' | 'increment' | 'decrement' | 'clear',
|
||||
...any[]
|
||||
]): void;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Strict typing (from sdk)
|
||||
|
||||
<Steps>
|
||||
##### Step 1: Install the SDK
|
||||
|
||||
```bash
|
||||
npm install @openpanel/web
|
||||
```
|
||||
|
||||
##### Step 2: Create a type definition file
|
||||
|
||||
Create a `op.d.ts`file and paste the following code:
|
||||
|
||||
```ts title="op.d.ts"
|
||||
/// <reference types="@openpanel/web" />
|
||||
```
|
||||
</Steps>
|
||||
183
apps/public/content/docs/(tracking)/sdks/swift.mdx
Normal file
183
apps/public/content/docs/(tracking)/sdks/swift.mdx
Normal file
@@ -0,0 +1,183 @@
|
||||
---
|
||||
title: Swift
|
||||
description: The OpenPanel Swift SDK allows you to integrate OpenPanel analytics into your iOS, macOS, tvOS, and watchOS applications.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
|
||||
The OpenPanel Swift SDK allows you to integrate OpenPanel analytics into your iOS, macOS, tvOS, and watchOS applications.
|
||||
|
||||
## Features
|
||||
|
||||
- Easy-to-use API for tracking events and user properties
|
||||
- Automatic collection of app states
|
||||
- Support for custom event properties
|
||||
- Shared instance for easy access throughout your app
|
||||
|
||||
## Requirements
|
||||
|
||||
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
|
||||
- Xcode 12.0+
|
||||
- Swift 5.3+
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Step 1: Add Package via Swift Package Manager
|
||||
|
||||
You can add OpenPanel to an Xcode project by adding it as a package dependency.
|
||||
|
||||
1. From the **File** menu, select **Add Packages...**
|
||||
2. Enter `https://github.com/Openpanel-dev/swift-sdk` into the package repository URL text field
|
||||
3. Click **Add Package**
|
||||
|
||||
Alternatively, if you have a `Package.swift` file, you can add OpenPanel as a dependency:
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Openpanel-dev/swift-sdk")
|
||||
]
|
||||
```
|
||||
|
||||
### Step 2: Import and Initialize
|
||||
|
||||
First, import the SDK in your Swift file:
|
||||
|
||||
```swift
|
||||
import OpenPanel
|
||||
```
|
||||
|
||||
Then, initialize the OpenPanel SDK with your client ID:
|
||||
|
||||
```swift
|
||||
OpenPanel.initialize(options: .init(
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET"
|
||||
))
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
|
||||
Additional Swift-specific options:
|
||||
|
||||
- `filter` - A closure that will be called before tracking an event. If it returns false, the event will not be tracked
|
||||
- `disabled` - Set to `true` to disable all event tracking
|
||||
- `automaticTracking` - Set to `true` to automatically track app lifecycle events
|
||||
|
||||
#### Filter Example
|
||||
|
||||
```swift
|
||||
OpenPanel.initialize(options: .init(
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
filter: { payload in
|
||||
// Your custom filtering logic here
|
||||
return true // or false to filter out the event
|
||||
}
|
||||
))
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
### Tracking Events
|
||||
|
||||
To track an event:
|
||||
|
||||
```swift
|
||||
OpenPanel.track(name: "Button Clicked", properties: ["button_id": "submit_form"])
|
||||
```
|
||||
|
||||
### Identifying Users
|
||||
|
||||
To identify a user:
|
||||
|
||||
```swift
|
||||
OpenPanel.identify(payload: IdentifyPayload(
|
||||
profileId: "user123",
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
email: "john@example.com",
|
||||
properties: ["subscription": "premium"]
|
||||
))
|
||||
```
|
||||
|
||||
### Setting Global Properties
|
||||
|
||||
To set properties that will be sent with every event:
|
||||
|
||||
```swift
|
||||
OpenPanel.setGlobalProperties([
|
||||
"app_version": "1.0.2",
|
||||
"environment": "production"
|
||||
])
|
||||
```
|
||||
|
||||
### Incrementing Properties
|
||||
|
||||
To increment a numeric property:
|
||||
|
||||
```swift
|
||||
OpenPanel.increment(payload: IncrementPayload(profileId: "user123", property: "login_count"))
|
||||
```
|
||||
|
||||
### Decrementing Properties
|
||||
|
||||
To decrement a numeric property:
|
||||
|
||||
```swift
|
||||
OpenPanel.decrement(payload: DecrementPayload(profileId: "user123", property: "credits_remaining"))
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Disabling Tracking
|
||||
|
||||
You can temporarily disable tracking during initialization:
|
||||
|
||||
```swift
|
||||
OpenPanel.initialize(options: .init(
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
disabled: true
|
||||
))
|
||||
```
|
||||
|
||||
### Custom Event Filtering
|
||||
|
||||
You can set up custom event filtering during initialization:
|
||||
|
||||
```swift
|
||||
OpenPanel.initialize(options: .init(
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
filter: { payload in
|
||||
// Your custom filtering logic here
|
||||
return true // or false to filter out the event
|
||||
}
|
||||
))
|
||||
```
|
||||
|
||||
### Automatic Tracking
|
||||
|
||||
The SDK automatically tracks app lifecycle events (`app_opened` and `app_closed`) if `automaticTracking` is set to `true` during initialization:
|
||||
|
||||
```swift
|
||||
OpenPanel.initialize(options: .init(
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
clientSecret: "YOUR_CLIENT_SECRET",
|
||||
automaticTracking: true
|
||||
))
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
<Callout>
|
||||
The OpenPanel SDK is designed to be thread-safe. You can call its methods from any thread without additional synchronization.
|
||||
</Callout>
|
||||
|
||||
5
apps/public/content/docs/(tracking)/sdks/vue.mdx
Normal file
5
apps/public/content/docs/(tracking)/sdks/vue.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Vue
|
||||
---
|
||||
|
||||
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated vue sdk soon.
|
||||
49
apps/public/content/docs/(tracking)/sdks/web.mdx
Normal file
49
apps/public/content/docs/(tracking)/sdks/web.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Javascript (Web)
|
||||
description: The OpenPanel Web SDK allows you to track user behavior on your website using a simple script tag. This guide provides instructions for installing and using the Web SDK in your project.
|
||||
---
|
||||
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
### Step 1: Install
|
||||
|
||||
```bash
|
||||
npm install @openpanel/web
|
||||
```
|
||||
|
||||
### Step 2: Initialize
|
||||
|
||||
```js title="op.ts"
|
||||
import { OpenPanel } from '@openpanel/web';
|
||||
|
||||
const op = new OpenPanel({
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
<CommonSdkConfig />
|
||||
<WebSdkConfig />
|
||||
|
||||
### Step 3: Usage
|
||||
|
||||
```js title="main.ts"
|
||||
import { op } from './op.js';
|
||||
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
</Steps>
|
||||
|
||||
## Usage
|
||||
|
||||
Refer to the [Javascript SDK](/docs/sdks/javascript#usage) for usage instructions.
|
||||
Reference in New Issue
Block a user