@knocklabs/react-core 0.6.9 → 0.6.11

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.11
4
+
5
+ ### Patch Changes
6
+
7
+ - dbbbaf7: Dispose of feed on unmount in `useNotifications` hook
8
+
9
+ Previously, the `useNotifications` hook did not clean up old instances of `Feed`
10
+ on unmount. This has been fixed.
11
+
12
+ ## 0.6.10
13
+
14
+ ### Patch Changes
15
+
16
+ - 337bade: feat: introduce ability to override slack scopes
17
+
3
18
  ## 0.6.9
4
19
 
5
20
  ### Patch Changes
@@ -1,2 +1,2 @@
1
- "use strict";const i=require("react");require("../../core/context/KnockProvider.js");require("@knocklabs/client");const o=require("../../core/hooks/useStableOptions.js");require("date-fns");function a(r,t,n={}){const e=i.useRef(),s=o(n);return i.useMemo(()=>(e.current&&e.current.dispose(),e.current=r.feeds.initialize(t,s),e.current.store.subscribe(c=>{var u;return(u=e==null?void 0:e.current)==null?void 0:u.store.setState(c)}),e.current.listenForUpdates(),e.current),[r,t,s])}module.exports=a;
1
+ "use strict";const n=require("react");require("../../core/context/KnockProvider.js");require("@knocklabs/client");const p=require("../../core/hooks/useStableOptions.js");require("date-fns");function b(c,s,l={}){const r=n.useCallback((u,f)=>{const i=c.feeds.initialize(u,f);return i.store.subscribe(d=>i.store.setState(d)),i.listenForUpdates(),i},[c]),e=p(l),[t,a]=n.useState(()=>({feedClient:r(s,e),options:e})),o=n.useRef(!1);return n.useEffect(()=>{const u=o.current;if(t.feedClient.feedId!==s||t.options!==e||u){o.current=!1,a({feedClient:r(s,e),options:e});return}return()=>{o.current=!0,t.feedClient.dispose()}},[r,s,e,t]),t.feedClient}module.exports=b;
2
2
  //# sourceMappingURL=useNotifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNotifications.js","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"sourcesContent":["import Knock, { Feed, FeedClientOptions } from \"@knocklabs/client\";\nimport { useMemo, useRef } from \"react\";\n\nimport { useStableOptions } from \"../../core\";\n\nfunction useNotifications(\n knock: Knock,\n feedChannelId: string,\n options: FeedClientOptions = {},\n) {\n const feedClientRef = useRef<Feed>();\n const stableOptions = useStableOptions(options);\n\n return useMemo(() => {\n if (feedClientRef.current) {\n feedClientRef.current.dispose();\n }\n\n feedClientRef.current = knock.feeds.initialize(\n feedChannelId,\n stableOptions,\n );\n\n // In development, we need to introduce this extra set state to force a render\n // for Zustand as otherwise the state doesn't get reflected correctly\n feedClientRef.current.store.subscribe((t) =>\n feedClientRef?.current?.store.setState(t),\n );\n\n feedClientRef.current.listenForUpdates();\n\n return feedClientRef.current;\n }, [knock, feedChannelId, stableOptions]);\n}\n\nexport default useNotifications;\n"],"names":["useNotifications","knock","feedChannelId","options","feedClientRef","useRef","stableOptions","useStableOptions","useMemo","current","dispose","feeds","initialize","store","subscribe","t","setState","listenForUpdates"],"mappings":"8LAKA,SAASA,EACPC,EACAC,EACAC,EAA6B,CAAA,EAC7B,CACA,MAAMC,EAAgBC,EAAAA,OAAa,EAC7BC,EAAgBC,EAAiBJ,CAAO,EAE9C,OAAOK,UAAQ,KACTJ,EAAcK,SAChBL,EAAcK,QAAQC,QAAQ,EAGhCN,EAAcK,QAAUR,EAAMU,MAAMC,WAClCV,EACAI,CACF,EAIcG,EAAAA,QAAQI,MAAMC,UAAWC,UACrCX,OAAAA,EAAAA,GAAAA,YAAAA,EAAeK,UAAfL,YAAAA,EAAwBS,MAAMG,SAASD,GACzC,EAEAX,EAAcK,QAAQQ,iBAAiB,EAEhCb,EAAcK,SACpB,CAACR,EAAOC,EAAeI,CAAa,CAAC,CAC1C"}
