Skip to content

Commit a03af8c

Browse files
fix(analytics): DATA-13050 Generate text correctly when product name has number sign in name
1 parent 2cd4478 commit a03af8c

File tree

2 files changed

+16
-8
lines changed

2 files changed

+16
-8
lines changed

src/app/api/app/load/route.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@ const jwtSchema = z.object({
3030
});
3131

3232
export 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
});

src/app/productDescription/[productId]/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ import { headers } from 'next/headers';
66

77
interface PageProps {
88
params: { productId: string };
9-
searchParams: { product_name: string; exchangeToken: string };
9+
searchParams: { product_name?: string; exchangeToken?: string };
1010
}
1111

1212
export 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);

0 commit comments

Comments
 (0)