Skip to content

Add an enabled prop to KnockProvider#1033

Draft
kylemcd wants to merge 13 commits into
mainfrom
kyle-kno-14095-add-enabled-prop-to-knockprovider-full-implementation-plan
Draft

Add an enabled prop to KnockProvider#1033
kylemcd wants to merge 13 commits into
mainfrom
kyle-kno-14095-add-enabled-prop-to-knockprovider-full-implementation-plan

Conversation

@kylemcd

@kylemcd kylemcd commented Jul 9, 2026

Copy link
Copy Markdown
Member

Description

Adds an enabled prop to <KnockProvider>. When it's false, children still render but the Knock client sits idle: no identify call, no API requests, no websocket. Set it to true and it connects like a login; set it back to false and it shuts down like a logout. It defaults to true, so existing code is unaffected.

Today, if you don't have a user yet (common when the user token loads asynchronously), your only option is to conditionally mount the provider. This replaces that:

<KnockProvider apiKey={apiKey} user={{ id: userId }} userToken={userToken} enabled={Boolean(userId && userToken)} />

The prop itself is small. Most of the work is that a lot of the SDK used to throw, or fire requests with userId=undefined, when no user was set. The worst case: rendering Guides before a user was set would throw and crash the app. This makes all of those paths do nothing when there's no user.

Heads up: most of the +1.5k lines are tests. The actual code changes are the ~22 files below, each linked straight to its spot in the diff so you can skip the test files.

Code changes

@knocklabs/client — do nothing when signed out, add logout() + auth state

  • knock.ts — new logout() (disconnects socket + token timer + visibility listener), authStatus, a subscribable authStore, and syncAuthState(). authenticate() also rewires surviving feeds after a logout() via the new feeds.hasInstances() check.
  • interfaces.tsKnockAuthStatus / KnockAuthState types.
  • clients/feed/feed.ts — a canMutate() gate; every markAs* / markAll* / fetchNextPage no-ops (and skips the optimistic store update) when signed out.
  • clients/feed/index.ts — the hasInstances() helper.
  • clients/guide/client.tsfetch / subscribe / step-marks stop throwing and no-op; subscribe() re-reads the socket from the client (fixes real-time after a re-login); and the history monkey-patch is now shared + idempotent (fixes a bug on provider remount).
  • clients/slack/index.ts · clients/ms-teams/index.tsauthCheck returns "not connected"; getChannels / getTeams return empty.
  • clients/messages/index.tsbatchUpdateStatuses returns [].

@knocklabs/react-core — the enabled prop

  • KnockProvider.tsx — the enabled prop.
  • useAuthenticatedKnockClient.ts — the implementation: when off, pass no user; toggling builds a fresh client (so the feed reloads on login and clears on logout); tear down on unmount.
  • useFeedSettings.ts — skips the branding fetch (no more GET /users/undefined/...) when signed out.

react-core + expo — react to sign-in changes

Docs

  • KnockGuideProvider.tsx — JSDoc on readyToTarget vs auth. Plus README updates and changesets for every affected package.

Tests

The bulk of the diff. They cover the transitions (enable = login, disable = logout, user switch), the client-side no-ops per method, the guide history-patch and socket re-read, the Slack/Teams re-check, and the Expo gating. Client + react-core + expo suites pass.

Todos

Checklist

  • Tests have been added for new features or major refactors to existing features.

kylemcd added 4 commits July 9, 2026 14:25
Add a subscribable authStore + authStatus getter and a Knock.logout() that
tears down all stateful connections (socket, token-expiration timer, and
page-visibility listener) and lazily re-creates the API client.

Make user-scoped calls quiescent while unauthenticated instead of throwing
or firing blind:
- Feed markAs*/markAll*/fetchNextPage no-op without an optimistic store write.
- Guide fetch/subscribe/step-marks no longer throw (fixes the crash when
  Guides render before a user is set).
- Slack/MS Teams authCheck return a disconnected shape; getChannels/getTeams
  return empty; messages.batchUpdateStatuses returns [].

