Skip to content

Commit bd6226e

Browse files
committed
sdk(astro,nextjs): add astro sdk and ensure window.op always first on nextjs
1 parent 0189b92 commit bd6226e

17 files changed

+2112
-85
lines changed

apps/public/components/common-sdk-config.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@
55
- `clientSecret` - The client secret of your application (**only required for server-side events**)
66
- `filter` - A function that will be called before sending an event. If it returns false, the event will not be sent
77
- `disabled` - If true, the library will not send any events
8-
- `waitForProfile` - If true, the library will wait for the profile to be set before sending events

apps/public/content/docs/sdks/astro.mdx

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,132 @@
22
title: Astro
33
---
44

5-
You can use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) to track events in Astro.
5+
import { Step, Steps } from 'fumadocs-ui/components/steps';
6+
import CommonSdkConfig from '@/components/common-sdk-config.mdx';
7+
import WebSdkConfig from '@/components/web-sdk-config.mdx';
8+
9+
## Installation
10+
11+
<Steps>
12+
### Install dependencies
13+
14+
```bash
15+
pnpm install @openpanel/astro
16+
```
17+
18+
### Initialize
19+
20+
Add `OpenPanelComponent` to your root layout component.
21+
22+
```astro
23+
---
24+
import { OpenPanelComponent } from '@openpanel/astro';
25+
---
26+
27+
<html>
28+
<head>
29+
<OpenPanelComponent
30+
clientId="your-client-id"
31+
trackScreenViews={true}
32+
// trackAttributes={true}
33+
// trackOutgoingLinks={true}
34+
// If you have a user id, you can pass it here to identify the user
35+
// profileId={'123'}
36+
/>
37+
</head>
38+
<body>
39+
<slot />
40+
</body>
41+
</html>
42+
```
43+
44+
#### Options
45+
46+
<CommonSdkConfig />
47+
<WebSdkConfig />
48+
49+
##### Astro options
50+
51+
- `profileId` - If you have a user id, you can pass it here to identify the user
52+
- `cdnUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`)
53+
- `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)
54+
- `globalProperties` - This is an object of properties that will be sent with every event.
55+
56+
##### `filter`
57+
58+
This options needs to be a stringified function and cannot access any variables outside of the function.
59+
60+
```astro
61+
<OpenPanelComponent
62+
clientId="your-client-id"
63+
filter={`
64+
function filter(event) {
65+
return event.name !== 'my_event';
66+
}
67+
`}
68+
/>
69+
```
70+
71+
To take advantage of typescript you can do the following. _Note `toString`_
72+
```ts
73+
import { type TrackHandlerPayload } from '@openpanel/astro';
74+
75+
const opFilter = ((event: TrackHandlerPayload) => {
76+
return event.type === 'track' && event.payload.name === 'my_event';
77+
}).toString();
78+
79+
<OpenPanelComponent
80+
clientId="your-client-id"
81+
filter={opFilter}
82+
/>
83+
```
84+
85+
</Steps>
86+
87+
## Usage
88+
89+
### Client-side Tracking
90+
91+
You can track events with the global op function or you can use data attributes.
92+
93+
```astro
94+
<button onclick="window.op('track', 'clicky')">Click me</button>
95+
<button data-track="clicky" data-prop1="prop1" data-prop2="prop2">Click me</button>
96+
```
97+
98+
### Identifying Users
99+
100+
To identify a user, you can use either the `identify` function or the `IdentifyComponent`.
101+
102+
```astro
103+
---
104+
import { IdentifyComponent } from '@openpanel/astro';
105+
---
106+
107+
<IdentifyComponent
108+
profileId="123"
109+
firstName="Joe"
110+
lastName="Doe"
111+
email="joe@doe.com"
112+
properties={{
113+
tier: 'premium',
114+
}}
115+
/>
116+
```
117+
118+
### Setting Global Properties
119+
120+
You can set global properties that will be sent with every event using either the `setGlobalProperties` function or the `SetGlobalPropertiesComponent`.
121+
122+
```astro
123+
---
124+
import { SetGlobalPropertiesComponent } from '@openpanel/astro';
125+
---
126+
127+
<SetGlobalPropertiesComponent
128+
properties={{
129+
app_version: '1.0.2',
130+
environment: 'production',
131+
}}
132+
/>
133+
```

packages/sdks/astro/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# OpenPanel Astro SDK
2+
3+
Read full documentation [here](https://openpanel.dev/docs/sdks/astro)

packages/sdks/astro/env.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare module '*.astro' {
2+
import type { AstroComponentFactory } from 'astro';
3+
const component: AstroComponentFactory;
4+
export default component;
5+
}
6+
7+
/// <reference types="astro/client" />

packages/sdks/astro/index.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type {
2+
DecrementPayload,
3+
IdentifyPayload,
4+
IncrementPayload,
5+
TrackProperties,
6+
} from '@openpanel/web';
7+
import IdentifyComponent from './src/IdentifyComponent.astro';
8+
import OpenPanelComponent from './src/OpenPanelComponent.astro';
9+
import SetGlobalPropertiesComponent from './src/SetGlobalPropertiesComponent.astro';
10+
11+
export * from '@openpanel/web';
12+
13+
export { OpenPanelComponent, IdentifyComponent, SetGlobalPropertiesComponent };
14+
15+
export function setGlobalProperties(properties: Record<string, unknown>) {
16+
window.op?.('setGlobalProperties', properties);
17+
}
18+
19+
export function track(name: string, properties?: TrackProperties) {
20+
window.op?.('track', name, properties);
21+
}
22+
23+
export function screenView(properties?: TrackProperties): void;
24+
export function screenView(path: string, properties?: TrackProperties): void;
25+
export function screenView(
26+
pathOrProperties?: string | TrackProperties,
27+
propertiesOrUndefined?: TrackProperties,
28+
) {
29+
window.op?.('screenView', pathOrProperties, propertiesOrUndefined);
30+
}
31+
32+
export function identify(payload: IdentifyPayload) {
33+
window.op?.('identify', payload);
34+
}
35+
36+
export function increment(payload: IncrementPayload) {
37+
window.op?.('increment', payload);
38+
}
39+
40+
export function decrement(payload: DecrementPayload) {
41+
window.op('decrement', payload);
42+
}
43+
44+
export function clear() {
45+
window.op?.('clear');
46+
}

packages/sdks/astro/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@openpanel/astro",
3+
"version": "1.0.1-local",
4+
"config": {
5+
"transformPackageJson": false,
6+
"transformEnvs": true
7+
},
8+
"exports": {
9+
".": "./index.ts"
10+
},
11+
"scripts": {
12+
"typecheck": "tsc --noEmit"
13+
},
14+
"files": ["src", "index.ts"],
15+
"keywords": ["astro-component"],
16+
"dependencies": {
17+
"@openpanel/web": "1.0.1-local"
18+
},
19+
"devDependencies": {
20+
"astro": "^5.7.7"
21+
},
22+
"peerDependencies": {
23+
"astro": "^4.0.0 || ^5.0.0"
24+
},
25+
"private": false,
26+
"type": "module"
27+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
import type { IdentifyPayload } from '@openpanel/web';
3+
import { filterProps } from './asto-utils';
4+
interface Props extends IdentifyPayload {}
5+
6+
const props = Astro.props as Props;
7+
---
8+
9+
<script is:inline set:html={`window.op('identify', ${JSON.stringify(filterProps(props))});`} />
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
import type {
3+
OpenPanelMethodNames,
4+
OpenPanelOptions
5+
} from '@openpanel/web';
6+
7+
type Props = Omit<OpenPanelOptions, 'filter'> & {
8+
profileId?: string;
9+
cdnUrl?: string;
10+
filter?: string;
11+
globalProperties?: Record<string, unknown>;
12+
};
13+
14+
const { profileId, cdnUrl, globalProperties, ...options } = Astro.props;
15+
16+
const CDN_URL = 'https://openpanel.dev/op1.js';
17+
18+
const stringify = (obj: unknown) => {
19+
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
20+
const entries = Object.entries(obj).map(([key, value]) => {
21+
if (key === 'filter') {
22+
return `"${key}":${value}`;
23+
}
24+
return `"${key}":${JSON.stringify(value)}`;
25+
});
26+
return `{${entries.join(',')}}`;
27+
}
28+
29+
return JSON.stringify(obj);
30+
};
31+
32+
const methods: { name: OpenPanelMethodNames; value: unknown }[] = [
33+
{
34+
name: 'init',
35+
value: {
36+
...options,
37+
sdk: 'astro',
38+
sdkVersion: '1.0.1',
39+
},
40+
},
41+
];
42+
if (profileId) {
43+
methods.push({
44+
name: 'identify',
45+
value: {
46+
profileId,
47+
},
48+
});
49+
}
50+
if (globalProperties) {
51+
methods.push({
52+
name: 'setGlobalProperties',
53+
value: globalProperties,
54+
});
55+
}
56+
57+
const scriptContent = `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
58+
${methods
59+
.map((method) => {
60+
return `window.op('${method.name}', ${stringify(method.value)});`;
61+
})
62+
.join('\n')}`;
63+
---
64+
65+
<script src={cdnUrl ?? CDN_URL} async defer />
66+
<script is:inline set:html={scriptContent} />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
import { filterProps } from './asto-utils';
3+
4+
type Props = Record<string, unknown>;
5+
6+
const props = Astro.props as Props;
7+
---
8+
9+
<script is:inline set:html={`window.op('setGlobalProperties', ${JSON.stringify(filterProps(props))});`} />

packages/sdks/astro/src/asto-utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const BLACKLISTED_PROPS = ['class'];
2+
3+
export function filterProps(props: Record<string, unknown>) {
4+
return Object.fromEntries(
5+
Object.entries(props).filter(([key]) => !BLACKLISTED_PROPS.includes(key)),
6+
);
7+
}

packages/sdks/astro/tsconfig.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "astro/tsconfigs/strict",
3+
"plugins": [
4+
{
5+
"name": "@astrojs/ts-plugin"
6+
}
7+
],
8+
"include": [".astro/types.d.ts", "**/*"],
9+
"exclude": ["dist"],
10+
"compilerOptions": {
11+
"jsx": "preserve"
12+
}
13+
}

packages/sdks/nextjs/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function OpenPanelComponent({
7171
<>
7272
<Script src={cdnUrl ?? CDN_URL} async defer />
7373
<Script
74+
strategy="beforeInteractive"
7475
dangerouslySetInnerHTML={{
7576
__html: `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
7677
${methods

packages/sdks/nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openpanel/nextjs",
3-
"version": "1.0.7-local",
3+
"version": "1.0.8-local",
44
"module": "index.ts",
55
"scripts": {
66
"build": "rm -rf dist && tsup",

packages/sdks/nextjs/tsup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { defineConfig } from 'tsup';
22

33
export default defineConfig({
4+
format: ['cjs', 'esm'],
45
entry: ['index.tsx', 'server.ts'],
56
external: ['react', 'next'],
67
dts: true,

0 commit comments

Comments
 (0)