committed by
GitHub
parent
3bd1f99d28
commit
1f088d2208
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"]
|
||||
}
|
||||
Reference in New Issue
Block a user