feat: sdks and docs (#239)

* init

* fix

* update docs

* bump: all sdks

* rename types test
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-19 21:56:47 +01:00
committed by GitHub
parent 790801b728
commit 83e223a496
50 changed files with 793 additions and 137 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@openpanel/astro",
"version": "1.0.2-local",
"version": "1.0.3-local",
"config": {
"transformPackageJson": false,
"transformEnvs": true
@@ -14,7 +14,7 @@
"files": ["src", "index.ts"],
"keywords": ["astro-component"],
"dependencies": {
"@openpanel/web": "workspace:1.0.2-local"
"@openpanel/web": "workspace:1.0.3-local"
},
"devDependencies": {
"astro": "^5.7.7"

View File

@@ -1,5 +1,6 @@
---
import type { OpenPanelMethodNames, OpenPanelOptions } from '@openpanel/web';
import { getInitSnippet } from '@openpanel/web';
type Props = Omit<OpenPanelOptions, 'filter'> & {
profileId?: string;
@@ -32,7 +33,7 @@ const methods: { name: OpenPanelMethodNames; value: unknown }[] = [
value: {
...options,
sdk: 'astro',
sdkVersion: '1.0.2',
sdkVersion: '1.0.3',
},
},
];
@@ -51,7 +52,7 @@ if (globalProperties) {
});
}
const scriptContent = `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
const scriptContent = `${getInitSnippet()}
${methods
.map((method) => {
return `window.op('${method.name}', ${stringify(method.value)});`;

View File

@@ -1,13 +1,13 @@
{
"name": "@openpanel/express",
"version": "1.0.1-local",
"version": "1.0.2-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@openpanel/sdk": "workspace:1.0.0-local",
"@openpanel/sdk": "workspace:1.0.1-local",
"@openpanel/common": "workspace:*"
},
"peerDependencies": {

View File

@@ -1,5 +1,5 @@
{
"extends": "@openpanel/tsconfig/base.json",
"extends": "@openpanel/tsconfig/sdk.json",
"compilerOptions": {
"incremental": false,
"outDir": "dist"

View File

@@ -11,6 +11,7 @@ import type {
OpenPanelOptions,
TrackProperties,
} from '@openpanel/web';
import { getInitSnippet } from '@openpanel/web';
export * from '@openpanel/web';
@@ -73,7 +74,7 @@ export function OpenPanelComponent({
<Script
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
__html: `${getInitSnippet()}
${methods
.map((method) => {
return `window.op('${method.name}', ${stringify(method.value)});`;

View File

@@ -1,13 +1,13 @@
{
"name": "@openpanel/nextjs",
"version": "1.0.15-local",
"version": "1.0.16-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@openpanel/web": "workspace:1.0.2-local"
"@openpanel/web": "workspace:1.0.3-local"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",

View File

@@ -1,5 +1,5 @@
{
"extends": "@openpanel/tsconfig/base.json",
"extends": "@openpanel/tsconfig/sdk.json",
"compilerOptions": {
"incremental": false,
"outDir": "dist"

View File

@@ -1,13 +1,13 @@
{
"name": "@openpanel/react-native",
"version": "1.0.1-local",
"version": "1.0.2-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@openpanel/sdk": "workspace:1.0.0-local"
"@openpanel/sdk": "workspace:1.0.1-local"
},
"devDependencies": {
"@openpanel/tsconfig": "workspace:*",

View File

@@ -1,5 +1,5 @@
{
"extends": "@openpanel/tsconfig/base.json",
"extends": "@openpanel/tsconfig/sdk.json",
"compilerOptions": {
"incremental": false,
"outDir": "dist"

View File

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

View File

@@ -1,5 +1,5 @@
{
"extends": "@openpanel/tsconfig/base.json",
"extends": "@openpanel/tsconfig/sdk.json",
"compilerOptions": {
"incremental": false,
"outDir": "dist"

View File

@@ -1,2 +1,3 @@
export * from './src/index';
export * from './src/types.d';
export { getInitSnippet } from './src/init-snippet';

View File

@@ -1,13 +1,13 @@
{
"name": "@openpanel/web",
"version": "1.0.2-local",
"version": "1.0.3-local",
"module": "index.ts",
"scripts": {
"build": "rm -rf dist && tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@openpanel/sdk": "workspace:1.0.0-local"
"@openpanel/sdk": "workspace:1.0.1-local"
},
"devDependencies": {
"@openpanel/tsconfig": "workspace:*",

View File

@@ -0,0 +1,23 @@
// Source:
// window.op = window.op || (function() {
// var q = [];
// var op = new Proxy(function() {
// if (arguments.length > 0) {
// q.push(Array.prototype.slice.call(arguments));
// }
// }, {
// get: function(_, prop) {
// if (prop === 'q') {
// return q;
// }
// return function() {
// q.push([prop].concat(Array.prototype.slice.call(arguments)));
// };
// }
// });
// return op;
// })();
export function getInitSnippet(): string {
return `window.op=window.op||function(){var n=[],o=new Proxy((function(){arguments.length>0&&n.push(Array.prototype.slice.call(arguments))}),{get:function(o,t){return"q"===t?n:function(){n.push([t].concat(Array.prototype.slice.call(arguments)))}}});return o}();`;
}

View File

@@ -1,7 +1,7 @@
import { OpenPanel } from './index';
((window) => {
if (window.op && 'q' in window.op) {
if (window.op) {
const queue = window.op.q || [];
// @ts-expect-error
const op = new OpenPanel(queue.shift()[1]);
@@ -12,16 +12,36 @@ import { OpenPanel } from './index';
}
});
window.op = (t, ...args) => {
const fn = op[t] ? op[t].bind(op) : undefined;
if (typeof fn === 'function') {
// @ts-expect-error
fn(...args);
} else {
console.warn(`OpenPanel: ${t} is not a function`);
}
};
// Create a Proxy that supports both window.op('track', ...) and window.op.track(...)
const opCallable = new Proxy(
((method: string, ...args: any[]) => {
const fn = (op as any)[method]
? (op as any)[method].bind(op)
: undefined;
if (typeof fn === 'function') {
fn(...args);
} else {
console.warn(`OpenPanel: ${method} is not a function`);
}
}) as typeof op & ((method: string, ...args: any[]) => void),
{
get(target, prop) {
// Handle special properties
if (prop === 'q') {
return undefined; // q doesn't exist after SDK loads
}
// If accessing a method on op, return the bound method
const value = (op as any)[prop];
if (typeof value === 'function') {
return value.bind(op);
}
// Otherwise return the property from op (for things like options, etc.)
return value;
},
},
);
window.op = opCallable;
window.openpanel = op;
}
})(window);

View File

@@ -12,7 +12,9 @@ type ExposedMethodsNames =
| 'revenue'
| 'flushRevenue'
| 'clearRevenue'
| 'pendingRevenue';
| 'pendingRevenue'
| 'screenView'
| 'fetchDeviceId';
export type ExposedMethods = {
[K in ExposedMethodsNames]: OpenPanel[K] extends (...args: any[]) => any
@@ -20,7 +22,7 @@ export type ExposedMethods = {
: never;
}[ExposedMethodsNames];
export type OpenPanelMethodNames = ExposedMethodsNames | 'init' | 'screenView';
export type OpenPanelMethodNames = ExposedMethodsNames | 'init';
export type OpenPanelMethods =
| ExposedMethods
| ['init', OpenPanelOptions]
@@ -30,12 +32,26 @@ export type OpenPanelMethods =
TrackProperties | undefined,
];
// Extract method signatures from OpenPanel for direct method calls
type OpenPanelMethodSignatures = {
[K in ExposedMethodsNames]: OpenPanel[K];
} & {
screenView(
pathOrProperties?: string | TrackProperties,
properties?: TrackProperties,
): void;
};
// Create a type that supports both callable and direct method access
type OpenPanelAPI = OpenPanelMethodSignatures & {
q?: OpenPanelMethods[];
// Callable function API: window.op('track', 'event', {...})
(...args: OpenPanelMethods): void;
};
declare global {
interface Window {
openpanel?: OpenPanel;
op: {
q?: OpenPanelMethods[];
(...args: OpenPanelMethods): void;
};
op: OpenPanelAPI;
}
}

View File

@@ -0,0 +1,89 @@
// Test callable function API
function testCallableAPI() {
// ✅ Should work - correct callable syntax
window.op('track', 'button_clicked', { location: 'header' });
window.op('identify', { profileId: 'user123', email: 'test@example.com' });
window.op('init', { clientId: 'test-client-id' });
window.op('screenView', '/page', { title: 'Test Page' });
window.op('setGlobalProperties', { version: '1.0.0' });
// ❌ Should error - wrong method name
// @ts-expect-error - 'invalidMethod' is not a valid method
window.op('invalidMethod', 'test');
// ❌ Should error - wrong arguments for track
// @ts-expect-error - track expects (name: string, properties?: TrackProperties)
window.op('track', 123);
}
// Test direct method API
function testDirectMethodAPI() {
// ✅ Should work - correct direct method syntax
window.op.track('button_clicked', { location: 'header' });
window.op.identify({ profileId: 'user123', email: 'test@example.com' });
window.op.screenView('/page', { title: 'Test Page' });
window.op.screenView({ title: 'Test Page' }); // Overload with just properties
window.op.setGlobalProperties({ version: '1.0.0' });
window.op.revenue(1000, { currency: 'USD' });
window.op.pendingRevenue(500, { productId: '123' });
window.op.flushRevenue();
window.op.clearRevenue();
window.op.fetchDeviceId();
// ❌ Should error - wrong arguments for track
// @ts-expect-error - track expects (name: string, properties?: TrackProperties)
window.op.track(123);
// ❌ Should error - wrong arguments for identify
// @ts-expect-error - identify expects IdentifyPayload
window.op.identify('user123');
}
// Test queue property
function testQueueProperty() {
// ✅ Should work - q is optional and can be accessed
const queue = window.op.q;
if (queue) {
queue.forEach((item) => {
// Queue items should be properly typed
if (item[0] === 'track') {
const eventName = item[1]; // Should be string
const properties = item[2]; // Should be TrackProperties | undefined
}
});
}
}
// Test that both APIs work together
function testBothAPIs() {
// Mix and match - both should work
window.op('track', 'event1', { prop: 'value' });
window.op.track('event2', { prop: 'value' });
window.op('identify', { profileId: '123' });
window.op.identify({ profileId: '456' });
}
// Test autocomplete and type inference
function testTypeInference() {
// TypeScript should infer the correct types
const trackCall = window.op.track;
// trackCall should be: (name: string, properties?: TrackProperties) => Promise<void>
const identifyCall = window.op.identify;
// identifyCall should be: (payload: IdentifyPayload) => Promise<void>
// Callable function should accept OpenPanelMethods
const callable = window.op;
// callable should be callable with OpenPanelMethods
}
function testExpectedErrors() {
// @ts-expect-error - 'invalidMethod' is not a valid method
window.op('invalidMethod', 'test');
// @ts-expect-error - track expects (name: string, properties?: TrackProperties)
window.op.track(123);
// @ts-expect-error - identify expects IdentifyPayload
window.op.identify('user123');
}
export {};

View File

@@ -1,5 +1,5 @@
{
"extends": "@openpanel/tsconfig/base.json",
"extends": "@openpanel/tsconfig/sdk.json",
"compilerOptions": {
"incremental": false,
"outDir": "dist"