Skip to content

Commit 7e3174a

Browse files
authored
feat(js): expose loaded @clerk/ui version via Clerk.uiVersion (#8931)
1 parent 1693915 commit 7e3174a

6 files changed

Lines changed: 80 additions & 0 deletions

File tree

.changeset/clerk-ui-version.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/shared': minor
4+
'@clerk/react': minor
5+
---
6+
7+
Expose the loaded `@clerk/ui` version on the `Clerk` object via a new `uiVersion` property. It returns the version of the prebuilt UI bundle once it has loaded (for example `Clerk.uiVersion` in the browser console), or `undefined` if the UI has not been loaded yet. `@clerk/clerk-js` and `@clerk/ui` are versioned and loaded independently, so `uiVersion` can differ from `Clerk.version`.

.typedoc/__tests__/__snapshots__/clerk-properties.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
| <a id="session"></a> `session` | <code>undefined \| null \| [SignedInSessionResource](/docs/reference/objects/session)</code> | The currently active `Session`, which is guaranteed to be one of the sessions in `Client.sessions`. If there is no active session, this field will be `null`. If the session is loading, this field will be `undefined`. |
1818
| <a id="status"></a> `status` | <code>"error" \| "degraded" \| "loading" \| "ready"</code> | The status of the `Clerk` instance. Possible values are: <ul> <li>`"error"`: Set when hotloading `clerk-js` or `Clerk.load()` failed.</li> <li>`"loading"`: Set during initialization.</li> <li>`"ready"`: Set when Clerk is fully operational.</li> <li>`"degraded"`: Set when Clerk is partially operational.</li> </ul> |
1919
| <a id="telemetry"></a> `telemetry` | <code>undefined \| \{ isDebug: boolean; isEnabled: boolean; record: void; recordLog: void; \}</code> | [Telemetry](/docs/guides/how-clerk-works/security/clerk-telemetry) configuration. |
20+
| <a id="uiversion"></a> `uiVersion` | <code>undefined \| string</code> | The version of `@clerk/ui` that is currently loaded, or `undefined` if the prebuilt UI has not been loaded yet. |
2021
| <a id="user"></a> `user` | <code>undefined \| null \| [UserResource](/docs/reference/objects/user)</code> | A shortcut to `Session.user` which holds the currently active `User` object. If the session is `null` or `undefined`, the user field will match. |
2122
| <a id="version"></a> `version` | <code>undefined \| string</code> | The Clerk SDK version number. |
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import { Clerk } from '../clerk';
4+
import { Client, Environment } from '../resources/internal';
5+
6+
vi.mock('../resources/Client');
7+
vi.mock('../resources/Environment');
8+
9+
vi.mock('../auth/devBrowser', () => ({
10+
createDevBrowser: () => ({
11+
clear: vi.fn(),
12+
setup: vi.fn(),
13+
getDevBrowser: vi.fn(() => 'deadbeef'),
14+
setDevBrowser: vi.fn(),
15+
removeDevBrowser: vi.fn(),
16+
refreshCookies: vi.fn(),
17+
}),
18+
}));
19+
20+
Client.getOrCreateInstance = vi.fn().mockImplementation(() => ({ fetch: vi.fn() }));
21+
Environment.getInstance = vi.fn().mockImplementation(() => ({ fetch: vi.fn(() => Promise.resolve({})) }));
22+
23+
const publishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZXJrLmNvbSQ';
24+
25+
describe('Clerk.uiVersion', () => {
26+
let clerk: Clerk;
27+
28+
beforeEach(() => {
29+
clerk = new Clerk(publishableKey);
30+
});
31+
32+
afterEach(() => {
33+
delete (window as any).__internal_ClerkUICtor;
34+
vi.restoreAllMocks();
35+
});
36+
37+
it('exposes the property on the instance', () => {
38+
expect('uiVersion' in clerk).toBe(true);
39+
});
40+
41+
it('returns undefined when the @clerk/ui bundle has not loaded', () => {
42+
expect((window as any).__internal_ClerkUICtor).toBeUndefined();
43+
expect(clerk.uiVersion).toBeUndefined();
44+
});
45+
46+
it('returns the version published by the loaded @clerk/ui constructor', () => {
47+
(window as any).__internal_ClerkUICtor = { version: '1.18.1' };
48+
expect(clerk.uiVersion).toBe('1.18.1');
49+
});
50+
51+
it('reports a UI version independent of the clerk-js version', () => {
52+
(window as any).__internal_ClerkUICtor = { version: '1.18.1' };
53+
expect(clerk.uiVersion).not.toBe(clerk.version);
54+
});
55+
});

packages/clerk-js/src/core/clerk.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ export class Clerk implements ClerkInterface {
337337
return Clerk.version;
338338
}
339339

340+
get uiVersion(): string | undefined {
341+
// `@clerk/ui` publishes its constructor (which carries its package version) on this global when hot-loaded
342+
// from the CDN; bundled (no-RHC) builds pass the constructor directly via `options.ui.ClerkUI` instead.
343+
const globalCtor = typeof window !== 'undefined' ? window.__internal_ClerkUICtor : undefined;
344+
const bundledCtor = this.#options.ui?.ClerkUI;
345+
return globalCtor?.version ?? (bundledCtor instanceof Promise ? undefined : bundledCtor?.version);
346+
}
347+
340348
set sdkMetadata(metadata: SDKMetadata) {
341349
Clerk.sdkMetadata = metadata;
342350
}

packages/react/src/isomorphicClerk.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
776776
return this.clerkjs?.version;
777777
}
778778

779+
get uiVersion(): string | undefined {
780+
return this.clerkjs?.uiVersion;
781+
}
782+
779783
get client(): ClientResource | undefined {
780784
if (this.clerkjs) {
781785
return this.clerkjs.client;

packages/shared/src/types/clerk.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ export interface Clerk {
256256
*/
257257
version: string | undefined;
258258

259+
/**
260+
* The version of `@clerk/ui` that is currently loaded, or `undefined` if the prebuilt UI has not been loaded yet.
261+
*/
262+
uiVersion: string | undefined;
263+
259264
/**
260265
* If present, contains information about the SDK that the host application is using.
261266
* For example, if Clerk is loaded through `@clerk/nextjs`, this would be `{ name: '@clerk/nextjs', version: '1.0.0' }`. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/guides/development/sdk-development/overview).

0 commit comments

Comments
 (0)