@clerk/electron 0.0.1-snapshot.v20260617204522

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/dist/cjs/index.js +275 -0
  4. package/dist/cjs/index.js.map +1 -0
  5. package/dist/cjs/passkeys/index.js +267 -0
  6. package/dist/cjs/passkeys/index.js.map +1 -0
  7. package/dist/cjs/preload/index.js +65 -0
  8. package/dist/cjs/preload/index.js.map +1 -0
  9. package/dist/cjs/react/index.js +115 -0
  10. package/dist/cjs/react/index.js.map +1 -0
  11. package/dist/cjs/storage/index.js +133 -0
  12. package/dist/cjs/storage/index.js.map +1 -0
  13. package/dist/esm/chunk-ICCH5DQT.js +19 -0
  14. package/dist/esm/chunk-ICCH5DQT.js.map +1 -0
  15. package/dist/esm/index.js +254 -0
  16. package/dist/esm/index.js.map +1 -0
  17. package/dist/esm/passkeys/index.js +263 -0
  18. package/dist/esm/passkeys/index.js.map +1 -0
  19. package/dist/esm/preload/index.js +44 -0
  20. package/dist/esm/preload/index.js.map +1 -0
  21. package/dist/esm/react/index.js +107 -0
  22. package/dist/esm/react/index.js.map +1 -0
  23. package/dist/esm/storage/index.js +127 -0
  24. package/dist/esm/storage/index.js.map +1 -0
  25. package/dist/types/index.d.ts +4 -0
  26. package/dist/types/index.d.ts.map +1 -0
  27. package/dist/types/main/create-clerk-bridge.d.ts +10 -0
  28. package/dist/types/main/create-clerk-bridge.d.ts.map +1 -0
  29. package/dist/types/main/ipc-handlers.d.ts +3 -0
  30. package/dist/types/main/ipc-handlers.d.ts.map +1 -0
  31. package/dist/types/main/oauth-transport.d.ts +7 -0
  32. package/dist/types/main/oauth-transport.d.ts.map +1 -0
  33. package/dist/types/main/passkey-handlers.d.ts +4 -0
  34. package/dist/types/main/passkey-handlers.d.ts.map +1 -0
  35. package/dist/types/passkeys/index.d.ts +56 -0
  36. package/dist/types/passkeys/index.d.ts.map +1 -0
  37. package/dist/types/passkeys/preload.d.ts +3 -0
  38. package/dist/types/passkeys/preload.d.ts.map +1 -0
  39. package/dist/types/passkeys/renderer/native-bridge.d.ts +6 -0
  40. package/dist/types/passkeys/renderer/native-bridge.d.ts.map +1 -0
  41. package/dist/types/passkeys/renderer/strategy.d.ts +17 -0
  42. package/dist/types/passkeys/renderer/strategy.d.ts.map +1 -0
  43. package/dist/types/passkeys/shared/errors.d.ts +8 -0
  44. package/dist/types/passkeys/shared/errors.d.ts.map +1 -0
  45. package/dist/types/passkeys/shared/serialization.d.ts +11 -0
  46. package/dist/types/passkeys/shared/serialization.d.ts.map +1 -0
  47. package/dist/types/preload/index.d.ts +11 -0
  48. package/dist/types/preload/index.d.ts.map +1 -0
  49. package/dist/types/react/create-clerk-instance.d.ts +6 -0
  50. package/dist/types/react/create-clerk-instance.d.ts.map +1 -0
  51. package/dist/types/react/index.d.ts +25 -0
  52. package/dist/types/react/index.d.ts.map +1 -0
  53. package/dist/types/shared/ipc.d.ts +15 -0
  54. package/dist/types/shared/ipc.d.ts.map +1 -0
  55. package/dist/types/shared/types.d.ts +144 -0
  56. package/dist/types/shared/types.d.ts.map +1 -0
  57. package/dist/types/storage/index.d.ts +47 -0
  58. package/dist/types/storage/index.d.ts.map +1 -0
  59. package/package.json +137 -0
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ var electron = require('electron');
4
+ var Store = require('electron-store');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var Store__default = /*#__PURE__*/_interopDefault(Store);
9
+
10
+ // src/storage/index.ts
11
+ var ENCRYPTED_PREFIX = "enc:";
12
+ var RAW_PREFIX = "raw:";
13
+ var syncCipher = {
14
+ encrypt: (value) => Promise.resolve(electron.safeStorage.encryptString(value).toString("base64")),
15
+ decrypt: (payload) => Promise.resolve({ value: electron.safeStorage.decryptString(Buffer.from(payload, "base64")), shouldReEncrypt: false })
16
+ };
17
+ function hasAsyncSafeStorage(storage2) {
18
+ const candidate = storage2;
19
+ return typeof candidate.encryptStringAsync === "function" && typeof candidate.decryptStringAsync === "function" && typeof candidate.isAsyncEncryptionAvailable === "function";
20
+ }
21
+ function createAsyncCipher(storage2) {
22
+ return {
23
+ encrypt: async (value) => (await storage2.encryptStringAsync(value)).toString("base64"),
24
+ decrypt: async (payload) => {
25
+ const { result, shouldReEncrypt } = await storage2.decryptStringAsync(Buffer.from(payload, "base64"));
26
+ return { value: result, shouldReEncrypt };
27
+ }
28
+ };
29
+ }
30
+ async function resolveCipher() {
31
+ if (hasAsyncSafeStorage(electron.safeStorage)) {
32
+ try {
33
+ if (await electron.safeStorage.isAsyncEncryptionAvailable()) {
34
+ return createAsyncCipher(electron.safeStorage);
35
+ }
36
+ } catch {
37
+ }
38
+ }
39
+ if (typeof electron.safeStorage.isEncryptionAvailable === "function" && electron.safeStorage.isEncryptionAvailable()) {
40
+ return syncCipher;
41
+ }
42
+ return null;
43
+ }
44
+ function storage(options = {}) {
45
+ var _a;
46
+ const store = new Store__default.default({
47
+ name: (_a = options.name) != null ? _a : "clerk-tokens",
48
+ ...options.path ? { cwd: options.path } : {}
49
+ });
50
+ let cachedCipher = null;
51
+ let resolving = null;
52
+ const getCipher = () => {
53
+ if (cachedCipher) {
54
+ return Promise.resolve(cachedCipher);
55
+ }
56
+ resolving != null ? resolving : resolving = resolveCipher().then((resolved) => {
57
+ resolving = null;
58
+ if (resolved) {
59
+ cachedCipher = resolved;
60
+ }
61
+ return resolved;
62
+ });
63
+ return resolving;
64
+ };
65
+ let warned = false;
66
+ const warnOnce = (message) => {
67
+ if (warned) {
68
+ return;
69
+ }
70
+ warned = true;
71
+ console.warn(message);
72
+ };
73
+ return {
74
+ async getItem(key) {
75
+ const stored = store.get(key);
76
+ if (!stored) {
77
+ return null;
78
+ }
79
+ if (stored.startsWith(RAW_PREFIX)) {
80
+ return stored.slice(RAW_PREFIX.length);
81
+ }
82
+ if (stored.startsWith(ENCRYPTED_PREFIX)) {
83
+ const cipher = await getCipher();
84
+ if (!cipher) {
85
+ return null;
86
+ }
87
+ const payload = stored.slice(ENCRYPTED_PREFIX.length);
88
+ try {
89
+ const { value, shouldReEncrypt } = await cipher.decrypt(payload);
90
+ if (shouldReEncrypt) {
91
+ try {
92
+ store.set(key, ENCRYPTED_PREFIX + await cipher.encrypt(value));
93
+ } catch {
94
+ }
95
+ }
96
+ return value;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ store.delete(key);
102
+ return null;
103
+ },
104
+ async setItem(key, value) {
105
+ const cipher = await getCipher();
106
+ if (!cipher) {
107
+ if (options.unencryptedFallback) {
108
+ warnOnce(
109
+ "Clerk: OS encryption is unavailable; falling back to unencrypted storage. Session tokens are being stored unencrypted on local disk."
110
+ );
111
+ store.set(key, RAW_PREFIX + value);
112
+ } else {
113
+ warnOnce(
114
+ "Clerk: OS encryption is unavailable and unencryptedFallback is not enabled, so tokens are not being persisted. The user will be signed out on the next launch. Pass `storage({ unencryptedFallback: true })` to persist unencrypted (less secure)."
115
+ );
116
+ }
117
+ return;
118
+ }
119
+ try {
120
+ store.set(key, ENCRYPTED_PREFIX + await cipher.encrypt(value));
121
+ } catch {
122
+ warnOnce("Clerk: failed to encrypt the session token; it was not persisted.");
123
+ }
124
+ },
125
+ removeItem(key) {
126
+ store.delete(key);
127
+ }
128
+ };
129
+ }
130
+
131
+ exports.storage = storage;
132
+ //# sourceMappingURL=index.js.map
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/storage/index.ts"],"names":["safeStorage","storage","Store"],"mappings":"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","file":"index.js","sourcesContent":["import { safeStorage } from 'electron';\nimport Store from 'electron-store';\n\nimport type { TokenStorage } from '../shared/types';\n\ntype StorageOptions = {\n /**\n * The name of the file (without extension) used to persist tokens.\n *\n * @default 'clerk-tokens'\n */\n name?: string;\n /**\n * The directory in which the token file is stored. Maps to `electron-store`'s `cwd` option.\n * When omitted, the OS default user config directory is used.\n */\n path?: string;\n /**\n * When OS-level encryption is unavailable (e.g. a Linux machine without a keyring), tokens are\n * not persisted by default, which signs the user out on the next app launch. Set this to `true`\n * to instead persist tokens **unencrypted** in that scenario, keeping the user signed in across\n * restarts at the cost of storing tokens in plaintext on disk.\n *\n * @default false\n */\n unencryptedFallback?: boolean;\n};\n\n/**\n * Marks a value that was encrypted via {@link safeStorage}; the remainder is base64 ciphertext.\n * values will be stored as: `enc:<encrypted value>`\n */\nconst ENCRYPTED_PREFIX = 'enc:';\n/**\n * Marks a value persisted unencrypted via the `unencryptedFallback` option.\n * values will be stored as: `raw:<value>`\n */\nconst RAW_PREFIX = 'raw:';\n\ntype Cipher = {\n encrypt: (value: string) => Promise<string>;\n /**\n * Decrypts a base64 payload. `shouldReEncrypt` is `true` (async backend only) when the OS key\n * has rotated and the payload should be re-encrypted with the new key.\n */\n decrypt: (payload: string) => Promise<{ value: string; shouldReEncrypt: boolean }>;\n};\n\ntype AsyncSafeStorage = {\n encryptStringAsync: (plainText: string) => Promise<Buffer>;\n decryptStringAsync: (encrypted: Buffer) => Promise<{ result: string; shouldReEncrypt: boolean }>;\n isAsyncEncryptionAvailable: () => Promise<boolean>;\n};\n\nconst syncCipher: Cipher = {\n encrypt: value => Promise.resolve(safeStorage.encryptString(value).toString('base64')),\n decrypt: payload =>\n Promise.resolve({ value: safeStorage.decryptString(Buffer.from(payload, 'base64')), shouldReEncrypt: false }),\n};\n\nfunction hasAsyncSafeStorage(storage: typeof safeStorage): storage is typeof safeStorage & AsyncSafeStorage {\n const candidate = storage as Partial<AsyncSafeStorage>;\n\n return (\n typeof candidate.encryptStringAsync === 'function' &&\n typeof candidate.decryptStringAsync === 'function' &&\n typeof candidate.isAsyncEncryptionAvailable === 'function'\n );\n}\n\nfunction createAsyncCipher(storage: AsyncSafeStorage): Cipher {\n return {\n encrypt: async value => (await storage.encryptStringAsync(value)).toString('base64'),\n decrypt: async payload => {\n const { result, shouldReEncrypt } = await storage.decryptStringAsync(Buffer.from(payload, 'base64'));\n return { value: result, shouldReEncrypt };\n },\n };\n}\n\n/**\n * Resolves the crypto backend to use, or `null` when no OS encryption is currently available.\n */\nasync function resolveCipher(): Promise<Cipher | null> {\n // Prefer the async API, but only when the full optional function surface exists.\n if (hasAsyncSafeStorage(safeStorage)) {\n try {\n if (await safeStorage.isAsyncEncryptionAvailable()) {\n return createAsyncCipher(safeStorage);\n }\n } catch {\n /* fall through to the synchronous API */\n }\n }\n\n // The synchronous API blocks the calling thread on the OS prompt during the first encrypt/decrypt,\n if (typeof safeStorage.isEncryptionAvailable === 'function' && safeStorage.isEncryptionAvailable()) {\n return syncCipher;\n }\n\n return null;\n}\n\n/**\n * Creates a secure {@link TokenStorage} adapter for the Electron main process.\n *\n * Tokens are persisted with `electron-store` and encrypted at rest using Electron's\n * {@link safeStorage} API, which is backed by the OS keystore (Keychain on macOS, DPAPI on\n * Windows, libsecret/kwallet on Linux). It uses Electron 42's async `safeStorage` API only when it\n * reports itself available (which generally requires a code-signed app) and otherwise falls back to\n * the synchronous API. Pass the result to `createClerkBridge({ storage: storage() })`.\n *\n * Behavior is secure by default: when OS encryption is unavailable the adapter does not persist\n * tokens (the user will be signed out on restart) unless {@link StorageOptions.unencryptedFallback}\n * is enabled. Undecryptable entries return `null`.\n *\n * @example\n * ```ts\n * import { createClerkBridge } from '@clerk/electron';\n * import { storage } from '@clerk/electron/storage';\n *\n * createClerkBridge({ storage: storage({ name: 'my-app-tokens' }) });\n * ```\n */\nexport function storage(options: StorageOptions = {}): TokenStorage {\n const store = new Store<Record<string, string>>({\n name: options.name ?? 'clerk-tokens',\n ...(options.path ? { cwd: options.path } : {}),\n });\n\n let cachedCipher: Cipher | null = null;\n let resolving: Promise<Cipher | null> | null = null;\n const getCipher = (): Promise<Cipher | null> => {\n if (cachedCipher) {\n return Promise.resolve(cachedCipher);\n }\n resolving ??= resolveCipher().then(resolved => {\n resolving = null;\n if (resolved) {\n cachedCipher = resolved;\n }\n return resolved;\n });\n return resolving;\n };\n\n let warned = false;\n const warnOnce = (message: string) => {\n if (warned) {\n return;\n }\n warned = true;\n console.warn(message);\n };\n\n return {\n async getItem(key) {\n const stored = store.get(key);\n\n if (!stored) {\n return null;\n }\n\n if (stored.startsWith(RAW_PREFIX)) {\n return stored.slice(RAW_PREFIX.length);\n }\n\n if (stored.startsWith(ENCRYPTED_PREFIX)) {\n const cipher = await getCipher();\n\n // No usable OS encryption, preserve entry.\n if (!cipher) {\n return null;\n }\n\n const payload = stored.slice(ENCRYPTED_PREFIX.length);\n\n try {\n const { value, shouldReEncrypt } = await cipher.decrypt(payload);\n\n if (shouldReEncrypt) {\n // OS key has rotated, persist with new value\n try {\n store.set(key, ENCRYPTED_PREFIX + (await cipher.encrypt(value)));\n } catch {\n // keep the existing payload; it still decrypts for now\n }\n }\n\n return value;\n } catch {\n // Decryption failed, preserve the entry, new write on next sign-in.\n return null;\n }\n }\n\n // Unknown or unrecognized format, drop the entry so we don't repeatedly fail on it.\n store.delete(key);\n return null;\n },\n async setItem(key, value) {\n const cipher = await getCipher();\n\n if (!cipher) {\n if (options.unencryptedFallback) {\n warnOnce(\n 'Clerk: OS encryption is unavailable; falling back to unencrypted storage. Session tokens are being stored unencrypted on local disk.',\n );\n store.set(key, RAW_PREFIX + value);\n } else {\n warnOnce(\n 'Clerk: OS encryption is unavailable and unencryptedFallback is not enabled, so tokens are not being persisted. The user will be signed out on the next launch. Pass `storage({ unencryptedFallback: true })` to persist unencrypted (less secure).',\n );\n }\n return;\n }\n\n try {\n store.set(key, ENCRYPTED_PREFIX + (await cipher.encrypt(value)));\n } catch {\n // Encryption is available but encryption failed\n warnOnce('Clerk: failed to encrypt the session token; it was not persisted.');\n }\n },\n removeItem(key) {\n store.delete(key);\n },\n };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ // src/shared/ipc.ts
2
+ var TOKEN_CACHE_CHANNELS = {
3
+ getToken: "clerk:token-cache:get",
4
+ saveToken: "clerk:token-cache:save",
5
+ clearToken: "clerk:token-cache:clear"
6
+ };
7
+ var OAUTH_TRANSPORT_CHANNELS = {
8
+ getRedirectUrl: "clerk:oauth-transport:get-redirect-url",
9
+ open: "clerk:oauth-transport:open"
10
+ };
11
+ var PASSKEY_CHANNELS = {
12
+ create: "clerk:passkeys:create",
13
+ get: "clerk:passkeys:get",
14
+ capabilities: "clerk:passkeys:capabilities"
15
+ };
16
+
17
+ export { OAUTH_TRANSPORT_CHANNELS, PASSKEY_CHANNELS, TOKEN_CACHE_CHANNELS };
18
+ //# sourceMappingURL=chunk-ICCH5DQT.js.map
19
+ //# sourceMappingURL=chunk-ICCH5DQT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared/ipc.ts"],"names":[],"mappings":";AAAO,IAAM,oBAAA,GAAuB;AAAA,EAClC,QAAA,EAAU,uBAAA;AAAA,EACV,SAAA,EAAW,wBAAA;AAAA,EACX,UAAA,EAAY;AACd;AAEO,IAAM,wBAAA,GAA2B;AAAA,EACtC,cAAA,EAAgB,wCAAA;AAAA,EAChB,IAAA,EAAM;AACR;AAEO,IAAM,gBAAA,GAAmB;AAAA,EAC9B,MAAA,EAAQ,uBAAA;AAAA,EACR,GAAA,EAAK,oBAAA;AAAA,EACL,YAAA,EAAc;AAChB","file":"chunk-ICCH5DQT.js","sourcesContent":["export const TOKEN_CACHE_CHANNELS = {\n getToken: 'clerk:token-cache:get',\n saveToken: 'clerk:token-cache:save',\n clearToken: 'clerk:token-cache:clear',\n} as const;\n\nexport const OAUTH_TRANSPORT_CHANNELS = {\n getRedirectUrl: 'clerk:oauth-transport:get-redirect-url',\n open: 'clerk:oauth-transport:open',\n} as const;\n\nexport const PASSKEY_CHANNELS = {\n create: 'clerk:passkeys:create',\n get: 'clerk:passkeys:get',\n capabilities: 'clerk:passkeys:capabilities',\n} as const;\n"]}
@@ -0,0 +1,254 @@
1
+ import { PASSKEY_CHANNELS, TOKEN_CACHE_CHANNELS, OAUTH_TRANSPORT_CHANNELS } from './chunk-ICCH5DQT.js';
2
+ import { ipcMain, protocol, BrowserWindow, app, shell } from 'electron';
3
+
4
+ function setupTokenCacheIpcHandlers(storage) {
5
+ ipcMain.handle(TOKEN_CACHE_CHANNELS.getToken, (_event, key) => {
6
+ return storage.getItem(key);
7
+ });
8
+ ipcMain.handle(TOKEN_CACHE_CHANNELS.saveToken, (_event, key, value) => {
9
+ return storage.setItem(key, value);
10
+ });
11
+ ipcMain.handle(TOKEN_CACHE_CHANNELS.clearToken, (_event, key) => {
12
+ return storage.removeItem(key);
13
+ });
14
+ return () => {
15
+ ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.getToken);
16
+ ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.saveToken);
17
+ ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.clearToken);
18
+ };
19
+ }
20
+ var CALLBACK_TIMEOUT_MS = 3 * 60 * 1e3;
21
+ function buildRedirectUrl(options) {
22
+ return `${options.renderer.scheme}://${options.renderer.host}/`;
23
+ }
24
+ function isMatchingCallbackUrl(url, redirectUrl) {
25
+ try {
26
+ const callback = new URL(url);
27
+ const expected = new URL(redirectUrl);
28
+ return callback.protocol === expected.protocol && callback.host === expected.host && callback.pathname === expected.pathname;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+ function assertExternalOAuthUrl(url) {
34
+ const parsedUrl = new URL(url);
35
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
36
+ throw new TypeError(`Clerk: refusing to open unsupported OAuth URL protocol: ${parsedUrl.protocol}`);
37
+ }
38
+ }
39
+ function setupOAuthTransportIpcHandlers(options) {
40
+ const redirectUrl = buildRedirectUrl(options);
41
+ let pendingOAuthFlow = null;
42
+ const disposePendingOAuthFlow = (reason) => {
43
+ if (!pendingOAuthFlow) {
44
+ return;
45
+ }
46
+ const pending = pendingOAuthFlow;
47
+ clearTimeout(pendingOAuthFlow.timeout);
48
+ pendingOAuthFlow = null;
49
+ if (reason) {
50
+ pending.reject(reason);
51
+ }
52
+ };
53
+ const handleCallbackUrl = (url) => {
54
+ if (!pendingOAuthFlow || !isMatchingCallbackUrl(url, redirectUrl)) {
55
+ return;
56
+ }
57
+ const pending = pendingOAuthFlow;
58
+ disposePendingOAuthFlow();
59
+ pending.resolve({ callbackUrl: url });
60
+ };
61
+ const openUrlListener = (event, url) => {
62
+ if (!isMatchingCallbackUrl(url, redirectUrl)) {
63
+ return;
64
+ }
65
+ event.preventDefault();
66
+ handleCallbackUrl(url);
67
+ };
68
+ const secondInstanceListener = (_event, argv) => {
69
+ const callbackUrl = argv.find((url) => isMatchingCallbackUrl(url, redirectUrl));
70
+ if (callbackUrl) {
71
+ handleCallbackUrl(callbackUrl);
72
+ }
73
+ };
74
+ app.setAsDefaultProtocolClient(options.renderer.scheme);
75
+ app.on("open-url", openUrlListener);
76
+ app.on("second-instance", secondInstanceListener);
77
+ ipcMain.handle(OAUTH_TRANSPORT_CHANNELS.getRedirectUrl, () => {
78
+ return redirectUrl;
79
+ });
80
+ ipcMain.handle(OAUTH_TRANSPORT_CHANNELS.open, async (_event, url) => {
81
+ if (pendingOAuthFlow) {
82
+ throw new Error("Clerk: an OAuth flow is already pending.");
83
+ }
84
+ assertExternalOAuthUrl(url);
85
+ const callbackPromise = new Promise((resolve, reject) => {
86
+ const timeout = setTimeout(() => {
87
+ disposePendingOAuthFlow();
88
+ reject(new Error("Clerk: OAuth callback timed out."));
89
+ }, CALLBACK_TIMEOUT_MS);
90
+ pendingOAuthFlow = { resolve, reject, timeout };
91
+ });
92
+ try {
93
+ await shell.openExternal(url);
94
+ } catch (err) {
95
+ disposePendingOAuthFlow(err instanceof Error ? err : new Error(String(err)));
96
+ }
97
+ return callbackPromise;
98
+ });
99
+ return () => {
100
+ disposePendingOAuthFlow(new Error("Clerk: OAuth flow was cancelled."));
101
+ app.removeListener("open-url", openUrlListener);
102
+ app.removeListener("second-instance", secondInstanceListener);
103
+ ipcMain.removeHandler(OAUTH_TRANSPORT_CHANNELS.getRedirectUrl);
104
+ ipcMain.removeHandler(OAUTH_TRANSPORT_CHANNELS.open);
105
+ };
106
+ }
107
+ var nativeModulePromise;
108
+ function loadNativeModule() {
109
+ nativeModulePromise != null ? nativeModulePromise : nativeModulePromise = import('@clerk/electron-passkeys').then(
110
+ (module) => {
111
+ var _a;
112
+ return (_a = module.default) != null ? _a : module;
113
+ },
114
+ (error) => {
115
+ nativeModulePromise = void 0;
116
+ throw new Error(
117
+ "Clerk: createClerkBridge({ passkeys: true }) requires the optional @clerk/electron-passkeys package. Install it with your package manager to enable native passkey support.",
118
+ { cause: error }
119
+ );
120
+ }
121
+ );
122
+ return nativeModulePromise;
123
+ }
124
+ var NATIVE_ERROR_CODES = ["cancelled", "invalid_rp", "not_supported", "timeout", "unknown"];
125
+ function isPasskeyIpcResult(value) {
126
+ var _a;
127
+ if (!value || typeof value !== "object" || typeof value.ok !== "boolean") {
128
+ return false;
129
+ }
130
+ const result = value;
131
+ return result.ok ? result.credential !== void 0 : NATIVE_ERROR_CODES.includes((_a = result.error) == null ? void 0 : _a.code);
132
+ }
133
+ async function invokeNative(method, event, options) {
134
+ let native;
135
+ try {
136
+ native = await loadNativeModule();
137
+ } catch (error) {
138
+ return {
139
+ ok: false,
140
+ error: { code: "not_supported", message: error instanceof Error ? error.message : String(error) }
141
+ };
142
+ }
143
+ if (!native.isAvailable()) {
144
+ return {
145
+ ok: false,
146
+ error: { code: "not_supported", message: "Native passkeys are not supported on this platform." }
147
+ };
148
+ }
149
+ if (!event.senderFrame || event.senderFrame !== event.sender.mainFrame) {
150
+ return {
151
+ ok: false,
152
+ error: { code: "unknown", message: "The passkey request did not originate from a window's main frame." }
153
+ };
154
+ }
155
+ const window = BrowserWindow.fromWebContents(event.sender);
156
+ if (!window) {
157
+ return {
158
+ ok: false,
159
+ error: { code: "unknown", message: "The passkey request did not originate from a visible window." }
160
+ };
161
+ }
162
+ try {
163
+ const resultJson = await native[method](window.getNativeWindowHandle(), JSON.stringify(options));
164
+ const result = JSON.parse(resultJson);
165
+ if (!isPasskeyIpcResult(result)) {
166
+ return { ok: false, error: { code: "unknown", message: "The native module returned an unexpected result." } };
167
+ }
168
+ return result;
169
+ } catch (error) {
170
+ return { ok: false, error: { code: "unknown", message: error instanceof Error ? error.message : String(error) } };
171
+ }
172
+ }
173
+ function setupPasskeysMain() {
174
+ loadNativeModule().catch((error) => console.warn(error.message));
175
+ ipcMain.handle(
176
+ PASSKEY_CHANNELS.create,
177
+ (event, options) => invokeNative("createCredential", event, options)
178
+ );
179
+ ipcMain.handle(
180
+ PASSKEY_CHANNELS.get,
181
+ (event, options) => invokeNative("getCredential", event, options)
182
+ );
183
+ ipcMain.handle(PASSKEY_CHANNELS.capabilities, async () => {
184
+ try {
185
+ const native = await loadNativeModule();
186
+ if (!native.isAvailable()) {
187
+ return { available: false, platformAuthenticator: false, securityKeys: false };
188
+ }
189
+ return { available: true, ...native.capabilities() };
190
+ } catch {
191
+ return { available: false, platformAuthenticator: false, securityKeys: false };
192
+ }
193
+ });
194
+ return {
195
+ cleanup() {
196
+ ipcMain.removeHandler(PASSKEY_CHANNELS.create);
197
+ ipcMain.removeHandler(PASSKEY_CHANNELS.get);
198
+ ipcMain.removeHandler(PASSKEY_CHANNELS.capabilities);
199
+ }
200
+ };
201
+ }
202
+
203
+ // src/main/create-clerk-bridge.ts
204
+ function assertValidRendererOriginConfig(renderer) {
205
+ if (renderer.scheme.includes(":") || renderer.scheme.includes("/")) {
206
+ throw new Error(
207
+ 'Clerk: renderer.scheme must be a scheme name like "my-app", not a URL or protocol like "my-app://".'
208
+ );
209
+ }
210
+ if (renderer.host.includes(":") || renderer.host.includes("/")) {
211
+ throw new Error(
212
+ 'Clerk: renderer.host must be a host name like "renderer", not a URL or origin like "my-app://renderer".'
213
+ );
214
+ }
215
+ }
216
+ function createClerkBridge(options) {
217
+ if (!options.storage) {
218
+ throw new Error(
219
+ "Clerk: createClerkBridge requires a storage adapter. Pass createClerkBridge({ storage: storage() }) from @clerk/electron/storage, or provide a custom storage adapter."
220
+ );
221
+ }
222
+ const cleanupTokenPersistence = setupTokenCacheIpcHandlers(options.storage);
223
+ let cleanupOAuthTransport;
224
+ const passkeys = options.passkeys ? setupPasskeysMain() : null;
225
+ if (options.renderer) {
226
+ assertValidRendererOriginConfig(options.renderer);
227
+ protocol.registerSchemesAsPrivileged([
228
+ {
229
+ scheme: options.renderer.scheme,
230
+ privileges: {
231
+ standard: true,
232
+ secure: true,
233
+ supportFetchAPI: true,
234
+ corsEnabled: true,
235
+ stream: true
236
+ }
237
+ }
238
+ ]);
239
+ cleanupOAuthTransport = setupOAuthTransportIpcHandlers({
240
+ renderer: options.renderer
241
+ });
242
+ }
243
+ return {
244
+ cleanup() {
245
+ cleanupTokenPersistence();
246
+ cleanupOAuthTransport == null ? void 0 : cleanupOAuthTransport();
247
+ passkeys == null ? void 0 : passkeys.cleanup();
248
+ }
249
+ };
250
+ }
251
+
252
+ export { createClerkBridge, setupPasskeysMain };
253
+ //# sourceMappingURL=index.js.map
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/main/ipc-handlers.ts","../../src/main/oauth-transport.ts","../../src/main/passkey-handlers.ts","../../src/main/create-clerk-bridge.ts"],"names":["ipcMain"],"mappings":"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","file":"index.js","sourcesContent":["import { ipcMain } from 'electron';\n\nimport { TOKEN_CACHE_CHANNELS } from '../shared/ipc';\nimport type { TokenStorage } from '../shared/types';\n\nexport function setupTokenCacheIpcHandlers(storage: TokenStorage): () => void {\n ipcMain.handle(TOKEN_CACHE_CHANNELS.getToken, (_event, key: string) => {\n return storage.getItem(key);\n });\n\n ipcMain.handle(TOKEN_CACHE_CHANNELS.saveToken, (_event, key: string, value: string) => {\n return storage.setItem(key, value);\n });\n\n ipcMain.handle(TOKEN_CACHE_CHANNELS.clearToken, (_event, key: string) => {\n return storage.removeItem(key);\n });\n\n return () => {\n ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.getToken);\n ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.saveToken);\n ipcMain.removeHandler(TOKEN_CACHE_CHANNELS.clearToken);\n };\n}\n","import { app, ipcMain, shell } from 'electron';\n\nimport { OAUTH_TRANSPORT_CHANNELS } from '../shared/ipc';\nimport type { RendererSchemeOptions } from '../shared/types';\n\nconst CALLBACK_TIMEOUT_MS = 3 * 60 * 1000;\n\ntype PendingOAuthFlow = {\n resolve: (value: { callbackUrl: string }) => void;\n reject: (reason: Error) => void;\n timeout: NodeJS.Timeout;\n};\n\ntype OAuthTransportOptions = {\n renderer: RendererSchemeOptions;\n};\n\nfunction buildRedirectUrl(options: OAuthTransportOptions): string {\n return `${options.renderer.scheme}://${options.renderer.host}/`;\n}\n\nfunction isMatchingCallbackUrl(url: string, redirectUrl: string): boolean {\n try {\n const callback = new URL(url);\n const expected = new URL(redirectUrl);\n\n return (\n callback.protocol === expected.protocol &&\n callback.host === expected.host &&\n callback.pathname === expected.pathname\n );\n } catch {\n return false;\n }\n}\n\nfunction assertExternalOAuthUrl(url: string): void {\n const parsedUrl = new URL(url);\n\n if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new TypeError(`Clerk: refusing to open unsupported OAuth URL protocol: ${parsedUrl.protocol}`);\n }\n}\n\nexport function setupOAuthTransportIpcHandlers(options: OAuthTransportOptions): () => void {\n const redirectUrl = buildRedirectUrl(options);\n let pendingOAuthFlow: PendingOAuthFlow | null = null;\n\n const disposePendingOAuthFlow = (reason?: Error): void => {\n if (!pendingOAuthFlow) {\n return;\n }\n\n const pending = pendingOAuthFlow;\n clearTimeout(pendingOAuthFlow.timeout);\n pendingOAuthFlow = null;\n\n if (reason) {\n pending.reject(reason);\n }\n };\n\n const handleCallbackUrl = (url: string): void => {\n if (!pendingOAuthFlow || !isMatchingCallbackUrl(url, redirectUrl)) {\n return;\n }\n\n const pending = pendingOAuthFlow;\n disposePendingOAuthFlow();\n pending.resolve({ callbackUrl: url });\n };\n\n const openUrlListener = (event: Electron.Event, url: string): void => {\n if (!isMatchingCallbackUrl(url, redirectUrl)) {\n return;\n }\n\n event.preventDefault();\n handleCallbackUrl(url);\n };\n\n const secondInstanceListener = (_event: Electron.Event, argv: string[]): void => {\n const callbackUrl = argv.find(url => isMatchingCallbackUrl(url, redirectUrl));\n\n if (callbackUrl) {\n handleCallbackUrl(callbackUrl);\n }\n };\n\n app.setAsDefaultProtocolClient(options.renderer.scheme);\n app.on('open-url', openUrlListener);\n app.on('second-instance', secondInstanceListener);\n\n ipcMain.handle(OAUTH_TRANSPORT_CHANNELS.getRedirectUrl, () => {\n return redirectUrl;\n });\n\n ipcMain.handle(OAUTH_TRANSPORT_CHANNELS.open, async (_event, url: string) => {\n if (pendingOAuthFlow) {\n throw new Error('Clerk: an OAuth flow is already pending.');\n }\n\n assertExternalOAuthUrl(url);\n\n const callbackPromise = new Promise<{ callbackUrl: string }>((resolve, reject) => {\n const timeout = setTimeout(() => {\n disposePendingOAuthFlow();\n reject(new Error('Clerk: OAuth callback timed out.'));\n }, CALLBACK_TIMEOUT_MS);\n\n pendingOAuthFlow = { resolve, reject, timeout };\n });\n\n try {\n await shell.openExternal(url);\n } catch (err) {\n disposePendingOAuthFlow(err instanceof Error ? err : new Error(String(err)));\n }\n\n return callbackPromise;\n });\n\n return () => {\n disposePendingOAuthFlow(new Error('Clerk: OAuth flow was cancelled.'));\n app.removeListener('open-url', openUrlListener);\n app.removeListener('second-instance', secondInstanceListener);\n ipcMain.removeHandler(OAUTH_TRANSPORT_CHANNELS.getRedirectUrl);\n ipcMain.removeHandler(OAUTH_TRANSPORT_CHANNELS.open);\n };\n}\n","import type { IpcMainInvokeEvent } from 'electron';\nimport { BrowserWindow, ipcMain } from 'electron';\n\nimport { PASSKEY_CHANNELS } from '../shared/ipc';\nimport type {\n AuthenticationResponseJSON,\n PasskeyCapabilities,\n PasskeyIpcResult,\n PasskeyNativeErrorCode,\n RegistrationResponseJSON,\n SerializedPublicKeyCredentialCreationOptions,\n SerializedPublicKeyCredentialRequestOptions,\n SetupPasskeysMainReturn,\n} from '../shared/types';\n\n/**\n * Optional native module. Ceremony failures resolve as JSON envelopes so error\n * codes survive both the FFI and Electron IPC boundaries.\n */\ntype NativePasskeysModule = {\n isAvailable: () => boolean;\n capabilities: () => Omit<PasskeyCapabilities, 'available'>;\n createCredential: (windowHandle: Buffer, optionsJson: string) => Promise<string>;\n getCredential: (windowHandle: Buffer, optionsJson: string) => Promise<string>;\n};\n\nlet nativeModulePromise: Promise<NativePasskeysModule> | undefined;\n\nfunction loadNativeModule(): Promise<NativePasskeysModule> {\n // Keep the native module optional for apps that do not use passkeys.\n nativeModulePromise ??= import('@clerk/electron-passkeys').then(\n (module: { default?: NativePasskeysModule } & NativePasskeysModule) => module.default ?? module,\n error => {\n nativeModulePromise = undefined;\n throw new Error(\n 'Clerk: createClerkBridge({ passkeys: true }) requires the optional @clerk/electron-passkeys package. Install it with your package manager to enable native passkey support.',\n { cause: error },\n );\n },\n );\n return nativeModulePromise;\n}\n\nconst NATIVE_ERROR_CODES: PasskeyNativeErrorCode[] = ['cancelled', 'invalid_rp', 'not_supported', 'timeout', 'unknown'];\n\nfunction isPasskeyIpcResult<T>(value: unknown): value is PasskeyIpcResult<T> {\n if (!value || typeof value !== 'object' || typeof (value as { ok?: unknown }).ok !== 'boolean') {\n return false;\n }\n const result = value as { ok: boolean; credential?: unknown; error?: { code?: unknown } };\n return result.ok\n ? result.credential !== undefined\n : NATIVE_ERROR_CODES.includes(result.error?.code as PasskeyNativeErrorCode);\n}\n\nasync function invokeNative<T>(\n method: 'createCredential' | 'getCredential',\n event: IpcMainInvokeEvent,\n options: SerializedPublicKeyCredentialCreationOptions | SerializedPublicKeyCredentialRequestOptions,\n): Promise<PasskeyIpcResult<T>> {\n let native: NativePasskeysModule;\n try {\n native = await loadNativeModule();\n } catch (error) {\n return {\n ok: false,\n error: { code: 'not_supported', message: error instanceof Error ? error.message : String(error) },\n };\n }\n\n if (!native.isAvailable()) {\n return {\n ok: false,\n error: { code: 'not_supported', message: 'Native passkeys are not supported on this platform.' },\n };\n }\n\n // Subframes and webviews can host third-party content that must not be able\n // to run credential ceremonies for the app's RP ID.\n if (!event.senderFrame || event.senderFrame !== event.sender.mainFrame) {\n return {\n ok: false,\n error: { code: 'unknown', message: \"The passkey request did not originate from a window's main frame.\" },\n };\n }\n\n const window = BrowserWindow.fromWebContents(event.sender);\n if (!window) {\n return {\n ok: false,\n error: { code: 'unknown', message: 'The passkey request did not originate from a visible window.' },\n };\n }\n\n try {\n const resultJson = await native[method](window.getNativeWindowHandle(), JSON.stringify(options));\n const result: unknown = JSON.parse(resultJson);\n if (!isPasskeyIpcResult<T>(result)) {\n return { ok: false, error: { code: 'unknown', message: 'The native module returned an unexpected result.' } };\n }\n return result;\n } catch (error) {\n return { ok: false, error: { code: 'unknown', message: error instanceof Error ? error.message : String(error) } };\n }\n}\n\n/** Registers IPC handlers for native platform WebAuthn. */\nexport function setupPasskeysMain(): SetupPasskeysMainReturn {\n // Surface a missing optional dependency during setup, before the first ceremony.\n loadNativeModule().catch((error: Error) => console.warn(error.message));\n\n ipcMain.handle(\n PASSKEY_CHANNELS.create,\n (\n event,\n options: SerializedPublicKeyCredentialCreationOptions,\n ): Promise<PasskeyIpcResult<RegistrationResponseJSON>> => invokeNative('createCredential', event, options),\n );\n\n ipcMain.handle(\n PASSKEY_CHANNELS.get,\n (\n event,\n options: SerializedPublicKeyCredentialRequestOptions,\n ): Promise<PasskeyIpcResult<AuthenticationResponseJSON>> => invokeNative('getCredential', event, options),\n );\n\n ipcMain.handle(PASSKEY_CHANNELS.capabilities, async (): Promise<PasskeyCapabilities> => {\n try {\n const native = await loadNativeModule();\n if (!native.isAvailable()) {\n return { available: false, platformAuthenticator: false, securityKeys: false };\n }\n return { available: true, ...native.capabilities() };\n } catch {\n return { available: false, platformAuthenticator: false, securityKeys: false };\n }\n });\n\n return {\n cleanup() {\n ipcMain.removeHandler(PASSKEY_CHANNELS.create);\n ipcMain.removeHandler(PASSKEY_CHANNELS.get);\n ipcMain.removeHandler(PASSKEY_CHANNELS.capabilities);\n },\n };\n}\n","import { protocol } from 'electron';\n\nimport type { ClerkBridge, CreateClerkBridgeOptions } from '../shared/types';\nimport { setupTokenCacheIpcHandlers } from './ipc-handlers';\nimport { setupOAuthTransportIpcHandlers } from './oauth-transport';\nimport { setupPasskeysMain } from './passkey-handlers';\n\nfunction assertValidRendererOriginConfig(renderer: NonNullable<CreateClerkBridgeOptions['renderer']>): void {\n if (renderer.scheme.includes(':') || renderer.scheme.includes('/')) {\n throw new Error(\n 'Clerk: renderer.scheme must be a scheme name like \"my-app\", not a URL or protocol like \"my-app://\".',\n );\n }\n\n if (renderer.host.includes(':') || renderer.host.includes('/')) {\n throw new Error(\n 'Clerk: renderer.host must be a host name like \"renderer\", not a URL or origin like \"my-app://renderer\".',\n );\n }\n}\n\n/**\n * Creates the Clerk bridge for Electron's main process.\n *\n * The bridge owns Clerk's main-process IPC handlers, token persistence, and OAuth deep-link\n * transport. Call this before creating renderer windows, and call the returned `cleanup` method\n * when tearing down the app or test environment.\n */\nexport function createClerkBridge(options: CreateClerkBridgeOptions): ClerkBridge {\n if (!options.storage) {\n throw new Error(\n 'Clerk: createClerkBridge requires a storage adapter. Pass createClerkBridge({ storage: storage() }) from @clerk/electron/storage, or provide a custom storage adapter.',\n );\n }\n\n const cleanupTokenPersistence = setupTokenCacheIpcHandlers(options.storage);\n let cleanupOAuthTransport: (() => void) | undefined;\n const passkeys = options.passkeys ? setupPasskeysMain() : null;\n\n if (options.renderer) {\n assertValidRendererOriginConfig(options.renderer);\n\n protocol.registerSchemesAsPrivileged([\n {\n scheme: options.renderer.scheme,\n privileges: {\n standard: true,\n secure: true,\n supportFetchAPI: true,\n corsEnabled: true,\n stream: true,\n },\n },\n ]);\n\n cleanupOAuthTransport = setupOAuthTransportIpcHandlers({\n renderer: options.renderer,\n });\n }\n\n return {\n cleanup() {\n cleanupTokenPersistence();\n cleanupOAuthTransport?.();\n passkeys?.cleanup();\n },\n };\n}\n"]}