Skip to content

Commit

Permalink
chore(astro,clerk-react,shared,nextjs): Migrate functions that can be…
Browse files Browse the repository at this point in the history
… reused across JavaScript SDKs (#3849)
  • Loading branch information
wobsoriano authored Aug 9, 2024
1 parent def3a38 commit 7e0ced3
Show file tree
Hide file tree
Showing 23 changed files with 370 additions and 110 deletions.
8 changes: 8 additions & 0 deletions .changeset/nasty-baboons-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@clerk/astro": patch
"@clerk/shared": patch
"@clerk/clerk-react": patch
"@clerk/nextjs": patch
---

Introduce functions that can be reused across front-end SDKs
14 changes: 7 additions & 7 deletions packages/astro/src/internal/create-clerk-instance.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { loadClerkJsScript, setClerkJsLoadingErrorPackageName } from '@clerk/shared/loadClerkJsScript';
import type { ClerkOptions } from '@clerk/types';

import { $clerk, $csrState } from '../stores/internal';
import type { AstroClerkIntegrationParams, AstroClerkUpdateOptions } from '../types';
import type { AstroClerkCreateInstanceParams, AstroClerkUpdateOptions } from '../types';
import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions';
import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components';
import { runOnce } from './run-once';
import { waitForClerkScript } from './utils/loadClerkJSScript';

let initOptions: ClerkOptions | undefined;

// TODO-SHARED: copied from `clerk-js`
export const CLERK_BEFORE_UNLOAD_EVENT = 'clerk:beforeunload';

setClerkJsLoadingErrorPackageName(PACKAGE_NAME);

function windowNavigate(to: URL | string): void {
const toURL = new URL(to, window.location.href);
window.dispatchEvent(new CustomEvent(CLERK_BEFORE_UNLOAD_EVENT));
Expand All @@ -35,10 +37,10 @@ function createNavigationHandler(
*/
const createClerkInstance = runOnce(createClerkInstanceInternal);

async function createClerkInstanceInternal(options?: AstroClerkIntegrationParams) {
async function createClerkInstanceInternal(options?: AstroClerkCreateInstanceParams) {
let clerkJSInstance = window.Clerk;
if (!clerkJSInstance) {
await waitForClerkScript();
await loadClerkJsScript(options);

if (!window.Clerk) {
throw new Error('Failed to download latest ClerkJS. Contact [email protected].');
Expand All @@ -47,7 +49,6 @@ async function createClerkInstanceInternal(options?: AstroClerkIntegrationParams
}

if (!$clerk.get()) {
// @ts-ignore
$clerk.set(clerkJSInstance);
}

Expand All @@ -57,8 +58,7 @@ async function createClerkInstanceInternal(options?: AstroClerkIntegrationParams
routerReplace: createNavigationHandler(window.history.replaceState.bind(window.history)),
};

// TODO: Update Clerk type from @clerk/types to include this method
return (clerkJSInstance as any)
return clerkJSInstance
.load(initOptions)
.then(() => {
$csrState.setKey('isLoaded', true);
Expand Down
17 changes: 0 additions & 17 deletions packages/astro/src/internal/utils/loadClerkJSScript.ts

This file was deleted.

9 changes: 0 additions & 9 deletions packages/astro/src/internal/utils/versionSelector.ts

This file was deleted.

45 changes: 5 additions & 40 deletions packages/astro/src/server/build-clerk-hotload-script.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,8 @@
import { createDevOrStagingUrlCache, parsePublishableKey } from '@clerk/shared/keys';
import { isValidProxyUrl, proxyUrlToAbsoluteURL } from '@clerk/shared/proxy';
import { addClerkPrefix } from '@clerk/shared/url';
import { clerkJsScriptUrl } from '@clerk/shared/loadClerkJsScript';
import type { APIContext } from 'astro';

import { versionSelector } from '../internal/utils/versionSelector';
import { getSafeEnv } from './get-safe-env';

const { isDevOrStagingUrl } = createDevOrStagingUrlCache();

type BuildClerkJsScriptOptions = {
proxyUrl: string;
domain: string;
clerkJSUrl?: string;
clerkJSVariant?: 'headless' | '';
clerkJSVersion?: string;
publishableKey: string;
};

const clerkJsScriptUrl = (opts: BuildClerkJsScriptOptions) => {
const { clerkJSUrl, clerkJSVariant, clerkJSVersion, proxyUrl, domain, publishableKey } = opts;

if (clerkJSUrl) {
return clerkJSUrl;
}

let scriptHost = '';
if (!!proxyUrl && isValidProxyUrl(proxyUrl)) {
scriptHost = proxyUrlToAbsoluteURL(proxyUrl).replace(/http(s)?:\/\//, '');
} else if (domain && !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '')) {
scriptHost = addClerkPrefix(domain);
} else {
scriptHost = parsePublishableKey(publishableKey)?.frontendApi || '';
}

const variant = clerkJSVariant ? `${clerkJSVariant.replace(/\.+$/, '')}.` : '';
const version = versionSelector(clerkJSVersion);
return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.${variant}browser.js`;
};

function buildClerkHotloadScript(locals: APIContext['locals']) {
const publishableKey = getSafeEnv(locals).pk!;
const proxyUrl = getSafeEnv(locals).proxyUrl!;
Expand All @@ -51,10 +16,10 @@ function buildClerkHotloadScript(locals: APIContext['locals']) {
publishableKey,
});
return `
<script src="${scriptSrc}"
data-clerk-script
async
crossOrigin='anonymous'
<script src="${scriptSrc}"
data-clerk-js-script
async
crossOrigin='anonymous'
${publishableKey ? `data-clerk-publishable-key="${publishableKey}"` : ``}
${proxyUrl ? `data-clerk-proxy-url="${proxyUrl}"` : ``}
${domain ? `data-clerk-domain="${domain}"` : ``}
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/stores/external.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { deriveState } from '@clerk/shared/deriveState';
import { eventMethodCalled } from '@clerk/shared/telemetry';
import { computed, onMount, type Store } from 'nanostores';

import { $clerk, $csrState, $initialState } from './internal';
import { deriveState } from './utils';

/**
* A client side store that is prepopulated with the authentication context during SSR.
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/src/pages/ClerkProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ClerkProvider as ReactClerkProvider } from '@clerk/clerk-react';
// Override Clerk React error thrower to show that errors come from @clerk/nextjs
import { setErrorThrowerOptions } from '@clerk/clerk-react/internal';
import { setClerkJsLoadingErrorPackageName, setErrorThrowerOptions } from '@clerk/clerk-react/internal';
import { useRouter } from 'next/router';
import React from 'react';

Expand All @@ -12,6 +12,7 @@ import { invalidateNextRouterCache } from '../utils/invalidateNextRouterCache';
import { mergeNextClerkPropsWithEnv } from '../utils/mergeNextClerkPropsWithEnv';

setErrorThrowerOptions({ packageName: PACKAGE_NAME });
setClerkJsLoadingErrorPackageName(PACKAGE_NAME);

export function ClerkProvider({ children, ...props }: NextClerkProviderProps): JSX.Element {
const { __unstable_invokeMiddlewareOnAuthStateChange = true } = props;
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import './polyfills';

import { setClerkJsLoadingErrorPackageName } from '@clerk/shared/loadClerkJsScript';

import { setErrorThrowerOptions } from './errors/errorThrower';

export * from './components';
Expand All @@ -10,3 +12,4 @@ export { useEmailLink } from './hooks/useEmailLink';
export type { BrowserClerk, ClerkProp, HeadlessBrowserClerk, ClerkProviderProps } from './types';

setErrorThrowerOptions({ packageName: PACKAGE_NAME });
setClerkJsLoadingErrorPackageName(PACKAGE_NAME);
6 changes: 5 additions & 1 deletion packages/react/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ export { setErrorThrowerOptions } from './errors/errorThrower';
export { MultisessionAppSupport } from './components/controlComponents';
export { useRoutingProps } from './hooks/useRoutingProps';

export { clerkJsScriptUrl, buildClerkJsScriptAttributes } from './utils/loadClerkJsScript';
export {
clerkJsScriptUrl,
buildClerkJsScriptAttributes,
setClerkJsLoadingErrorPackageName,
} from '@clerk/shared/loadClerkJsScript';
3 changes: 2 additions & 1 deletion packages/react/src/isomorphicClerk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { inBrowser } from '@clerk/shared/browser';
import { handleValueOrFn } from '@clerk/shared/handleValueOrFn';
import { loadClerkJsScript } from '@clerk/shared/loadClerkJsScript';
import type { TelemetryCollector } from '@clerk/shared/telemetry';
import type {
ActiveSessionResource,
Expand Down Expand Up @@ -49,7 +50,7 @@ import type {
HeadlessBrowserClerkConstructor,
IsomorphicClerkOptions,
} from './types';
import { isConstructor, loadClerkJsScript } from './utils';
import { isConstructor } from './utils';

const SDK_METADATA = {
name: PACKAGE_NAME,
Expand Down
1 change: 0 additions & 1 deletion packages/react/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './childrenUtils';
export * from './isConstructor';
export { loadClerkJsScript } from './loadClerkJsScript';
export * from './useMaxAllowedInstancesGuard';
export * from './useCustomElementPortal';
export * from './useCustomPages';
Expand Down
1 change: 1 addition & 0 deletions packages/shared/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export {};

declare global {
const PACKAGE_VERSION: string;
const JS_PACKAGE_VERSION: string;
const __DEV__: boolean;
}
5 changes: 5 additions & 0 deletions packages/shared/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { name } = require('./package.json');
const { version: clerkJsVersion } = require('../clerk-js/package.json');

/** @type {import('ts-jest').JestConfigWithTsJest} */
const config = {
Expand All @@ -17,6 +18,10 @@ const config = {
transform: {
'^.+\\.m?tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json', diagnostics: false }],
},

globals: {
JS_PACKAGE_VERSION: clerkJsVersion,
},
};

module.exports = config;
3 changes: 3 additions & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,22 @@
"cookie",
"date",
"deprecated",
"deriveState",
"error",
"file",
"globs",
"handleValueOrFn",
"isomorphicAtob",
"isomorphicBtoa",
"keys",
"loadClerkJsScript",
"loadScript",
"localStorageBroadcastChannel",
"poller",
"proxy",
"underscore",
"url",
"versionSelector",
"react",
"constants",
"apiUrlFromPublishableKey",
Expand Down
36 changes: 36 additions & 0 deletions packages/shared/src/__tests__/deriveState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { InitialState, Resources } from '@clerk/types';

import { deriveState } from '../deriveState';

describe('deriveState', () => {
const mockInitialState = {
userId: 'user_2U330vGHg3llBga8Oi0fzzeNAaG',
sessionId: 'sess_2j1R7g3AUeKMx9M23dBO0XLEQGY',
orgId: 'org_2U330vGHg3llBga8Oi0fzzeNAaG',
} as InitialState;

const mockResources = {
client: {},
user: { id: mockInitialState.userId },
session: { id: mockInitialState.sessionId },
organization: { id: mockInitialState.orgId },
} as Resources;

it('uses SSR state when !clerkLoaded and initialState is provided', () => {
expect(deriveState(false, {} as Resources, mockInitialState)).toEqual(mockInitialState);
});

it('uses CSR state when clerkLoaded', () => {
const result = deriveState(true, mockResources, undefined);
expect(result.userId).toBe(mockInitialState.userId);
expect(result.sessionId).toBe(mockInitialState.sessionId);
expect(result.orgId).toBe(mockInitialState.orgId);
});

it('handles !clerkLoaded and undefined initialState', () => {
const result = deriveState(false, {} as Resources, undefined);
expect(result.userId).toBeUndefined();
expect(result.sessionId).toBeUndefined();
expect(result.orgId).toBeUndefined();
});
});
Loading

0 comments on commit 7e0ced3

Please sign in to comment.