feat: add nuxt sdk (#260)

* wip

* fix: improve api route for nuxt
This commit is contained in:
Carl-Gerhard Lindesvärd
2026-01-07 10:28:11 +01:00
committed by GitHub
parent 3bd1f99d28
commit 1f088d2208
13 changed files with 4990 additions and 215 deletions

View File

@@ -0,0 +1,6 @@
import { useNuxtApp } from '#app';
export function useOpenPanel() {
const { $openpanel } = useNuxtApp();
return $openpanel;
}

View 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,
},
};
},
});

View 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);
});