Skip to content
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
import type { HandleOAuthCallbackParams } from '@clerk/types';

type Props = HandleOAuthCallbackParams

import { generateSafeId } from '@clerk/astro/internal';

const safeId = generateSafeId();

// TODO: Move this to a prop when we implement more control components.
const functionName = 'handleRedirectCallback';
---

<div data-clerk-function-id={`clerk-${functionName}-${safeId}`}></div>

<script is:inline define:vars={{ props: Astro.props, functionName, safeId }}>
/**
* Store the id and the props for the Astro component in order to invoice the correct Clerk function once clerk is loaded.
* The above is handled by `invokeClerkAstroJSFunctions`.
*
* TODO: This should be moved to a separate file once we implement more control components.
Comment on lines +10 to +21

@wobsoriano wobsoriano Jul 15, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some TODOs here so that in the future when we want to implement more control components that call invoke Clerk functions, we can extract and reuse it

*/
const setOrCreatePropMap = ({ functionName, id, props }) => {
if (!window.__astro_clerk_function_props) {
window.__astro_clerk_function_props = new Map();
}

if (!window.__astro_clerk_function_props.has(functionName)) {
const _ = new Map();
_.set(id, props);
window.__astro_clerk_function_props.set(functionName, _);
}

window.__astro_clerk_function_props.get(functionName)?.set(id, props);
};

setOrCreatePropMap({
functionName,
id: safeId,
props,
});
</script>
1 change: 1 addition & 0 deletions packages/astro/src/astro-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export { default as SignedIn } from './control/SignedIn.astro';
export { default as SignedOut } from './control/SignedOut.astro';
export { default as Protect } from './control/Protect.astro';
export { default as AuthenticateWithRedirectCallback } from './control/AuthenticateWithRedirectCallback.astro';

/**
* Unstyled Components
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/internal/create-clerk-instance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { waitForClerkScript } from '../internal/utils/loadClerkJSScript';
import { $clerk, $csrState } from '../stores/internal';
import type { AstroClerkIntegrationParams, AstroClerkUpdateOptions } from '../types';
import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions';
import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components';
import { runOnce } from './run-once';

Expand Down Expand Up @@ -35,6 +36,7 @@ async function createClerkInstanceInternal(options?: AstroClerkIntegrationParams
$csrState.setKey('isLoaded', true);

mountAllClerkAstroJSComponents();
invokeClerkAstroJSFunctions();

clerkJSInstance.addListener(payload => {
$csrState.setKey('client', payload.client);
Expand Down
19 changes: 19 additions & 0 deletions packages/astro/src/internal/invoke-clerk-astro-js-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { $clerk } from '../stores/internal';

/**
* Loop through any Astro component that has requested to invoke a function and invoke it with its respective props.
*/
const invokeClerkAstroJSFunctions = () => {
const functionNames = ['handleRedirectCallback'] as const;

functionNames.forEach(fnName => {
const elementsOfCategory = document.querySelectorAll(`[data-clerk-function-id^="clerk-${fnName}"]`);
elementsOfCategory.forEach(el => {
const id = el.getAttribute('data-clerk-function-id');
const props = window.__astro_clerk_function_props?.get(fnName)?.get(id!) ?? {};
void $clerk.get()?.[fnName]?.(props);
});
});
};

export { invokeClerkAstroJSFunctions };
2 changes: 2 additions & 0 deletions packages/astro/src/internal/run-once.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Clerk } from '@clerk/clerk-js';

import { invokeClerkAstroJSFunctions } from './invoke-clerk-astro-js-functions';
import { mountAllClerkAstroJSComponents } from './mount-clerk-astro-js-components';
import type { CreateClerkInstanceInternalFn } from './types';

Expand All @@ -19,6 +20,7 @@ const runOnce = (onFirst: CreateClerkInstanceInternalFn) => {

if (clerkJSInstance.loaded) {
mountAllClerkAstroJSComponents();
invokeClerkAstroJSFunctions();
}
return res(clerkJSInstance.loaded);
});
Expand Down
19 changes: 18 additions & 1 deletion packages/astro/src/react/controlComponents.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { CheckAuthorizationWithCustomPermissions } from '@clerk/types';
import type { CheckAuthorizationWithCustomPermissions, HandleOAuthCallbackParams } from '@clerk/types';
import type { PropsWithChildren } from 'react';
import React from 'react';

import type { ProtectComponentDefaultProps } from '../types';
import { useAuth } from './hooks';
import type { WithClerkProp } from './utils';
import { withClerk } from './utils';

export function SignedOut(props: PropsWithChildren) {
const { userId } = useAuth();
Expand Down Expand Up @@ -83,3 +86,17 @@ export const Protect = ({ children, fallback, ...restAuthorizedParams }: Protect
*/
return authorized;
};

/**
* Use `<AuthenticateWithRedirectCallback/>` to complete a custom OAuth flow.
*/
export const AuthenticateWithRedirectCallback = withClerk(
({ clerk, ...handleRedirectCallbackParams }: WithClerkProp<HandleOAuthCallbackParams>) => {
React.useEffect(() => {
void clerk?.handleRedirectCallback(handleRedirectCallbackParams);
}, []);

return null;
},
'AuthenticateWithRedirectCallback',
);
1 change: 1 addition & 0 deletions packages/astro/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { publishabl
declare global {
interface Window {
__astro_clerk_component_props: Map<string, Map<string, Record<string, unknown>>>;
__astro_clerk_function_props: Map<string, Map<string, Record<string, unknown>>>;
}
}

Expand Down