@knocklabs/react-core 0.13.8 → 0.13.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/modules/core/hooks/useAuthPostMessageListener.js +1 -1
  4. package/dist/cjs/modules/core/hooks/useAuthPostMessageListener.js.map +1 -1
  5. package/dist/cjs/modules/slack/hooks/useSlackAuth.js +1 -1
  6. package/dist/cjs/modules/slack/hooks/useSlackAuth.js.map +1 -1
  7. package/dist/esm/index.mjs +26 -25
  8. package/dist/esm/modules/core/hooks/useAuthPostMessageListener.mjs +36 -12
  9. package/dist/esm/modules/core/hooks/useAuthPostMessageListener.mjs.map +1 -1
  10. package/dist/esm/modules/slack/hooks/useSlackAuth.mjs +38 -31
  11. package/dist/esm/modules/slack/hooks/useSlackAuth.mjs.map +1 -1
  12. package/dist/types/index.d.ts +1 -1
  13. package/dist/types/index.d.ts.map +1 -1
  14. package/dist/types/modules/core/hooks/useAuthPostMessageListener.d.ts +9 -1
  15. package/dist/types/modules/core/hooks/useAuthPostMessageListener.d.ts.map +1 -1
  16. package/dist/types/modules/slack/hooks/index.d.ts +1 -1
  17. package/dist/types/modules/slack/hooks/index.d.ts.map +1 -1
  18. package/dist/types/modules/slack/hooks/useSlackAuth.d.ts +1 -0
  19. package/dist/types/modules/slack/hooks/useSlackAuth.d.ts.map +1 -1
  20. package/dist/types/modules/slack/index.d.ts +1 -1
  21. package/dist/types/modules/slack/index.d.ts.map +1 -1
  22. package/package.json +6 -6
  23. package/src/index.ts +1 -0
  24. package/src/modules/core/hooks/useAuthPostMessageListener.ts +70 -9
  25. package/src/modules/slack/hooks/index.ts +4 -1
  26. package/src/modules/slack/hooks/useSlackAuth.ts +15 -0
  27. package/src/modules/slack/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [e4fe593]
8
+ - @knocklabs/client@0.21.9
9
+
10
+ ## 0.13.9
11
+
12
+ ### Patch Changes
13
+
14
+ - 4863fe5: [Slack] Add support for nonce verification in slack auth
15
+
3
16
  ## 0.13.8
4
17
 
5
18
  ### Patch Changes
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./modules/core/context/KnockProvider.js"),c=require("./modules/core/hooks/useAuthenticatedKnockClient.js"),a=require("./modules/core/hooks/useStableOptions.js"),d=require("./modules/core/hooks/useAuthPostMessageListener.js"),l=require("./modules/core/hooks/useAuthPolling.js"),k=require("./modules/core/constants.js"),e=require("./modules/core/utils.js"),n=require("./modules/feed/context/KnockFeedProvider.js"),C=require("./modules/feed/hooks/useNotifications.js"),K=require("./modules/feed/hooks/useFeedSettings.js"),t=require("./modules/feed/hooks/useNotificationStore.js"),r=require("./modules/guide/context/KnockGuideProvider.js"),P=require("./modules/guide/hooks/useGuide.js"),S=require("./modules/guide/hooks/useGuides.js"),m=require("./modules/guide/hooks/useGuideContext.js"),o=require("./modules/ms-teams/context/KnockMsTeamsProvider.js"),q=require("./modules/ms-teams/hooks/useMsTeamsConnectionStatus.js"),v=require("./modules/ms-teams/hooks/useMsTeamsAuth.js"),T=require("./modules/ms-teams/hooks/useMsTeamsTeams.js"),h=require("./modules/ms-teams/hooks/useMsTeamsChannels.js"),M=require("./modules/ms-teams/hooks/useConnectedMsTeamsChannels.js"),u=require("./modules/slack/context/KnockSlackProvider.js"),f=require("./modules/slack/hooks/useSlackConnectionStatus.js"),g=require("./modules/slack/hooks/useSlackChannels.js"),A=require("./modules/slack/hooks/useConnectedSlackChannels.js"),G=require("./modules/slack/hooks/useSlackAuth.js"),i=require("./modules/i18n/context/KnockI18nProvider.js"),F=require("./modules/i18n/hooks/useTranslations.js"),b=require("./modules/i18n/languages/index.js"),y=require("./modules/preferences/hooks/usePreferences.js"),x=require("@tanstack/react-store");exports.KnockProvider=s.KnockProvider;exports.useKnockClient=s.useKnockClient;exports.useAuthenticatedKnockClient=c;exports.useStableOptions=a;exports.useAuthPostMessageListener=d.useAuthPostMessageListener;exports.useAuthPolling=l.useAuthPolling;exports.FilterStatus=k.FilterStatus;exports.feedProviderKey=e.feedProviderKey;exports.formatBadgeCount=e.formatBadgeCount;exports.formatTimestamp=e.formatTimestamp;exports.getBadgeAriaLabel=e.getBadgeAriaLabel;exports.msTeamsProviderKey=e.msTeamsProviderKey;exports.renderNodeOrFallback=e.renderNodeOrFallback;exports.slackProviderKey=e.slackProviderKey;exports.toSentenceCase=e.toSentenceCase;exports.KnockFeedProvider=n.KnockFeedProvider;exports.useKnockFeed=n.useKnockFeed;exports.useNotifications=C;exports.useFeedSettings=K;exports.useCreateNotificationStore=t.useCreateNotificationStore;exports.useNotificationStore=t.default;exports.KnockGuideContext=r.KnockGuideContext;exports.KnockGuideProvider=r.KnockGuideProvider;exports.useGuide=P.useGuide;exports.useGuides=S.useGuides;exports.useGuideContext=m.useGuideContext;exports.KnockMsTeamsProvider=o.KnockMsTeamsProvider;exports.useKnockMsTeamsClient=o.useKnockMsTeamsClient;exports.useMsTeamsConnectionStatus=q;exports.useMsTeamsAuth=v;exports.useMsTeamsTeams=T;exports.useMsTeamsChannels=h;exports.useConnectedMsTeamsChannels=M;exports.KnockSlackProvider=u.KnockSlackProvider;exports.useKnockSlackClient=u.useKnockSlackClient;exports.useSlackConnectionStatus=f;exports.useSlackChannels=g;exports.useConnectedSlackChannels=A;exports.useSlackAuth=G;exports.I18nContext=i.I18nContext;exports.KnockI18nProvider=i.KnockI18nProvider;exports.useTranslations=F.useTranslations;exports.locales=b.locales;exports.usePreferences=y.usePreferences;Object.defineProperty(exports,"useStore",{enumerable:!0,get:()=>x.useStore});
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./modules/core/context/KnockProvider.js"),a=require("./modules/core/hooks/useAuthenticatedKnockClient.js"),d=require("./modules/core/hooks/useStableOptions.js"),l=require("./modules/core/hooks/useAuthPostMessageListener.js"),k=require("./modules/core/hooks/useAuthPolling.js"),K=require("./modules/core/constants.js"),e=require("./modules/core/utils.js"),t=require("./modules/feed/context/KnockFeedProvider.js"),S=require("./modules/feed/hooks/useNotifications.js"),C=require("./modules/feed/hooks/useFeedSettings.js"),n=require("./modules/feed/hooks/useNotificationStore.js"),o=require("./modules/guide/context/KnockGuideProvider.js"),P=require("./modules/guide/hooks/useGuide.js"),m=require("./modules/guide/hooks/useGuides.js"),q=require("./modules/guide/hooks/useGuideContext.js"),r=require("./modules/ms-teams/context/KnockMsTeamsProvider.js"),v=require("./modules/ms-teams/hooks/useMsTeamsConnectionStatus.js"),T=require("./modules/ms-teams/hooks/useMsTeamsAuth.js"),g=require("./modules/ms-teams/hooks/useMsTeamsTeams.js"),h=require("./modules/ms-teams/hooks/useMsTeamsChannels.js"),f=require("./modules/ms-teams/hooks/useConnectedMsTeamsChannels.js"),u=require("./modules/slack/context/KnockSlackProvider.js"),M=require("./modules/slack/hooks/useSlackConnectionStatus.js"),A=require("./modules/slack/hooks/useSlackChannels.js"),G=require("./modules/slack/hooks/useConnectedSlackChannels.js"),i=require("./modules/slack/hooks/useSlackAuth.js"),c=require("./modules/i18n/context/KnockI18nProvider.js"),y=require("./modules/i18n/hooks/useTranslations.js"),F=require("./modules/i18n/languages/index.js"),b=require("./modules/preferences/hooks/usePreferences.js"),N=require("@tanstack/react-store");exports.KnockProvider=s.KnockProvider;exports.useKnockClient=s.useKnockClient;exports.useAuthenticatedKnockClient=a;exports.useStableOptions=d;exports.useAuthPostMessageListener=l.useAuthPostMessageListener;exports.useAuthPolling=k.useAuthPolling;exports.FilterStatus=K.FilterStatus;exports.feedProviderKey=e.feedProviderKey;exports.formatBadgeCount=e.formatBadgeCount;exports.formatTimestamp=e.formatTimestamp;exports.getBadgeAriaLabel=e.getBadgeAriaLabel;exports.msTeamsProviderKey=e.msTeamsProviderKey;exports.renderNodeOrFallback=e.renderNodeOrFallback;exports.slackProviderKey=e.slackProviderKey;exports.toSentenceCase=e.toSentenceCase;exports.KnockFeedProvider=t.KnockFeedProvider;exports.useKnockFeed=t.useKnockFeed;exports.useNotifications=S;exports.useFeedSettings=C;exports.useCreateNotificationStore=n.useCreateNotificationStore;exports.useNotificationStore=n.default;exports.KnockGuideContext=o.KnockGuideContext;exports.KnockGuideProvider=o.KnockGuideProvider;exports.useGuide=P.useGuide;exports.useGuides=m.useGuides;exports.useGuideContext=q.useGuideContext;exports.KnockMsTeamsProvider=r.KnockMsTeamsProvider;exports.useKnockMsTeamsClient=r.useKnockMsTeamsClient;exports.useMsTeamsConnectionStatus=v;exports.useMsTeamsAuth=T;exports.useMsTeamsTeams=g;exports.useMsTeamsChannels=h;exports.useConnectedMsTeamsChannels=f;exports.KnockSlackProvider=u.KnockSlackProvider;exports.useKnockSlackClient=u.useKnockSlackClient;exports.useSlackConnectionStatus=M;exports.useSlackChannels=A;exports.useConnectedSlackChannels=G;exports.getSlackNonceStorageKey=i.getSlackNonceStorageKey;exports.useSlackAuth=i.default;exports.I18nContext=c.I18nContext;exports.KnockI18nProvider=c.KnockI18nProvider;exports.useTranslations=y.useTranslations;exports.locales=F.locales;exports.usePreferences=b.usePreferences;Object.defineProperty(exports,"useStore",{enumerable:!0,get:()=>N.useStore});
2
2
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react");function o(n){const{knockHost:u,popupWindowRef:s,setConnectionStatus:t,onAuthenticationComplete:e}=n;c.useEffect(()=>{const a=r=>{r.origin===u&&(r.data==="authComplete"?(t("connected"),e==null||e(r.data),s.current&&!s.current.closed&&s.current.close(),s.current=null):r.data==="authFailed"&&(t("error"),e==null||e(r.data),s.current=null))};return window.addEventListener("message",a,!1),()=>window.removeEventListener("message",a)},[u,e,t,s])}exports.useAuthPostMessageListener=o;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("react");function y(e){return typeof e=="object"&&e!==null&&"type"in e?e.type:e}function p(e){if(typeof e=="object"&&e!==null&&"nonce"in e){const o=e.nonce;return typeof o=="string"?o:void 0}}function a(e){const{knockHost:o,popupWindowRef:r,setConnectionStatus:t,onAuthenticationComplete:s,nonceStorageKey:n}=e;d.useEffect(()=>{const i=()=>{r.current&&!r.current.closed&&r.current.close(),r.current=null},f=u=>{if(u.origin!==o)return;const c=y(u.data);if(c==="authComplete"){if(n){const g=p(u.data),l=sessionStorage.getItem(n);if(sessionStorage.removeItem(n),!g||l&&l!==g){t("error"),s==null||s("authFailed"),i();return}}t("connected"),s==null||s(c),i()}else c==="authFailed"&&(n&&sessionStorage.removeItem(n),t("error"),s==null||s(c),r.current=null)};return window.addEventListener("message",f,!1),()=>window.removeEventListener("message",f)},[o,s,t,r,n])}exports.useAuthPostMessageListener=a;
2
2
  //# sourceMappingURL=useAuthPostMessageListener.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthPostMessageListener.js","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { ConnectionStatus } from \"../types\";\n\nexport interface UseAuthPostMessageListenerOptions {\n knockHost: string;\n popupWindowRef: React.MutableRefObject<Window | null>;\n setConnectionStatus: (status: ConnectionStatus) => void;\n onAuthenticationComplete?: (authenticationResp: string) => void;\n}\n\n/**\n * Hook that listens for postMessage events from OAuth popup windows.\n *\n * Handles \"authComplete\" and \"authFailed\" messages sent from the OAuth flow popup,\n * validates the message origin, updates connection status, and closes the popup.\n *\n * @param options - Configuration options for the postMessage listener\n *\n * @example\n * ```tsx\n * useAuthPostMessageListener({\n * knockHost: knock.host,\n * popupWindowRef,\n * setConnectionStatus,\n * onAuthenticationComplete,\n * });\n * ```\n */\nexport function useAuthPostMessageListener(\n options: UseAuthPostMessageListenerOptions,\n): void {\n const {\n knockHost,\n popupWindowRef,\n setConnectionStatus,\n onAuthenticationComplete,\n } = options;\n\n useEffect(() => {\n const receiveMessage = (event: MessageEvent) => {\n // Validate message origin for security\n if (event.origin !== knockHost) {\n return;\n }\n\n if (event.data === \"authComplete\") {\n setConnectionStatus(\"connected\");\n onAuthenticationComplete?.(event.data);\n // Clear popup ref so polling stops and doesn't trigger callback again\n if (popupWindowRef.current && !popupWindowRef.current.closed) {\n popupWindowRef.current.close();\n }\n popupWindowRef.current = null;\n } else if (event.data === \"authFailed\") {\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(event.data);\n popupWindowRef.current = null;\n }\n };\n\n window.addEventListener(\"message\", receiveMessage, false);\n return () => window.removeEventListener(\"message\", receiveMessage);\n }, [\n knockHost,\n onAuthenticationComplete,\n setConnectionStatus,\n popupWindowRef,\n ]);\n}\n"],"names":["useAuthPostMessageListener","options","knockHost","popupWindowRef","setConnectionStatus","onAuthenticationComplete","useEffect","receiveMessage","event","origin","data","current","closed","close","addEventListener","window","removeEventListener"],"mappings":"yGA6BO,SAASA,EACdC,EACM,CACA,KAAA,CACJC,UAAAA,EACAC,eAAAA,EACAC,oBAAAA,EACAC,yBAAAA,CAAAA,EACEJ,EAEJK,EAAAA,UAAU,IAAM,CACRC,MAAAA,EAAkBC,GAAwB,CAE1CA,EAAMC,SAAWP,IAIjBM,EAAME,OAAS,gBACjBN,EAAoB,WAAW,EAC/BC,GAAAA,MAAAA,EAA2BG,EAAME,MAE7BP,EAAeQ,SAAW,CAACR,EAAeQ,QAAQC,QACpDT,EAAeQ,QAAQE,MAAM,EAE/BV,EAAeQ,QAAU,MAChBH,EAAME,OAAS,eACxBN,EAAoB,OAAO,EAC3BC,GAAAA,MAAAA,EAA2BG,EAAME,MACjCP,EAAeQ,QAAU,MAE7B,EAEOG,cAAAA,iBAAiB,UAAWP,EAAgB,EAAK,EACjD,IAAMQ,OAAOC,oBAAoB,UAAWT,CAAc,GAChE,CACDL,EACAG,EACAD,EACAD,CAAc,CACf,CACH"}
