diff --git a/.changeset/wild-guests-battle.md b/.changeset/wild-guests-battle.md new file mode 100644 index 0000000000..cd78e0a229 --- /dev/null +++ b/.changeset/wild-guests-battle.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': minor +--- + +Now sending the Frontend API version through query string params diff --git a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts b/packages/clerk-js/src/core/__tests__/fapiClient.test.ts index 50e80c8916..9aaa045e02 100644 --- a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts +++ b/packages/clerk-js/src/core/__tests__/fapiClient.test.ts @@ -1,5 +1,6 @@ import type { Clerk } from '@clerk/types'; +import { SUPPORTED_FAPI_VERSION } from '../constants'; import { createFapiClient } from '../fapiClient'; const mockedClerkInstance = { @@ -73,38 +74,40 @@ afterAll(() => { describe('buildUrl(options)', () => { it('returns the full frontend API URL', () => { expect(fapiClient.buildUrl({ path: '/foo' }).href).toBe( - 'https://clerk.example.com/v1/foo?_clerk_js_version=42.0.0', + `https://clerk.example.com/v1/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, ); }); it('returns the full frontend API URL using proxy url', () => { - expect(fapiClientWithProxy.buildUrl({ path: '/foo' }).href).toBe(`${proxyUrl}/v1/foo?_clerk_js_version=42.0.0`); + expect(fapiClientWithProxy.buildUrl({ path: '/foo' }).href).toBe( + `${proxyUrl}/v1/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, + ); }); it('adds _clerk_session_id as a query parameter if provided and path does not start with client', () => { expect(fapiClient.buildUrl({ path: '/foo', sessionId: 'sess_42' }).href).toBe( - 'https://clerk.example.com/v1/foo?_clerk_js_version=42.0.0&_clerk_session_id=sess_42', + `https://clerk.example.com/v1/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0&_clerk_session_id=sess_42`, ); expect(fapiClient.buildUrl({ path: '/client/foo', sessionId: 'sess_42' }).href).toBe( - 'https://clerk.example.com/v1/client/foo?_clerk_js_version=42.0.0', + `https://clerk.example.com/v1/client/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, ); }); it('parses search params is an object with string values', () => { expect(fapiClient.buildUrl({ path: '/foo', search: { test: '1' } }).href).toBe( - 'https://clerk.example.com/v1/foo?test=1&_clerk_js_version=42.0.0', + `https://clerk.example.com/v1/foo?test=1&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, ); }); it('parses string search params ', () => { expect(fapiClient.buildUrl({ path: '/foo', search: 'test=2' }).href).toBe( - 'https://clerk.example.com/v1/foo?test=2&_clerk_js_version=42.0.0', + `https://clerk.example.com/v1/foo?test=2&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, ); }); it('parses search params when value contains invalid url symbols', () => { expect(fapiClient.buildUrl({ path: '/foo', search: { bar: 'test=2' } }).href).toBe( - 'https://clerk.example.com/v1/foo?bar=test%3D2&_clerk_js_version=42.0.0', + `https://clerk.example.com/v1/foo?bar=test%3D2&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, ); }); @@ -116,7 +119,9 @@ describe('buildUrl(options)', () => { array: ['item1', 'item2'], }, }).href, - ).toBe('https://clerk.example.com/v1/foo?array=item1&array=item2&_clerk_js_version=42.0.0'); + ).toBe( + `https://clerk.example.com/v1/foo?array=item1&array=item2&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0`, + ); }); // The return value isn't as expected. @@ -152,7 +157,7 @@ describe('request', () => { }); expect(fetch).toHaveBeenCalledWith( - 'https://clerk.example.com/v1/foo?_clerk_js_version=42.0.0&_clerk_session_id=deadbeef', + `https://clerk.example.com/v1/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0&_clerk_session_id=deadbeef`, expect.objectContaining({ credentials: 'include', method: 'GET', @@ -167,7 +172,7 @@ describe('request', () => { }); expect(fetch).toHaveBeenCalledWith( - `${proxyUrl}/v1/foo?_clerk_js_version=42.0.0&_clerk_session_id=deadbeef`, + `${proxyUrl}/v1/foo?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=42.0.0&_clerk_session_id=deadbeef`, expect.objectContaining({ credentials: 'include', method: 'GET', diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts index 3905e61f75..28c6cfd203 100644 --- a/packages/clerk-js/src/core/constants.ts +++ b/packages/clerk-js/src/core/constants.ts @@ -38,3 +38,6 @@ export const SIGN_UP_MODES: Record = { RESTRICTED: 'restricted', WAITLIST: 'waitlist', }; + +// This is the currently supported version of the Frontend API +export const SUPPORTED_FAPI_VERSION = '2021-02-05'; diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index e8ea7011a4..5b0e1fc46a 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -4,6 +4,7 @@ import { runWithExponentialBackOff } from '@clerk/shared/utils'; import type { Clerk, ClerkAPIErrorJSON, ClientJSON } from '@clerk/types'; import { buildEmailAddress as buildEmailAddressUtil, buildURL as buildUrlUtil, stringifyQueryParams } from '../utils'; +import { SUPPORTED_FAPI_VERSION } from './constants'; import { clerkNetworkError } from './errors'; export type HTTPMethod = 'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'; @@ -93,6 +94,9 @@ export function createFapiClient(clerkInstance: Clerk): FapiClient { const searchParams = new URLSearchParams(search as any); // the above will parse {key: ['val1','val2']} as key: 'val1,val2' and we need to recreate the array bellow + // Append supported FAPI version to the query string + searchParams.append('__clerk_api_version', SUPPORTED_FAPI_VERSION); + if (clerkInstance.version) { searchParams.append('_clerk_js_version', clerkInstance.version); } diff --git a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts b/packages/clerk-js/src/core/resources/__tests__/Token.test.ts index 91116e46ac..e9b3e18cf6 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Token.test.ts @@ -1,3 +1,4 @@ +import { SUPPORTED_FAPI_VERSION } from '../../constants'; import { createFapiClient } from '../../fapiClient'; import { mockDevClerkInstance, mockFetch, mockNetworkFailedFetch } from '../../test/fixtures'; import { BaseResource } from '../internal'; @@ -20,7 +21,7 @@ describe('Token', () => { }); expect(global.fetch).toHaveBeenCalledWith( - 'https://clerk.example.com/v1/path/to/tokens?_clerk_js_version=test-0.0.0', + `https://clerk.example.com/v1/path/to/tokens?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test-0.0.0`, // TODO(dimkl): omit extra params from fetch request (eg path, url) - remove expect.objectContaining expect.objectContaining({ method: 'POST', @@ -57,7 +58,7 @@ describe('Token', () => { const token = await Token.create('/path/to/tokens'); expect(global.fetch).toHaveBeenCalledWith( - 'https://clerk.example.com/v1/path/to/tokens?_clerk_js_version=test-0.0.0', + `https://clerk.example.com/v1/path/to/tokens?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test-0.0.0`, // TODO(dimkl): omit extra params from fetch request (eg path, url) - remove expect.objectContaining expect.objectContaining({ method: 'POST', @@ -77,13 +78,12 @@ describe('Token', () => { mockNetworkFailedFetch(); BaseResource.clerk = { getFapiClient: () => createFapiClient(mockDevClerkInstance) } as any; - await expect(Token.create('/path/to/tokens')).rejects.toMatchObject({ - message: - 'ClerkJS: Network error at "https://clerk.example.com/v1/path/to/tokens?_clerk_js_version=test-0.0.0" - TypeError: Failed to fetch. Please try again.', - }); + await expect(Token.create('/path/to/tokens')).rejects.toThrow( + `ClerkJS: Network error at "https://clerk.example.com/v1/path/to/tokens?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test-0.0.0" - TypeError: Failed to fetch. Please try again.`, + ); expect(global.fetch).toHaveBeenCalledWith( - 'https://clerk.example.com/v1/path/to/tokens?_clerk_js_version=test-0.0.0', + `https://clerk.example.com/v1/path/to/tokens?__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test-0.0.0`, // TODO(dimkl): omit extra params from fetch request (eg path, url) - remove expect.objectContaining expect.objectContaining({ method: 'POST',