Compare commits
2 Commits
api
...
feature/nu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba86316218 | ||
|
|
684cba9c84 |
256
apps/public/content/docs/(tracking)/sdks/nuxt.mdx
Normal file
256
apps/public/content/docs/(tracking)/sdks/nuxt.mdx
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
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,4 +2,244 @@
|
||||
title: React
|
||||
---
|
||||
|
||||
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated react sdk soon.
|
||||
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';
|
||||
|
||||
## 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>;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,4 +2,219 @@
|
||||
title: Vue
|
||||
---
|
||||
|
||||
Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated vue sdk soon.
|
||||
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 [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>
|
||||
```
|
||||
|
||||
5
packages/sdks/nuxt/build.config.ts
Normal file
5
packages/sdks/nuxt/build.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
failOnWarn: false,
|
||||
});
|
||||
2
packages/sdks/nuxt/index.ts
Normal file
2
packages/sdks/nuxt/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// This file is for development - the built version uses src/module.ts
|
||||
export { default, type ModuleOptions } from './src/module';
|
||||
40
packages/sdks/nuxt/package.json
Normal file
40
packages/sdks/nuxt/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
56
packages/sdks/nuxt/src/module.ts
Normal file
56
packages/sdks/nuxt/src/module.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
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'),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { useNuxtApp } from '#app';
|
||||
|
||||
export function useOpenPanel() {
|
||||
const { $openpanel } = useNuxtApp();
|
||||
return $openpanel;
|
||||
}
|
||||
30
packages/sdks/nuxt/src/runtime/plugin.client.ts
Normal file
30
packages/sdks/nuxt/src/runtime/plugin.client.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
90
packages/sdks/nuxt/src/runtime/server/api/[...openpanel].ts
Normal file
90
packages/sdks/nuxt/src/runtime/server/api/[...openpanel].ts
Normal file
@@ -0,0 +1,90 @@
|
||||
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
Normal file
20
packages/sdks/nuxt/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
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 {};
|
||||
17
packages/sdks/nuxt/tsconfig.json
Normal file
17
packages/sdks/nuxt/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
4224
pnpm-lock.yaml
generated
4224
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user