1
+ {"version":3,"file":"useAuthPostMessageListener.js","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { ConnectionStatus } from \"../types\";\n\nexport interface UseAuthPostMessageListenerOptions {\n knockHost: string;\n popupWindowRef: React.MutableRefObject<Window | null>;\n setConnectionStatus: (status: ConnectionStatus) => void;\n onAuthenticationComplete?: (authenticationResp: string) => void;\n /**\n * The sessionStorage key where the CSRF nonce was stored when the auth URL\n * was built. When provided, the listener will verify the nonce returned in\n * the postMessage payload matches the stored value.\n */\n nonceStorageKey?: string;\n}\n\n/**\n * Extracts the message type from a postMessage event data payload.\n * Supports both the legacy string format (\"authComplete\") and the new\n * structured format ({ type: \"authComplete\", nonce: \"...\" }).\n */\nfunction getMessageType(data: unknown): string {\n if (typeof data === \"object\" && data !== null && \"type\" in data) {\n return (data as { type: string }).type;\n }\n return data as string;\n}\n\n/**\n * Extracts the nonce from a structured postMessage event data payload.\n * Returns undefined for legacy string-format messages.\n */\nfunction getMessageNonce(data: unknown): string | undefined {\n if (typeof data === \"object\" && data !== null && \"nonce\" in data) {\n const nonce = (data as { nonce: unknown }).nonce;\n return typeof nonce === \"string\" ? nonce : undefined;\n }\n return undefined;\n}\n\n/**\n * Hook that listens for postMessage events from OAuth popup windows.\n *\n * Handles \"authComplete\" and \"authFailed\" messages sent from the OAuth flow popup,\n * validates the message origin, optionally verifies the CSRF nonce, updates\n * connection status, and closes the popup.\n *\n * @param options - Configuration options for the postMessage listener\n *\n * @example\n * ```tsx\n * useAuthPostMessageListener({\n * knockHost: knock.host,\n * popupWindowRef,\n * setConnectionStatus,\n * onAuthenticationComplete,\n * nonceStorageKey: \"knock:slack-auth-nonce:channel_123:user_1\",\n * });\n * ```\n */\nexport function useAuthPostMessageListener(\n options: UseAuthPostMessageListenerOptions,\n): void {\n const {\n knockHost,\n popupWindowRef,\n setConnectionStatus,\n onAuthenticationComplete,\n nonceStorageKey,\n } = options;\n\n useEffect(() => {\n const closePopup = () => {\n if (popupWindowRef.current && !popupWindowRef.current.closed) {\n popupWindowRef.current.close();\n }\n popupWindowRef.current = null;\n };\n\n const receiveMessage = (event: MessageEvent) => {\n // Validate message origin for security\n if (event.origin !== knockHost) {\n return;\n }\n\n const messageType = getMessageType(event.data);\n\n if (messageType === \"authComplete\") {\n // Verify CSRF nonce when a nonceStorageKey is configured.\n if (nonceStorageKey) {\n const returnedNonce = getMessageNonce(event.data);\n const storedNonce = sessionStorage.getItem(nonceStorageKey);\n sessionStorage.removeItem(nonceStorageKey);\n\n // If nonce already consumed by a prior handler invocation, then bail\n // out from checking again.\n if (\n !returnedNonce ||\n (storedNonce && storedNonce !== returnedNonce)\n ) {\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(\"authFailed\");\n closePopup();\n return;\n }\n }\n\n setConnectionStatus(\"connected\");\n onAuthenticationComplete?.(messageType);\n closePopup();\n } else if (messageType === \"authFailed\") {\n if (nonceStorageKey) {\n sessionStorage.removeItem(nonceStorageKey);\n }\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(messageType);\n popupWindowRef.current = null;\n }\n };\n\n window.addEventListener(\"message\", receiveMessage, false);\n return () => window.removeEventListener(\"message\", receiveMessage);\n }, [\n knockHost,\n onAuthenticationComplete,\n setConnectionStatus,\n popupWindowRef,\n nonceStorageKey,\n ]);\n}\n"],"names":["getMessageType","data","type","getMessageNonce","nonce","undefined","useAuthPostMessageListener","options","knockHost","popupWindowRef","setConnectionStatus","onAuthenticationComplete","nonceStorageKey","useEffect","closePopup","current","closed","close","receiveMessage","event","origin","messageType","returnedNonce","storedNonce","sessionStorage","getItem","removeItem","addEventListener","window","removeEventListener"],"mappings":"yGAsBA,SAASA,EAAeC,EAAuB,CAC7C,OAAI,OAAOA,GAAS,UAAYA,IAAS,MAAQ,SAAUA,EACjDA,EAA0BC,KAE7BD,CACT,CAMA,SAASE,EAAgBF,EAAmC,CAC1D,GAAI,OAAOA,GAAS,UAAYA,IAAS,MAAQ,UAAWA,EAAM,CAChE,MAAMG,EAASH,EAA4BG,MACpC,OAAA,OAAOA,GAAU,SAAWA,EAAQC,MAAAA,CAG/C,CAsBO,SAASC,EACdC,EACM,CACA,KAAA,CACJC,UAAAA,EACAC,eAAAA,EACAC,oBAAAA,EACAC,yBAAAA,EACAC,gBAAAA,CAAAA,EACEL,EAEJM,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAaA,IAAM,CACnBL,EAAeM,SAAW,CAACN,EAAeM,QAAQC,QACpDP,EAAeM,QAAQE,MAAM,EAE/BR,EAAeM,QAAU,IAC3B,EAEMG,EAAkBC,GAAwB,CAE1CA,GAAAA,EAAMC,SAAWZ,EACnB,OAGIa,MAAAA,EAAcrB,EAAemB,EAAMlB,IAAI,EAE7C,GAAIoB,IAAgB,eAAgB,CAElC,GAAIT,EAAiB,CACbU,MAAAA,EAAgBnB,EAAgBgB,EAAMlB,IAAI,EAC1CsB,EAAcC,eAAeC,QAAQb,CAAe,EAK1D,GAJAY,eAAeE,WAAWd,CAAe,EAKvC,CAACU,GACAC,GAAeA,IAAgBD,EAChC,CACAZ,EAAoB,OAAO,EAC3BC,GAAAA,MAAAA,EAA2B,cAChBG,EAAA,EACX,MAAA,CACF,CAGFJ,EAAoB,WAAW,EAC/BC,GAAAA,MAAAA,EAA2BU,GAChBP,EAAA,CAAA,MACFO,IAAgB,eACrBT,GACFY,eAAeE,WAAWd,CAAe,EAE3CF,EAAoB,OAAO,EAC3BC,GAAAA,MAAAA,EAA2BU,GAC3BZ,EAAeM,QAAU,KAE7B,EAEOY,cAAAA,iBAAiB,UAAWT,EAAgB,EAAK,EACjD,IAAMU,OAAOC,oBAAoB,UAAWX,CAAc,CAAA,EAChE,CACDV,EACAG,EACAD,EACAD,EACAG,CAAe,CAChB,CACH"}
@@ -1,2 +1,2 @@
1
- "use strict";const p=require("../context/KnockSlackProvider.js"),l=require("react");require("../../i18n/context/KnockI18nProvider.js");require("swr/infinite");const _=require("../../core/context/KnockProvider.js"),b=require("@knocklabs/client");require("fast-deep-equal");require("date-fns");require("swr");const A="https://slack.com/oauth/v2/authorize",s=["chat:write","chat:write.public","channels:read","groups:read"];function C(e){return e?Array.isArray(e)?{scopes:s,additionalScopes:e}:{scopes:e.scopes??s,additionalScopes:e.additionalScopes??[]}:{scopes:s,additionalScopes:[]}}function q(e,a,k){const c=_.useKnockClient(),{setConnectionStatus:n,knockSlackChannelId:r,tenantId:o,setActionLabel:i}=p.useKnockSlackClient(),{scopes:d,additionalScopes:S}=C(k),u=Array.from(new Set(d.concat(S))),h=l.useCallback(async()=>{i(null),n("disconnecting");try{const t=await c.slack.revokeAccessToken({tenant:o,knockChannelId:r});n(t==="ok"?"disconnected":"error")}catch{n("error")}},[n,c.slack,o,r,i]);return{buildSlackAuthUrl:l.useCallback(()=>{const t={state:JSON.stringify({redirect_url:a,access_token_object:{object_id:o,collection:b.TENANT_OBJECT_COLLECTION},channel_id:r,public_key:c.apiKey,user_token:c.userToken,branch_slug:c.branch}),client_id:e,scope:u.join(",")};return`${A}?${new URLSearchParams(t)}`},[a,o,r,c.apiKey,c.userToken,c.branch,e,u]),disconnectFromSlack:h}}module.exports=q;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const b=require("../context/KnockSlackProvider.js"),l=require("react");require("../../i18n/context/KnockI18nProvider.js");require("swr/infinite");const y=require("../../core/context/KnockProvider.js"),A=require("@knocklabs/client");require("fast-deep-equal");require("date-fns");require("swr");const C="https://slack.com/oauth/v2/authorize",s=["chat:write","chat:write.public","channels:read","groups:read"];function k(e,t){return`knock:slack-auth-nonce:${e}:${t}`}function g(e){return e?Array.isArray(e)?{scopes:s,additionalScopes:e}:{scopes:e.scopes??s,additionalScopes:e.additionalScopes??[]}:{scopes:s,additionalScopes:[]}}function K(e,t,d){const c=y.useKnockClient(),{setConnectionStatus:n,knockSlackChannelId:r,tenantId:a,setActionLabel:i}=b.useKnockSlackClient(),{scopes:S,additionalScopes:h}=g(d),u=Array.from(new Set(S.concat(h))),_=l.useCallback(async()=>{i(null),n("disconnecting");try{const o=await c.slack.revokeAccessToken({tenant:a,knockChannelId:r});n(o==="ok"?"disconnected":"error")}catch{n("error")}},[n,c.slack,a,r,i]);return{buildSlackAuthUrl:l.useCallback(()=>{const o=crypto.randomUUID();sessionStorage.setItem(k(r,c.userId),o);const p={state:JSON.stringify({nonce:o,redirect_url:t,access_token_object:{object_id:a,collection:A.TENANT_OBJECT_COLLECTION},channel_id:r,public_key:c.apiKey,user_token:c.userToken,branch_slug:c.branch}),client_id:e,scope:u.join(",")};return`${C}?${new URLSearchParams(p)}`},[t,a,r,c.apiKey,c.userId,c.userToken,c.branch,e,u]),disconnectFromSlack:_}}exports.default=K;exports.getSlackNonceStorageKey=k;
2
2
  //# sourceMappingURL=useSlackAuth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSlackAuth.js","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"sourcesContent":["import { useKnockSlackClient } from \"..\";\nimport { TENANT_OBJECT_COLLECTION } from \"@knocklabs/client\";\nimport { useCallback } from \"react\";\n\nimport { useKnockClient } from \"../../core\";\n\nconst SLACK_AUTHORIZE_URL = \"https://slack.com/oauth/v2/authorize\";\nconst DEFAULT_SLACK_SCOPES = [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\",\n \"groups:read\",\n];\n\ntype UseSlackAuthOutput = {\n buildSlackAuthUrl: () => string;\n disconnectFromSlack: () => void;\n};\n\ntype UseSlackAuthOptions = {\n // When provided, the default scopes will be overridden with the provided scopes\n scopes?: string[];\n // Additional scopes to add to the default scopes\n additionalScopes?: string[];\n};\n\n// Here we normalize the options to be a single object with scopes and additionalScopes\n// The \"options\" parameter can be an array of scopes, an object with scopes and additionalScopes, or undefined\n// We handle the array case because it was the previous way to pass options so we're being backward compatible\nfunction normalizeOptions(options?: UseSlackAuthOptions | string[]): {\n scopes: string[];\n additionalScopes: string[];\n} {\n if (!options) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: [] };\n }\n\n if (Array.isArray(options)) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: options };\n }\n\n return {\n scopes: options.scopes ?? DEFAULT_SLACK_SCOPES,\n additionalScopes: options.additionalScopes ?? [],\n };\n}\n\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n options?: UseSlackAuthOptions | string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const { scopes, additionalScopes } = normalizeOptions(options);\n const combinedScopes = Array.from(new Set(scopes.concat(additionalScopes)));\n\n const disconnectFromSlack = useCallback(async () => {\n setActionLabel(null);\n setConnectionStatus(\"disconnecting\");\n try {\n const revoke = await knock.slack.revokeAccessToken({\n tenant: tenantId,\n knockChannelId: knockSlackChannelId,\n });\n\n if (revoke === \"ok\") {\n setConnectionStatus(\"disconnected\");\n } else {\n setConnectionStatus(\"error\");\n }\n } catch (_error) {\n setConnectionStatus(\"error\");\n }\n }, [\n setConnectionStatus,\n knock.slack,\n tenantId,\n knockSlackChannelId,\n setActionLabel,\n ]);\n\n const buildSlackAuthUrl = useCallback(() => {\n const rawParams = {\n state: JSON.stringify({\n redirect_url: redirectUrl,\n access_token_object: {\n object_id: tenantId,\n collection: TENANT_OBJECT_COLLECTION,\n },\n channel_id: knockSlackChannelId,\n public_key: knock.apiKey,\n user_token: knock.userToken,\n branch_slug: knock.branch,\n }),\n client_id: slackClientId,\n scope: combinedScopes.join(\",\"),\n };\n return `${SLACK_AUTHORIZE_URL}?${new URLSearchParams(rawParams)}`;\n }, [\n redirectUrl,\n tenantId,\n knockSlackChannelId,\n knock.apiKey,\n knock.userToken,\n knock.branch,\n slackClientId,\n combinedScopes,\n ]);\n\n return {\n buildSlackAuthUrl,\n disconnectFromSlack,\n };\n}\n\nexport default useSlackAuth;\n"],"names":["SLACK_AUTHORIZE_URL","DEFAULT_SLACK_SCOPES","normalizeOptions","options","Array","isArray","scopes","additionalScopes","useSlackAuth","slackClientId","redirectUrl","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","from","Set","concat","disconnectFromSlack","useCallback","revoke","slack","revokeAccessToken","tenant","knockChannelId","buildSlackAuthUrl","rawParams","state","JSON","stringify","redirect_url","access_token_object","object_id","collection","TENANT_OBJECT_COLLECTION","channel_id","public_key","apiKey","user_token","userToken","branch_slug","branch","client_id","scope","join","URLSearchParams"],"mappings":"mTAMA,MAAMA,EAAsB,uCACtBC,EAAuB,CAC3B,aACA,oBACA,gBACA,aAAa,EAkBf,SAASC,EAAiBC,EAGxB,CACA,OAAKA,EAIDC,MAAMC,QAAQF,CAAO,EAChB,CAAEG,OAAQL,EAAsBM,iBAAkBJ,CAAQ,EAG5D,CACLG,OAAQH,EAAQG,QAAUL,EAC1BM,iBAAkBJ,EAAQI,kBAAoB,CAAA,CAChD,EAVS,CAAED,OAAQL,EAAsBM,iBAAkB,CAAA,CAAG,CAWhE,CAEA,SAASC,EACPC,EACAC,EACAP,EACoB,CACpB,MAAMQ,EAAQC,EAAAA,eAAe,EACvB,CAAEC,oBAAAA,EAAqBC,oBAAAA,EAAqBC,SAAAA,EAAUC,eAAAA,GAC1DC,sBAAoB,EAEhB,CAAEX,OAAAA,EAAQC,iBAAAA,CAAAA,EAAqBL,EAAiBC,CAAO,EACvDe,EAAiBd,MAAMe,KAAK,IAAIC,IAAId,EAAOe,OAAOd,CAAgB,CAAC,CAAC,EAEpEe,EAAsBC,EAAAA,YAAY,SAAY,CAClDP,EAAe,IAAI,EACnBH,EAAoB,eAAe,EAC/B,GAAA,CACF,MAAMW,EAAS,MAAMb,EAAMc,MAAMC,kBAAkB,CACjDC,OAAQZ,EACRa,eAAgBd,CAAAA,CACjB,EAGCD,EADEW,IAAW,KACO,eAEA,OAFc,OAIrB,CACfX,EAAoB,OAAO,CAAA,CAC7B,EACC,CACDA,EACAF,EAAMc,MACNV,EACAD,EACAE,CAAc,CACf,EA8BM,MAAA,CACLa,kBA7BwBN,EAAAA,YAAY,IAAM,CAC1C,MAAMO,EAAY,CAChBC,MAAOC,KAAKC,UAAU,CACpBC,aAAcxB,EACdyB,oBAAqB,CACnBC,UAAWrB,EACXsB,WAAYC,EAAAA,wBACd,EACAC,WAAYzB,EACZ0B,WAAY7B,EAAM8B,OAClBC,WAAY/B,EAAMgC,UAClBC,YAAajC,EAAMkC,MAAAA,CACpB,EACDC,UAAWrC,EACXsC,MAAO7B,EAAe8B,KAAK,GAAG,CAChC,EACA,MAAO,GAAGhD,CAAmB,IAAI,IAAIiD,gBAAgBnB,CAAS,CAAC,EAC9D,EAAA,CACDpB,EACAK,EACAD,EACAH,EAAM8B,OACN9B,EAAMgC,UACNhC,EAAMkC,OACNpC,EACAS,CAAc,CACf,EAICI,oBAAAA,CACF,CACF"}
