File tree Expand file tree Collapse file tree 2 files changed +16
-8
lines changed
productDescription/[productId] Expand file tree Collapse file tree 2 files changed +16
-8
lines changed Original file line number Diff line number Diff line change @@ -30,12 +30,6 @@ const jwtSchema = z.object({
3030} ) ;
3131
3232export async function GET ( request : NextRequest ) {
33- function appendExchangeToken ( url : string , token : string ) : string {
34- const delimiter = new URL ( url , env . APP_ORIGIN ) . search ? '&' : '?' ;
35-
36- return `${ url } ${ delimiter } exchangeToken=${ token } ` ;
37- }
38-
3933 const parsedParams = queryParamSchema . safeParse (
4034 Object . fromEntries ( request . nextUrl . searchParams )
4135 ) ;
@@ -65,7 +59,14 @@ export async function GET(request: NextRequest) {
6559
6660 const exchangeToken = await db . saveClientToken ( clientToken ) ;
6761
68- return NextResponse . redirect ( new URL ( appendExchangeToken ( path , exchangeToken ) , env . APP_ORIGIN ) , {
62+ // IMPORTANT: product names can contain '#' and BigCommerce may include them in the `url`.
63+ // If we append query params by string concatenation, we can accidentally place `exchangeToken`
64+ // after the '#' fragment, which browsers do not send to the server. Always mutate `searchParams`
65+ // on a URL object so the token is guaranteed to be in the query string.
66+ const redirectUrl = new URL ( path , env . APP_ORIGIN ) ;
67+ redirectUrl . searchParams . set ( 'exchangeToken' , exchangeToken ) ;
68+
69+ return NextResponse . redirect ( redirectUrl , {
6970 status : 302 ,
7071 statusText : 'Found' ,
7172 } ) ;
Original file line number Diff line number Diff line change @@ -6,13 +6,20 @@ import { headers } from 'next/headers';
66
77interface PageProps {
88 params : { productId : string } ;
9- searchParams : { product_name : string ; exchangeToken : string } ;
9+ searchParams : { product_name ? : string ; exchangeToken ? : string } ;
1010}
1111
1212export default async function Page ( props : PageProps ) {
1313 const { productId } = props . params ;
1414 const { product_name : name , exchangeToken } = props . searchParams ;
1515
16+ if ( ! exchangeToken ) {
17+ // This typically happens when a product name contains an unencoded '#', which turns the rest of
18+ // the URL into a fragment (not sent to the server). The /api/app/load redirect now ensures the
19+ // exchangeToken is always appended before any fragment, but keep this guard to avoid a hard crash.
20+ throw new Error ( 'Missing exchange token. Try to re-open the app.' ) ;
21+ }
22+
1623 const authToken = await db . getClientTokenMaybeAndDelete ( exchangeToken ) || 'missing' ;
1724
1825 const authorized = authorize ( authToken ) ;
You can’t perform that action at this time.
0 commit comments