Compare commits
1 Commits
feature/nu
...
feature/gu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc3d7b7ea8 |
@@ -1,256 +0,0 @@
|
|||||||
---
|
|
||||||
title: Nuxt
|
|
||||||
---
|
|
||||||
|
|
||||||
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 { Callout } from 'fumadocs-ui/components/callout';
|
|
||||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
|
||||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
|
||||||
|
|
||||||
<Callout>
|
|
||||||
Looking for a step-by-step tutorial? Check out the [Nuxt analytics guide](/guides/nuxt-analytics).
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## 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/nuxt
|
|
||||||
```
|
|
||||||
|
|
||||||
### Initialize
|
|
||||||
|
|
||||||
Add the module to your `nuxt.config.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export default defineNuxtConfig({
|
|
||||||
modules: ['@openpanel/nuxt'],
|
|
||||||
openpanel: {
|
|
||||||
clientId: 'your-client-id',
|
|
||||||
trackScreenViews: true,
|
|
||||||
trackOutgoingLinks: true,
|
|
||||||
trackAttributes: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
<CommonSdkConfig />
|
|
||||||
<WebSdkConfig />
|
|
||||||
|
|
||||||
##### Nuxt options
|
|
||||||
|
|
||||||
- `clientId` - Your OpenPanel client ID (required)
|
|
||||||
- `apiUrl` - The API URL to send events to (default: `https://api.openpanel.dev`)
|
|
||||||
- `trackScreenViews` - Automatically track screen views (default: `true`)
|
|
||||||
- `trackOutgoingLinks` - Automatically track outgoing links (default: `true`)
|
|
||||||
- `trackAttributes` - Automatically track elements with `data-track` attributes (default: `true`)
|
|
||||||
- `trackHashChanges` - Track hash changes in URL (default: `false`)
|
|
||||||
- `disabled` - Disable tracking (default: `false`)
|
|
||||||
- `proxy` - Enable server-side proxy to avoid adblockers (default: `false`)
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Using the composable
|
|
||||||
|
|
||||||
The `useOpenPanel` composable is auto-imported, so you can use it directly in any component:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel(); // Auto-imported!
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
op.track('button_click', { button: 'signup' });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button @click="handleClick">Trigger event</button>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Accessing via useNuxtApp
|
|
||||||
|
|
||||||
You can also access the OpenPanel instance directly via `useNuxtApp()`:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const { $openpanel } = useNuxtApp();
|
|
||||||
|
|
||||||
$openpanel.track('my_event', { foo: 'bar' });
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tracking Events
|
|
||||||
|
|
||||||
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
op.track('my_event', { foo: 'bar' });
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Identifying Users
|
|
||||||
|
|
||||||
To identify a user, call the `op.identify()` method with a unique identifier.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
op.identify({
|
|
||||||
profileId: '123', // Required
|
|
||||||
firstName: 'Joe',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'joe@doe.com',
|
|
||||||
properties: {
|
|
||||||
tier: 'premium',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Global Properties
|
|
||||||
|
|
||||||
To set properties that will be sent with every event:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
op.setGlobalProperties({
|
|
||||||
app_version: '1.0.2',
|
|
||||||
environment: 'production',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
op.increment({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
op.decrement({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clearing User Data
|
|
||||||
|
|
||||||
To clear the current user's data:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
op.clear();
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Server side
|
|
||||||
|
|
||||||
If you want to track server-side events, you should create an instance of our Javascript SDK. Import `OpenPanel` from `@openpanel/sdk`
|
|
||||||
|
|
||||||
<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 { OpenPanel } from '@openpanel/sdk';
|
|
||||||
|
|
||||||
const opServer = new OpenPanel({
|
|
||||||
clientId: '{YOUR_CLIENT_ID}',
|
|
||||||
clientSecret: '{YOUR_CLIENT_SECRET}',
|
|
||||||
});
|
|
||||||
|
|
||||||
opServer.track('my_server_event', { ok: '✅' });
|
|
||||||
|
|
||||||
// Pass `profileId` to track events for a specific user
|
|
||||||
opServer.track('my_server_event', { profileId: '123', ok: '✅' });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Serverless & Edge Functions
|
|
||||||
|
|
||||||
If you log events in a serverless environment, make sure to await the event call to ensure it completes before the function terminates.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { OpenPanel } from '@openpanel/sdk';
|
|
||||||
|
|
||||||
const opServer = new OpenPanel({
|
|
||||||
clientId: '{YOUR_CLIENT_ID}',
|
|
||||||
clientSecret: '{YOUR_CLIENT_SECRET}',
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
// Await to ensure event is logged before function completes
|
|
||||||
await opServer.track('my_server_event', { foo: 'bar' });
|
|
||||||
return { message: 'Event logged!' };
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Proxy events
|
|
||||||
|
|
||||||
With the `proxy` option enabled, you can proxy your events through your server, which ensures all events are tracked since many adblockers block requests to third-party domains.
|
|
||||||
|
|
||||||
```typescript title="nuxt.config.ts"
|
|
||||||
export default defineNuxtConfig({
|
|
||||||
modules: ['@openpanel/nuxt'],
|
|
||||||
openpanel: {
|
|
||||||
clientId: 'your-client-id',
|
|
||||||
proxy: true, // Enables proxy at /api/openpanel/*
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
When `proxy: true` is set:
|
|
||||||
- The module automatically sets `apiUrl` to `/api/openpanel`
|
|
||||||
- A server handler is registered at `/api/openpanel/**`
|
|
||||||
- All tracking requests route through your server
|
|
||||||
|
|
||||||
This helps bypass adblockers that might block requests to `api.openpanel.dev`.
|
|
||||||
@@ -2,244 +2,4 @@
|
|||||||
title: React
|
title: React
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated react sdk soon.
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
For React SPAs, you can use `@openpanel/web` directly - no need for a separate React SDK. Simply create an OpenPanel instance and use it throughout your application.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
### Step 1: Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @openpanel/web
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Initialize
|
|
||||||
|
|
||||||
Create a shared OpenPanel instance in your project:
|
|
||||||
|
|
||||||
```ts title="src/openpanel.ts"
|
|
||||||
import { OpenPanel } from '@openpanel/web';
|
|
||||||
|
|
||||||
export const op = new OpenPanel({
|
|
||||||
clientId: 'YOUR_CLIENT_ID',
|
|
||||||
trackScreenViews: true,
|
|
||||||
trackOutgoingLinks: true,
|
|
||||||
trackAttributes: true,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
<CommonSdkConfig />
|
|
||||||
<WebSdkConfig />
|
|
||||||
|
|
||||||
- `clientId` - Your OpenPanel client ID (required)
|
|
||||||
- `apiUrl` - The API URL to send events to (default: `https://api.openpanel.dev`)
|
|
||||||
- `trackScreenViews` - Automatically track screen views (default: `true`)
|
|
||||||
- `trackOutgoingLinks` - Automatically track outgoing links (default: `true`)
|
|
||||||
- `trackAttributes` - Automatically track elements with `data-track` attributes (default: `true`)
|
|
||||||
- `trackHashChanges` - Track hash changes in URL (default: `false`)
|
|
||||||
- `disabled` - Disable tracking (default: `false`)
|
|
||||||
|
|
||||||
### Step 3: Usage
|
|
||||||
|
|
||||||
Import and use the instance in your React components:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const handleClick = () => {
|
|
||||||
op.track('button_click', { button: 'signup' });
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handleClick}>Trigger event</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Tracking Events
|
|
||||||
|
|
||||||
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
useEffect(() => {
|
|
||||||
op.track('my_event', { foo: 'bar' });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <div>My Component</div>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Identifying Users
|
|
||||||
|
|
||||||
To identify a user, call the `op.identify()` method with a unique identifier.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function LoginComponent() {
|
|
||||||
const handleLogin = (user: User) => {
|
|
||||||
op.identify({
|
|
||||||
profileId: user.id, // Required
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
email: user.email,
|
|
||||||
properties: {
|
|
||||||
tier: 'premium',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={() => handleLogin(user)}>Login</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Global Properties
|
|
||||||
|
|
||||||
To set properties that will be sent with every event:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
useEffect(() => {
|
|
||||||
op.setGlobalProperties({
|
|
||||||
app_version: '1.0.2',
|
|
||||||
environment: 'production',
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <div>App</div>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const handleAction = () => {
|
|
||||||
op.increment({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handleAction}>Increment</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const handleAction = () => {
|
|
||||||
op.decrement({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handleAction}>Decrement</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clearing User Data
|
|
||||||
|
|
||||||
To clear the current user's data:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function LogoutComponent() {
|
|
||||||
const handleLogout = () => {
|
|
||||||
op.clear();
|
|
||||||
// ... logout logic
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handleLogout}>Logout</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Revenue Tracking
|
|
||||||
|
|
||||||
Track revenue events:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function CheckoutComponent() {
|
|
||||||
const handlePurchase = async () => {
|
|
||||||
// Track revenue immediately
|
|
||||||
await op.revenue(29.99, { currency: 'USD' });
|
|
||||||
|
|
||||||
// Or accumulate revenue and flush later
|
|
||||||
op.pendingRevenue(29.99, { currency: 'USD' });
|
|
||||||
op.pendingRevenue(19.99, { currency: 'USD' });
|
|
||||||
await op.flushRevenue(); // Sends both revenue events
|
|
||||||
|
|
||||||
// Clear pending revenue
|
|
||||||
op.clearRevenue();
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handlePurchase}>Purchase</button>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional: Create a Hook
|
|
||||||
|
|
||||||
If you prefer using a React hook pattern, you can create your own wrapper:
|
|
||||||
|
|
||||||
```ts title="src/hooks/useOpenPanel.ts"
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
export function useOpenPanel() {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use it in your components:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { useOpenPanel } from '@/hooks/useOpenPanel';
|
|
||||||
|
|
||||||
function MyComponent() {
|
|
||||||
const op = useOpenPanel();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
op.track('my_event', { foo: 'bar' });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <div>My Component</div>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -216,4 +216,3 @@ tracker = OpenPanel::SDK::Tracker.new(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,219 +2,4 @@
|
|||||||
title: Vue
|
title: Vue
|
||||||
---
|
---
|
||||||
|
|
||||||
import Link from 'next/link';
|
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated vue sdk soon.
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|
||||||
|
|
||||||
import { DeviceIdWarning } from '@/components/device-id-warning';
|
|
||||||
import { PersonalDataWarning } from '@/components/personal-data-warning';
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout';
|
|
||||||
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
|
|
||||||
import WebSdkConfig from '@/components/web-sdk-config.mdx';
|
|
||||||
|
|
||||||
<Callout>
|
|
||||||
Looking for a step-by-step tutorial? Check out the [Vue analytics guide](/guides/vue-analytics).
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Good to know
|
|
||||||
|
|
||||||
Keep in mind that all tracking here happens on the client!
|
|
||||||
|
|
||||||
For Vue SPAs, you can use `@openpanel/web` directly - no need for a separate Vue SDK. Simply create an OpenPanel instance and use it throughout your application.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
### Step 1: Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install @openpanel/web
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Initialize
|
|
||||||
|
|
||||||
Create a shared OpenPanel instance in your project:
|
|
||||||
|
|
||||||
```ts title="src/openpanel.ts"
|
|
||||||
import { OpenPanel } from '@openpanel/web';
|
|
||||||
|
|
||||||
export const op = new OpenPanel({
|
|
||||||
clientId: 'YOUR_CLIENT_ID',
|
|
||||||
trackScreenViews: true,
|
|
||||||
trackOutgoingLinks: true,
|
|
||||||
trackAttributes: true,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Options
|
|
||||||
|
|
||||||
<CommonSdkConfig />
|
|
||||||
<WebSdkConfig />
|
|
||||||
|
|
||||||
- `clientId` - Your OpenPanel client ID (required)
|
|
||||||
- `apiUrl` - The API URL to send events to (default: `https://api.openpanel.dev`)
|
|
||||||
- `trackScreenViews` - Automatically track screen views (default: `true`)
|
|
||||||
- `trackOutgoingLinks` - Automatically track outgoing links (default: `true`)
|
|
||||||
- `trackAttributes` - Automatically track elements with `data-track` attributes (default: `true`)
|
|
||||||
- `trackHashChanges` - Track hash changes in URL (default: `false`)
|
|
||||||
- `disabled` - Disable tracking (default: `false`)
|
|
||||||
|
|
||||||
### Step 3: Usage
|
|
||||||
|
|
||||||
Import and use the instance in your Vue components:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
op.track('button_click', { button: 'signup' });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button @click="handleClick">Trigger event</button>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Tracking Events
|
|
||||||
|
|
||||||
You can track events with two different methods: by calling the `op.track()` method directly or by adding `data-track` attributes to your HTML elements.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.track('my_event', { foo: 'bar' });
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Identifying Users
|
|
||||||
|
|
||||||
To identify a user, call the `op.identify()` method with a unique identifier.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.identify({
|
|
||||||
profileId: '123', // Required
|
|
||||||
firstName: 'Joe',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'joe@doe.com',
|
|
||||||
properties: {
|
|
||||||
tier: 'premium',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Global Properties
|
|
||||||
|
|
||||||
To set properties that will be sent with every event:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.setGlobalProperties({
|
|
||||||
app_version: '1.0.2',
|
|
||||||
environment: 'production',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.increment({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.decrement({
|
|
||||||
profileId: '1',
|
|
||||||
property: 'visits',
|
|
||||||
value: 1, // optional
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clearing User Data
|
|
||||||
|
|
||||||
To clear the current user's data:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
op.clear();
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Revenue Tracking
|
|
||||||
|
|
||||||
Track revenue events:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
// Track revenue immediately
|
|
||||||
await op.revenue(29.99, { currency: 'USD' });
|
|
||||||
|
|
||||||
// Or accumulate revenue and flush later
|
|
||||||
op.pendingRevenue(29.99, { currency: 'USD' });
|
|
||||||
op.pendingRevenue(19.99, { currency: 'USD' });
|
|
||||||
await op.flushRevenue(); // Sends both revenue events
|
|
||||||
|
|
||||||
// Clear pending revenue
|
|
||||||
op.clearRevenue();
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional: Create a Composable
|
|
||||||
|
|
||||||
If you prefer using a composable pattern, you can create your own wrapper:
|
|
||||||
|
|
||||||
```ts title="src/composables/useOpenPanel.ts"
|
|
||||||
import { op } from '@/openpanel';
|
|
||||||
|
|
||||||
export function useOpenPanel() {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use it in your components:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { useOpenPanel } from '@/composables/useOpenPanel';
|
|
||||||
|
|
||||||
const op = useOpenPanel();
|
|
||||||
op.track('my_event', { foo: 'bar' });
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ difficulty: intermediate
|
|||||||
timeToComplete: 10
|
timeToComplete: 10
|
||||||
date: 2025-12-15
|
date: 2025-12-15
|
||||||
lastUpdated: 2025-12-15
|
lastUpdated: 2025-12-15
|
||||||
team: OpenPanel Team
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Add the dependency"
|
- name: "Add the dependency"
|
||||||
anchor: "install"
|
anchor: "install"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ difficulty: beginner
|
|||||||
timeToComplete: 7
|
timeToComplete: 7
|
||||||
date: 2025-12-15
|
date: 2025-12-15
|
||||||
lastUpdated: 2025-12-15
|
lastUpdated: 2025-12-15
|
||||||
team: OpenPanel Team
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Install the SDK"
|
- name: "Install the SDK"
|
||||||
anchor: "install"
|
anchor: "install"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -121,7 +121,7 @@ export default async function Page({
|
|||||||
/>
|
/>
|
||||||
<div className="row gap-4 items-center mt-8">
|
<div className="row gap-4 items-center mt-8">
|
||||||
<div className="size-10 center-center bg-black rounded-full">
|
<div className="size-10 center-center bg-black rounded-full">
|
||||||
{author?.image ? (
|
{author.image ? (
|
||||||
<Image
|
<Image
|
||||||
className="size-10 object-cover rounded-full"
|
className="size-10 object-cover rounded-full"
|
||||||
src={author.image}
|
src={author.image}
|
||||||
@@ -134,7 +134,7 @@ export default async function Page({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col flex-1">
|
<div className="col flex-1">
|
||||||
<p className="font-medium">{author?.name || 'OpenPanel Team'}</p>
|
<p className="font-medium">{author.name}</p>
|
||||||
<div className="row gap-4 items-center">
|
<div className="row gap-4 items-center">
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{guide?.data.date.toLocaleDateString()}
|
{guide?.data.date.toLocaleDateString()}
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ import {
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
type PageProps = {
|
export default async function Page(props: PageProps<'/docs/[[...slug]]'>) {
|
||||||
params: Promise<{ slug: string[] }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page(props: PageProps) {
|
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const page = source.getPage(params.slug);
|
const page = source.getPage(params.slug);
|
||||||
if (!page) notFound();
|
if (!page) notFound();
|
||||||
@@ -43,7 +39,9 @@ export async function generateStaticParams() {
|
|||||||
return source.generateParams();
|
return source.generateParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata(props: PageProps): Promise<Metadata> {
|
export async function generateMetadata(
|
||||||
|
props: PageProps<'/docs/[[...slug]]'>,
|
||||||
|
): Promise<Metadata> {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const page = source.getPage(params.slug);
|
const page = source.getPage(params.slug);
|
||||||
if (!page) notFound();
|
if (!page) notFound();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { baseOptions } from '@/lib/layout.shared';
|
|
||||||
import { source } from '@/lib/source';
|
import { source } from '@/lib/source';
|
||||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||||
|
import { baseOptions } from '@/lib/layout.shared';
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: LayoutProps<'/docs'>) {
|
||||||
return (
|
return (
|
||||||
<DocsLayout tree={source.pageTree} {...baseOptions()}>
|
<DocsLayout tree={source.pageTree} {...baseOptions()}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const viewport: Viewport = {
|
|||||||
|
|
||||||
export const metadata: Metadata = getRootMetadata();
|
export const metadata: Metadata = getRootMetadata();
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: LayoutProps<'/'>) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
|
|||||||
@@ -84,34 +84,6 @@ async function getOgData(
|
|||||||
data?.data.description || 'Whooops, could not find this page',
|
data?.data.description || 'Whooops, could not find this page',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'tools': {
|
|
||||||
if (segments.length > 1) {
|
|
||||||
const tool = segments[1];
|
|
||||||
switch (tool) {
|
|
||||||
case 'ip-lookup':
|
|
||||||
return {
|
|
||||||
title: 'IP Lookup Tool',
|
|
||||||
description:
|
|
||||||
'Find detailed information about any IP address including geolocation, ISP, and network details.',
|
|
||||||
};
|
|
||||||
case 'url-checker':
|
|
||||||
return {
|
|
||||||
title: 'URL Checker',
|
|
||||||
description:
|
|
||||||
'Analyze any website for SEO, social media, technical, and security information. Get comprehensive insights about any URL.',
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
title: 'Tools',
|
|
||||||
description: 'Free web tools for developers and website owners',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: 'Tools',
|
|
||||||
description: 'Free web tools for developers and website owners',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
const data = await pageSource.getPage(segments);
|
const data = await pageSource.getPage(segments);
|
||||||
return {
|
return {
|
||||||
|
|||||||
10
packages/auth/nextjs.ts
Normal file
10
packages/auth/nextjs.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { unstable_cache } from 'next/cache';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
import { validateSessionToken } from './src/session';
|
||||||
|
|
||||||
|
export const auth = async () => {
|
||||||
|
const token = (await cookies().get('session')?.value) ?? null;
|
||||||
|
return cachedAuth(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedAuth = unstable_cache(validateSessionToken);
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"next": "14.2.1",
|
||||||
"react": "catalog:"
|
"react": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/astro",
|
"name": "@openpanel/astro",
|
||||||
"version": "1.0.6-local",
|
"version": "1.0.5-local",
|
||||||
"config": {
|
"config": {
|
||||||
"transformPackageJson": false,
|
"transformPackageJson": false,
|
||||||
"transformEnvs": true
|
"transformEnvs": true
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"files": ["src", "index.ts"],
|
"files": ["src", "index.ts"],
|
||||||
"keywords": ["astro-component"],
|
"keywords": ["astro-component"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/web": "workspace:1.0.6-local"
|
"@openpanel/web": "workspace:1.0.5-local"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^5.7.7"
|
"astro": "^5.7.7"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const methods: { name: OpenPanelMethodNames; value: unknown }[] = [
|
|||||||
value: {
|
value: {
|
||||||
...options,
|
...options,
|
||||||
sdk: 'astro',
|
sdk: 'astro',
|
||||||
sdkVersion: '1.0.6',
|
sdkVersion: '1.0.5',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/express",
|
"name": "@openpanel/express",
|
||||||
"version": "1.0.4-local",
|
"version": "1.0.3-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/sdk": "workspace:1.0.3-local",
|
"@openpanel/sdk": "workspace:1.0.2-local",
|
||||||
"@openpanel/common": "workspace:*"
|
"@openpanel/common": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/nextjs",
|
"name": "@openpanel/nextjs",
|
||||||
"version": "1.1.2-local",
|
"version": "1.1.1-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/web": "workspace:1.0.6-local"
|
"@openpanel/web": "workspace:1.0.5-local"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { defineBuildConfig } from 'unbuild';
|
|
||||||
|
|
||||||
export default defineBuildConfig({
|
|
||||||
failOnWarn: false,
|
|
||||||
});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// This file is for development - the built version uses src/module.ts
|
|
||||||
export { default, type ModuleOptions } from './src/module';
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@openpanel/nuxt",
|
|
||||||
"version": "0.0.2-local",
|
|
||||||
"type": "module",
|
|
||||||
"main": "./dist/module.mjs",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/module.d.mts",
|
|
||||||
"import": "./dist/module.mjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"files": ["dist"],
|
|
||||||
"config": {
|
|
||||||
"transformPackageJson": false,
|
|
||||||
"transformEnvs": false
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "npx nuxt-module-build build",
|
|
||||||
"dev:prepare": "npx nuxt-module-build build --stub",
|
|
||||||
"prepack": "npx nuxt-module-build build",
|
|
||||||
"typecheck": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@openpanel/web": "workspace:1.0.6-local"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"h3": "^1.0.0",
|
|
||||||
"nuxt": "^3.0.0 || ^4.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nuxt/kit": "^3.0.0",
|
|
||||||
"@nuxt/module-builder": "^1.0.2",
|
|
||||||
"@nuxt/types": "^2.18.1",
|
|
||||||
"@openpanel/tsconfig": "workspace:*",
|
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@vue/runtime-core": "^3.5.25",
|
|
||||||
"typescript": "catalog:",
|
|
||||||
"unbuild": "^3.6.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import {
|
|
||||||
addImports,
|
|
||||||
addPlugin,
|
|
||||||
addServerHandler,
|
|
||||||
createResolver,
|
|
||||||
defineNuxtModule,
|
|
||||||
} from '@nuxt/kit';
|
|
||||||
import type { ModuleOptions } from './types';
|
|
||||||
|
|
||||||
export type { ModuleOptions };
|
|
||||||
|
|
||||||
export default defineNuxtModule<ModuleOptions>({
|
|
||||||
meta: {
|
|
||||||
name: '@openpanel/nuxt',
|
|
||||||
configKey: 'openpanel',
|
|
||||||
},
|
|
||||||
defaults: {
|
|
||||||
trackScreenViews: true,
|
|
||||||
trackOutgoingLinks: true,
|
|
||||||
trackAttributes: true,
|
|
||||||
trackHashChanges: false,
|
|
||||||
disabled: false,
|
|
||||||
proxy: false, // Disabled by default
|
|
||||||
},
|
|
||||||
setup(options, nuxt) {
|
|
||||||
const resolver = createResolver(import.meta.url);
|
|
||||||
|
|
||||||
// If proxy is enabled, override apiUrl to use the proxy route
|
|
||||||
if (options.proxy) {
|
|
||||||
options.apiUrl = '/api/openpanel';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expose options to runtime config
|
|
||||||
nuxt.options.runtimeConfig.public.openpanel = options;
|
|
||||||
|
|
||||||
// Add client plugin (creates OpenPanel instance)
|
|
||||||
addPlugin({
|
|
||||||
src: resolver.resolve('./runtime/plugin.client'),
|
|
||||||
mode: 'client',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only register server proxy handler if proxy is enabled
|
|
||||||
if (options.proxy) {
|
|
||||||
addServerHandler({
|
|
||||||
route: '/api/openpanel/**',
|
|
||||||
handler: resolver.resolve('./runtime/server/api/[...openpanel]'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-import the useOpenPanel composable
|
|
||||||
addImports({
|
|
||||||
name: 'useOpenPanel',
|
|
||||||
from: resolver.resolve('./runtime/composables/useOpenPanel'),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { useNuxtApp } from '#app';
|
|
||||||
|
|
||||||
export function useOpenPanel() {
|
|
||||||
const { $openpanel } = useNuxtApp();
|
|
||||||
return $openpanel;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { OpenPanel } from '@openpanel/web';
|
|
||||||
import { defineNuxtPlugin, useRuntimeConfig } from '#app';
|
|
||||||
import type { ModuleOptions } from '../types';
|
|
||||||
|
|
||||||
declare module '#app' {
|
|
||||||
interface NuxtApp {
|
|
||||||
$openpanel: OpenPanel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
|
||||||
interface ComponentCustomProperties {
|
|
||||||
$openpanel: OpenPanel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineNuxtPlugin({
|
|
||||||
name: 'openpanel',
|
|
||||||
parallel: true,
|
|
||||||
setup() {
|
|
||||||
const config = useRuntimeConfig().public.openpanel as ModuleOptions;
|
|
||||||
const op = new OpenPanel(config);
|
|
||||||
|
|
||||||
return {
|
|
||||||
provide: {
|
|
||||||
openpanel: op,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import {
|
|
||||||
type EventHandlerRequest,
|
|
||||||
type H3Event,
|
|
||||||
createError,
|
|
||||||
defineEventHandler,
|
|
||||||
getHeader,
|
|
||||||
getRequestIP,
|
|
||||||
getRequestURL,
|
|
||||||
readBody,
|
|
||||||
setResponseStatus,
|
|
||||||
} from 'h3';
|
|
||||||
|
|
||||||
const API_URL = 'https://api.openpanel.dev';
|
|
||||||
|
|
||||||
function getClientHeaders(event: H3Event<EventHandlerRequest>): Headers {
|
|
||||||
const headers = new Headers();
|
|
||||||
|
|
||||||
// Get IP from multiple possible headers (like Next.js does)
|
|
||||||
const ip =
|
|
||||||
getHeader(event, 'cf-connecting-ip') ||
|
|
||||||
getHeader(event, 'x-forwarded-for')?.split(',')[0] ||
|
|
||||||
getRequestIP(event);
|
|
||||||
|
|
||||||
headers.set('Content-Type', 'application/json');
|
|
||||||
headers.set(
|
|
||||||
'openpanel-client-id',
|
|
||||||
getHeader(event, 'openpanel-client-id') || '',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Construct origin: browsers send Origin header for POST requests and cross-origin requests,
|
|
||||||
// but not for same-origin GET requests. Fallback to constructing from request URL.
|
|
||||||
const origin =
|
|
||||||
getHeader(event, 'origin') ||
|
|
||||||
(() => {
|
|
||||||
const url = getRequestURL(event);
|
|
||||||
return `${url.protocol}//${url.host}`;
|
|
||||||
})();
|
|
||||||
headers.set('origin', origin);
|
|
||||||
|
|
||||||
headers.set('User-Agent', getHeader(event, 'user-agent') || '');
|
|
||||||
if (ip) {
|
|
||||||
headers.set('openpanel-client-ip', ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleApiRoute(
|
|
||||||
event: H3Event<EventHandlerRequest>,
|
|
||||||
apiPath: string,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}${apiPath}`, {
|
|
||||||
method: event.method,
|
|
||||||
headers: getClientHeaders(event),
|
|
||||||
body:
|
|
||||||
event.method === 'POST'
|
|
||||||
? JSON.stringify(await readBody(event))
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
setResponseStatus(event, res.status);
|
|
||||||
|
|
||||||
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.text();
|
|
||||||
} catch (e) {
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
message: 'Failed to proxy request',
|
|
||||||
data: e instanceof Error ? e.message : String(e),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const url = getRequestURL(event);
|
|
||||||
const pathname = url.pathname;
|
|
||||||
|
|
||||||
// Handle API routes: /track/*
|
|
||||||
const apiPathMatch = pathname.indexOf('/track');
|
|
||||||
if (apiPathMatch === -1) {
|
|
||||||
throw createError({ statusCode: 404, message: 'Not found' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiPath = pathname.substring(apiPathMatch);
|
|
||||||
return handleApiRoute(event, apiPath);
|
|
||||||
});
|
|
||||||
20
packages/sdks/nuxt/src/types.d.ts
vendored
20
packages/sdks/nuxt/src/types.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
import type { OpenPanel, OpenPanelOptions } from '@openpanel/web';
|
|
||||||
|
|
||||||
export interface ModuleOptions extends OpenPanelOptions {
|
|
||||||
proxy?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '#app' {
|
|
||||||
interface NuxtApp {
|
|
||||||
$openpanel: OpenPanel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'vue' {
|
|
||||||
interface ComponentCustomProperties {
|
|
||||||
$openpanel: OpenPanel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noUselessEmptyExport: we need to export an empty object to satisfy the type checker
|
|
||||||
export {};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@openpanel/tsconfig/sdk.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"incremental": false,
|
|
||||||
"outDir": "dist",
|
|
||||||
"paths": {
|
|
||||||
"#app": [
|
|
||||||
"./node_modules/nuxt/dist/app/index"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"types": [
|
|
||||||
"@types/node",
|
|
||||||
"@nuxt/types"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exclude": ["dist"]
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/react-native",
|
"name": "@openpanel/react-native",
|
||||||
"version": "1.0.4-local",
|
"version": "1.0.3-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/sdk": "workspace:1.0.3-local"
|
"@openpanel/sdk": "workspace:1.0.2-local"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openpanel/tsconfig": "workspace:*",
|
"@openpanel/tsconfig": "workspace:*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/sdk",
|
"name": "@openpanel/sdk",
|
||||||
"version": "1.0.3-local",
|
"version": "1.0.2-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export type OpenPanelOptions = {
|
|||||||
waitForProfile?: boolean;
|
waitForProfile?: boolean;
|
||||||
filter?: (payload: TrackHandlerPayload) => boolean;
|
filter?: (payload: TrackHandlerPayload) => boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
debug?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class OpenPanel {
|
export class OpenPanel {
|
||||||
@@ -130,7 +129,6 @@ export class OpenPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async track(name: string, properties?: TrackProperties) {
|
async track(name: string, properties?: TrackProperties) {
|
||||||
this.log('track event', name, properties);
|
|
||||||
return this.send({
|
return this.send({
|
||||||
type: 'track',
|
type: 'track',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -145,7 +143,6 @@ export class OpenPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async identify(payload: IdentifyPayload) {
|
async identify(payload: IdentifyPayload) {
|
||||||
this.log('identify user', payload);
|
|
||||||
if (payload.profileId) {
|
if (payload.profileId) {
|
||||||
this.profileId = payload.profileId;
|
this.profileId = payload.profileId;
|
||||||
this.flush();
|
this.flush();
|
||||||
@@ -165,10 +162,12 @@ export class OpenPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async alias(payload: AliasPayload) {
|
||||||
* @deprecated This method is deprecated and will be removed in a future version.
|
return this.send({
|
||||||
*/
|
type: 'alias',
|
||||||
async alias(payload: AliasPayload) {}
|
payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async increment(payload: IncrementPayload) {
|
async increment(payload: IncrementPayload) {
|
||||||
return this.send({
|
return this.send({
|
||||||
@@ -225,10 +224,4 @@ export class OpenPanel {
|
|||||||
});
|
});
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
log(...args: any[]) {
|
|
||||||
if (this.options.debug) {
|
|
||||||
console.log('[OpenPanel.dev]', ...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@openpanel/web",
|
"name": "@openpanel/web",
|
||||||
"version": "1.0.6-local",
|
"version": "1.0.5-local",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/sdk": "workspace:1.0.3-local"
|
"@openpanel/sdk": "workspace:1.0.2-local"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openpanel/tsconfig": "workspace:*",
|
"@openpanel/tsconfig": "workspace:*",
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export class OpenPanel extends OpenPanelBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!this.isServer()) {
|
if (!this.isServer()) {
|
||||||
|
console.log('OpenPanel.dev - Initialized', this.options);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pending = sessionStorage.getItem('openpanel-pending-revenues');
|
const pending = sessionStorage.getItem('openpanel-pending-revenues');
|
||||||
if (pending) {
|
if (pending) {
|
||||||
@@ -200,6 +202,7 @@ export class OpenPanel extends OpenPanelBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lastPath = path;
|
this.lastPath = path;
|
||||||
|
console.log('OpenPanel.dev - Track page view');
|
||||||
super.track('screen_view', {
|
super.track('screen_view', {
|
||||||
...(properties ?? {}),
|
...(properties ?? {}),
|
||||||
__path: path,
|
__path: path,
|
||||||
|
|||||||
6833
pnpm-lock.yaml
generated
6833
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,10 @@ packages:
|
|||||||
# Define a catalog of version ranges.
|
# Define a catalog of version ranges.
|
||||||
catalog:
|
catalog:
|
||||||
zod: ^3.24.2
|
zod: ^3.24.2
|
||||||
react: ^19.2.3
|
react: ^19.0.0
|
||||||
"@types/react": ^19.2.3
|
"@types/react": ^19.0.0
|
||||||
"react-dom": ^19.2.3
|
"react-dom": ^19.0.0
|
||||||
"@types/react-dom": ^19.2.3
|
"@types/react-dom": ^19.0.0
|
||||||
"@types/node": ^24.7.1
|
"@types/node": ^24.7.1
|
||||||
typescript: ^5.9.3
|
typescript: ^5.9.3
|
||||||
groupmq: 1.1.1-next.2
|
groupmq: 1.1.1-next.2
|
||||||
|
|||||||
Reference in New Issue
Block a user