1
+ {"version":3,"file":"useNotifications.js","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"sourcesContent":["import Knock, { Feed, FeedClientOptions } from \"@knocklabs/client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useStableOptions } from \"../../core\";\n\ninterface State {\n feedClient: Feed;\n options: FeedClientOptions;\n}\n\nfunction useNotifications(\n knock: Knock,\n feedChannelId: string,\n options: FeedClientOptions = {},\n): Feed {\n const initFeedClient = useCallback(\n (feedChannelId: string, options: FeedClientOptions) => {\n const feedClient = knock.feeds.initialize(feedChannelId, options);\n\n // In development, we need to introduce this extra set state to force a render\n // for Zustand as otherwise the state doesn't get reflected correctly\n feedClient.store.subscribe((t) => feedClient.store.setState(t));\n\n feedClient.listenForUpdates();\n\n return feedClient;\n },\n [knock],\n );\n\n const stableOptions = useStableOptions(options);\n const [state, setState] = useState<State>(() => ({\n // FIXME In strict mode, useState initializer functions are called twice,\n // which results in one extra instance of the feed client being created\n // and not disposed of. This only affects strict mode.\n feedClient: initFeedClient(feedChannelId, stableOptions),\n options: stableOptions,\n }));\n const disposedRef = useRef(false);\n\n useEffect(() => {\n const isDisposed = disposedRef.current;\n\n // Initialize a new feed client if the feed ID has changed,\n // options have changed, or the current feed client has been disposed\n const needsReinit =\n state.feedClient.feedId !== feedChannelId ||\n state.options !== stableOptions ||\n isDisposed;\n\n if (needsReinit) {\n disposedRef.current = false;\n setState({\n feedClient: initFeedClient(feedChannelId, stableOptions),\n options: stableOptions,\n });\n return;\n }\n\n return () => {\n disposedRef.current = true;\n state.feedClient.dispose();\n };\n }, [initFeedClient, feedChannelId, stableOptions, state]);\n\n return state.feedClient;\n}\n\nexport default useNotifications;\n"],"names":["useNotifications","knock","feedChannelId","options","initFeedClient","useCallback","feedClient","feeds","initialize","store","subscribe","t","setState","listenForUpdates","stableOptions","useStableOptions","state","useState","disposedRef","useRef","useEffect","isDisposed","current","feedId","dispose"],"mappings":"8LAUA,SAASA,EACPC,EACAC,EACAC,EAA6B,CAAA,EACvB,CACN,MAAMC,EAAiBC,EAAAA,YACrB,CAACH,EAAuBC,IAA+B,CACrD,MAAMG,EAAaL,EAAMM,MAAMC,WAAWN,EAAeC,CAAO,EAIhEG,OAAAA,EAAWG,MAAMC,UAAWC,GAAML,EAAWG,MAAMG,SAASD,CAAC,CAAC,EAE9DL,EAAWO,iBAAiB,EAErBP,CAAAA,EAET,CAACL,CAAK,CACR,EAEMa,EAAgBC,EAAiBZ,CAAO,EACxC,CAACa,EAAOJ,CAAQ,EAAIK,WAAgB,KAAO,CAI/CX,WAAYF,EAAeF,EAAeY,CAAa,EACvDX,QAASW,CAAAA,EACT,EACII,EAAcC,SAAO,EAAK,EAEhCC,OAAAA,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAaH,EAAYI,QAS/B,GAJEN,EAAMV,WAAWiB,SAAWrB,GAC5Bc,EAAMb,UAAYW,GAClBO,EAEe,CACfH,EAAYI,QAAU,GACbV,EAAA,CACPN,WAAYF,EAAeF,EAAeY,CAAa,EACvDX,QAASW,CAAAA,CACV,EACD,MAAA,CAGF,MAAO,IAAM,CACXI,EAAYI,QAAU,GACtBN,EAAMV,WAAWkB,QAAQ,CAC3B,GACC,CAACpB,EAAgBF,EAAeY,EAAeE,CAAK,CAAC,EAEjDA,EAAMV,UACf"}
@@ -1,2 +1,2 @@
1
- "use strict";const _=require("../context/KnockSlackProvider.js"),k=require("react");require("../../i18n/context/KnockI18nProvider.js");require("swr/infinite");const S=require("../../core/context/KnockProvider.js"),d=require("@knocklabs/client");require("fast-deep-equal");require("date-fns");require("swr");const C="https://slack.com/oauth/v2/authorize",l=["chat:write","chat:write.public","channels:read","groups:read"];function A(s,a,o){const e=S.useKnockClient(),{setConnectionStatus:c,knockSlackChannelId:n,tenantId:r,setActionLabel:i}=_.useKnockSlackClient(),u=o&&o.length>0?Array.from(new Set(l.concat(o))):l,h=k.useCallback(async()=>{i(null),c("disconnecting");try{const t=await e.slack.revokeAccessToken({tenant:r,knockChannelId:n});c(t==="ok"?"disconnected":"error")}catch{c("error")}},[c,e.slack,r,n,i]);return{buildSlackAuthUrl:k.useCallback(()=>{const t={state:JSON.stringify({redirect_url:a,access_token_object:{object_id:r,collection:d.TENANT_OBJECT_COLLECTION},channel_id:n,public_key:e.apiKey,user_token:e.userToken}),client_id:s,scope:u.join(",")};return`${C}?${new URLSearchParams(t)}`},[a,r,n,e.apiKey,e.userToken,s,u]),disconnectFromSlack:h}}module.exports=A;
1
+ "use strict";const _=require("../context/KnockSlackProvider.js"),l=require("react");require("../../i18n/context/KnockI18nProvider.js");require("swr/infinite");const h=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 b(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=h.useKnockClient(),{setConnectionStatus:n,knockSlackChannelId:r,tenantId:o,setActionLabel:i}=_.useKnockSlackClient(),{scopes:d,additionalScopes:S}=b(k),u=Array.from(new Set(d.concat(S))),p=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:A.TENANT_OBJECT_COLLECTION},channel_id:r,public_key:c.apiKey,user_token:c.userToken}),client_id:e,scope:u.join(",")};return`${C}?${new URLSearchParams(t)}`},[a,o,r,c.apiKey,c.userToken,e,u]),disconnectFromSlack:p}}module.exports=q;
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\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n additionalScopes?: string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const combinedScopes =\n additionalScopes && additionalScopes.length > 0\n ? Array.from(new Set(DEFAULT_SLACK_SCOPES.concat(additionalScopes)))\n : DEFAULT_SLACK_SCOPES;\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 }),\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 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","useSlackAuth","slackClientId","redirectUrl","additionalScopes","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","length","Array","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","client_id","scope","join","URLSearchParams"],"mappings":"mTAMA,MAAMA,EAAsB,uCACtBC,EAAuB,CAC3B,aACA,oBACA,gBACA,aAAa,EAQf,SAASC,EACPC,EACAC,EACAC,EACoB,CACpB,MAAMC,EAAQC,EAAAA,eAAe,EACvB,CAAEC,oBAAAA,EAAqBC,oBAAAA,EAAqBC,SAAAA,EAAUC,eAAAA,GAC1DC,sBAAoB,EAEhBC,EACJR,GAAoBA,EAAiBS,OAAS,EAC1CC,MAAMC,KAAK,IAAIC,IAAIhB,EAAqBiB,OAAOb,CAAgB,CAAC,CAAC,EACjEJ,EAEAkB,EAAsBC,EAAAA,YAAY,SAAY,CAClDT,EAAe,IAAI,EACnBH,EAAoB,eAAe,EAC/B,GAAA,CACF,MAAMa,EAAS,MAAMf,EAAMgB,MAAMC,kBAAkB,CACjDC,OAAQd,EACRe,eAAgBhB,CAAAA,CACjB,EAGCD,EADEa,IAAW,KACO,eAEA,OAFc,OAIrB,CACfb,EAAoB,OAAO,CAAA,CAC7B,EACC,CACDA,EACAF,EAAMgB,MACNZ,EACAD,EACAE,CAAc,CACf,EA4BM,MAAA,CACLe,kBA3BwBN,EAAAA,YAAY,IAAM,CAC1C,MAAMO,EAAY,CAChBC,MAAOC,KAAKC,UAAU,CACpBC,aAAc3B,EACd4B,oBAAqB,CACnBC,UAAWvB,EACXwB,WAAYC,EAAAA,wBACd,EACAC,WAAY3B,EACZ4B,WAAY/B,EAAMgC,OAClBC,WAAYjC,EAAMkC,SAAAA,CACnB,EACDC,UAAWtC,EACXuC,MAAO7B,EAAe8B,KAAK,GAAG,CAChC,EACA,MAAO,GAAG3C,CAAmB,IAAI,IAAI4C,gBAAgBjB,CAAS,CAAC,EACjE,EAAG,CACDvB,EACAM,EACAD,EACAH,EAAMgC,OACNhC,EAAMkC,UACNrC,EACAU,CAAc,CACf,EAICM,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\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 }),\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 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","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,EA4BM,MAAA,CACLa,kBA3BwBN,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,SAAAA,CACnB,EACDC,UAAWnC,EACXoC,MAAO3B,EAAe4B,KAAK,GAAG,CAChC,EACA,MAAO,GAAG9C,CAAmB,IAAI,IAAI+C,gBAAgBjB,CAAS,CAAC,EACjE,EAAG,CACDpB,EACAK,EACAD,EACAH,EAAM8B,OACN9B,EAAMgC,UACNlC,EACAS,CAAc,CACf,EAICI,oBAAAA,CACF,CACF"}
@@ -1,16 +1,34 @@
1
- import { useRef as n, useMemo as c } from "react";
1
+ import { useCallback as a, useState as d, useRef as m, useEffect as b } from "react";
2
2
  import "../../core/context/KnockProvider.mjs";