Also fix two guide bugs the enabled prop exacerbates: re-read the socket from
the API client on each subscribe so real-time survives re-auth, and share the
history pushState/replaceState patch per window so remounting a guide provider
no longer nests patches or leaves the originals unrestored.
Implement `enabled` (default true) as credential-nulling through
useAuthenticatedKnockClient: when false the client is created but left
unauthenticated and fully quiescent while children still render. A fresh
client is built on every enable/disable transition so enabling remounts the
feed subtree (and refetches) and disabling clears the previous user's stores.

Also guard useFeedSettings against firing GET /v1/users/undefined/.../settings
for an unauthenticated user, and tear the client down on provider unmount
(StrictMode-safe via a dispose/re-init flag) instead of leaking the socket,
token timer, and page-visibility listener.
Add useKnockAuthState(knock), which subscribes to the client's authStore and
re-renders on login, logout, or a user switch.

Wire it into the integrations that previously latched their state:
- Slack/MS Teams connection status resets and re-runs authCheck when the
  authenticated user changes, and the provider keys now include the userId so
  a user switch reliably re-renders consumers.
- Expo autoRegister waits for an authenticated user before registering a push
  token, deferring the OS permission prompt for logged-out users and
  re-registering on sign-in; a notification tapped while logged out no longer
  fires a message-status update.
Add the readyToTarget x enabled/auth matrix to KnockGuideProvider's JSDoc, and
document the enabled prop in the react, react-native, and expo READMEs plus the
vanilla logout/quiescence story in the client README.
@linear-code

linear-code Bot commented Jul 9, 2026

Copy link
Copy Markdown

KNO-14095

@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd3ca65

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@knocklabs/client Minor
@knocklabs/react-core Minor
@knocklabs/react Minor
@knocklabs/react-native Minor
@knocklabs/expo Minor
client-example Patch
guide-example Patch
ms-teams-connect-example Patch
nextjs-app-dir-example Patch
nextjs-example Patch
slack-connect-example Patch
slack-kit-example Patch
@knocklabs/expo-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
javascript-ms-teams-connect-example Ready Ready Preview, Comment Jul 10, 2026 3:01pm
javascript-nextjs-example Ready Ready Preview, Comment Jul 10, 2026 3:01pm
javascript-slack-connect-example Ready Ready Preview, Comment Jul 10, 2026 3:01pm
javascript-slack-kit-example Ready Ready Preview, Comment Jul 10, 2026 3:01pm

