From 9372081b99d0e03f113b615559307a3e263dfd1e Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Wed, 21 Jan 2026 09:30:49 -0800 Subject: [PATCH 1/3] fix: add broadcast channel listener for Slack OAuth flow --- .../src/modules/slack/hooks/useSlackAuth.ts | 5 ++- .../SlackAuthButton/SlackAuthButton.tsx | 43 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/react-core/src/modules/slack/hooks/useSlackAuth.ts b/packages/react-core/src/modules/slack/hooks/useSlackAuth.ts index 91f5c9eb1..321c862b8 100644 --- a/packages/react-core/src/modules/slack/hooks/useSlackAuth.ts +++ b/packages/react-core/src/modules/slack/hooks/useSlackAuth.ts @@ -13,7 +13,7 @@ const DEFAULT_SLACK_SCOPES = [ ]; type UseSlackAuthOutput = { - buildSlackAuthUrl: () => string; + buildSlackAuthUrl: (requestId: string) => string; disconnectFromSlack: () => void; }; @@ -82,10 +82,11 @@ function useSlackAuth( setActionLabel, ]); - const buildSlackAuthUrl = useCallback(() => { + const buildSlackAuthUrl = useCallback((requestId: string) => { const rawParams = { state: JSON.stringify({ redirect_url: redirectUrl, + request_id: requestId, access_token_object: { object_id: tenantId, collection: TENANT_OBJECT_COLLECTION, diff --git a/packages/react/src/modules/slack/components/SlackAuthButton/SlackAuthButton.tsx b/packages/react/src/modules/slack/components/SlackAuthButton/SlackAuthButton.tsx index 589a3f3a7..883c5b200 100644 --- a/packages/react/src/modules/slack/components/SlackAuthButton/SlackAuthButton.tsx +++ b/packages/react/src/modules/slack/components/SlackAuthButton/SlackAuthButton.tsx @@ -4,7 +4,7 @@ import { useSlackAuth, useTranslations, } from "@knocklabs/react-core"; -import { FunctionComponent, useMemo } from "react"; +import { FunctionComponent, useId, useMemo } from "react"; import { useEffect } from "react"; import { openPopupWindow } from "../../../core/utils"; @@ -33,6 +33,8 @@ export const SlackAuthButton: FunctionComponent = ({ const { t } = useTranslations(); const knock = useKnockClient(); + const requestId = useId(); + const { setConnectionStatus, connectionStatus, @@ -56,35 +58,50 @@ export const SlackAuthButton: FunctionComponent = ({ ); useEffect(() => { - const receiveMessage = (event: MessageEvent) => { - if (event.origin !== knock.host) { - return; - } - + const handleAuthMessage = (data: string) => { try { - if (event.data === "authComplete") { + if (data === "authComplete") { setConnectionStatus("connected"); } - if (event.data === "authFailed") { + if (data === "authFailed") { setConnectionStatus("error"); } if (onAuthenticationComplete) { - onAuthenticationComplete(event.data); + onAuthenticationComplete(data); } } catch (_error) { setConnectionStatus("error"); } }; + // Listen for both window.postMessage and BroadcastChannel + const receiveMessage = (event: MessageEvent) => { + if (event.origin !== knock.host) { + return; + } + handleAuthMessage(event.data); + }; + window.addEventListener("message", receiveMessage, false); - // Cleanup the event listener when the component unmounts + let broadcastChannel: BroadcastChannel | null = null; + if (typeof BroadcastChannel !== "undefined") { + broadcastChannel = new BroadcastChannel(`knock:oauth:${requestId}`); + broadcastChannel.onmessage = (event) => { + if (event.data.origin === knock.host) { + handleAuthMessage(event.data.type || event.data); + } + }; + } + + // Cleanup listeners when component unmounts return () => { window.removeEventListener("message", receiveMessage); + broadcastChannel?.close(); }; - }, [knock.host, onAuthenticationComplete, setConnectionStatus]); + }, [knock.host, requestId, onAuthenticationComplete, setConnectionStatus]); const disconnectLabel = t("slackDisconnect") || null; const reconnectLabel = t("slackReconnect") || null; @@ -110,7 +127,7 @@ export const SlackAuthButton: FunctionComponent = ({ if (connectionStatus === "error") { return (