1
+ {"version":3,"file":"useSlackAuth.js","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"sourcesContent":["import { useKnockSlackClient } from \"..\";\nimport { TENANT_OBJECT_COLLECTION } from \"@knocklabs/client\";\nimport { useCallback } from \"react\";\n\nimport { useKnockClient } from \"../../core\";\n\nconst SLACK_AUTHORIZE_URL = \"https://slack.com/oauth/v2/authorize\";\nconst DEFAULT_SLACK_SCOPES = [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\",\n \"groups:read\",\n];\n\nexport function getSlackNonceStorageKey(\n channelId: string,\n userId: string,\n): string {\n return `knock:slack-auth-nonce:${channelId}:${userId}`;\n}\n\ntype UseSlackAuthOutput = {\n buildSlackAuthUrl: () => string;\n disconnectFromSlack: () => void;\n};\n\ntype UseSlackAuthOptions = {\n // When provided, the default scopes will be overridden with the provided scopes\n scopes?: string[];\n // Additional scopes to add to the default scopes\n additionalScopes?: string[];\n};\n\n// Here we normalize the options to be a single object with scopes and additionalScopes\n// The \"options\" parameter can be an array of scopes, an object with scopes and additionalScopes, or undefined\n// We handle the array case because it was the previous way to pass options so we're being backward compatible\nfunction normalizeOptions(options?: UseSlackAuthOptions | string[]): {\n scopes: string[];\n additionalScopes: string[];\n} {\n if (!options) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: [] };\n }\n\n if (Array.isArray(options)) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: options };\n }\n\n return {\n scopes: options.scopes ?? DEFAULT_SLACK_SCOPES,\n additionalScopes: options.additionalScopes ?? [],\n };\n}\n\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n options?: UseSlackAuthOptions | string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const { scopes, additionalScopes } = normalizeOptions(options);\n const combinedScopes = Array.from(new Set(scopes.concat(additionalScopes)));\n\n const disconnectFromSlack = useCallback(async () => {\n setActionLabel(null);\n setConnectionStatus(\"disconnecting\");\n try {\n const revoke = await knock.slack.revokeAccessToken({\n tenant: tenantId,\n knockChannelId: knockSlackChannelId,\n });\n\n if (revoke === \"ok\") {\n setConnectionStatus(\"disconnected\");\n } else {\n setConnectionStatus(\"error\");\n }\n } catch (_error) {\n setConnectionStatus(\"error\");\n }\n }, [\n setConnectionStatus,\n knock.slack,\n tenantId,\n knockSlackChannelId,\n setActionLabel,\n ]);\n\n const buildSlackAuthUrl = useCallback(() => {\n const nonce = crypto.randomUUID();\n sessionStorage.setItem(\n getSlackNonceStorageKey(knockSlackChannelId, knock.userId!),\n nonce,\n );\n\n const rawParams = {\n state: JSON.stringify({\n nonce,\n redirect_url: redirectUrl,\n access_token_object: {\n object_id: tenantId,\n collection: TENANT_OBJECT_COLLECTION,\n },\n channel_id: knockSlackChannelId,\n public_key: knock.apiKey,\n user_token: knock.userToken,\n branch_slug: knock.branch,\n }),\n client_id: slackClientId,\n scope: combinedScopes.join(\",\"),\n };\n return `${SLACK_AUTHORIZE_URL}?${new URLSearchParams(rawParams)}`;\n }, [\n redirectUrl,\n tenantId,\n knockSlackChannelId,\n knock.apiKey,\n knock.userId,\n knock.userToken,\n knock.branch,\n slackClientId,\n combinedScopes,\n ]);\n\n return {\n buildSlackAuthUrl,\n disconnectFromSlack,\n };\n}\n\nexport default useSlackAuth;\n"],"names":["SLACK_AUTHORIZE_URL","DEFAULT_SLACK_SCOPES","getSlackNonceStorageKey","channelId","userId","normalizeOptions","options","Array","isArray","scopes","additionalScopes","useSlackAuth","slackClientId","redirectUrl","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","from","Set","concat","disconnectFromSlack","useCallback","revoke","slack","revokeAccessToken","tenant","knockChannelId","buildSlackAuthUrl","nonce","crypto","randomUUID","sessionStorage","setItem","rawParams","state","JSON","stringify","redirect_url","access_token_object","object_id","collection","TENANT_OBJECT_COLLECTION","channel_id","public_key","apiKey","user_token","userToken","branch_slug","branch","client_id","scope","join","URLSearchParams"],"mappings":"kZAMA,MAAMA,EAAsB,uCACtBC,EAAuB,CAC3B,aACA,oBACA,gBACA,aAAa,EAGCC,SAAAA,EACdC,EACAC,EACQ,CACD,MAAA,0BAA0BD,CAAS,IAAIC,CAAM,EACtD,CAiBA,SAASC,EAAiBC,EAGxB,CACA,OAAKA,EAIDC,MAAMC,QAAQF,CAAO,EAChB,CAAEG,OAAQR,EAAsBS,iBAAkBJ,CAAQ,EAG5D,CACLG,OAAQH,EAAQG,QAAUR,EAC1BS,iBAAkBJ,EAAQI,kBAAoB,CAAA,CAChD,EAVS,CAAED,OAAQR,EAAsBS,iBAAkB,CAAA,CAAG,CAWhE,CAEA,SAASC,EACPC,EACAC,EACAP,EACoB,CACpB,MAAMQ,EAAQC,EAAAA,eAAe,EACvB,CAAEC,oBAAAA,EAAqBC,oBAAAA,EAAqBC,SAAAA,EAAUC,eAAAA,GAC1DC,sBAAoB,EAEhB,CAAEX,OAAAA,EAAQC,iBAAAA,CAAAA,EAAqBL,EAAiBC,CAAO,EACvDe,EAAiBd,MAAMe,KAAK,IAAIC,IAAId,EAAOe,OAAOd,CAAgB,CAAC,CAAC,EAEpEe,EAAsBC,EAAAA,YAAY,SAAY,CAClDP,EAAe,IAAI,EACnBH,EAAoB,eAAe,EAC/B,GAAA,CACF,MAAMW,EAAS,MAAMb,EAAMc,MAAMC,kBAAkB,CACjDC,OAAQZ,EACRa,eAAgBd,CAAAA,CACjB,EAGCD,EADEW,IAAW,KACO,eAEA,OAFc,OAIrB,CACfX,EAAoB,OAAO,CAAA,CAC7B,EACC,CACDA,EACAF,EAAMc,MACNV,EACAD,EACAE,CAAc,CACf,EAsCM,MAAA,CACLa,kBArCwBN,EAAAA,YAAY,IAAM,CACpCO,MAAAA,EAAQC,OAAOC,WAAW,EAChCC,eAAeC,QACbnC,EAAwBe,EAAqBH,EAAMV,MAAO,EAC1D6B,CACF,EAEA,MAAMK,EAAY,CAChBC,MAAOC,KAAKC,UAAU,CACpBR,MAAAA,EACAS,aAAc7B,EACd8B,oBAAqB,CACnBC,UAAW1B,EACX2B,WAAYC,EAAAA,wBACd,EACAC,WAAY9B,EACZ+B,WAAYlC,EAAMmC,OAClBC,WAAYpC,EAAMqC,UAClBC,YAAatC,EAAMuC,MAAAA,CACpB,EACDC,UAAW1C,EACX2C,MAAOlC,EAAemC,KAAK,GAAG,CAChC,EACA,MAAO,GAAGxD,CAAmB,IAAI,IAAIyD,gBAAgBnB,CAAS,CAAC,EAAA,EAC9D,CACDzB,EACAK,EACAD,EACAH,EAAMmC,OACNnC,EAAMV,OACNU,EAAMqC,UACNrC,EAAMuC,OACNzC,EACAS,CAAc,CACf,EAICI,oBAAAA,CACF,CACF"}
@@ -1,14 +1,14 @@
1
1
  import { KnockProvider as t, useKnockClient as r } from "./modules/core/context/KnockProvider.mjs";
