Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/electron-auto-allowed-redirect-protocols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/electron': patch
---

`<ClerkProvider>` from `@clerk/electron/react` now allows the renderer's own custom scheme as a redirect protocol by default, so apps no longer need to set `allowedRedirectProtocols={['<scheme>:']}` manually.

This applies when the renderer is served from the custom scheme registered with `createClerkBridge({ renderer })`. Local `file:` renderers are not allowlisted automatically, and explicit `allowedRedirectProtocols` values are still respected.
61 changes: 61 additions & 0 deletions packages/electron/src/react/__tests__/ClerkProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ vi.mock('@clerk/shared/loadClerkJsScript', async importOriginal => ({
loadClerkUIScript,
}));

function stubWindowProtocol(protocol: string) {
vi.stubGlobal('window', {
...window,
location: { protocol },
});
}

describe('Electron ClerkProvider', () => {
const tokenCache = {
clearToken: vi.fn(),
Expand Down Expand Up @@ -92,6 +99,60 @@ describe('Electron ClerkProvider', () => {
});
});

it('defaults allowedRedirectProtocols to the renderer custom scheme', () => {
stubWindowProtocol('clerk:');

renderToStaticMarkup(<ClerkProvider publishableKey='pk_test_scheme_default'>App</ClerkProvider>);

expect(capturedProviderProps?.allowedRedirectProtocols).toEqual(['clerk:']);
});

it('does not add standard web protocols to allowedRedirectProtocols', () => {
stubWindowProtocol('https:');

renderToStaticMarkup(<ClerkProvider publishableKey='pk_test_https_origin'>App</ClerkProvider>);

expect(capturedProviderProps?.allowedRedirectProtocols).toBeUndefined();
});

it('does not add file protocol to allowedRedirectProtocols', () => {
stubWindowProtocol('file:');

renderToStaticMarkup(<ClerkProvider publishableKey='pk_test_file_origin'>App</ClerkProvider>);

expect(capturedProviderProps?.allowedRedirectProtocols).toBeUndefined();
});

it('respects an explicit allowedRedirectProtocols value over the scheme default', () => {
stubWindowProtocol('clerk:');

renderToStaticMarkup(
<ClerkProvider
allowedRedirectProtocols={['myscheme:']}
publishableKey='pk_test_explicit_protocols'
>
App
</ClerkProvider>,
);

expect(capturedProviderProps?.allowedRedirectProtocols).toEqual(['myscheme:']);
});

it('respects an explicit empty allowedRedirectProtocols array', () => {
stubWindowProtocol('clerk:');

renderToStaticMarkup(
<ClerkProvider
allowedRedirectProtocols={[]}
publishableKey='pk_test_empty_protocols'
>
App
</ClerkProvider>,
);

expect(capturedProviderProps?.allowedRedirectProtocols).toEqual([]);
});

it('registers an OAuth transport backed by the Electron bridge', async () => {
oauthTransport.getRedirectUrl.mockResolvedValue('my-app://renderer/');
oauthTransport.open.mockResolvedValue({ callbackUrl: 'my-app://renderer/?code=123' });
Expand Down
24 changes: 23 additions & 1 deletion packages/electron/src/react/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ClerkProviderProps as ReactClerkProviderProps } from '@clerk/react';
import { InternalClerkProvider as ReactClerkProvider } from '@clerk/react/internal';
import { ALLOWED_PROTOCOLS } from '@clerk/shared/internal/clerk-js/windowNavigate';
import { loadClerkUIScript } from '@clerk/shared/loadClerkJsScript';
import type { ClerkUIConstructor } from '@clerk/shared/ui';
import type { ReactNode } from 'react';
Expand Down Expand Up @@ -78,7 +79,27 @@ function createOAuthTransport(): ClerkOAuthTransport | undefined {
};
}

export function ClerkProvider({ children, publishableKey, passkeys, ...props }: ClerkProviderProps): JSX.Element {
/**
* Infer the custom renderer scheme registered with `createClerkBridge({ renderer })`.
* Built-in Clerk protocols and local file renderers are not inferred.
*/
function defaultAllowedRedirectProtocols(): string[] | undefined {
const protocol = typeof window !== 'undefined' ? window.location?.protocol : undefined;

if (!protocol || ALLOWED_PROTOCOLS.includes(protocol) || protocol === 'file:') {
return undefined;
}

return [protocol];
}

export function ClerkProvider({
children,
publishableKey,
passkeys,
allowedRedirectProtocols,
...props
}: ClerkProviderProps): JSX.Element {
const clerk = createClerkInstance(publishableKey, passkeys);
const oauthTransport = createOAuthTransport();
const clerkUI = loadClerkUI(publishableKey, props);
Expand All @@ -88,6 +109,7 @@ export function ClerkProvider({ children, publishableKey, passkeys, ...props }:
{...props}
Clerk={clerk}
__internal_oauthTransport={oauthTransport}
allowedRedirectProtocols={allowedRedirectProtocols ?? defaultAllowedRedirectProtocols()}
publishableKey={publishableKey}
standardBrowser={false}
ui={{ ClerkUI: clerkUI }}
Expand Down
Loading