Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/short-otters-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@clerk/astro": patch
---

Introduce `<AuthenticateWithRedirectCallback/>` as an Astro and as a React component
Original file line numberDiff line numberDiff 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

@wobsorianowobsorianoJul 15, 2024

Copy link
Copy Markdown
MemberAuthor

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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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