Skip to content

Commit 0909f36

Browse files
fix: load Clerk browser bundle from CDN to enable mountSignIn in Electron
Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/d833ade0-d70c-484f-abc9-cb1e3ad7bda9 Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
1 parent 55794f6 commit 0909f36

1 file changed

Lines changed: 56 additions & 8 deletions

File tree

gui-js/libs/core/src/lib/services/clerk/clerk.service.ts

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { Injectable } from '@angular/core';
22
import { ElectronService } from '../electron/electron.service';
33
import { events } from '@minsky/shared';
4-
import { Clerk } from '@clerk/clerk-js';
4+
import type { Clerk } from '@clerk/clerk-js';
55
import { AppConfig } from '@minsky/environment';
66

7+
declare global {
8+
interface Window {
9+
Clerk?: Clerk;
10+
__clerk_publishable_key?: string;
11+
}
12+
}
13+
714
@Injectable({
815
providedIn: 'root',
916
})
@@ -16,15 +23,56 @@ export class ClerkService {
1623
async initialize(): Promise<void> {
1724
if (this.initialized) return;
1825

19-
this.clerk = new Clerk(AppConfig.clerkPublishableKey);
20-
// standardBrowser: true forces the UI component renderer to initialise even
21-
// in non-standard browser contexts such as Electron (where environment
22-
// detection may otherwise return false and leave #componentControls null,
23-
// causing mountSignIn() to throw "Clerk was not loaded with Ui components").
24-
await this.clerk.load({ standardBrowser: true });
26+
// Load Clerk via its CDN browser bundle rather than the npm-imported module.
27+
// The npm dist files (clerk.js / clerk.mjs) do not bundle the ClerkUI
28+
// implementation, so clerk.mountSignIn() would always throw
29+
// "Clerk was not loaded with Ui components" when called on an instance
30+
// created with `new Clerk(key)` from the npm package.
31+
// The browser bundle served from Clerk's CDN lazily loads the UI chunks
32+
// (React-based pre-built components) from the same CDN origin, enabling
33+
// mountSignIn() to work correctly.
34+
await this.loadClerkBrowserBundle();
35+
this.clerk = window.Clerk!;
36+
await this.clerk.load();
2537
this.initialized = true;
2638
}
2739

40+
/**
41+
* Dynamically injects Clerk's CDN browser bundle script into the document.
42+
* The CDN URL is derived from the publishable key's embedded frontendApi.
43+
* Returns a Promise that resolves once the script has loaded and
44+
* window.Clerk has been set by the bundle.
45+
*/
46+
private loadClerkBrowserBundle(): Promise<void> {
47+
return new Promise<void>((resolve, reject) => {
48+
if (window.Clerk) {
49+
resolve();
50+
return;
51+
}
52+
53+
const pk = AppConfig.clerkPublishableKey;
54+
// Publishable key format: pk_<type>_<base64(frontendApi + '$')>
55+
const encoded = pk.split('_')[2] ?? '';
56+
const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);
57+
let frontendApi: string;
58+
try {
59+
frontendApi = atob(padded).replace(/\$$/, '');
60+
} catch {
61+
reject(new Error('Invalid Clerk publishable key'));
62+
return;
63+
}
64+
65+
const script = document.createElement('script');
66+
script.src = `https://${frontendApi}/npm/@clerk/clerk-js@latest/dist/clerk.browser.js`;
67+
script.setAttribute('data-clerk-publishable-key', pk);
68+
script.async = true;
69+
script.onload = () => resolve();
70+
script.onerror = () =>
71+
reject(new Error('Failed to load Clerk authentication service'));
72+
document.head.appendChild(script);
73+
});
74+
}
75+
2876
async isSignedIn(): Promise<boolean> {
2977
if (!this.clerk) return false;
3078
return !!this.clerk.user;
@@ -35,7 +83,7 @@ export class ClerkService {
3583
return await this.clerk.session.getToken();
3684
}
3785

38-
mountSignIn(element: HTMLElement): void {
86+
mountSignIn(element: HTMLDivElement): void {
3987
if (!this.clerk) throw new Error('Clerk is not initialized.');
4088
this.clerk.mountSignIn(element);
4189
}

0 commit comments

Comments
 (0)