@@ -3,80 +3,141 @@ import { createHash } from 'node:crypto';
33// with esm and nextjs (when using pages dir)
44import { NextResponse } from 'next/server.js' ;
55
6- type CreateNextRouteHandlerOptions = {
6+ type RouteHandlerOptions = {
77 apiUrl ?: string ;
88} ;
99
10- export function createNextRouteHandler (
11- options ?: CreateNextRouteHandlerOptions ,
12- ) {
13- return async function POST ( req : Request ) {
14- const apiUrl = options ?. apiUrl ?? 'https://api.openpanel.dev' ;
15- const headers = new Headers ( ) ;
16-
17- const ip =
18- req . headers . get ( 'cf-connecting-ip' ) ??
19- req . headers . get ( 'x-forwarded-for' ) ?. split ( ',' ) [ 0 ] ??
20- req . headers . get ( 'x-vercel-forwarded-for' ) ;
21- headers . set ( 'Content-Type' , 'application/json' ) ;
22- headers . set (
23- 'openpanel-client-id' ,
24- req . headers . get ( 'openpanel-client-id' ) ?? '' ,
25- ) ;
26- headers . set ( 'origin' , req . headers . get ( 'origin' ) ?? '' ) ;
27- headers . set ( 'User-Agent' , req . headers . get ( 'user-agent' ) ?? '' ) ;
28- if ( ip ) {
29- headers . set ( 'openpanel-client-ip' , ip ) ;
30- }
10+ const DEFAULT_API_URL = 'https://api.openpanel.dev' ;
11+ const SCRIPT_URL = 'https://openpanel.dev' ;
12+ const SCRIPT_PATH = '/op1.js' ;
13+
14+ function getClientHeaders ( req : Request ) : Headers {
15+ const headers = new Headers ( ) ;
16+ const ip =
17+ req . headers . get ( 'cf-connecting-ip' ) ??
18+ req . headers . get ( 'x-forwarded-for' ) ?. split ( ',' ) [ 0 ] ??
19+ req . headers . get ( 'x-vercel-forwarded-for' ) ;
20+
21+ headers . set ( 'Content-Type' , 'application/json' ) ;
22+ headers . set (
23+ 'openpanel-client-id' ,
24+ req . headers . get ( 'openpanel-client-id' ) ?? '' ,
25+ ) ;
3126
32- try {
33- const res = await fetch ( `${ apiUrl } /track` , {
34- method : 'POST' ,
35- headers,
36- body : JSON . stringify ( await req . json ( ) ) ,
37- } ) ;
38- return NextResponse . json ( await res . text ( ) , { status : res . status } ) ;
39- } catch ( e ) {
40- return NextResponse . json ( e ) ;
27+ // Construct origin: browsers send Origin header for POST requests and cross-origin requests,
28+ // but not for same-origin GET requests. Fallback to constructing from request URL.
29+ const origin =
30+ req . headers . get ( 'origin' ) ??
31+ ( ( ) => {
32+ const url = new URL ( req . url ) ;
33+ return `${ url . protocol } //${ url . host } ` ;
34+ } ) ( ) ;
35+ headers . set ( 'origin' , origin ) ;
36+
37+ headers . set ( 'User-Agent' , req . headers . get ( 'user-agent' ) ?? '' ) ;
38+ if ( ip ) {
39+ headers . set ( 'openpanel-client-ip' , ip ) ;
40+ }
41+
42+ return headers ;
43+ }
44+
45+ async function handleApiRoute (
46+ req : Request ,
47+ apiUrl : string ,
48+ apiPath : string ,
49+ ) : Promise < NextResponse > {
50+ const headers = getClientHeaders ( req ) ;
51+
52+ try {
53+ const res = await fetch ( `${ apiUrl } ${ apiPath } ` , {
54+ method : req . method ,
55+ headers,
56+ body :
57+ req . method === 'POST' ? JSON . stringify ( await req . json ( ) ) : undefined ,
58+ } ) ;
59+
60+ if ( res . headers . get ( 'content-type' ) ?. includes ( 'application/json' ) ) {
61+ return NextResponse . json ( await res . json ( ) , { status : res . status } ) ;
4162 }
42- } ;
63+ return NextResponse . json ( await res . text ( ) , { status : res . status } ) ;
64+ } catch ( e ) {
65+ return NextResponse . json (
66+ {
67+ error : 'Failed to proxy request' ,
68+ message : e instanceof Error ? e . message : String ( e ) ,
69+ } ,
70+ { status : 500 } ,
71+ ) ;
72+ }
4373}
4474
45- export function createScriptHandler ( ) {
46- return async function GET ( req : Request ) {
75+ async function handleScriptProxyRoute ( req : Request ) : Promise < NextResponse > {
76+ const url = new URL ( req . url ) ;
77+ const pathname = url . pathname ;
78+
79+ if ( ! pathname . endsWith ( SCRIPT_PATH ) ) {
80+ return NextResponse . json ( { error : 'Not found' } , { status : 404 } ) ;
81+ }
82+
83+ let scriptUrl = `${ SCRIPT_URL } ${ SCRIPT_PATH } ` ;
84+ if ( url . searchParams . size > 0 ) {
85+ scriptUrl += `?${ url . searchParams . toString ( ) } ` ;
86+ }
87+
88+ try {
89+ const res = await fetch ( scriptUrl , {
90+ // @ts -ignore
91+ next : { revalidate : 86400 } ,
92+ } ) ;
93+ const text = await res . text ( ) ;
94+ const etag = `"${ createHash ( 'md5' )
95+ . update ( scriptUrl + text )
96+ . digest ( 'hex' ) } "`;
97+
98+ return new NextResponse ( text , {
99+ headers : {
100+ 'Content-Type' : 'text/javascript' ,
101+ 'Cache-Control' : 'public, max-age=86400, stale-while-revalidate=86400' ,
102+ ETag : etag ,
103+ } ,
104+ } ) ;
105+ } catch ( e ) {
106+ return NextResponse . json (
107+ {
108+ error : 'Failed to fetch script' ,
109+ message : e instanceof Error ? e . message : String ( e ) ,
110+ } ,
111+ { status : 500 } ,
112+ ) ;
113+ }
114+ }
115+
116+ function createRouteHandler ( options ?: RouteHandlerOptions ) {
117+ const apiUrl = options ?. apiUrl ?? DEFAULT_API_URL ;
118+
119+ return async function handler ( req : Request ) : Promise < NextResponse > {
47120 const url = new URL ( req . url ) ;
48- const query = url . searchParams . toString ( ) ;
121+ const pathname = url . pathname ;
122+ const method = req . method ;
49123
50- if ( ! url . pathname . endsWith ( 'op1.js' ) ) {
51- return NextResponse . json ( { error : 'Not found' } , { status : 404 } ) ;
124+ // Handle script proxy: GET /op1.js
125+ if ( method === 'GET' && pathname . endsWith ( SCRIPT_PATH ) ) {
126+ return handleScriptProxyRoute ( req ) ;
52127 }
53128
54- const scriptUrl = 'https://openpanel.dev/op1.js' ;
55- try {
56- const res = await fetch ( scriptUrl , {
57- // @ts -ignore
58- next : { revalidate : 86400 } ,
59- } ) ;
60- const text = await res . text ( ) ;
61- const etag = `"${ createHash ( 'md5' )
62- . update ( text + query )
63- . digest ( 'hex' ) } "`;
64- return new NextResponse ( text , {
65- headers : {
66- 'Content-Type' : 'text/javascript' ,
67- 'Cache-Control' :
68- 'public, max-age=86400, stale-while-revalidate=86400' ,
69- ETag : etag ,
70- } ,
71- } ) ;
72- } catch ( e ) {
73- return NextResponse . json (
74- {
75- error : 'Failed to fetch script' ,
76- message : e instanceof Error ? e . message : String ( e ) ,
77- } ,
78- { status : 500 } ,
79- ) ;
129+ const apiPathMatch = pathname . indexOf ( '/track' ) ;
130+ if ( apiPathMatch === - 1 ) {
131+ return NextResponse . json ( { error : 'Not found' } , { status : 404 } ) ;
80132 }
133+
134+ const apiPath = pathname . substring ( apiPathMatch ) ;
135+ return handleApiRoute ( req , apiUrl , apiPath ) ;
81136 } ;
82137}
138+
139+ export { createRouteHandler } ;
140+
141+ // const routeHandler = createRouteHandler();
142+ // export const GET = routeHandler;
143+ // export const POST = routeHandler;
0 commit comments