sdk(astro,nextjs): add astro sdk and ensure window.op always first on nextjs

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-05-06 22:10:38 +02:00
parent 0189b922f2
commit 2d8f6f36f6
17 changed files with 2110 additions and 83 deletions

View File

@@ -0,0 +1,3 @@
# OpenPanel Astro SDK
Read full documentation [here](https://openpanel.dev/docs/sdks/astro)

7
packages/sdks/astro/env.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
declare module '*.astro' {
import type { AstroComponentFactory } from 'astro';
const component: AstroComponentFactory;
export default component;
}
/// <reference types="astro/client" />

View File

@@ -0,0 +1,46 @@
import type {
DecrementPayload,
IdentifyPayload,
IncrementPayload,
TrackProperties,
} from '@openpanel/web';
import IdentifyComponent from './src/IdentifyComponent.astro';
import OpenPanelComponent from './src/OpenPanelComponent.astro';
import SetGlobalPropertiesComponent from './src/SetGlobalPropertiesComponent.astro';
export * from '@openpanel/web';
export { OpenPanelComponent, IdentifyComponent, SetGlobalPropertiesComponent };
export function setGlobalProperties(properties: Record<string, unknown>) {
window.op?.('setGlobalProperties', properties);
}
export function track(name: string, properties?: TrackProperties) {
window.op?.('track', name, properties);
}
export function screenView(properties?: TrackProperties): void;
export function screenView(path: string, properties?: TrackProperties): void;
export function screenView(
pathOrProperties?: string | TrackProperties,
propertiesOrUndefined?: TrackProperties,
) {
window.op?.('screenView', pathOrProperties, propertiesOrUndefined);
}
export function identify(payload: IdentifyPayload) {
window.op?.('identify', payload);
}
export function increment(payload: IncrementPayload) {
window.op?.('increment', payload);
}
export function decrement(payload: DecrementPayload) {
window.op('decrement', payload);
}
export function clear() {
window.op?.('clear');
}

View File

@@ -0,0 +1,27 @@
{
"name": "@openpanel/astro",
"version": "1.0.1-local",
"config": {
"transformPackageJson": false,
"transformEnvs": true
},
"exports": {
".": "./index.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"files": ["src", "index.ts"],
"keywords": ["astro-component"],
"dependencies": {
"@openpanel/web": "workspace:1.0.1-local"
},
"devDependencies": {
"astro": "^5.7.7"
},
"peerDependencies": {
"astro": "^4.0.0 || ^5.0.0"
},
"private": false,
"type": "module"
}

View File

@@ -0,0 +1,9 @@
---
import type { IdentifyPayload } from '@openpanel/web';
import { filterProps } from './asto-utils';
interface Props extends IdentifyPayload {}
const props = Astro.props as Props;
---
<script is:inline set:html={`window.op('identify', ${JSON.stringify(filterProps(props))});`} />

View File

@@ -0,0 +1,66 @@
---
import type {
OpenPanelMethodNames,
OpenPanelOptions
} from '@openpanel/web';
type Props = Omit<OpenPanelOptions, 'filter'> & {
profileId?: string;
cdnUrl?: string;
filter?: string;
globalProperties?: Record<string, unknown>;
};
const { profileId, cdnUrl, globalProperties, ...options } = Astro.props;
const CDN_URL = 'https://openpanel.dev/op1.js';
const stringify = (obj: unknown) => {
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
const entries = Object.entries(obj).map(([key, value]) => {
if (key === 'filter') {
return `"${key}":${value}`;
}
return `"${key}":${JSON.stringify(value)}`;
});
return `{${entries.join(',')}}`;
}
return JSON.stringify(obj);
};
const methods: { name: OpenPanelMethodNames; value: unknown }[] = [
{
name: 'init',
value: {
...options,
sdk: 'astro',
sdkVersion: '1.0.1',
},
},
];
if (profileId) {
methods.push({
name: 'identify',
value: {
profileId,
},
});
}
if (globalProperties) {
methods.push({
name: 'setGlobalProperties',
value: globalProperties,
});
}
const scriptContent = `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
${methods
.map((method) => {
return `window.op('${method.name}', ${stringify(method.value)});`;
})
.join('\n')}`;
---
<script src={cdnUrl ?? CDN_URL} async defer />
<script is:inline set:html={scriptContent} />

View File

@@ -0,0 +1,9 @@
---
import { filterProps } from './asto-utils';
type Props = Record<string, unknown>;
const props = Astro.props as Props;
---
<script is:inline set:html={`window.op('setGlobalProperties', ${JSON.stringify(filterProps(props))});`} />

View File

@@ -0,0 +1,7 @@
const BLACKLISTED_PROPS = ['class'];
export function filterProps(props: Record<string, unknown>) {
return Object.fromEntries(
Object.entries(props).filter(([key]) => !BLACKLISTED_PROPS.includes(key)),
);
}

View File

@@ -0,0 +1,13 @@
{
"extends": "astro/tsconfigs/strict",
"plugins": [
{
"name": "@astrojs/ts-plugin"
}
],
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"jsx": "preserve"
}
}

View File

@@ -71,6 +71,7 @@ export function OpenPanelComponent({
<>
<Script src={cdnUrl ?? CDN_URL} async defer />
<Script
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
${methods

View File

@@ -1,6 +1,6 @@
{
"name": "@openpanel/nextjs",
"version": "1.0.7-local",
"version": "1.0.8-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",

View File

@@ -1,6 +1,7 @@
import { defineConfig } from 'tsup';
export default defineConfig({
format: ['cjs', 'esm'],
entry: ['index.tsx', 'server.ts'],
external: ['react', 'next'],
dts: true,