fix: use correct client ip header
This commit is contained in:
@@ -41,7 +41,6 @@
|
|||||||
"groupmq": "1.0.0-next.19",
|
"groupmq": "1.0.0-next.19",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"ramda": "^0.29.1",
|
"ramda": "^0.29.1",
|
||||||
"request-ip": "^3.3.0",
|
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"sqlstring": "^2.3.3",
|
"sqlstring": "^2.3.3",
|
||||||
@@ -58,7 +57,6 @@
|
|||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/ramda": "^0.30.2",
|
"@types/ramda": "^0.30.2",
|
||||||
"@types/request-ip": "^0.0.41",
|
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/sqlstring": "^2.3.2",
|
"@types/sqlstring": "^2.3.2",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getClientIp } from '@/utils/get-client-ip';
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
import { generateDeviceId, parseUserAgent } from '@openpanel/common/server';
|
import { generateDeviceId, parseUserAgent } from '@openpanel/common/server';
|
||||||
@@ -21,7 +20,7 @@ export async function postEvent(
|
|||||||
request.timestamp,
|
request.timestamp,
|
||||||
request.body,
|
request.body,
|
||||||
);
|
);
|
||||||
const ip = getClientIp(request)!;
|
const ip = request.clientIp;
|
||||||
const ua = request.headers['user-agent']!;
|
const ua = request.headers['user-agent']!;
|
||||||
const projectId = request.client?.projectId;
|
const projectId = request.client?.projectId;
|
||||||
const headers = getStringHeaders(request.headers);
|
const headers = getStringHeaders(request.headers);
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { parseUrlMeta } from '@/utils/parseUrlMeta';
|
|||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
|
||||||
import { getClientIp } from '@/utils/get-client-ip';
|
import {
|
||||||
|
DEFAULT_HEADER_ORDER,
|
||||||
|
getClientIpFromHeaders,
|
||||||
|
} from '@openpanel/common/server/get-client-ip';
|
||||||
import { TABLE_NAMES, ch, chQuery, formatClickhouseDate } from '@openpanel/db';
|
import { TABLE_NAMES, ch, chQuery, formatClickhouseDate } from '@openpanel/db';
|
||||||
import { getGeoLocation } from '@openpanel/geo';
|
import { type GeoLocation, getGeoLocation } from '@openpanel/geo';
|
||||||
import { getCache, getRedisCache } from '@openpanel/redis';
|
import { getCache, getRedisCache } from '@openpanel/redis';
|
||||||
|
|
||||||
interface GetFaviconParams {
|
interface GetFaviconParams {
|
||||||
@@ -394,12 +397,35 @@ export async function stats(request: FastifyRequest, reply: FastifyReply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getGeo(request: FastifyRequest, reply: FastifyReply) {
|
export async function getGeo(request: FastifyRequest, reply: FastifyReply) {
|
||||||
const ip = getClientIp(request);
|
const ip = getClientIpFromHeaders(request.headers);
|
||||||
|
const others = await Promise.all(
|
||||||
|
DEFAULT_HEADER_ORDER.map(async (header) => {
|
||||||
|
const ip = getClientIpFromHeaders(request.headers, header);
|
||||||
|
return {
|
||||||
|
header,
|
||||||
|
ip,
|
||||||
|
geo: await getGeoLocation(ip),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
return reply.status(400).send('Bad Request');
|
return reply.status(400).send('Bad Request');
|
||||||
}
|
}
|
||||||
const geo = await getGeoLocation(ip);
|
const geo = await getGeoLocation(ip);
|
||||||
return reply.status(200).send(geo);
|
return reply.status(200).send({
|
||||||
|
selected: {
|
||||||
|
geo,
|
||||||
|
ip,
|
||||||
|
},
|
||||||
|
...others.reduce(
|
||||||
|
(acc, other) => {
|
||||||
|
acc[other.header] = other;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, { ip: string; geo: GeoLocation }>,
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOgImage(
|
export async function getOgImage(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getClientIp } from '@/utils/get-client-ip';
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { assocPath, pathOr } from 'ramda';
|
import { assocPath, pathOr } from 'ramda';
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ export async function updateProfile(
|
|||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
return reply.status(400).send('No projectId');
|
return reply.status(400).send('No projectId');
|
||||||
}
|
}
|
||||||
const ip = getClientIp(request)!;
|
const ip = request.clientIp;
|
||||||
const ua = request.headers['user-agent']!;
|
const ua = request.headers['user-agent']!;
|
||||||
const uaInfo = parseUserAgent(ua, properties);
|
const uaInfo = parseUserAgent(ua, properties);
|
||||||
const geo = await getGeoLocation(ip);
|
const geo = await getGeoLocation(ip);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getClientIp } from '@/utils/get-client-ip';
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { path, assocPath, pathOr, pick } from 'ramda';
|
import { path, assocPath, pathOr, pick } from 'ramda';
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ export async function handler(
|
|||||||
const timestamp = getTimestamp(request.timestamp, request.body.payload);
|
const timestamp = getTimestamp(request.timestamp, request.body.payload);
|
||||||
const ip =
|
const ip =
|
||||||
path<string>(['properties', '__ip'], request.body.payload) ||
|
path<string>(['properties', '__ip'], request.body.payload) ||
|
||||||
getClientIp(request)!;
|
request.clientIp;
|
||||||
const ua = request.headers['user-agent']!;
|
const ua = request.headers['user-agent']!;
|
||||||
const projectId = request.client?.projectId;
|
const projectId = request.client?.projectId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { getClientIp } from '@/utils/get-client-ip';
|
import { getClientIpFromHeaders } from '@openpanel/common/server/get-client-ip';
|
||||||
import type {
|
import type { FastifyRequest } from 'fastify';
|
||||||
FastifyReply,
|
|
||||||
FastifyRequest,
|
|
||||||
HookHandlerDoneFunction,
|
|
||||||
} from 'fastify';
|
|
||||||
|
|
||||||
export async function ipHook(request: FastifyRequest) {
|
export async function ipHook(request: FastifyRequest) {
|
||||||
const ip = getClientIp(request);
|
const ip = getClientIpFromHeaders(request.headers);
|
||||||
|
|
||||||
if (ip) {
|
if (ip) {
|
||||||
request.clientIp = ip;
|
request.clientIp = ip;
|
||||||
|
} else {
|
||||||
|
request.clientIp = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ process.env.TZ = 'UTC';
|
|||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
client: IServiceClientWithProject | null;
|
client: IServiceClientWithProject | null;
|
||||||
clientIp?: string;
|
clientIp: string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
session: SessionValidationResult;
|
session: SessionValidationResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import type { FastifyRequest } from 'fastify';
|
|
||||||
import requestIp from 'request-ip';
|
|
||||||
|
|
||||||
const ignore = ['127.0.0.1', '::1'];
|
|
||||||
|
|
||||||
export function getClientIp(req: FastifyRequest) {
|
|
||||||
return requestIp.getClientIp(req);
|
|
||||||
}
|
|
||||||
67
apps/public/app/api/[...op]/route.ts
Normal file
67
apps/public/app/api/[...op]/route.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
import { getClientIpFromHeaders } from '@openpanel/common/server/get-client-ip';
|
||||||
|
// adding .js next/script import fixes an issues
|
||||||
|
// with esm and nextjs (when using pages dir)
|
||||||
|
import { NextResponse } from 'next/server.js';
|
||||||
|
|
||||||
|
type CreateNextRouteHandlerOptions = {
|
||||||
|
apiUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createNextRouteHandler(options: CreateNextRouteHandlerOptions) {
|
||||||
|
return async function POST(req: Request) {
|
||||||
|
const apiUrl = options.apiUrl ?? 'https://api.openpanel.dev';
|
||||||
|
const headers = new Headers(req.headers);
|
||||||
|
const clientIp = getClientIpFromHeaders(headers);
|
||||||
|
console.log('debug', {
|
||||||
|
clientIp,
|
||||||
|
userAgent: req.headers.get('user-agent'),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${apiUrl}/track`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(await req.json()),
|
||||||
|
});
|
||||||
|
return NextResponse.json(await res.text(), { status: res.status });
|
||||||
|
} catch (e) {
|
||||||
|
return NextResponse.json(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptHandler() {
|
||||||
|
return async function GET(req: Request) {
|
||||||
|
if (!req.url.endsWith('op1.js')) {
|
||||||
|
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptUrl = 'https://openpanel.dev/op1.js';
|
||||||
|
try {
|
||||||
|
const res = await fetch(scriptUrl, {
|
||||||
|
next: { revalidate: 86400 },
|
||||||
|
});
|
||||||
|
const text = await res.text();
|
||||||
|
const etag = `"${createHash('md5').update(text).digest('hex')}"`;
|
||||||
|
return new NextResponse(text, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/javascript',
|
||||||
|
'Cache-Control':
|
||||||
|
'public, max-age=86400, stale-while-revalidate=86400',
|
||||||
|
ETag: etag,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Failed to fetch script',
|
||||||
|
message: e instanceof Error ? e.message : String(e),
|
||||||
|
},
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST = createNextRouteHandler({});
|
||||||
|
export const GET = createScriptHandler();
|
||||||
@@ -61,12 +61,9 @@ export default async function Layout({ children }: { children: ReactNode }) {
|
|||||||
<RootProvider>
|
<RootProvider>
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
</RootProvider>
|
</RootProvider>
|
||||||
<Script
|
|
||||||
defer
|
|
||||||
src="http://localhost:3000/script.js"
|
|
||||||
data-website-id="44d65df1-e9cb-4c2c-917d-4bf1c7850948"
|
|
||||||
/>
|
|
||||||
<OpenPanelComponent
|
<OpenPanelComponent
|
||||||
|
apiUrl="/api/op"
|
||||||
|
cdnUrl="/api/op/op1.js"
|
||||||
clientId="301c6dc1-424c-4bc3-9886-a8beab09b615"
|
clientId="301c6dc1-424c-4bc3-9886-a8beab09b615"
|
||||||
trackAttributes
|
trackAttributes
|
||||||
trackScreenViews
|
trackScreenViews
|
||||||
|
|||||||
@@ -273,19 +273,21 @@ export function GET() {
|
|||||||
|
|
||||||
### Proxy events
|
### Proxy events
|
||||||
|
|
||||||
With `createNextRouteHandler` you can proxy your events through your server, this will ensure all events are tracked since there is a lot of adblockers that block requests to third party domains.
|
With `createNextRouteHandler` you can proxy your events through your server, this will ensure all events are tracked since there is a lot of adblockers that block requests to third party domains. You'll also need to either host our tracking script or you can use `createScriptHandler` function which proxies this as well.
|
||||||
|
|
||||||
```typescript title="/app/api/[...op]/route.ts"
|
```typescript title="/app/api/[...op]/route.ts"
|
||||||
import { createNextRouteHandler } from '@openpanel/nextjs/server';
|
import { createNextRouteHandler, createScriptHandler } from '@openpanel/nextjs/server';
|
||||||
|
|
||||||
export const POST = createNextRouteHandler();
|
export const POST = createNextRouteHandler();
|
||||||
|
export const GET = createScriptHandler()
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember to change the `apiUrl` in the `OpenPanelComponent` to your own server.
|
Remember to change the `apiUrl` and `cdnUrl` in the `OpenPanelComponent` to your own server.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
<OpenPanelComponent
|
<OpenPanelComponent
|
||||||
apiUrl="/api/op" // [!code highlight]
|
apiUrl="/api/op" // [!code highlight]
|
||||||
|
cdnUrl="/api/op/op1.js" // [!code highlight]
|
||||||
clientId="your-client-id"
|
clientId="your-client-id"
|
||||||
trackScreenViews={true}
|
trackScreenViews={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hyperdx/node-opentelemetry": "^0.8.1",
|
"@hyperdx/node-opentelemetry": "^0.8.1",
|
||||||
"@number-flow/react": "0.3.5",
|
"@number-flow/react": "0.3.5",
|
||||||
|
"@openpanel/common": "workspace:*",
|
||||||
"@openpanel/nextjs": "^1.0.5",
|
"@openpanel/nextjs": "^1.0.5",
|
||||||
"@openpanel/payments": "workspace:^",
|
"@openpanel/payments": "workspace:^",
|
||||||
"@openpanel/sdk-info": "workspace:^",
|
"@openpanel/sdk-info": "workspace:^",
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export * from './src/url';
|
|||||||
export * from './src/id';
|
export * from './src/id';
|
||||||
export * from './src/get-previous-metric';
|
export * from './src/get-previous-metric';
|
||||||
export * from './src/group-by-labels';
|
export * from './src/group-by-labels';
|
||||||
|
export * from './server/get-client-ip';
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./server": "./server/index.ts"
|
"./server": "./server/index.ts",
|
||||||
|
"./server/get-client-ip": "./server/get-client-ip.ts"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
|||||||
86
packages/common/server/get-client-ip.ts
Normal file
86
packages/common/server/get-client-ip.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Get client IP from headers
|
||||||
|
*
|
||||||
|
* Can be configured via IP_HEADER_ORDER env variable
|
||||||
|
* Example: IP_HEADER_ORDER="cf-connecting-ip,x-real-ip,x-forwarded-for"
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_HEADER_ORDER = [
|
||||||
|
'cf-connecting-ip',
|
||||||
|
'true-client-ip',
|
||||||
|
'x-real-ip',
|
||||||
|
'x-client-ip',
|
||||||
|
'fastly-client-ip',
|
||||||
|
'x-cluster-client-ip',
|
||||||
|
'x-appengine-user-ip',
|
||||||
|
'do-connecting-ip',
|
||||||
|
'x-nf-client-connection-ip',
|
||||||
|
'x-forwarded-for',
|
||||||
|
'x-forwarded',
|
||||||
|
'forwarded',
|
||||||
|
];
|
||||||
|
|
||||||
|
function getHeaderOrder(): string[] {
|
||||||
|
if (typeof process !== 'undefined' && process.env?.IP_HEADER_ORDER) {
|
||||||
|
return process.env.IP_HEADER_ORDER.split(',').map((h) => h.trim());
|
||||||
|
}
|
||||||
|
return DEFAULT_HEADER_ORDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidIp(ip: string): boolean {
|
||||||
|
// Basic IP validation
|
||||||
|
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||||
|
const ipv6 = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
||||||
|
return ipv4.test(ip) || ipv6.test(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getClientIpFromHeaders(
|
||||||
|
headers: Record<string, string | string[] | undefined> | Headers,
|
||||||
|
overrideHeaderName?: string,
|
||||||
|
): string {
|
||||||
|
let headerOrder = getHeaderOrder();
|
||||||
|
|
||||||
|
if (overrideHeaderName) {
|
||||||
|
headerOrder = [overrideHeaderName];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const headerName of headerOrder) {
|
||||||
|
let value: string | null = null;
|
||||||
|
|
||||||
|
// Get header value
|
||||||
|
if (headers instanceof Headers) {
|
||||||
|
value = headers.get(headerName);
|
||||||
|
} else {
|
||||||
|
const headerValue = headers[headerName];
|
||||||
|
if (Array.isArray(headerValue)) {
|
||||||
|
value = headerValue[0] || null;
|
||||||
|
} else {
|
||||||
|
value = headerValue || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) continue;
|
||||||
|
|
||||||
|
// Handle x-forwarded-for (comma separated)
|
||||||
|
if (headerName === 'x-forwarded-for') {
|
||||||
|
const firstIp = value.split(',')[0]?.trim();
|
||||||
|
if (firstIp && isValidIp(firstIp)) {
|
||||||
|
return firstIp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle forwarded header (RFC 7239)
|
||||||
|
else if (headerName === 'forwarded') {
|
||||||
|
const match = value.match(/for=(?:"?\[?([^\]"]+)\]?"?)/i);
|
||||||
|
const ip = match?.[1];
|
||||||
|
if (ip && isValidIp(ip)) {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Regular headers
|
||||||
|
else if (isValidIp(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { getClientIpFromHeaders } from '@openpanel/common/server/get-client-ip';
|
||||||
import type { NextFunction, Request, Response } from 'express';
|
import type { NextFunction, Request, Response } from 'express';
|
||||||
import { getClientIp } from 'request-ip';
|
|
||||||
|
|
||||||
import type { OpenPanelOptions } from '@openpanel/sdk';
|
import type { OpenPanelOptions } from '@openpanel/sdk';
|
||||||
import { OpenPanel } from '@openpanel/sdk';
|
import { OpenPanel } from '@openpanel/sdk';
|
||||||
@@ -22,7 +22,7 @@ export type OpenpanelOptions = OpenPanelOptions & {
|
|||||||
export default function createMiddleware(options: OpenpanelOptions) {
|
export default function createMiddleware(options: OpenpanelOptions) {
|
||||||
return function middleware(req: Request, res: Response, next: NextFunction) {
|
return function middleware(req: Request, res: Response, next: NextFunction) {
|
||||||
const sdk = new OpenPanel(options);
|
const sdk = new OpenPanel(options);
|
||||||
const ip = getClientIp(req);
|
const ip = getClientIpFromHeaders(req.headers);
|
||||||
if (ip) {
|
if (ip) {
|
||||||
sdk.api.addHeader('x-client-ip', ip);
|
sdk.api.addHeader('x-client-ip', ip);
|
||||||
}
|
}
|
||||||
@@ -30,20 +30,15 @@ export default function createMiddleware(options: OpenpanelOptions) {
|
|||||||
sdk.api.addHeader('x-user-agent', req.headers['user-agent'] as string);
|
sdk.api.addHeader('x-user-agent', req.headers['user-agent'] as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.getProfileId) {
|
|
||||||
const profileId = options.getProfileId(req);
|
|
||||||
if (profileId) {
|
|
||||||
sdk.identify({
|
|
||||||
profileId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.trackRequest?.(req.url)) {
|
if (options.trackRequest?.(req.url)) {
|
||||||
|
const profileId = options.getProfileId
|
||||||
|
? options.getProfileId(req)
|
||||||
|
: undefined;
|
||||||
sdk.track('request', {
|
sdk.track('request', {
|
||||||
url: req.url,
|
url: req.url,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
query: req.query,
|
query: req.query,
|
||||||
|
profileId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpanel/sdk": "workspace:1.0.0-local",
|
"@openpanel/sdk": "workspace:1.0.0-local",
|
||||||
"request-ip": "^3.3.0"
|
"@openpanel/common": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"express": "^4.17.0 || ^5.0.0"
|
"express": "^4.17.0 || ^5.0.0"
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ export default defineConfig({
|
|||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
clean: true,
|
clean: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
|
// Bundle @openpanel/common into the output instead of treating it as external
|
||||||
|
noExternal: ['@openpanel/common'],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createHash } from 'node:crypto';
|
||||||
// adding .js next/script import fixes an issues
|
// adding .js next/script import fixes an issues
|
||||||
// with esm and nextjs (when using pages dir)
|
// with esm and nextjs (when using pages dir)
|
||||||
import { NextResponse } from 'next/server.js';
|
import { NextResponse } from 'next/server.js';
|
||||||
@@ -16,9 +17,43 @@ export function createNextRouteHandler(options: CreateNextRouteHandlerOptions) {
|
|||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(await req.json()),
|
body: JSON.stringify(await req.json()),
|
||||||
});
|
});
|
||||||
return NextResponse.json(await res.text());
|
return NextResponse.json(await res.text(), { status: res.status });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return NextResponse.json(e);
|
return NextResponse.json(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createScriptHandler() {
|
||||||
|
return async function GET(req: Request) {
|
||||||
|
if (!req.url.endsWith('op1.js')) {
|
||||||
|
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptUrl = 'https://openpanel.dev/op1.js';
|
||||||
|
try {
|
||||||
|
const res = await fetch(scriptUrl, {
|
||||||
|
// @ts-expect-error
|
||||||
|
next: { revalidate: 86400 },
|
||||||
|
});
|
||||||
|
const text = await res.text();
|
||||||
|
const etag = `"${createHash('md5').update(text).digest('hex')}"`;
|
||||||
|
return new NextResponse(text, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/javascript',
|
||||||
|
'Cache-Control':
|
||||||
|
'public, max-age=86400, stale-while-revalidate=86400',
|
||||||
|
ETag: etag,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Failed to fetch script',
|
||||||
|
message: e instanceof Error ? e.message : String(e),
|
||||||
|
},
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -198,9 +198,6 @@ importers:
|
|||||||
ramda:
|
ramda:
|
||||||
specifier: ^0.29.1
|
specifier: ^0.29.1
|
||||||
version: 0.29.1
|
version: 0.29.1
|
||||||
request-ip:
|
|
||||||
specifier: ^3.3.0
|
|
||||||
version: 3.3.0
|
|
||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.33.5
|
specifier: ^0.33.5
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
@@ -277,6 +274,9 @@ importers:
|
|||||||
'@number-flow/react':
|
'@number-flow/react':
|
||||||
specifier: 0.3.5
|
specifier: 0.3.5
|
||||||
version: 0.3.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 0.3.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@openpanel/common':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/common
|
||||||
'@openpanel/nextjs':
|
'@openpanel/nextjs':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 1.0.5(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
@@ -1410,15 +1410,15 @@ importers:
|
|||||||
|
|
||||||
packages/sdks/express:
|
packages/sdks/express:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@openpanel/common':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../common
|
||||||
'@openpanel/sdk':
|
'@openpanel/sdk':
|
||||||
specifier: workspace:1.0.0-local
|
specifier: workspace:1.0.0-local
|
||||||
version: link:../sdk
|
version: link:../sdk
|
||||||
express:
|
express:
|
||||||
specifier: ^4.17.0 || ^5.0.0
|
specifier: ^4.17.0 || ^5.0.0
|
||||||
version: 4.19.2
|
version: 4.19.2
|
||||||
request-ip:
|
|
||||||
specifier: ^3.3.0
|
|
||||||
version: 3.3.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@openpanel/tsconfig':
|
'@openpanel/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -14604,9 +14604,6 @@ packages:
|
|||||||
remove-trailing-slash@0.1.1:
|
remove-trailing-slash@0.1.1:
|
||||||
resolution: {integrity: sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==}
|
resolution: {integrity: sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==}
|
||||||
|
|
||||||
request-ip@3.3.0:
|
|
||||||
resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==}
|
|
||||||
|
|
||||||
require-directory@2.1.1:
|
require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -32008,8 +32005,6 @@ snapshots:
|
|||||||
|
|
||||||
remove-trailing-slash@0.1.1: {}
|
remove-trailing-slash@0.1.1: {}
|
||||||
|
|
||||||
request-ip@3.3.0: {}
|
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user