3
3
  import "@knocklabs/client";
4
- import p from "../../core/hooks/useStableOptions.mjs";
4
+ import C from "../../core/hooks/useStableOptions.mjs";
5
5
  import "date-fns";
6
- function O(r, e, i = {}) {
7
- const t = n(), s = p(i);
8
- return c(() => (t.current && t.current.dispose(), t.current = r.feeds.initialize(e, s), t.current.store.subscribe((u) => {
9
- var o;
10
- return (o = t == null ? void 0 : t.current) == null ? void 0 : o.store.setState(u);
11
- }), t.current.listenForUpdates(), t.current), [r, e, s]);
6
+ function z(f, s, p = {}) {
7
+ const o = a((r, u) => {
8
+ const i = f.feeds.initialize(r, u);
9
+ return i.store.subscribe((l) => i.store.setState(l)), i.listenForUpdates(), i;
10
+ }, [f]), e = C(p), [t, c] = d(() => ({
11
+ // FIXME In strict mode, useState initializer functions are called twice,
12
+ // which results in one extra instance of the feed client being created
13
+ // and not disposed of. This only affects strict mode.
14
+ feedClient: o(s, e),
15
+ options: e
16
+ })), n = m(!1);
17
+ return b(() => {
18
+ const r = n.current;
19
+ if (t.feedClient.feedId !== s || t.options !== e || r) {
20
+ n.current = !1, c({
21
+ feedClient: o(s, e),
22
+ options: e
23
+ });
24
+ return;
25
+ }
26
+ return () => {
27
+ n.current = !0, t.feedClient.dispose();
28
+ };
29
+ }, [o, s, e, t]), t.feedClient;
12
30
  }
13
31
  export {
14
- O as default
32
+ z as default
15
33
  };
16
34
  //# sourceMappingURL=useNotifications.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNotifications.mjs","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"sourcesContent":["import Knock, { Feed, FeedClientOptions } from \"@knocklabs/client\";\nimport { useMemo, useRef } from \"react\";\n\nimport { useStableOptions } from \"../../core\";\n\nfunction useNotifications(\n knock: Knock,\n feedChannelId: string,\n options: FeedClientOptions = {},\n) {\n const feedClientRef = useRef<Feed>();\n const stableOptions = useStableOptions(options);\n\n return useMemo(() => {\n if (feedClientRef.current) {\n feedClientRef.current.dispose();\n }\n\n feedClientRef.current = knock.feeds.initialize(\n feedChannelId,\n stableOptions,\n );\n\n // In development, we need to introduce this extra set state to force a render\n // for Zustand as otherwise the state doesn't get reflected correctly\n feedClientRef.current.store.subscribe((t) =>\n feedClientRef?.current?.store.setState(t),\n );\n\n feedClientRef.current.listenForUpdates();\n\n return feedClientRef.current;\n }, [knock, feedChannelId, stableOptions]);\n}\n\nexport default useNotifications;\n"],"names":["useNotifications","knock","feedChannelId","options","feedClientRef","useRef","stableOptions","useStableOptions","useMemo","current","dispose","feeds","initialize","store","subscribe","t","setState","listenForUpdates"],"mappings":";;;;;AAKA,SAASA,EACPC,GACAC,GACAC,IAA6B,CAAA,GAC7B;AACA,QAAMC,IAAgBC,EAAa,GAC7BC,IAAgBC,EAAiBJ,CAAO;AAE9C,SAAOK,EAAQ,OACTJ,EAAcK,WAChBL,EAAcK,QAAQC,QAAQ,GAGhCN,EAAcK,UAAUR,EAAMU,MAAMC,WAClCV,GACAI,CACF,GAIcG,EAAAA,QAAQI,MAAMC,UAAWC,CAAAA;;AACrCX,YAAAA,IAAAA,KAAAA,gBAAAA,EAAeK,YAAfL,gBAAAA,EAAwBS,MAAMG,SAASD;AAAAA,GACzC,GAEAX,EAAcK,QAAQQ,iBAAiB,GAEhCb,EAAcK,UACpB,CAACR,GAAOC,GAAeI,CAAa,CAAC;AAC1C;"}
1
+ {"version":3,"file":"useNotifications.mjs","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"sourcesContent":["import Knock, { Feed, FeedClientOptions } from \"@knocklabs/client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useStableOptions } from \"../../core\";\n\ninterface State {\n feedClient: Feed;\n options: FeedClientOptions;\n}\n\nfunction useNotifications(\n knock: Knock,\n feedChannelId: string,\n options: FeedClientOptions = {},\n): Feed {\n const initFeedClient = useCallback(\n (feedChannelId: string, options: FeedClientOptions) => {\n const feedClient = knock.feeds.initialize(feedChannelId, options);\n\n // In development, we need to introduce this extra set state to force a render\n // for Zustand as otherwise the state doesn't get reflected correctly\n feedClient.store.subscribe((t) => feedClient.store.setState(t));\n\n feedClient.listenForUpdates();\n\n return feedClient;\n },\n [knock],\n );\n\n const stableOptions = useStableOptions(options);\n const [state, setState] = useState<State>(() => ({\n // FIXME In strict mode, useState initializer functions are called twice,\n // which results in one extra instance of the feed client being created\n // and not disposed of. This only affects strict mode.\n feedClient: initFeedClient(feedChannelId, stableOptions),\n options: stableOptions,\n }));\n const disposedRef = useRef(false);\n\n useEffect(() => {\n const isDisposed = disposedRef.current;\n\n // Initialize a new feed client if the feed ID has changed,\n // options have changed, or the current feed client has been disposed\n const needsReinit =\n state.feedClient.feedId !== feedChannelId ||\n state.options !== stableOptions ||\n isDisposed;\n\n if (needsReinit) {\n disposedRef.current = false;\n setState({\n feedClient: initFeedClient(feedChannelId, stableOptions),\n options: stableOptions,\n });\n return;\n }\n\n return () => {\n disposedRef.current = true;\n state.feedClient.dispose();\n };\n }, [initFeedClient, feedChannelId, stableOptions, state]);\n\n return state.feedClient;\n}\n\nexport default useNotifications;\n"],"names":["useNotifications","knock","feedChannelId","options","initFeedClient","useCallback","feedClient","feeds","initialize","store","subscribe","t","setState","listenForUpdates","stableOptions","useStableOptions","state","useState","disposedRef","useRef","useEffect","isDisposed","current","feedId","dispose"],"mappings":";;;;;AAUA,SAASA,EACPC,GACAC,GACAC,IAA6B,CAAA,GACvB;AACN,QAAMC,IAAiBC,EACrB,CAACH,GAAuBC,MAA+B;AACrD,UAAMG,IAAaL,EAAMM,MAAMC,WAAWN,GAAeC,CAAO;AAIhEG,WAAAA,EAAWG,MAAMC,UAAWC,CAAAA,MAAML,EAAWG,MAAMG,SAASD,CAAC,CAAC,GAE9DL,EAAWO,iBAAiB,GAErBP;AAAAA,EAAAA,GAET,CAACL,CAAK,CACR,GAEMa,IAAgBC,EAAiBZ,CAAO,GACxC,CAACa,GAAOJ,CAAQ,IAAIK,EAAgB,OAAO;AAAA;AAAA;AAAA;AAAA,IAI/CX,YAAYF,EAAeF,GAAeY,CAAa;AAAA,IACvDX,SAASW;AAAAA,EAAAA,EACT,GACII,IAAcC,EAAO,EAAK;AAEhCC,SAAAA,EAAU,MAAM;AACd,UAAMC,IAAaH,EAAYI;AAS/B,QAJEN,EAAMV,WAAWiB,WAAWrB,KAC5Bc,EAAMb,YAAYW,KAClBO,GAEe;AACfH,MAAAA,EAAYI,UAAU,IACbV,EAAA;AAAA,QACPN,YAAYF,EAAeF,GAAeY,CAAa;AAAA,QACvDX,SAASW;AAAAA,MAAAA,CACV;AACD;AAAA,IAAA;AAGF,WAAO,MAAM;AACXI,MAAAA,EAAYI,UAAU,IACtBN,EAAMV,WAAWkB,QAAQ;AAAA,IAC3B;AAAA,KACC,CAACpB,GAAgBF,GAAeY,GAAeE,CAAK,CAAC,GAEjDA,EAAMV;AACf;"}
@@ -1,53 +1,68 @@
1
- import { useKnockSlackClient as p } from "../context/KnockSlackProvider.mjs";
2
- import { useCallback as k } from "react";
1
+ import { useKnockSlackClient as S } from "../context/KnockSlackProvider.mjs";
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 h } from "@knocklabs/client";
7
7
  import "fast-deep-equal";