2
2
  import { default as a } from "./modules/core/hooks/useAuthenticatedKnockClient.mjs";
3
- import { default as n } from "./modules/core/hooks/useStableOptions.mjs";
3
+ import { default as u } from "./modules/core/hooks/useStableOptions.mjs";
4
4
  import { useAuthPostMessageListener as m } from "./modules/core/hooks/useAuthPostMessageListener.mjs";
5
- import { useAuthPolling as l } from "./modules/core/hooks/useAuthPolling.mjs";
5
+ import { useAuthPolling as d } from "./modules/core/hooks/useAuthPolling.mjs";
6
6
  import { FilterStatus as x } from "./modules/core/constants.mjs";
7
- import { feedProviderKey as c, formatBadgeCount as k, formatTimestamp as C, getBadgeAriaLabel as K, msTeamsProviderKey as S, renderNodeOrFallback as P, slackProviderKey as T, toSentenceCase as h } from "./modules/core/utils.mjs";
8
- import { KnockFeedProvider as M, useKnockFeed as g } from "./modules/feed/context/KnockFeedProvider.mjs";
7
+ import { feedProviderKey as p, formatBadgeCount as k, formatTimestamp as C, getBadgeAriaLabel as S, msTeamsProviderKey as K, renderNodeOrFallback as P, slackProviderKey as T, toSentenceCase as h } from "./modules/core/utils.mjs";
8
+ import { KnockFeedProvider as g, useKnockFeed as M } from "./modules/feed/context/KnockFeedProvider.mjs";
9
9
  import { default as F } from "./modules/feed/hooks/useNotifications.mjs";
10
10
  import { default as N } from "./modules/feed/hooks/useFeedSettings.mjs";
11
- import { useCreateNotificationStore as y, default as B } from "./modules/feed/hooks/useNotificationStore.mjs";
11
+ import { useCreateNotificationStore as b, default as B } from "./modules/feed/hooks/useNotificationStore.mjs";
12
12
  import { KnockGuideContext as L, KnockGuideProvider as O } from "./modules/guide/context/KnockGuideProvider.mjs";
13
13
  import { useGuide as q } from "./modules/guide/hooks/useGuide.mjs";
14
14
  import { useGuides as z } from "./modules/guide/hooks/useGuides.mjs";
@@ -21,45 +21,46 @@ import { default as _ } from "./modules/ms-teams/hooks/useMsTeamsChannels.mjs";
21
21
  import { default as ee } from "./modules/ms-teams/hooks/useConnectedMsTeamsChannels.mjs";
22
22
  import { KnockSlackProvider as te, useKnockSlackClient as re } from "./modules/slack/context/KnockSlackProvider.mjs";
23
23
  import { default as ae } from "./modules/slack/hooks/useSlackConnectionStatus.mjs";
24
- import { default as ne } from "./modules/slack/hooks/useSlackChannels.mjs";
24
+ import { default as ue } from "./modules/slack/hooks/useSlackChannels.mjs";
25
25
  import { default as me } from "./modules/slack/hooks/useConnectedSlackChannels.mjs";
26
- import { default as le } from "./modules/slack/hooks/useSlackAuth.mjs";
27
- import { I18nContext as xe, KnockI18nProvider as pe } from "./modules/i18n/context/KnockI18nProvider.mjs";
28
- import { useTranslations as ke } from "./modules/i18n/hooks/useTranslations.mjs";
26
+ import { getSlackNonceStorageKey as de, default as ie } from "./modules/slack/hooks/useSlackAuth.mjs";
27
+ import { I18nContext as ce, KnockI18nProvider as pe } from "./modules/i18n/context/KnockI18nProvider.mjs";
28
+ import { useTranslations as Ce } from "./modules/i18n/hooks/useTranslations.mjs";
29
29
  import { locales as Ke } from "./modules/i18n/languages/index.mjs";