Request Review

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 11.94kB (2.13%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
@knocklabs/react-core-react-core-cjs 38.05kB 1.6kB (4.4%) ⬆️
@knocklabs/react-react-esm 117.11kB 52 bytes (0.04%) ⬆️
@knocklabs/expo-expo-cjs 7.57kB 161 bytes (2.17%) ⬆️
@knocklabs/expo-expo-esm 8.16kB 240 bytes (3.03%) ⬆️
@knocklabs/react-react-cjs 107.02kB 95 bytes (0.09%) ⬆️
@knocklabs/client-client-esm 87.0kB 5.01kB (6.11%) ⬆️
@knocklabs/react-core-react-core-esm 43.38kB 1.82kB (4.39%) ⬆️
@knocklabs/client-client-cjs 63.37kB 2.95kB (4.88%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: @knocklabs/react-react-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.js 95 bytes 7.68kB 1.25%

Files in index.js:

  • ./src/index.ts → Total Size: 0 bytes
view changes for bundle: @knocklabs/react-react-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.mjs 52 bytes 6.12kB 0.86%

Files in index.mjs:

  • ./src/index.ts → Total Size: 0 bytes
view changes for bundle: @knocklabs/expo-expo-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
modules/push/KnockExpoPushNotificationProvider.js 161 bytes 2.78kB 6.14% ⚠️

Files in modules/push/KnockExpoPushNotificationProvider.js:

  • ./src/modules/push/KnockExpoPushNotificationProvider.tsx → Total Size: 5.16kB
view changes for bundle: @knocklabs/client-client-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
clients/guide/client.mjs 825 bytes 25.35kB 3.36%
clients/feed/feed.mjs 1.11kB 15.65kB 7.61% ⚠️
knock.mjs 1.88kB 6.6kB 39.77% ⚠️
clients/ms-*.mjs 545 bytes 3.05kB 21.79% ⚠️
clients/slack/index.mjs 367 bytes 2.4kB 18.07% ⚠️
clients/messages/index.mjs 158 bytes 1.99kB 8.62% ⚠️
clients/feed/index.mjs 132 bytes 1.45kB 10.05% ⚠️

Files in clients/guide/client.mjs:

  • ./src/clients/guide/client.ts → Total Size: 30.04kB

Files in clients/feed/feed.mjs:

  • ./src/clients/feed/feed.ts → Total Size: 18.06kB

Files in knock.mjs:

  • ./src/knock.ts → Total Size: 7.09kB

Files in clients/ms-*.mjs:

  • ./src/clients/ms-teams/index.ts → Total Size: 3.17kB

Files in clients/slack/index.mjs:

  • ./src/clients/slack/index.ts → Total Size: 2.37kB

Files in clients/messages/index.mjs:

  • ./src/clients/messages/index.ts → Total Size: 2.0kB

Files in clients/feed/index.mjs:

  • ./src/clients/feed/index.ts → Total Size: 1.2kB
view changes for bundle: @knocklabs/react-core-react-core-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.js 101 bytes 3.82kB 2.72%
modules/slack/hooks/useSlackAuth.js 33 bytes 1.71kB 1.97%
modules/slack/hooks/useSlackChannels.js 33 bytes 1.69kB 1.99%
modules/ms-*.js 33 bytes 1.41kB 2.4%
modules/ms-*.js 33 bytes 1.25kB 2.7%
modules/ms-*.js 49 bytes 1.52kB 3.34%
modules/ms-*.js 235 bytes 1.21kB 24.1% ⚠️
modules/ms-*.js 33 bytes 1.63kB 2.07%
modules/ms-*.js 33 bytes 1.15kB 2.96%
modules/slack/context/KnockSlackProvider.js 49 bytes 1.55kB 3.27%
modules/guide/context/KnockGuideProvider.js 33 bytes 1.42kB 2.38%
modules/slack/hooks/useConnectedSlackChannels.js 33 bytes 1.4kB 2.42%
modules/slack/hooks/useSlackConnectionStatus.js 235 bytes 1.36kB 20.8% ⚠️
modules/core/context/KnockProvider.js 55 bytes 1.3kB 4.41%
modules/feed/context/KnockFeedProvider.js 33 bytes 1.29kB 2.62%
modules/core/hooks/useAuthenticatedKnockClient.js 164 bytes 1.09kB 17.71% ⚠️
modules/core/utils.js 22 bytes 1.09kB 2.07%
modules/preferences/hooks/usePreferences.js 33 bytes 884 bytes 3.88%
modules/feed/hooks/useNotifications.js 33 bytes 695 bytes 4.98%
modules/feed/hooks/useFeedSettings.js 37 bytes 548 bytes 7.24% ⚠️
modules/core/hooks/useKnockAuthState.js (New) 292 bytes 292 bytes 100.0% 🚀

Files in index.js:

  • ./src/index.ts → Total Size: 0 bytes

Files in modules/guide/context/KnockGuideProvider.js:

  • ./src/modules/guide/context/KnockGuideProvider.tsx → Total Size: 1.55kB

Files in modules/core/context/KnockProvider.js:

  • ./src/modules/core/context/KnockProvider.tsx → Total Size: 1.19kB

Files in modules/core/hooks/useAuthenticatedKnockClient.js:

  • ./src/modules/core/hooks/useAuthenticatedKnockClient.ts → Total Size: 1.92kB

Files in modules/core/utils.js:

  • ./src/modules/core/utils.ts → Total Size: 1.48kB

Files in modules/feed/hooks/useFeedSettings.js:

  • ./src/modules/feed/hooks/useFeedSettings.ts → Total Size: 935 bytes

Files in modules/core/hooks/useKnockAuthState.js:

  • ./src/modules/core/hooks/useKnockAuthState.ts → Total Size: 102 bytes
view changes for bundle: @knocklabs/react-core-react-core-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.mjs 113 bytes 4.2kB 2.76%
modules/slack/hooks/useSlackAuth.mjs 32 bytes 2.11kB 1.54%
modules/slack/hooks/useSlackChannels.mjs 32 bytes 2.06kB 1.58%
modules/ms-*.mjs 32 bytes 2.01kB 1.62%
modules/ms-*.mjs 333 bytes 1.68kB 24.78% ⚠️
modules/ms-*.mjs 32 bytes 1.93kB 1.69%
modules/ms-*.mjs 32 bytes 1.38kB 2.37%
modules/ms-*.mjs 54 bytes 1.39kB 4.05%
modules/ms-*.mjs 32 bytes 1.6kB 2.04%
modules/slack/hooks/useSlackConnectionStatus.mjs 333 bytes 1.92kB 20.98% ⚠️
modules/slack/hooks/useConnectedSlackChannels.mjs 32 bytes 1.9kB 1.71%
modules/slack/context/KnockSlackProvider.mjs 54 bytes 1.52kB 3.68%
modules/guide/context/KnockGuideProvider.mjs 32 bytes 1.37kB 2.4%
modules/core/utils.mjs 32 bytes 1.25kB 2.64%
modules/core/hooks/useAuthenticatedKnockClient.mjs 220 bytes 1.23kB 21.7% ⚠️
modules/core/context/KnockProvider.mjs 65 bytes 1.14kB 6.02% ⚠️
modules/feed/hooks/useNotifications.mjs 32 bytes 1.1kB 3.0%
modules/feed/context/KnockFeedProvider.mjs 32 bytes 1.08kB 3.05%
modules/preferences/hooks/usePreferences.mjs 32 bytes 926 bytes 3.58%
modules/feed/hooks/useFeedSettings.mjs 60 bytes 751 bytes 8.68% ⚠️
modules/core/hooks/useKnockAuthState.mjs (New) 208 bytes 208 bytes 100.0% 🚀

Files in index.mjs:

  • ./src/index.ts → Total Size: 0 bytes

Files in modules/guide/context/KnockGuideProvider.mjs:

  • ./src/modules/guide/context/KnockGuideProvider.tsx → Total Size: 1.48kB

Files in modules/core/utils.mjs:

  • ./src/modules/core/utils.ts → Total Size: 1.46kB

Files in modules/core/hooks/useAuthenticatedKnockClient.mjs:

  • ./src/modules/core/hooks/useAuthenticatedKnockClient.ts → Total Size: 1.88kB

Files in modules/core/context/KnockProvider.mjs:

  • ./src/modules/core/context/KnockProvider.tsx → Total Size: 1.12kB

Files in modules/feed/hooks/useFeedSettings.mjs:

  • ./src/modules/feed/hooks/useFeedSettings.ts → Total Size: 917 bytes

Files in modules/core/hooks/useKnockAuthState.mjs:

  • ./src/modules/core/hooks/useKnockAuthState.ts → Total Size: 91 bytes
view changes for bundle: @knocklabs/client-client-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
clients/guide/client.js 687 bytes 18.35kB 3.89%
clients/feed/feed.js 634 bytes 10.74kB 6.27% ⚠️
knock.js 714 bytes 4.27kB 20.07% ⚠️
clients/ms-*.js 441 bytes 2.38kB 22.79% ⚠️
clients/slack/index.js 296 bytes 1.89kB 18.55% ⚠️
clients/messages/index.js 128 bytes 1.62kB 8.55% ⚠️
clients/feed/index.js 50 bytes 1.21kB 4.33%

Files in clients/guide/client.js:

  • ./src/clients/guide/client.ts → Total Size: 30.22kB

Files in clients/feed/feed.js:

  • ./src/clients/feed/feed.ts → Total Size: 18.21kB

Files in knock.js:

  • ./src/knock.ts → Total Size: 7.13kB

Files in clients/ms-*.js:

  • ./src/clients/ms-teams/index.ts → Total Size: 3.21kB

Files in clients/slack/index.js:

  • ./src/clients/slack/index.ts → Total Size: 2.4kB

Files in clients/messages/index.js:

  • ./src/clients/messages/index.ts → Total Size: 2.0kB

Files in clients/feed/index.js:

  • ./src/clients/feed/index.ts → Total Size: 1.22kB
view changes for bundle: @knocklabs/expo-expo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
modules/push/KnockExpoPushNotificationProvider.mjs 240 bytes 3.6kB 7.13% ⚠️

Files in modules/push/KnockExpoPushNotificationProvider.mjs:

  • ./src/modules/push/KnockExpoPushNotificationProvider.tsx → Total Size: 4.98kB

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b9854aa. Configure here.

kylemcd added 2 commits July 9, 2026 15:49
Replace the disposedRef/generation StrictMode self-heal with a plain effect
cleanup that tears the client down on unmount. This removes the one-render
window where a StrictMode simulated remount could return the previous
(torn-down) memoized client before the generation bump rebuilt it.

Addresses Cursor BugBot: "StrictMode serves torn-down client".
Wait for the connection status to resolve back to "connected" (which only
happens after the second authCheck promise resolves) rather than asserting it
synchronously right after waitFor(authCheck called twice), which was flaky
under full-suite timing.
kylemcd added 2 commits July 10, 2026 08:33
Replace the generation/disposedRef StrictMode self-heal with a plain
effect-cleanup teardown. Addresses a Cursor BugBot finding: the self-heal
could briefly return a torn-down client on a StrictMode simulated remount.
The simpler cleanup still fixes the real unmount leak (socket, token-refresh
timer, page-visibility listener) without that path.
Rewrite the changesets, READMEs, and doc comments in plain language,
dropping "quiescent"/"quiescence" and similar jargon, so the enabled-prop
behavior is easy to read.
Add a KnockSlackProvider render test, an Expo test that fires a
notification while unauthenticated (hitting the updateMessageStatus
guard), and a guide test that calls the patched history.pushState to
exercise the shared location-change patch.
kylemcd added 4 commits July 10, 2026 09:58
…ed-out instance

The onUserTokenExpiring callback re-authenticated after its await with no
check that logout()/teardown() had run in the meantime, which rebuilt the
API client and rescheduled the timer. Null the timer on teardown and bail
in the callback if the timer id is no longer current.
The react package curates its re-exports by name and this one was missing,
so it was unimportable despite being advertised. Guard it in the barrel test.
If a user switch happened while the first authCheck was still in flight,
the effect never re-ran (status was still "connecting") and the previous
users result latched. Depend on userId and ignore superseded results.
… true

Uses a real Knock (not the singleton-mocked one) so the fresh-instance +
feedProviderKey remount that drives the feed to refetch is actually observed.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.64413% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 66.00%. Comparing base (70e7669) to head (fd3ca65).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rc/modules/slack/hooks/useSlackConnectionStatus.ts 94.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1033      +/-   ##
==========================================
+ Coverage   64.49%   66.00%   +1.51%     
==========================================
  Files         212      213       +1     
  Lines       10217    10499     +282     
  Branches     1389     1506     +117     
==========================================
+ Hits         6589     6930     +341     
+ Misses       3603     3544      -59     
  Partials       25       25              
Files with missing lines Coverage Δ
packages/client/src/clients/feed/feed.ts 83.45% <100.00%> (+1.95%) ⬆️
packages/client/src/clients/feed/index.ts 100.00% <100.00%> (ø)
packages/client/src/clients/guide/client.ts 95.66% <100.00%> (+1.36%) ⬆️
packages/client/src/clients/messages/index.ts 100.00% <100.00%> (ø)
packages/client/src/clients/ms-teams/index.ts 100.00% <100.00%> (ø)
packages/client/src/clients/slack/index.ts 100.00% <100.00%> (ø)
packages/client/src/knock.ts 100.00% <100.00%> (ø)
...modules/push/KnockExpoPushNotificationProvider.tsx 96.38% <100.00%> (+0.20%) ⬆️
packages/react-core/src/index.ts 100.00% <ø> (ø)
...ct-core/src/modules/core/context/KnockProvider.tsx 96.42% <100.00%> (+0.20%) ⬆️
... and 12 more

... and 2 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Add enabled prop to KnockProvider

1 participant