Skip to content

Commit 0d27281

Browse files
feat(nextjs): Forward user-agent, arch, platform, and npm config with POST requests to /accountless_applications (#6483)
1 parent 58a5da1 commit 0d27281

File tree

5 files changed

+88
-4
lines changed

5 files changed

+88
-4
lines changed

.changeset/strong-chicken-lie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clerk/backend": patch
3+
"@clerk/nextjs": patch
4+
---
5+
6+
feat(nextjs): Forward user-agent, arch, platform, and npm config with POST requests to /accountless_applications

packages/backend/src/api/endpoints/AccountlessApplicationsAPI.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@ import { AbstractAPI } from './AbstractApi';
55
const basePath = '/accountless_applications';
66

77
export class AccountlessApplicationAPI extends AbstractAPI {
8-
public async createAccountlessApplication() {
8+
public async createAccountlessApplication(params?: { requestHeaders?: Headers }) {
9+
const headerParams = params?.requestHeaders ? Object.fromEntries(params.requestHeaders.entries()) : undefined;
910
return this.request<AccountlessApplication>({
1011
method: 'POST',
1112
path: basePath,
13+
headerParams,
1214
});
1315
}
1416

15-
public async completeAccountlessApplicationOnboarding() {
17+
public async completeAccountlessApplicationOnboarding(params?: { requestHeaders?: Headers }) {
18+
const headerParams = params?.requestHeaders ? Object.fromEntries(params.requestHeaders.entries()) : undefined;
1619
return this.request<AccountlessApplication>({
1720
method: 'POST',
1821
path: joinPaths(basePath, 'complete'),
22+
headerParams,
1923
});
2024
}
2125
}

packages/nextjs/src/app-router/server/keyless-provider.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { PropsWithChildren } from 'react';
55
import React from 'react';
66

77
import { createClerkClientWithOptions } from '../../server/createClerkClient';
8+
import { collectKeylessMetadata, formatMetadataHeaders } from '../../server/keyless-custom-headers';
89
import type { NextClerkProviderProps } from '../../types';
910
import { canUseKeyless } from '../../utils/feature-flags';
1011
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
@@ -92,12 +93,20 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
9293
secretKey,
9394
});
9495

96+
// Collect metadata
97+
const keylessHeaders = await collectKeylessMetadata()
98+
.then(formatMetadataHeaders)
99+
.catch(() => new Headers());
100+
95101
/**
96102
* Notifying the dashboard the should runs once. We are controlling this behaviour by caching the result of the request.
97103
* If the request fails, it will be considered stale after 10 minutes, otherwise it is cached for 24 hours.
98104
*/
99105
await clerkDevelopmentCache?.run(
100-
() => client.__experimental_accountlessApplications.completeAccountlessApplicationOnboarding(),
106+
() =>
107+
client.__experimental_accountlessApplications.completeAccountlessApplicationOnboarding({
108+
requestHeaders: keylessHeaders,
109+
}),
101110
{
102111
cacheKey: `${newOrReadKeys.publishableKey}_complete`,
103112
onSuccessStale: 24 * 60 * 60 * 1000, // 24 hours
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { headers } from 'next/headers';
2+
3+
interface MetadataHeaders {
4+
nodeVersion?: string;
5+
nextVersion?: string;
6+
npmConfigUserAgent?: string;
7+
userAgent?: string;
8+
}
9+
10+
/**
11+
* Collects metadata from the environment and request headers
12+
*/
13+
export async function collectKeylessMetadata(): Promise<MetadataHeaders> {
14+
const headerStore = await headers(); // eslint-disable-line
15+
16+
return {
17+
nodeVersion: process.version,
18+
nextVersion: getNextVersion(),
19+
npmConfigUserAgent: process.env.npm_config_user_agent, // eslint-disable-line
20+
userAgent: headerStore.get('User-Agent') ?? undefined,
21+
};
22+
}
23+
24+
/**
25+
* Extracts Next.js version from process title
26+
*/
27+
function getNextVersion(): string | undefined {
28+
try {
29+
return process.title ?? 'unknown-process-title'; // 'next-server (v15.4.5)'
30+
} catch {
31+
return undefined;
32+
}
33+
}
34+
35+
/**
36+
* Converts metadata to HTTP headers
37+
*/
38+
export function formatMetadataHeaders(metadata: MetadataHeaders): Headers {
39+
const headers = new Headers();
40+
41+
if (metadata.nodeVersion) {
42+
headers.set('Clerk-Node-Version', metadata.nodeVersion);
43+
}
44+
45+
if (metadata.nextVersion) {
46+
headers.set('Clerk-Next-Version', metadata.nextVersion);
47+
}
48+
49+
if (metadata.npmConfigUserAgent) {
50+
headers.set('Clerk-NPM-Config-User-Agent', metadata.npmConfigUserAgent);
51+
}
52+
53+
if (metadata.userAgent) {
54+
headers.set('Clerk-Client-User-Agent', metadata.userAgent);
55+
}
56+
57+
return headers;
58+
}

packages/nextjs/src/server/keyless-node.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AccountlessApplication } from '@clerk/backend';
22

33
import { createClerkClientWithOptions } from './createClerkClient';
44
import { nodeCwdOrThrow, nodeFsOrThrow, nodePathOrThrow } from './fs/utils';
5+
import { collectKeylessMetadata, formatMetadataHeaders } from './keyless-custom-headers';
56

67
/**
78
* The Clerk-specific directory name.
@@ -134,8 +135,14 @@ async function createOrReadKeyless(): Promise<AccountlessApplication | null> {
134135
* At this step, it is safe to create new keys and store them.
135136
*/
136137
const client = createClerkClientWithOptions({});
138+
139+
// Collect metadata
140+
const keylessHeaders = await collectKeylessMetadata()
141+
.then(formatMetadataHeaders)
142+
.catch(() => new Headers());
143+
137144
const accountlessApplication = await client.__experimental_accountlessApplications
138-
.createAccountlessApplication()
145+
.createAccountlessApplication({ requestHeaders: keylessHeaders })
139146
.catch(() => null);
140147

141148
if (accountlessApplication) {

0 commit comments

Comments
 (0)