8
8
  import "date-fns";
9
9
  import "swr";
10
- const S = "https://slack.com/oauth/v2/authorize", u = ["chat:write", "chat:write.public", "channels:read", "groups:read"];
11
- function K(a, s, r) {
12
- const e = h(), {
13
- setConnectionStatus: t,
14
- knockSlackChannelId: o,
10
+ const A = "https://slack.com/oauth/v2/authorize", a = ["chat:write", "chat:write.public", "channels:read", "groups:read"];
11
+ function f(e) {
12
+ return e ? Array.isArray(e) ? {
13
+ scopes: a,
14
+ additionalScopes: e
15
+ } : {
16
+ scopes: e.scopes ?? a,
17
+ additionalScopes: e.additionalScopes ?? []
18
+ } : {
19
+ scopes: a,
20
+ additionalScopes: []
21
+ };
22
+ }
23
+ function I(e, s, k) {
24
+ const o = _(), {
25
+ setConnectionStatus: c,
26
+ knockSlackChannelId: t,
15
27
  tenantId: n,
16
28
  setActionLabel: i
17
- } = p(), l = r && r.length > 0 ? Array.from(new Set(u.concat(r))) : u, m = k(async () => {
18
- i(null), t("disconnecting");
29
+ } = S(), {
30
+ scopes: d,
31
+ additionalScopes: p
32
+ } = f(k), l = Array.from(new Set(d.concat(p))), m = u(async () => {
33
+ i(null), c("disconnecting");
19
34
  try {
20
- const c = await e.slack.revokeAccessToken({
35
+ const r = await o.slack.revokeAccessToken({
21
36
  tenant: n,
22
- knockChannelId: o
37
+ knockChannelId: t
23
38
  });
24
- t(c === "ok" ? "disconnected" : "error");
39
+ c(r === "ok" ? "disconnected" : "error");
25
40
  } catch {
26
- t("error");
41
+ c("error");
27
42
  }
28
- }, [t, e.slack, n, o, i]);
43
+ }, [c, o.slack, n, t, i]);
29
44
  return {
30
- buildSlackAuthUrl: k(() => {
31
- const c = {
45
+ buildSlackAuthUrl: u(() => {
46
+ const r = {
32
47
  state: JSON.stringify({
33
48
  redirect_url: s,
34
49
  access_token_object: {
35
50
  object_id: n,
36
- collection: _
51
+ collection: h
37
52
  },
38
- channel_id: o,
39
- public_key: e.apiKey,
40
- user_token: e.userToken
53
+ channel_id: t,
54
+ public_key: o.apiKey,
55
+ user_token: o.userToken
41
56
  }),
42
- client_id: a,
57
+ client_id: e,
43
58
  scope: l.join(",")
44
59
  };
45
- return `${S}?${new URLSearchParams(c)}`;
46
- }, [s, n, o, e.apiKey, e.userToken, a, l]),
60
+ return `${A}?${new URLSearchParams(r)}`;
61
+ }, [s, n, t, o.apiKey, o.userToken, e, l]),
47
62
  disconnectFromSlack: m
48
63
  };
49
64
  }
50
65
  export {
51
- K as default
66
+ I as default
52
67
  };
53
68
  //# 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\nfunction useSlackAuth(\n slackClientId: string,\n redirectUrl?: string,\n additionalScopes?: string[],\n): UseSlackAuthOutput {\n const knock = useKnockClient();\n const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =\n useKnockSlackClient();\n\n const combinedScopes =\n additionalScopes && additionalScopes.length > 0\n ? Array.from(new Set(DEFAULT_SLACK_SCOPES.concat(additionalScopes)))\n : DEFAULT_SLACK_SCOPES;\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 }),\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 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","useSlackAuth","slackClientId","redirectUrl","additionalScopes","knock","useKnockClient","setConnectionStatus","knockSlackChannelId","tenantId","setActionLabel","useKnockSlackClient","combinedScopes","length","Array","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","client_id","scope","join","URLSearchParams"],"mappings":";;;;;;;;;AAMA,MAAMA,IAAsB,wCACtBC,IAAuB,CAC3B,cACA,qBACA,iBACA,aAAa;AAQf,SAASC,EACPC,GACAC,GACAC,GACoB;AACpB,QAAMC,IAAQC,EAAe,GACvB;AAAA,IAAEC,qBAAAA;AAAAA,IAAqBC,qBAAAA;AAAAA,IAAqBC,UAAAA;AAAAA,IAAUC,gBAAAA;AAAAA,MAC1DC,EAAoB,GAEhBC,IACJR,KAAoBA,EAAiBS,SAAS,IAC1CC,MAAMC,KAAK,IAAIC,IAAIhB,EAAqBiB,OAAOb,CAAgB,CAAC,CAAC,IACjEJ,GAEAkB,IAAsBC,EAAY,YAAY;AAClDT,IAAAA,EAAe,IAAI,GACnBH,EAAoB,eAAe;AAC/B,QAAA;AACF,YAAMa,IAAS,MAAMf,EAAMgB,MAAMC,kBAAkB;AAAA,QACjDC,QAAQd;AAAAA,QACRe,gBAAgBhB;AAAAA,MAAAA,CACjB;AAED,MACED,EADEa,MAAW,OACO,iBAEA,OAFc;AAAA,YAIrB;AACfb,MAAAA,EAAoB,OAAO;AAAA,IAAA;AAAA,EAC7B,GACC,CACDA,GACAF,EAAMgB,OACNZ,GACAD,GACAE,CAAc,CACf;AA4BM,SAAA;AAAA,IACLe,mBA3BwBN,EAAY,MAAM;AAC1C,YAAMO,IAAY;AAAA,QAChBC,OAAOC,KAAKC,UAAU;AAAA,UACpBC,cAAc3B;AAAAA,UACd4B,qBAAqB;AAAA,YACnBC,WAAWvB;AAAAA,YACXwB,YAAYC;AAAAA,UACd;AAAA,UACAC,YAAY3B;AAAAA,UACZ4B,YAAY/B,EAAMgC;AAAAA,UAClBC,YAAYjC,EAAMkC;AAAAA,QAAAA,CACnB;AAAA,QACDC,WAAWtC;AAAAA,QACXuC,OAAO7B,EAAe8B,KAAK,GAAG;AAAA,MAChC;AACA,aAAO,GAAG3C,CAAmB,IAAI,IAAI4C,gBAAgBjB,CAAS,CAAC;AAAA,IACjE,GAAG,CACDvB,GACAM,GACAD,GACAH,EAAMgC,QACNhC,EAAMkC,WACNrC,GACAU,CAAc,CACf;AAAA,IAICM,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\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 }),\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 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","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;AA4BM,SAAA;AAAA,IACLa,mBA3BwBN,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,QAAAA,CACnB;AAAA,QACDC,WAAWnC;AAAAA,QACXoC,OAAO3B,EAAe4B,KAAK,GAAG;AAAA,MAChC;AACA,aAAO,GAAG9C,CAAmB,IAAI,IAAI+C,gBAAgBjB,CAAS,CAAC;AAAA,IACjE,GAAG,CACDpB,GACAK,GACAD,GACAH,EAAM8B,QACN9B,EAAMgC,WACNlC,GACAS,CAAc,CACf;AAAA,IAICI,qBAAAA;AAAAA,EACF;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAKnE,iBAAS,gBAAgB,CACvB,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,iBAAsB,QAyBhC;AAED,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/modules/feed/hooks/useNotifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAUnE,iBAAS,gBAAgB,CACvB,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,iBAAsB,GAC9B,IAAI,CAoDN;AAED,eAAe,gBAAgB,CAAC"}
@@ -2,6 +2,10 @@ type UseSlackAuthOutput = {
2
2
  buildSlackAuthUrl: () => string;
3
3
  disconnectFromSlack: () => void;
4
4
  };
5
- declare function useSlackAuth(slackClientId: string, redirectUrl?: string, additionalScopes?: string[]): UseSlackAuthOutput;
5
+ type UseSlackAuthOptions = {
6
+ scopes?: string[];
7
+ additionalScopes?: string[];
8
+ };
9
+ declare function useSlackAuth(slackClientId: string, redirectUrl?: string, options?: UseSlackAuthOptions | string[]): UseSlackAuthOutput;
6
10
  export default useSlackAuth;
7
11
  //# sourceMappingURL=useSlackAuth.d.ts.map
@@ -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,iBAAS,YAAY,CACnB,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAC1B,kBAAkB,CAiEpB;AAED,eAAe,YAAY,CAAC"}
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,CA+DpB;AAED,eAAe,YAAY,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.6.9",
5
+ "version": "0.6.11",
6
6
  "license": "MIT",
7
7
  "main": "dist/cjs/index.js",
8
8
  "module": "dist/esm/index.mjs",
@@ -32,8 +32,6 @@
32
32
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
33
33
  "format": "prettier \"src/**/*.{js,ts,tsx}\" --write",
34
34
  "format:check": "prettier \"src/**/*.{js,ts,tsx}\" --check",
35
- "test": "vitest run",
36
- "test:watch": "vitest",
37
35
  "type:check": "tsc --noEmit",
38
36
  "coverage": "vitest run --coverage",
39
37
  "preview": "vite preview"
@@ -58,10 +56,10 @@
58
56
  },
59
57
  "devDependencies": {
60
58
  "@testing-library/dom": "^10.4.0",
61
- "@testing-library/react": "^16.2.0",
59
+ "@testing-library/react": "^16.3.0",
62
60
  "@types/react": "^18.3.6",
63
- "@typescript-eslint/eslint-plugin": "^8.19.1",
64
- "@typescript-eslint/parser": "^8.27.0",
61
+ "@typescript-eslint/eslint-plugin": "^8.32.0",
62
+ "@typescript-eslint/parser": "^8.32.1",
65
63
  "@vitejs/plugin-react": "^4.4.1",
66
64
  "babel-plugin-react-require": "^4.0.3",
67
65
  "eslint": "^8.56.0",
@@ -1,36 +1,69 @@
1
1
  import Knock, { Feed, FeedClientOptions } from "@knocklabs/client";
2
- import { useMemo, useRef } from "react";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
3
 
4
4
  import { useStableOptions } from "../../core";
5
5
 
6
+ interface State {
7
+ feedClient: Feed;
8
+ options: FeedClientOptions;
9
+ }
10
+
6
11
  function useNotifications(
7
12
  knock: Knock,
8
13
  feedChannelId: string,
9
14
  options: FeedClientOptions = {},
10
- ) {
11
- const feedClientRef = useRef<Feed>();
15
+ ): Feed {
16
+ const initFeedClient = useCallback(
17
+ (feedChannelId: string, options: FeedClientOptions) => {
18
+ const feedClient = knock.feeds.initialize(feedChannelId, options);
19
+
20
+ // In development, we need to introduce this extra set state to force a render
21
+ // for Zustand as otherwise the state doesn't get reflected correctly
22
+ feedClient.store.subscribe((t) => feedClient.store.setState(t));
23
+
24
+ feedClient.listenForUpdates();
25
+
26
+ return feedClient;
27
+ },
28
+ [knock],
29
+ );
30
+
12
31
  const stableOptions = useStableOptions(options);
32
+ const [state, setState] = useState<State>(() => ({
33
+ // FIXME In strict mode, useState initializer functions are called twice,
34
+ // which results in one extra instance of the feed client being created
35
+ // and not disposed of. This only affects strict mode.
36
+ feedClient: initFeedClient(feedChannelId, stableOptions),
37
+ options: stableOptions,
38
+ }));
39
+ const disposedRef = useRef(false);
13
40
 
14
- return useMemo(() => {
15
- if (feedClientRef.current) {
16
- feedClientRef.current.dispose();
17
- }
41
+ useEffect(() => {
42
+ const isDisposed = disposedRef.current;
18
43
 
19
- feedClientRef.current = knock.feeds.initialize(
20
- feedChannelId,
21
- stableOptions,
22
- );
44
+ // Initialize a new feed client if the feed ID has changed,
45
+ // options have changed, or the current feed client has been disposed
46
+ const needsReinit =
47
+ state.feedClient.feedId !== feedChannelId ||
48
+ state.options !== stableOptions ||
49
+ isDisposed;
23
50
 
24
- // In development, we need to introduce this extra set state to force a render
25
- // for Zustand as otherwise the state doesn't get reflected correctly
26
- feedClientRef.current.store.subscribe((t) =>
27
- feedClientRef?.current?.store.setState(t),
28
- );
51
+ if (needsReinit) {
52
+ disposedRef.current = false;
53
+ setState({
54
+ feedClient: initFeedClient(feedChannelId, stableOptions),
55
+ options: stableOptions,
56
+ });
57
+ return;
58
+ }
29
59
 
30
- feedClientRef.current.listenForUpdates();
60
+ return () => {
61
+ disposedRef.current = true;
62
+ state.feedClient.dispose();
63
+ };
64
+ }, [initFeedClient, feedChannelId, stableOptions, state]);
31
65
 
32
- return feedClientRef.current;
33
- }, [knock, feedChannelId, stableOptions]);
66
+ return state.feedClient;
34
67
  }
35
68
 
36
69
  export default useNotifications;
@@ -17,19 +17,45 @@ type UseSlackAuthOutput = {
17
17
  disconnectFromSlack: () => void;
18
18
  };
19
19
 
20
+ type UseSlackAuthOptions = {
21
+ // When provided, the default scopes will be overridden with the provided scopes
22
+ scopes?: string[];
23
+ // Additional scopes to add to the default scopes
24
+ additionalScopes?: string[];
25
+ };
26
+
27
+ // Here we normalize the options to be a single object with scopes and additionalScopes
28
+ // The "options" parameter can be an array of scopes, an object with scopes and additionalScopes, or undefined
29
+ // We handle the array case because it was the previous way to pass options so we're being backward compatible
30
+ function normalizeOptions(options?: UseSlackAuthOptions | string[]): {
31
+ scopes: string[];
32
+ additionalScopes: string[];
33
+ } {
34
+ if (!options) {
35
+ return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: [] };
36
+ }
37
+
38
+ if (Array.isArray(options)) {
39
+ return { scopes: DEFAULT_SLACK_SCOPES, additionalScopes: options };
40
+ }
41
+
42
+ return {
43
+ scopes: options.scopes ?? DEFAULT_SLACK_SCOPES,
44
+ additionalScopes: options.additionalScopes ?? [],
45
+ };
46
+ }
47
+
20
48
  function useSlackAuth(
21
49
  slackClientId: string,
22
50
  redirectUrl?: string,
23
- additionalScopes?: string[],
51
+ options?: UseSlackAuthOptions | string[],
24
52
  ): UseSlackAuthOutput {
25
53
  const knock = useKnockClient();
26
54
  const { setConnectionStatus, knockSlackChannelId, tenantId, setActionLabel } =
27
55
  useKnockSlackClient();
28
56
 
29
- const combinedScopes =
30
- additionalScopes && additionalScopes.length > 0
31
- ? Array.from(new Set(DEFAULT_SLACK_SCOPES.concat(additionalScopes)))
32
- : DEFAULT_SLACK_SCOPES;
57
+ const { scopes, additionalScopes } = normalizeOptions(options);
58
+ const combinedScopes = Array.from(new Set(scopes.concat(additionalScopes)));
33
59
 
34
60
  const disconnectFromSlack = useCallback(async () => {
35
61
  setActionLabel(null);