30
- import { usePreferences as Pe } from "./modules/preferences/hooks/usePreferences.mjs";
31
- import { useStore as he } from "@tanstack/react-store";
30
+ import { usePreferences as Te } from "./modules/preferences/hooks/usePreferences.mjs";
31
+ import { useStore as ve } from "@tanstack/react-store";
32
32
  export {
33
33
  x as FilterStatus,
34
- xe as I18nContext,
35
- M as KnockFeedProvider,
34
+ ce as I18nContext,
35
+ g as KnockFeedProvider,
36
36
  L as KnockGuideContext,
37
37
  O as KnockGuideProvider,
38
38
  pe as KnockI18nProvider,
39
39
  J as KnockMsTeamsProvider,
40
40
  t as KnockProvider,
41
41
  te as KnockSlackProvider,
42
- c as feedProviderKey,
42
+ p as feedProviderKey,
43
43
  k as formatBadgeCount,
44
44
  C as formatTimestamp,
45
- K as getBadgeAriaLabel,
45
+ S as getBadgeAriaLabel,
46
+ de as getSlackNonceStorageKey,
46
47
  Ke as locales,
47
- S as msTeamsProviderKey,
48
+ K as msTeamsProviderKey,
48
49
  P as renderNodeOrFallback,
49
50
  T as slackProviderKey,
50
51
  h as toSentenceCase,
51
- l as useAuthPolling,
52
+ d as useAuthPolling,
52
53
  m as useAuthPostMessageListener,
53
54
  a as useAuthenticatedKnockClient,
54
55
  ee as useConnectedMsTeamsChannels,
55
56
  me as useConnectedSlackChannels,
56
- y as useCreateNotificationStore,
57
+ b as useCreateNotificationStore,
57
58
  N as useFeedSettings,
58
59
  q as useGuide,
59
60
  E as useGuideContext,
60
61
  z as useGuides,
61
62
  r as useKnockClient,
62
- g as useKnockFeed,
63
+ M as useKnockFeed,
63
64
  Q as useKnockMsTeamsClient,
64
65
  re as useKnockSlackClient,
65
66
  W as useMsTeamsAuth,
@@ -68,12 +69,12 @@ export {
68
69
  Y as useMsTeamsTeams,
69
70
  B as useNotificationStore,
70
71
  F as useNotifications,
71
- Pe as usePreferences,
72
- le as useSlackAuth,
73
- ne as useSlackChannels,
72
+ Te as usePreferences,
73
+ ie as useSlackAuth,
74
+ ue as useSlackChannels,
74
75
  ae as useSlackConnectionStatus,
75
- n as useStableOptions,
76
- he as useStore,
77
- ke as useTranslations
76
+ u as useStableOptions,
77
+ ve as useStore,
78
+ Ce as useTranslations
78
79
  };
79
80
  //# sourceMappingURL=index.mjs.map
@@ -1,19 +1,43 @@
1
- import { useEffect as c } from "react";
2
- function d(u) {
1
+ import { useEffect as p } from "react";
2
+ function d(e) {
3
+ return typeof e == "object" && e !== null && "type" in e ? e.type : e;
4
+ }
5
+ function y(e) {
6
+ if (typeof e == "object" && e !== null && "nonce" in e) {
7
+ const o = e.nonce;
8
+ return typeof o == "string" ? o : void 0;
9
+ }
10
+ }
11
+ function v(e) {
3
12
  const {
4
- knockHost: a,
5
- popupWindowRef: r,
13
+ knockHost: o,
14
+ popupWindowRef: s,
6
15
  setConnectionStatus: t,
7
- onAuthenticationComplete: e
8
- } = u;
9
- c(() => {
10
- const n = (s) => {
11
- s.origin === a && (s.data === "authComplete" ? (t("connected"), e == null || e(s.data), r.current && !r.current.closed && r.current.close(), r.current = null) : s.data === "authFailed" && (t("error"), e == null || e(s.data), r.current = null));
16
+ onAuthenticationComplete: r,
17
+ nonceStorageKey: n
18
+ } = e;
19
+ p(() => {
20
+ const i = () => {
21
+ s.current && !s.current.closed && s.current.close(), s.current = null;
22
+ }, f = (u) => {
23
+ if (u.origin !== o)
24
+ return;
25
+ const c = d(u.data);
26
+ if (c === "authComplete") {
27
+ if (n) {
28
+ const g = y(u.data), l = sessionStorage.getItem(n);
29
+ if (sessionStorage.removeItem(n), !g || l && l !== g) {
30
+ t("error"), r == null || r("authFailed"), i();
31
+ return;
32
+ }
33
+ }
34
+ t("connected"), r == null || r(c), i();
35
+ } else c === "authFailed" && (n && sessionStorage.removeItem(n), t("error"), r == null || r(c), s.current = null);
12
36
  };
13
- return window.addEventListener("message", n, !1), () => window.removeEventListener("message", n);
14
- }, [a, e, t, r]);
37
+ return window.addEventListener("message", f, !1), () => window.removeEventListener("message", f);
38
+ }, [o, r, t, s, n]);
15
39
  }
16
40
  export {
17
- d as useAuthPostMessageListener
41
+ v as useAuthPostMessageListener
18
42
  };
19
43
  //# sourceMappingURL=useAuthPostMessageListener.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthPostMessageListener.mjs","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { ConnectionStatus } from \"../types\";\n\nexport interface UseAuthPostMessageListenerOptions {\n knockHost: string;\n popupWindowRef: React.MutableRefObject<Window | null>;\n setConnectionStatus: (status: ConnectionStatus) => void;\n onAuthenticationComplete?: (authenticationResp: string) => void;\n}\n\n/**\n * Hook that listens for postMessage events from OAuth popup windows.\n *\n * Handles \"authComplete\" and \"authFailed\" messages sent from the OAuth flow popup,\n * validates the message origin, updates connection status, and closes the popup.\n *\n * @param options - Configuration options for the postMessage listener\n *\n * @example\n * ```tsx\n * useAuthPostMessageListener({\n * knockHost: knock.host,\n * popupWindowRef,\n * setConnectionStatus,\n * onAuthenticationComplete,\n * });\n * ```\n */\nexport function useAuthPostMessageListener(\n options: UseAuthPostMessageListenerOptions,\n): void {\n const {\n knockHost,\n popupWindowRef,\n setConnectionStatus,\n onAuthenticationComplete,\n } = options;\n\n useEffect(() => {\n const receiveMessage = (event: MessageEvent) => {\n // Validate message origin for security\n if (event.origin !== knockHost) {\n return;\n }\n\n if (event.data === \"authComplete\") {\n setConnectionStatus(\"connected\");\n onAuthenticationComplete?.(event.data);\n // Clear popup ref so polling stops and doesn't trigger callback again\n if (popupWindowRef.current && !popupWindowRef.current.closed) {\n popupWindowRef.current.close();\n }\n popupWindowRef.current = null;\n } else if (event.data === \"authFailed\") {\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(event.data);\n popupWindowRef.current = null;\n }\n };\n\n window.addEventListener(\"message\", receiveMessage, false);\n return () => window.removeEventListener(\"message\", receiveMessage);\n }, [\n knockHost,\n onAuthenticationComplete,\n setConnectionStatus,\n popupWindowRef,\n ]);\n}\n"],"names":["useAuthPostMessageListener","options","knockHost","popupWindowRef","setConnectionStatus","onAuthenticationComplete","useEffect","receiveMessage","event","origin","data","current","closed","close","addEventListener","window","removeEventListener"],"mappings":";AA6BO,SAASA,EACdC,GACM;AACA,QAAA;AAAA,IACJC,WAAAA;AAAAA,IACAC,gBAAAA;AAAAA,IACAC,qBAAAA;AAAAA,IACAC,0BAAAA;AAAAA,EAAAA,IACEJ;AAEJK,EAAAA,EAAU,MAAM;AACRC,UAAAA,IAAiBA,CAACC,MAAwB;AAE1CA,MAAAA,EAAMC,WAAWP,MAIjBM,EAAME,SAAS,kBACjBN,EAAoB,WAAW,GAC/BC,KAAAA,QAAAA,EAA2BG,EAAME,OAE7BP,EAAeQ,WAAW,CAACR,EAAeQ,QAAQC,UACpDT,EAAeQ,QAAQE,MAAM,GAE/BV,EAAeQ,UAAU,QAChBH,EAAME,SAAS,iBACxBN,EAAoB,OAAO,GAC3BC,KAAAA,QAAAA,EAA2BG,EAAME,OACjCP,EAAeQ,UAAU;AAAA,IAE7B;AAEOG,kBAAAA,iBAAiB,WAAWP,GAAgB,EAAK,GACjD,MAAMQ,OAAOC,oBAAoB,WAAWT,CAAc;AAAA,KAChE,CACDL,GACAG,GACAD,GACAD,CAAc,CACf;AACH;"}
1
+ {"version":3,"file":"useAuthPostMessageListener.mjs","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nimport { ConnectionStatus } from \"../types\";\n\nexport interface UseAuthPostMessageListenerOptions {\n knockHost: string;\n popupWindowRef: React.MutableRefObject<Window | null>;\n setConnectionStatus: (status: ConnectionStatus) => void;\n onAuthenticationComplete?: (authenticationResp: string) => void;\n /**\n * The sessionStorage key where the CSRF nonce was stored when the auth URL\n * was built. When provided, the listener will verify the nonce returned in\n * the postMessage payload matches the stored value.\n */\n nonceStorageKey?: string;\n}\n\n/**\n * Extracts the message type from a postMessage event data payload.\n * Supports both the legacy string format (\"authComplete\") and the new\n * structured format ({ type: \"authComplete\", nonce: \"...\" }).\n */\nfunction getMessageType(data: unknown): string {\n if (typeof data === \"object\" && data !== null && \"type\" in data) {\n return (data as { type: string }).type;\n }\n return data as string;\n}\n\n/**\n * Extracts the nonce from a structured postMessage event data payload.\n * Returns undefined for legacy string-format messages.\n */\nfunction getMessageNonce(data: unknown): string | undefined {\n if (typeof data === \"object\" && data !== null && \"nonce\" in data) {\n const nonce = (data as { nonce: unknown }).nonce;\n return typeof nonce === \"string\" ? nonce : undefined;\n }\n return undefined;\n}\n\n/**\n * Hook that listens for postMessage events from OAuth popup windows.\n *\n * Handles \"authComplete\" and \"authFailed\" messages sent from the OAuth flow popup,\n * validates the message origin, optionally verifies the CSRF nonce, updates\n * connection status, and closes the popup.\n *\n * @param options - Configuration options for the postMessage listener\n *\n * @example\n * ```tsx\n * useAuthPostMessageListener({\n * knockHost: knock.host,\n * popupWindowRef,\n * setConnectionStatus,\n * onAuthenticationComplete,\n * nonceStorageKey: \"knock:slack-auth-nonce:channel_123:user_1\",\n * });\n * ```\n */\nexport function useAuthPostMessageListener(\n options: UseAuthPostMessageListenerOptions,\n): void {\n const {\n knockHost,\n popupWindowRef,\n setConnectionStatus,\n onAuthenticationComplete,\n nonceStorageKey,\n } = options;\n\n useEffect(() => {\n const closePopup = () => {\n if (popupWindowRef.current && !popupWindowRef.current.closed) {\n popupWindowRef.current.close();\n }\n popupWindowRef.current = null;\n };\n\n const receiveMessage = (event: MessageEvent) => {\n // Validate message origin for security\n if (event.origin !== knockHost) {\n return;\n }\n\n const messageType = getMessageType(event.data);\n\n if (messageType === \"authComplete\") {\n // Verify CSRF nonce when a nonceStorageKey is configured.\n if (nonceStorageKey) {\n const returnedNonce = getMessageNonce(event.data);\n const storedNonce = sessionStorage.getItem(nonceStorageKey);\n sessionStorage.removeItem(nonceStorageKey);\n\n // If nonce already consumed by a prior handler invocation, then bail\n // out from checking again.\n if (\n !returnedNonce ||\n (storedNonce && storedNonce !== returnedNonce)\n ) {\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(\"authFailed\");\n closePopup();\n return;\n }\n }\n\n setConnectionStatus(\"connected\");\n onAuthenticationComplete?.(messageType);\n closePopup();\n } else if (messageType === \"authFailed\") {\n if (nonceStorageKey) {\n sessionStorage.removeItem(nonceStorageKey);\n }\n setConnectionStatus(\"error\");\n onAuthenticationComplete?.(messageType);\n popupWindowRef.current = null;\n }\n };\n\n window.addEventListener(\"message\", receiveMessage, false);\n return () => window.removeEventListener(\"message\", receiveMessage);\n }, [\n knockHost,\n onAuthenticationComplete,\n setConnectionStatus,\n popupWindowRef,\n nonceStorageKey,\n ]);\n}\n"],"names":["getMessageType","data","type","getMessageNonce","nonce","undefined","useAuthPostMessageListener","options","knockHost","popupWindowRef","setConnectionStatus","onAuthenticationComplete","nonceStorageKey","useEffect","closePopup","current","closed","close","receiveMessage","event","origin","messageType","returnedNonce","storedNonce","sessionStorage","getItem","removeItem","addEventListener","window","removeEventListener"],"mappings":";AAsBA,SAASA,EAAeC,GAAuB;AAC7C,SAAI,OAAOA,KAAS,YAAYA,MAAS,QAAQ,UAAUA,IACjDA,EAA0BC,OAE7BD;AACT;AAMA,SAASE,EAAgBF,GAAmC;AAC1D,MAAI,OAAOA,KAAS,YAAYA,MAAS,QAAQ,WAAWA,GAAM;AAChE,UAAMG,IAASH,EAA4BG;AACpC,WAAA,OAAOA,KAAU,WAAWA,IAAQC;AAAAA,EAAAA;AAG/C;AAsBO,SAASC,EACdC,GACM;AACA,QAAA;AAAA,IACJC,WAAAA;AAAAA,IACAC,gBAAAA;AAAAA,IACAC,qBAAAA;AAAAA,IACAC,0BAAAA;AAAAA,IACAC,iBAAAA;AAAAA,EAAAA,IACEL;AAEJM,EAAAA,EAAU,MAAM;AACd,UAAMC,IAAaA,MAAM;AACvB,MAAIL,EAAeM,WAAW,CAACN,EAAeM,QAAQC,UACpDP,EAAeM,QAAQE,MAAM,GAE/BR,EAAeM,UAAU;AAAA,IAC3B,GAEMG,IAAiBA,CAACC,MAAwB;AAE1CA,UAAAA,EAAMC,WAAWZ;AACnB;AAGIa,YAAAA,IAAcrB,EAAemB,EAAMlB,IAAI;AAE7C,UAAIoB,MAAgB,gBAAgB;AAElC,YAAIT,GAAiB;AACbU,gBAAAA,IAAgBnB,EAAgBgB,EAAMlB,IAAI,GAC1CsB,IAAcC,eAAeC,QAAQb,CAAe;AAK1D,cAJAY,eAAeE,WAAWd,CAAe,GAKvC,CAACU,KACAC,KAAeA,MAAgBD,GAChC;AACAZ,YAAAA,EAAoB,OAAO,GAC3BC,KAAAA,QAAAA,EAA2B,eAChBG,EAAA;AACX;AAAA,UAAA;AAAA,QACF;AAGFJ,QAAAA,EAAoB,WAAW,GAC/BC,KAAAA,QAAAA,EAA2BU,IAChBP,EAAA;AAAA,MAAA,MACb,CAAWO,MAAgB,iBACrBT,KACFY,eAAeE,WAAWd,CAAe,GAE3CF,EAAoB,OAAO,GAC3BC,KAAAA,QAAAA,EAA2BU,IAC3BZ,EAAeM,UAAU;AAAA,IAE7B;AAEOY,kBAAAA,iBAAiB,WAAWT,GAAgB,EAAK,GACjD,MAAMU,OAAOC,oBAAoB,WAAWX,CAAc;AAAA,EAAA,GAChE,CACDV,GACAG,GACAD,GACAD,GACAG,CAAe,CAChB;AACH;"}
@@ -1,69 +1,76 @@
1
- import { useKnockSlackClient as S } from "../context/KnockSlackProvider.mjs";
1
+ import { useKnockSlackClient as h } from "../context/KnockSlackProvider.mjs";
2
2
  import { useCallback as u } from "react";
3
3
  import "../../i18n/context/KnockI18nProvider.mjs";
4
4
  import "swr/infinite";
5
- import { useKnockClient as h } from "../../core/context/KnockProvider.mjs";
6
- import { TENANT_OBJECT_COLLECTION as _ } from "@knocklabs/client";
5
+ import { useKnockClient as _ } from "../../core/context/KnockProvider.mjs";
6
+ import { TENANT_OBJECT_COLLECTION as f } from "@knocklabs/client";
7
7
  import "fast-deep-equal";
8
8
  import "date-fns";
9
9
  import "swr";
10
- const A = "https://slack.com/oauth/v2/authorize", a = ["chat:write", "chat:write.public", "channels:read", "groups:read"];
11
- function b(e) {
10
+ const A = "https://slack.com/oauth/v2/authorize", s = ["chat:write", "chat:write.public", "channels:read", "groups:read"];
11
+ function b(e, r) {
12
+ return `knock:slack-auth-nonce:${e}:${r}`;
13
+ }
14
+ function y(e) {
12
15
  return e ? Array.isArray(e) ? {
13
- scopes: a,
16
+ scopes: s,
14
17
  additionalScopes: e
15
18
  } : {
16
- scopes: e.scopes ?? a,
19
+ scopes: e.scopes ?? s,
17
20
  additionalScopes: e.additionalScopes ?? []
18
21
  } : {
19
- scopes: a,
22
+ scopes: s,
20
23
  additionalScopes: []
21
24
  };
22
25
  }
23
- function g(e, s, k) {
24
- const c = h(), {
25
- setConnectionStatus: o,
26
+ function N(e, r, k) {
27
+ const o = _(), {
28
+ setConnectionStatus: c,
26
29
  knockSlackChannelId: n,
27
- tenantId: t,
30
+ tenantId: a,
28
31
  setActionLabel: i
29
- } = S(), {
32
+ } = h(), {
30
33
  scopes: d,
31
34
  additionalScopes: p
32
- } = b(k), l = Array.from(new Set(d.concat(p))), m = u(async () => {
33
- i(null), o("disconnecting");
35
+ } = y(k), l = Array.from(new Set(d.concat(p))), S = u(async () => {
36
+ i(null), c("disconnecting");
34
37
  try {
35
- const r = await c.slack.revokeAccessToken({
36
- tenant: t,
38
+ const t = await o.slack.revokeAccessToken({
39
+ tenant: a,
37
40
  knockChannelId: n
38
41
  });
39
- o(r === "ok" ? "disconnected" : "error");
42
+ c(t === "ok" ? "disconnected" : "error");
40
43
  } catch {
41
- o("error");
44
+ c("error");
42
45
  }
43
- }, [o, c.slack, t, n, i]);
46
+ }, [c, o.slack, a, n, i]);
44
47
  return {
45
48
  buildSlackAuthUrl: u(() => {
46
- const r = {
49
+ const t = crypto.randomUUID();
50
+ sessionStorage.setItem(b(n, o.userId), t);
51
+ const m = {
47
52
  state: JSON.stringify({
48
- redirect_url: s,
53
+ nonce: t,
54
+ redirect_url: r,
49
55
  access_token_object: {
50
- object_id: t,
51
- collection: _
56
+ object_id: a,
57
+ collection: f
52
58
  },
53
59
  channel_id: n,
54
- public_key: c.apiKey,
55
- user_token: c.userToken,
56
- branch_slug: c.branch
60
+ public_key: o.apiKey,
61
+ user_token: o.userToken,
62
+ branch_slug: o.branch
57
63
  }),
58
64
  client_id: e,
59
65
  scope: l.join(",")
60
66
  };
61
- return `${A}?${new URLSearchParams(r)}`;
62
- }, [s, t, n, c.apiKey, c.userToken, c.branch, e, l]),
63
- disconnectFromSlack: m
67
+ return `${A}?${new URLSearchParams(m)}`;
68
+ }, [r, a, n, o.apiKey, o.userId, o.userToken, o.branch, e, l]),
69
+ disconnectFromSlack: S
64
70
  };
65
71
  }
66
72
  export {
67
- g as default
73
+ N as default,
74
+ b as getSlackNonceStorageKey
68
75
  };
69
76
  //# sourceMappingURL=useSlackAuth.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSlackAuth.mjs","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"sourcesContent":["import { useKnockSlackClient } from \"..\";\nimport { TENANT_OBJECT_COLLECTION } from \"@knocklabs/client\";\nimport { useCallback } from \"react\";\n\nimport { useKnockClient } from \"../../core\";\n\nconst SLACK_AUTHORIZE_URL = \"https://slack.com/oauth/v2/authorize\";\nconst DEFAULT_SLACK_SCOPES = [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\",\n \"groups:read\",\n];\n\ntype UseSlackAuthOutput = {\n buildSlackAuthUrl: () => string;\n disconnectFromSlack: () => void;\n};\n\ntype UseSlackAuthOptions = {\n // When provided, the default scopes will be overridden with the provided scopes\n scopes?: string[];\n // Additional scopes to add to the default scopes\n additionalScopes?: string[];\n};\n\n// Here we normalize the options to be a single object with scopes and additionalScopes\n// The \"options\" parameter can be an array of scopes, an object with scopes and additionalScopes, or undefined\n// We handle the array case because it was the previous way to pass options so we're being backward compatible\nfunction normalizeOptions(options?: UseSlackAuthOptions | string[]): {\n scopes: string[];\n additionalScopes: string[];\n} {\n if (!options) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: [] };\n }\n\n if (Array.isArray(options)) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: options };\n }\n\n return {\n scopes: options.scopes ?? DEFAULT_SLACK_SCOPES,\n additionalScopes: options.additionalScopes ?? [],\n };\n}\n\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n options?: UseSlackAuthOptions | string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const { scopes, additionalScopes } = normalizeOptions(options);\n const combinedScopes = Array.from(new Set(scopes.concat(additionalScopes)));\n\n const disconnectFromSlack = useCallback(async () => {\n setActionLabel(null);\n setConnectionStatus(\"disconnecting\");\n try {\n const revoke = await knock.slack.revokeAccessToken({\n tenant: tenantId,\n knockChannelId: knockSlackChannelId,\n });\n\n if (revoke === \"ok\") {\n setConnectionStatus(\"disconnected\");\n } else {\n setConnectionStatus(\"error\");\n }\n } catch (_error) {\n setConnectionStatus(\"error\");\n }\n }, [\n setConnectionStatus,\n knock.slack,\n tenantId,\n knockSlackChannelId,\n setActionLabel,\n ]);\n\n const buildSlackAuthUrl = useCallback(() => {\n const rawParams = {\n state: JSON.stringify({\n redirect_url: redirectUrl,\n access_token_object: {\n object_id: tenantId,\n collection: TENANT_OBJECT_COLLECTION,\n },\n channel_id: knockSlackChannelId,\n public_key: knock.apiKey,\n user_token: knock.userToken,\n branch_slug: knock.branch,\n }),\n client_id: slackClientId,\n scope: combinedScopes.join(\",\"),\n };\n return `${SLACK_AUTHORIZE_URL}?${new URLSearchParams(rawParams)}`;\n }, [\n redirectUrl,\n tenantId,\n knockSlackChannelId,\n knock.apiKey,\n knock.userToken,\n knock.branch,\n slackClientId,\n combinedScopes,\n ]);\n\n return {\n buildSlackAuthUrl,\n disconnectFromSlack,\n };\n}\n\nexport default useSlackAuth;\n"],"names":["SLACK_AUTHORIZE_URL","DEFAULT_SLACK_SCOPES","normalizeOptions","options","Array","isArray","scopes","additionalScopes","useSlackAuth","slackClientId","redirectUrl","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","from","Set","concat","disconnectFromSlack","useCallback","revoke","slack","revokeAccessToken","tenant","knockChannelId","buildSlackAuthUrl","rawParams","state","JSON","stringify","redirect_url","access_token_object","object_id","collection","TENANT_OBJECT_COLLECTION","channel_id","public_key","apiKey","user_token","userToken","branch_slug","branch","client_id","scope","join","URLSearchParams"],"mappings":";;;;;;;;;AAMA,MAAMA,IAAsB,wCACtBC,IAAuB,CAC3B,cACA,qBACA,iBACA,aAAa;AAkBf,SAASC,EAAiBC,GAGxB;AACA,SAAKA,IAIDC,MAAMC,QAAQF,CAAO,IAChB;AAAA,IAAEG,QAAQL;AAAAA,IAAsBM,kBAAkBJ;AAAAA,EAAQ,IAG5D;AAAA,IACLG,QAAQH,EAAQG,UAAUL;AAAAA,IAC1BM,kBAAkBJ,EAAQI,oBAAoB,CAAA;AAAA,EAChD,IAVS;AAAA,IAAED,QAAQL;AAAAA,IAAsBM,kBAAkB,CAAA;AAAA,EAAG;AAWhE;AAEA,SAASC,EACPC,GACAC,GACAP,GACoB;AACpB,QAAMQ,IAAQC,EAAe,GACvB;AAAA,IAAEC,qBAAAA;AAAAA,IAAqBC,qBAAAA;AAAAA,IAAqBC,UAAAA;AAAAA,IAAUC,gBAAAA;AAAAA,MAC1DC,EAAoB,GAEhB;AAAA,IAAEX,QAAAA;AAAAA,IAAQC,kBAAAA;AAAAA,EAAAA,IAAqBL,EAAiBC,CAAO,GACvDe,IAAiBd,MAAMe,KAAK,IAAIC,IAAId,EAAOe,OAAOd,CAAgB,CAAC,CAAC,GAEpEe,IAAsBC,EAAY,YAAY;AAClDP,IAAAA,EAAe,IAAI,GACnBH,EAAoB,eAAe;AAC/B,QAAA;AACF,YAAMW,IAAS,MAAMb,EAAMc,MAAMC,kBAAkB;AAAA,QACjDC,QAAQZ;AAAAA,QACRa,gBAAgBd;AAAAA,MAAAA,CACjB;AAED,MACED,EADEW,MAAW,OACO,iBAEA,OAFc;AAAA,YAIrB;AACfX,MAAAA,EAAoB,OAAO;AAAA,IAAA;AAAA,EAC7B,GACC,CACDA,GACAF,EAAMc,OACNV,GACAD,GACAE,CAAc,CACf;AA8BM,SAAA;AAAA,IACLa,mBA7BwBN,EAAY,MAAM;AAC1C,YAAMO,IAAY;AAAA,QAChBC,OAAOC,KAAKC,UAAU;AAAA,UACpBC,cAAcxB;AAAAA,UACdyB,qBAAqB;AAAA,YACnBC,WAAWrB;AAAAA,YACXsB,YAAYC;AAAAA,UACd;AAAA,UACAC,YAAYzB;AAAAA,UACZ0B,YAAY7B,EAAM8B;AAAAA,UAClBC,YAAY/B,EAAMgC;AAAAA,UAClBC,aAAajC,EAAMkC;AAAAA,QAAAA,CACpB;AAAA,QACDC,WAAWrC;AAAAA,QACXsC,OAAO7B,EAAe8B,KAAK,GAAG;AAAA,MAChC;AACA,aAAO,GAAGhD,CAAmB,IAAI,IAAIiD,gBAAgBnB,CAAS,CAAC;AAAA,IAC9D,GAAA,CACDpB,GACAK,GACAD,GACAH,EAAM8B,QACN9B,EAAMgC,WACNhC,EAAMkC,QACNpC,GACAS,CAAc,CACf;AAAA,IAICI,qBAAAA;AAAAA,EACF;AACF;"}
1
+ {"version":3,"file":"useSlackAuth.mjs","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"sourcesContent":["import { useKnockSlackClient } from \"..\";\nimport { TENANT_OBJECT_COLLECTION } from \"@knocklabs/client\";\nimport { useCallback } from \"react\";\n\nimport { useKnockClient } from \"../../core\";\n\nconst SLACK_AUTHORIZE_URL = \"https://slack.com/oauth/v2/authorize\";\nconst DEFAULT_SLACK_SCOPES = [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\",\n \"groups:read\",\n];\n\nexport function getSlackNonceStorageKey(\n channelId: string,\n userId: string,\n): string {\n return `knock:slack-auth-nonce:${channelId}:${userId}`;\n}\n\ntype UseSlackAuthOutput = {\n buildSlackAuthUrl: () => string;\n disconnectFromSlack: () => void;\n};\n\ntype UseSlackAuthOptions = {\n // When provided, the default scopes will be overridden with the provided scopes\n scopes?: string[];\n // Additional scopes to add to the default scopes\n additionalScopes?: string[];\n};\n\n// Here we normalize the options to be a single object with scopes and additionalScopes\n// The \"options\" parameter can be an array of scopes, an object with scopes and additionalScopes, or undefined\n// We handle the array case because it was the previous way to pass options so we're being backward compatible\nfunction normalizeOptions(options?: UseSlackAuthOptions | string[]): {\n scopes: string[];\n additionalScopes: string[];\n} {\n if (!options) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: [] };\n }\n\n if (Array.isArray(options)) {\n return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: options };\n }\n\n return {\n scopes: options.scopes ?? DEFAULT_SLACK_SCOPES,\n additionalScopes: options.additionalScopes ?? [],\n };\n}\n\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n options?: UseSlackAuthOptions | string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const { scopes, additionalScopes } = normalizeOptions(options);\n const combinedScopes = Array.from(new Set(scopes.concat(additionalScopes)));\n\n const disconnectFromSlack = useCallback(async () => {\n setActionLabel(null);\n setConnectionStatus(\"disconnecting\");\n try {\n const revoke = await knock.slack.revokeAccessToken({\n tenant: tenantId,\n knockChannelId: knockSlackChannelId,\n });\n\n if (revoke === \"ok\") {\n setConnectionStatus(\"disconnected\");\n } else {\n setConnectionStatus(\"error\");\n }\n } catch (_error) {\n setConnectionStatus(\"error\");\n }\n }, [\n setConnectionStatus,\n knock.slack,\n tenantId,\n knockSlackChannelId,\n setActionLabel,\n ]);\n\n const buildSlackAuthUrl = useCallback(() => {\n const nonce = crypto.randomUUID();\n sessionStorage.setItem(\n getSlackNonceStorageKey(knockSlackChannelId, knock.userId!),\n nonce,\n );\n\n const rawParams = {\n state: JSON.stringify({\n nonce,\n redirect_url: redirectUrl,\n access_token_object: {\n object_id: tenantId,\n collection: TENANT_OBJECT_COLLECTION,\n },\n channel_id: knockSlackChannelId,\n public_key: knock.apiKey,\n user_token: knock.userToken,\n branch_slug: knock.branch,\n }),\n client_id: slackClientId,\n scope: combinedScopes.join(\",\"),\n };\n return `${SLACK_AUTHORIZE_URL}?${new URLSearchParams(rawParams)}`;\n }, [\n redirectUrl,\n tenantId,\n knockSlackChannelId,\n knock.apiKey,\n knock.userId,\n knock.userToken,\n knock.branch,\n slackClientId,\n combinedScopes,\n ]);\n\n return {\n buildSlackAuthUrl,\n disconnectFromSlack,\n };\n}\n\nexport default useSlackAuth;\n"],"names":["SLACK_AUTHORIZE_URL","DEFAULT_SLACK_SCOPES","getSlackNonceStorageKey","channelId","userId","normalizeOptions","options","Array","isArray","scopes","additionalScopes","useSlackAuth","slackClientId","redirectUrl","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","from","Set","concat","disconnectFromSlack","useCallback","revoke","slack","revokeAccessToken","tenant","knockChannelId","buildSlackAuthUrl","nonce","crypto","randomUUID","sessionStorage","setItem","rawParams","state","JSON","stringify","redirect_url","access_token_object","object_id","collection","TENANT_OBJECT_COLLECTION","channel_id","public_key","apiKey","user_token","userToken","branch_slug","branch","client_id","scope","join","URLSearchParams"],"mappings":";;;;;;;;;AAMA,MAAMA,IAAsB,wCACtBC,IAAuB,CAC3B,cACA,qBACA,iBACA,aAAa;AAGCC,SAAAA,EACdC,GACAC,GACQ;AACD,SAAA,0BAA0BD,CAAS,IAAIC,CAAM;AACtD;AAiBA,SAASC,EAAiBC,GAGxB;AACA,SAAKA,IAIDC,MAAMC,QAAQF,CAAO,IAChB;AAAA,IAAEG,QAAQR;AAAAA,IAAsBS,kBAAkBJ;AAAAA,EAAQ,IAG5D;AAAA,IACLG,QAAQH,EAAQG,UAAUR;AAAAA,IAC1BS,kBAAkBJ,EAAQI,oBAAoB,CAAA;AAAA,EAChD,IAVS;AAAA,IAAED,QAAQR;AAAAA,IAAsBS,kBAAkB,CAAA;AAAA,EAAG;AAWhE;AAEA,SAASC,EACPC,GACAC,GACAP,GACoB;AACpB,QAAMQ,IAAQC,EAAe,GACvB;AAAA,IAAEC,qBAAAA;AAAAA,IAAqBC,qBAAAA;AAAAA,IAAqBC,UAAAA;AAAAA,IAAUC,gBAAAA;AAAAA,MAC1DC,EAAoB,GAEhB;AAAA,IAAEX,QAAAA;AAAAA,IAAQC,kBAAAA;AAAAA,EAAAA,IAAqBL,EAAiBC,CAAO,GACvDe,IAAiBd,MAAMe,KAAK,IAAIC,IAAId,EAAOe,OAAOd,CAAgB,CAAC,CAAC,GAEpEe,IAAsBC,EAAY,YAAY;AAClDP,IAAAA,EAAe,IAAI,GACnBH,EAAoB,eAAe;AAC/B,QAAA;AACF,YAAMW,IAAS,MAAMb,EAAMc,MAAMC,kBAAkB;AAAA,QACjDC,QAAQZ;AAAAA,QACRa,gBAAgBd;AAAAA,MAAAA,CACjB;AAED,MACED,EADEW,MAAW,OACO,iBAEA,OAFc;AAAA,YAIrB;AACfX,MAAAA,EAAoB,OAAO;AAAA,IAAA;AAAA,EAC7B,GACC,CACDA,GACAF,EAAMc,OACNV,GACAD,GACAE,CAAc,CACf;AAsCM,SAAA;AAAA,IACLa,mBArCwBN,EAAY,MAAM;AACpCO,YAAAA,IAAQC,OAAOC,WAAW;AAChCC,qBAAeC,QACbnC,EAAwBe,GAAqBH,EAAMV,MAAO,GAC1D6B,CACF;AAEA,YAAMK,IAAY;AAAA,QAChBC,OAAOC,KAAKC,UAAU;AAAA,UACpBR,OAAAA;AAAAA,UACAS,cAAc7B;AAAAA,UACd8B,qBAAqB;AAAA,YACnBC,WAAW1B;AAAAA,YACX2B,YAAYC;AAAAA,UACd;AAAA,UACAC,YAAY9B;AAAAA,UACZ+B,YAAYlC,EAAMmC;AAAAA,UAClBC,YAAYpC,EAAMqC;AAAAA,UAClBC,aAAatC,EAAMuC;AAAAA,QAAAA,CACpB;AAAA,QACDC,WAAW1C;AAAAA,QACX2C,OAAOlC,EAAemC,KAAK,GAAG;AAAA,MAChC;AACA,aAAO,GAAGxD,CAAmB,IAAI,IAAIyD,gBAAgBnB,CAAS,CAAC;AAAA,IAAA,GAC9D,CACDzB,GACAK,GACAD,GACAH,EAAMmC,QACNnC,EAAMV,QACNU,EAAMqC,WACNrC,EAAMuC,QACNzC,GACAS,CAAc,CACf;AAAA,IAICI,qBAAAA;AAAAA,EACF;AACF;"}
@@ -2,7 +2,7 @@ export { FilterStatus, KnockProvider, feedProviderKey, type BadgeCountType, form
2
2
  export { KnockFeedProvider, type KnockFeedProviderProps, type KnockFeedProviderState, type Selector, useCreateNotificationStore, useFeedSettings, useKnockFeed, useNotificationStore, useNotifications, } from './modules/feed';
3
3
  export { KnockGuideProvider, KnockGuideContext, type KnockGuideProviderProps, useGuide, useGuides, useGuideContext, } from './modules/guide';
4
4
  export { type MsTeamsChannelQueryOptions, type MsTeamsTeamQueryOptions, KnockMsTeamsProvider, type KnockMsTeamsProviderProps, type KnockMsTeamsProviderState, useConnectedMsTeamsChannels, useKnockMsTeamsClient, useMsTeamsAuth, useMsTeamsChannels, useMsTeamsConnectionStatus, useMsTeamsTeams, } from './modules/ms-teams';
5
- export { KnockSlackProvider, type ContainerObject, type KnockSlackProviderProps, type KnockSlackProviderState, type SlackChannelQueryOptions, useConnectedSlackChannels, useKnockSlackClient, useSlackAuth, useSlackChannels, useSlackConnectionStatus, } from './modules/slack';
5
+ export { KnockSlackProvider, type ContainerObject, type KnockSlackProviderProps, type KnockSlackProviderState, type SlackChannelQueryOptions, getSlackNonceStorageKey, useConnectedSlackChannels, useKnockSlackClient, useSlackAuth, useSlackChannels, useSlackConnectionStatus, } from './modules/slack';
6
6
  export { I18nContext, KnockI18nProvider, locales, type I18nContent, type KnockI18nProviderProps, type Translations, useTranslations, } from './modules/i18n';
7
7
  export { type RecipientObject } from './interfaces';
8
8
  export { usePreferences } from './modules/preferences';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,KAAK,cAAc,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,cAAc,EACd,0BAA0B,EAC1B,cAAc,EACd,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,QAAQ,EACb,0BAA0B,EAC1B,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,QAAQ,EACR,SAAS,EACT,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,2BAA2B,EAC3B,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,yBAAyB,EACzB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,KAAK,cAAc,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,2BAA2B,EAC3B,cAAc,EACd,0BAA0B,EAC1B,cAAc,EACd,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,QAAQ,EACb,0BAA0B,EAC1B,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,QAAQ,EACR,SAAS,EACT,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,2BAA2B,EAC3B,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
@@ -4,12 +4,19 @@ export interface UseAuthPostMessageListenerOptions {
4
4
  popupWindowRef: React.MutableRefObject<Window | null>;
5
5
  setConnectionStatus: (status: ConnectionStatus) => void;
6
6
  onAuthenticationComplete?: (authenticationResp: string) => void;
7
+ /**
8
+ * The sessionStorage key where the CSRF nonce was stored when the auth URL
9
+ * was built. When provided, the listener will verify the nonce returned in
10
+ * the postMessage payload matches the stored value.
11
+ */
12
+ nonceStorageKey?: string;
7
13
  }
8
14
  /**
9
15
  * Hook that listens for postMessage events from OAuth popup windows.
10
16
  *
11
17
  * Handles "authComplete" and "authFailed" messages sent from the OAuth flow popup,
12
- * validates the message origin, updates connection status, and closes the popup.
18
+ * validates the message origin, optionally verifies the CSRF nonce, updates
19
+ * connection status, and closes the popup.
13
20
  *
14
21
  * @param options - Configuration options for the postMessage listener
15
22
  *
@@ -20,6 +27,7 @@ export interface UseAuthPostMessageListenerOptions {
20
27
  * popupWindowRef,
21
28
  * setConnectionStatus,
22
29
  * onAuthenticationComplete,
30
+ * nonceStorageKey: "knock:slack-auth-nonce:channel_123:user_1",
23
31
  * });
24
32
  * ```
25
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthPostMessageListener.d.ts","sourceRoot":"","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,iCAAiC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,mBAAmB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,wBAAwB,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAsCN"}
1
+ {"version":3,"file":"useAuthPostMessageListener.d.ts","sourceRoot":"","sources":["../../../../../src/modules/core/hooks/useAuthPostMessageListener.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,iCAAiC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,mBAAmB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,wBAAwB,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA0BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAmEN"}
@@ -1,5 +1,5 @@
1
1
  export { default as useSlackConnectionStatus } from './useSlackConnectionStatus';
2
2
  export { default as useSlackChannels } from './useSlackChannels';
3
3
  export { default as useConnectedSlackChannels } from './useConnectedSlackChannels';
4
- export { default as useSlackAuth } from './useSlackAuth';
4
+ export { default as useSlackAuth, getSlackNonceStorageKey, } from './useSlackAuth';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/modules/slack/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/modules/slack/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EACL,OAAO,IAAI,YAAY,EACvB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC"}
@@ -1,3 +1,4 @@
1
+ export declare function getSlackNonceStorageKey(channelId: string, userId: string): string;
1
2
  type UseSlackAuthOutput = {
2
3
  buildSlackAuthUrl: () => string;
3
4
  disconnectFromSlack: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useSlackAuth.d.ts","sourceRoot":"","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"names":[],"mappings":"AAcA,KAAK,kBAAkB,GAAG;IACxB,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAEzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAuBF,iBAAS,YAAY,CACnB,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,EAAE,GACvC,kBAAkB,CAiEpB;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"useSlackAuth.d.ts","sourceRoot":"","sources":["../../../../../src/modules/slack/hooks/useSlackAuth.ts"],"names":[],"mappings":"AAcA,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED,KAAK,kBAAkB,GAAG;IACxB,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAEzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAuBF,iBAAS,YAAY,CACnB,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,EAAE,GACvC,kBAAkB,CAyEpB;AAED,eAAe,YAAY,CAAC"}
@@ -1,4 +1,4 @@
1
1
  export { KnockSlackProvider, useKnockSlackClient, type KnockSlackProviderState, type KnockSlackProviderProps, } from './context';
2
- export { useSlackConnectionStatus, useSlackChannels, useConnectedSlackChannels, useSlackAuth, } from './hooks';
2
+ export { useSlackConnectionStatus, useSlackChannels, useConnectedSlackChannels, useSlackAuth, getSlackNonceStorageKey, } from './hooks';
3
3
  export { type ContainerObject, type SlackChannelQueryOptions, } from './interfaces';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/slack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/slack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,GAC7B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@knocklabs/react-core",
3
3
  "description": "A set of React components to build notification experiences powered by Knock",
4
4
  "author": "@knocklabs",
5
- "version": "0.13.8",
5
+ "version": "0.13.10",
6
6
  "license": "MIT",
7
7
  "main": "dist/cjs/index.js",
8
8
  "module": "dist/esm/index.mjs",
@@ -47,7 +47,7 @@
47
47
  "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
48
48
  },
49
49
  "dependencies": {
50
- "@knocklabs/client": "^0.21.8",
50
+ "@knocklabs/client": "^0.21.9",
51
51
  "@tanstack/react-store": "^0.7.3",
52
52
  "date-fns": "^4.0.0",
53
53
  "fast-deep-equal": "^3.1.3",
@@ -57,18 +57,18 @@
57
57
  "@codecov/vite-plugin": "^1.9.1",
58
58
  "@testing-library/dom": "^10.4.1",
59
59
  "@testing-library/react": "^16.3.2",
60
- "@types/react": "^19.1.8",
60
+ "@types/react": "^19.2.14",
61
61
  "@types/react-dom": "^19.1.6",
62
62
  "@typescript-eslint/eslint-plugin": "^8.32.0",
63
- "@typescript-eslint/parser": "^8.39.1",
63
+ "@typescript-eslint/parser": "^8.58.0",
64
64
  "@vitejs/plugin-react": "^4.5.1",
65
65
  "babel-plugin-react-require": "^4.0.3",
66
66
  "eslint": "^8.56.0",
67
67
  "eslint-plugin-react-hooks": "^5.2.0",
68
68
  "eslint-plugin-react-refresh": "^0.5.2",
69
69
  "jsdom": "^27.1.0",
70
- "react": "^19.0.0",
71
- "react-dom": "^19.0.0",
70
+ "react": "^19.2.5",
71
+ "react-dom": "^19.2.5",
72
72
  "rimraf": "^6.0.1",
73
73
  "rollup-plugin-execute": "^1.1.1",
74
74
  "typescript": "^5.8.3",
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  type KnockSlackProviderProps,
60
60
  type KnockSlackProviderState,
61
61
  type SlackChannelQueryOptions,
62
+ getSlackNonceStorageKey,
62
63
  useConnectedSlackChannels,
63
64
  useKnockSlackClient,
64
65
  useSlackAuth,
@@ -7,13 +7,44 @@ export interface UseAuthPostMessageListenerOptions {
7
7
  popupWindowRef: React.MutableRefObject<Window | null>;
8
8
  setConnectionStatus: (status: ConnectionStatus) => void;
9
9
  onAuthenticationComplete?: (authenticationResp: string) => void;
10
+ /**
11
+ * The sessionStorage key where the CSRF nonce was stored when the auth URL
12
+ * was built. When provided, the listener will verify the nonce returned in
13
+ * the postMessage payload matches the stored value.
14
+ */
15
+ nonceStorageKey?: string;
16
+ }
17
+
18
+ /**
19
+ * Extracts the message type from a postMessage event data payload.
20
+ * Supports both the legacy string format ("authComplete") and the new
21
+ * structured format ({ type: "authComplete", nonce: "..." }).
22
+ */
23
+ function getMessageType(data: unknown): string {
24
+ if (typeof data === "object" && data !== null && "type" in data) {
25
+ return (data as { type: string }).type;
26
+ }
27
+ return data as string;
28
+ }
29
+
30
+ /**
31
+ * Extracts the nonce from a structured postMessage event data payload.
32
+ * Returns undefined for legacy string-format messages.
33
+ */
34
+ function getMessageNonce(data: unknown): string | undefined {
35
+ if (typeof data === "object" && data !== null && "nonce" in data) {
36
+ const nonce = (data as { nonce: unknown }).nonce;
37
+ return typeof nonce === "string" ? nonce : undefined;
38
+ }
39
+ return undefined;
10
40
  }
11
41
 
12
42
  /**
13
43
  * Hook that listens for postMessage events from OAuth popup windows.
14
44
  *
15
45
  * Handles "authComplete" and "authFailed" messages sent from the OAuth flow popup,
16
- * validates the message origin, updates connection status, and closes the popup.
46
+ * validates the message origin, optionally verifies the CSRF nonce, updates
47
+ * connection status, and closes the popup.
17
48
  *
18
49
  * @param options - Configuration options for the postMessage listener
19
50
  *
@@ -24,6 +55,7 @@ export interface UseAuthPostMessageListenerOptions {
24
55
  * popupWindowRef,
25
56
  * setConnectionStatus,
26
57
  * onAuthenticationComplete,
58
+ * nonceStorageKey: "knock:slack-auth-nonce:channel_123:user_1",
27
59
  * });
28
60
  * ```
29
61
  */
@@ -35,26 +67,54 @@ export function useAuthPostMessageListener(
35
67
  popupWindowRef,
36
68
  setConnectionStatus,
37
69
  onAuthenticationComplete,
70
+ nonceStorageKey,
38
71
  } = options;
39
72
 
40
73
  useEffect(() => {
74
+ const closePopup = () => {
75
+ if (popupWindowRef.current && !popupWindowRef.current.closed) {
76
+ popupWindowRef.current.close();
77
+ }
78
+ popupWindowRef.current = null;
79
+ };
80
+
41
81
  const receiveMessage = (event: MessageEvent) => {
42
82
  // Validate message origin for security
43
83
  if (event.origin !== knockHost) {
44
84
  return;
45
85
  }
46
86
 
47
- if (event.data === "authComplete") {
87
+ const messageType = getMessageType(event.data);
88
+
89
+ if (messageType === "authComplete") {
90
+ // Verify CSRF nonce when a nonceStorageKey is configured.
91
+ if (nonceStorageKey) {
92
+ const returnedNonce = getMessageNonce(event.data);
93
+ const storedNonce = sessionStorage.getItem(nonceStorageKey);
94
+ sessionStorage.removeItem(nonceStorageKey);
95
+
96
+ // If nonce already consumed by a prior handler invocation, then bail
97
+ // out from checking again.
98
+ if (
99
+ !returnedNonce ||
100
+ (storedNonce && storedNonce !== returnedNonce)
101
+ ) {
102
+ setConnectionStatus("error");
103
+ onAuthenticationComplete?.("authFailed");
104
+ closePopup();
105
+ return;
106
+ }
107
+ }
108
+
48
109
  setConnectionStatus("connected");
49
- onAuthenticationComplete?.(event.data);
50
- // Clear popup ref so polling stops and doesn't trigger callback again
51
- if (popupWindowRef.current && !popupWindowRef.current.closed) {
52
- popupWindowRef.current.close();
110
+ onAuthenticationComplete?.(messageType);
111
+ closePopup();
112
+ } else if (messageType === "authFailed") {
113
+ if (nonceStorageKey) {
114
+ sessionStorage.removeItem(nonceStorageKey);
53
115
  }
54
- popupWindowRef.current = null;
55
- } else if (event.data === "authFailed") {
56
116
  setConnectionStatus("error");
57
- onAuthenticationComplete?.(event.data);
117
+ onAuthenticationComplete?.(messageType);
58
118
  popupWindowRef.current = null;
59
119
  }
60
120
  };
@@ -66,5 +126,6 @@ export function useAuthPostMessageListener(
66
126
  onAuthenticationComplete,
67
127
  setConnectionStatus,
68
128
  popupWindowRef,
129
+ nonceStorageKey,
69
130
  ]);
70
131
  }
@@ -1,4 +1,7 @@
1
1
  export { default as useSlackConnectionStatus } from "./useSlackConnectionStatus";
2
2
  export { default as useSlackChannels } from "./useSlackChannels";
3
3
  export { default as useConnectedSlackChannels } from "./useConnectedSlackChannels";
4
- export { default as useSlackAuth } from "./useSlackAuth";
4
+ export {
5
+ default as useSlackAuth,
6
+ getSlackNonceStorageKey,
7
+ } from "./useSlackAuth";
@@ -12,6 +12,13 @@ const DEFAULT_SLACK_SCOPES = [
12
12
  "groups:read",
13
13
  ];
14
14
 
15
+ export function getSlackNonceStorageKey(
16
+ channelId: string,
17
+ userId: string,
18
+ ): string {
19
+ return `knock:slack-auth-nonce:${channelId}:${userId}`;
20
+ }
21
+
15
22
  type UseSlackAuthOutput = {
16
23
  buildSlackAuthUrl: () => string;
17
24
  disconnectFromSlack: () => void;
@@ -83,8 +90,15 @@ function useSlackAuth(
83
90
  ]);
84
91
 
85
92
  const buildSlackAuthUrl = useCallback(() => {
93
+ const nonce = crypto.randomUUID();
94
+ sessionStorage.setItem(
95
+ getSlackNonceStorageKey(knockSlackChannelId, knock.userId!),
96
+ nonce,
97
+ );
98
+
86
99
  const rawParams = {
87
100
  state: JSON.stringify({
101
+ nonce,
88
102
  redirect_url: redirectUrl,
89
103
  access_token_object: {
90
104
  object_id: tenantId,
@@ -104,6 +118,7 @@ function useSlackAuth(
104
118
  tenantId,
105
119
  knockSlackChannelId,
106
120
  knock.apiKey,
121
+ knock.userId,
107
122
  knock.userToken,
108
123
  knock.branch,
109
124
  slackClientId,
@@ -9,6 +9,7 @@ export {
9
9
  useSlackChannels,
10
10
  useConnectedSlackChannels,
11
11
  useSlackAuth,
12
+ getSlackNonceStorageKey,
12
13
  } from "./hooks";
13
14
  export {
14
15
  type ContainerObject,