@stytch/react 19.4.3 → 19.5.0

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 (52) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +7 -7
  3. package/b2b/headless/package.json +6 -0
  4. package/b2b/ui/package.json +6 -0
  5. package/dist/StytchB2BContext-9c062712.d.ts +272 -0
  6. package/dist/{StytchB2BContext-081081ee.js → StytchB2BContext-9c062712.js} +94 -49
  7. package/dist/StytchB2BContext-c5062f9b.d.ts +272 -0
  8. package/dist/StytchB2BContext-c5062f9b.js +280 -0
  9. package/dist/StytchSSRProxy-34c789b5.d.ts +3 -0
  10. package/dist/StytchSSRProxy-34c789b5.js +48 -0
  11. package/dist/StytchSSRProxy-86bc42b3.d.ts +3 -0
  12. package/dist/StytchSSRProxy-86bc42b3.js +54 -0
  13. package/dist/adminPortal/index.d.ts +1 -1
  14. package/dist/adminPortal/index.esm.d.ts +1 -1
  15. package/dist/adminPortal/index.esm.js +5 -4
  16. package/dist/adminPortal/index.js +5 -4
  17. package/dist/b2b/index.d.ts +180 -19
  18. package/dist/b2b/index.esm.d.ts +180 -19
  19. package/dist/b2b/index.esm.js +91 -18
  20. package/dist/b2b/index.headless.d.ts +16 -0
  21. package/dist/b2b/index.headless.esm.d.ts +16 -0
  22. package/dist/b2b/index.headless.esm.js +24 -0
  23. package/dist/b2b/index.headless.js +28 -0
  24. package/dist/b2b/index.js +99 -26
  25. package/dist/b2b/index.ui.d.ts +17 -0
  26. package/dist/b2b/index.ui.esm.d.ts +17 -0
  27. package/dist/b2b/index.ui.esm.js +25 -0
  28. package/dist/b2b/index.ui.js +29 -0
  29. package/dist/errors-d9d5fbc8.d.ts +5 -0
  30. package/dist/index-b14d4efe.d.ts +1 -1
  31. package/dist/index.d.ts +134 -77
  32. package/dist/index.esm.d.ts +134 -77
  33. package/dist/index.esm.js +153 -94
  34. package/dist/index.headless.d.ts +16 -0
  35. package/dist/index.headless.esm.d.ts +16 -0
  36. package/dist/index.headless.esm.js +24 -0
  37. package/dist/index.headless.js +28 -0
  38. package/dist/index.js +167 -108
  39. package/dist/index.ui.d.ts +17 -0
  40. package/dist/index.ui.esm.d.ts +17 -0
  41. package/dist/index.ui.esm.js +25 -0
  42. package/dist/index.ui.js +29 -0
  43. package/dist/useIsomorphicLayoutEffect-1babb81e.d.ts +24 -0
  44. package/dist/{invariant-568a7633.js → useIsomorphicLayoutEffect-1babb81e.js} +5 -13
  45. package/dist/useIsomorphicLayoutEffect-65746ef3.d.ts +24 -0
  46. package/dist/{invariant-ae5a5bce.js → useIsomorphicLayoutEffect-65746ef3.js} +4 -16
  47. package/package.json +5 -3
  48. package/dist/StytchB2BContext-081081ee.d.ts +0 -165
  49. package/dist/StytchB2BContext-865b6947.d.ts +0 -165
  50. package/dist/StytchB2BContext-865b6947.js +0 -235
  51. package/dist/invariant-568a7633.d.ts +0 -27
  52. package/dist/invariant-ae5a5bce.d.ts +0 -27
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @stytch/react
2
2
 
3
+ ## 19.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1e9ab0e: Add `createStytch*Client` helper functions:
8
+
9
+ - `import { createStytchHeadlessClient } from '@stytch/react/headless';`
10
+ - `import { createStytchUIClient } from '@stytch/react/ui';`
11
+ - `import { createStytchB2BHeadlessClient } from '@stytch/react/b2b/headless';`
12
+ - `import { createStytchB2BUIClient } from '@stytch/react/b2b/ui';`
13
+
14
+ These are equivalent to the same functions in `@stytch/nextjs` and may be used instead of constructing clients directly (e.g., `new StytchUIClient()`).
15
+
16
+ - 1e9ab0e: Add `isInitialized` as a returned value from `useStytchUser`, `useStytchSession`, `useStytchMember`, `useStytchMemberSession`, `useStytchOrganization`, and `useStytchIsAuthorized` hooks; add `*IsInitialized` as an injected prop to the `withStytchUser`, `withStytchSession`, `withStytchMember`, `withStytchMemberSession`, and `withStytchOrganization` HOCs. This provides parity with the same APIs in `@stytch/nextjs`.
17
+
18
+ If you set `assumeHydrated={false}` in your `StytchProvider` or `StytchB2BProvider`, `isInitialized` will initially be `false` and should be checked before accessing the other values returned by these hooks (or props injected by these HOCs). If you set `assumeHydrated={true}` (the default in `@stytch/react`), `isInitialized` will always be `true`, and it is always safe to access the other values, as in previous versions of `@stytch/react`.
19
+
20
+ - 1e9ab0e: Add a new `assumeHydrated` prop to the `StytchProvider` and `StytchB2BProvider` components. This prop allows the provider to assume whether or not the app might be hydrated in a browser environment after initially being rendered in a server environment. When `true`, the provider eagerly reads from the browser's local cache to retrieve cached session and related data. When `false`, the provider defers this initialization until after the first render, to avoid hydration errors. This prop defaults to `true` in `@stytch/react`, which preserves the behavior from previous versions.
21
+
22
+ We recommend setting `assumeHydrated={false}` if your app hydrates server-rendered content on the client. Otherwise, it is safe to set `assumeHydrated={true}`. If you're unsure or notice hydration errors in your app, set `assumeHydrated={false}`.
23
+
24
+ ### Patch Changes
25
+
26
+ - 1e9ab0e: Remove errors when attempting to use StytchProvider or StytchB2BProvider in a server environment
27
+
28
+ ## 19.4.4
29
+
30
+ ### Patch Changes
31
+
32
+ - 54275c2: Make some minor improvements to JSDoc comments
33
+ - 54275c2: Add missing default generic type parameters
34
+
3
35
  ## 19.4.3
4
36
 
5
37
  ### Patch Changes
package/README.md CHANGED
@@ -18,9 +18,9 @@ For full documentation please refer to Stytch's javascript SDK documentation at
18
18
 
19
19
  ```javascript
20
20
  import { StytchProvider } from '@stytch/react';
21
- import { StytchUIClient } from '@stytch/vanilla-js';
21
+ import { createStytchUIClient } from '@stytch/react/ui';
22
22
 
23
- const stytch = new StytchUIClient('public-token-<find yours in the stytch dashboard>');
23
+ const stytch = createStytchUIClient('public-token-<find yours in the stytch dashboard>');
24
24
 
25
25
  // Wrap your App in the StytchProvider
26
26
  const Root = () => (
@@ -96,18 +96,18 @@ yarn remove @stytch/stytch-react
96
96
  ### Step 3: Initialize the client in your Application Root
97
97
 
98
98
  - Determine if you need the Headless or UI client. If you plan to use the `<StytchLogin />` component in your application then you should use the the UI client. Otherwise use the Headless client to minimize application bundle size.
99
- - To create the Stytch Headless client, use `StytchHeadlessClient` from `@stytch/vanilla-js/headless`
100
- - To create the Stytch UI client, use `StytchUIClient` from `@stytch/vanilla-js`
99
+ - To create the Stytch Headless client, use `createStytchHeadlessClient` from `@stytch/react/headless`
100
+ - To create the Stytch UI client, use `createStytchUIClient` from `@stytch/react/ui`
101
101
  - Pass it into `<StytchProvider />` component from `@stytch/react`
102
102
 
103
103
  ```jsx
104
104
  import React from 'react';
105
105
  import { StytchProvider } from '@stytch/react';
106
- import { StytchHeadlessClient } from '@stytch/vanilla-js/headless';
106
+ import { createStytchHeadlessClient } from '@stytch/react/headless';
107
107
  // Or alternately
108
- // import { StytchUIClient } from '@stytch/vanilla-js';
108
+ // import { createStytchUIClient } from '@stytch/react/ui';
109
109
 
110
- const stytch = new StytchHeadlessClient(process.env.REACT_APP_STYTCH_PUBLIC_TOKEN);
110
+ const stytch = createStytchHeadlessClient(process.env.REACT_APP_STYTCH_PUBLIC_TOKEN);
111
111
 
112
112
  function WrappedApp() {
113
113
  return (
@@ -0,0 +1,6 @@
1
+ {
2
+ "internal": true,
3
+ "main": "../../dist/b2b/index.headless.js",
4
+ "module": "../../dist/b2b/index.headless.esm.js",
5
+ "types": "../../dist/b2b/index.headless.d.ts"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "internal": true,
3
+ "main": "../../dist/b2b/index.ui.js",
4
+ "module": "../../dist/b2b/index.ui.esm.js",
5
+ "types": "../../dist/b2b/index.ui.d.ts"
6
+ }
@@ -0,0 +1,272 @@
1
+ /// <reference types="react" />
2
+ import React from 'react';
3
+ import { ReactNode } from "react";
4
+ import { PermissionsMap } from '@stytch/core/public';
5
+ import { Member, MemberSession, Organization, StytchB2BUIClient } from '@stytch/vanilla-js/b2b';
6
+ import { StytchB2BHeadlessClient, StytchProjectConfigurationInput } from '@stytch/vanilla-js/b2b/headless';
7
+ /**
8
+ * The Stytch Client object passed in to <StytchB2BProvider /> in your application root.
9
+ * Either a StytchB2BUIClient or StytchB2BHeadlessClient.
10
+ */
11
+ type StytchB2BClient<TProjectConfiguration extends StytchProjectConfigurationInput> = StytchB2BHeadlessClient<TProjectConfiguration> | StytchB2BUIClient<TProjectConfiguration>;
12
+ type SWRMemberUninitialized = {
13
+ /**
14
+ * Either the active {@link Member} object, or null if the member is not logged in.
15
+ */
16
+ member: null;
17
+ /**
18
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
19
+ */
20
+ fromCache: false;
21
+ /**
22
+ * If true, indicates that the SDK has completed initialization.
23
+ */
24
+ isInitialized: false;
25
+ };
26
+ type SWRMemberInitialized = {
27
+ /**
28
+ * Either the active {@link Member} object, or null if the member is not logged in.
29
+ */
30
+ member: Member | null;
31
+ /**
32
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
33
+ */
34
+ fromCache: boolean;
35
+ /**
36
+ * If true, indicates that the SDK has completed initialization.
37
+ */
38
+ isInitialized: true;
39
+ };
40
+ type SWRMember<TAlwaysInitialized extends boolean = boolean> = TAlwaysInitialized extends true ? SWRMemberInitialized : SWRMemberInitialized | SWRMemberUninitialized;
41
+ type SWRMemberSessionUninitialized = {
42
+ /**
43
+ * Either the active {@link MemberSession} object, or null if the member is not logged in.
44
+ */
45
+ session: null;
46
+ /**
47
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
48
+ */
49
+ fromCache: false;
50
+ /**
51
+ * If true, indicates that the SDK has completed initialization.
52
+ */
53
+ isInitialized: false;
54
+ };
55
+ type SWRMemberSessionInitialized = {
56
+ /**
57
+ * Either the active {@link MemberSession} object, or null if the member is not logged in.
58
+ */
59
+ session: MemberSession | null;
60
+ /**
61
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
62
+ */
63
+ fromCache: boolean;
64
+ /**
65
+ * If true, indicates that the SDK has completed initialization.
66
+ */
67
+ isInitialized: true;
68
+ };
69
+ type SWRMemberSession<TAlwaysInitialized extends boolean = boolean> = TAlwaysInitialized extends true ? SWRMemberSessionInitialized : SWRMemberSessionInitialized | SWRMemberSessionUninitialized;
70
+ type SWROrganizationUninitialized = {
71
+ /**
72
+ * Either the active {@link Organization} object, or null if the member is not logged in.
73
+ */
74
+ organization: null;
75
+ /**
76
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
77
+ */
78
+ fromCache: false;
79
+ /**
80
+ * If true, indicates that the SDK has completed initialization.
81
+ */
82
+ isInitialized: false;
83
+ };
84
+ type SWROrganizationInitialized = {
85
+ /**
86
+ * Either the active {@link Organization} object, or null if the member is not logged in.
87
+ */
88
+ organization: Organization | null;
89
+ /**
90
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
91
+ */
92
+ fromCache: boolean;
93
+ /**
94
+ * If true, indicates that the SDK has completed initialization.
95
+ */
96
+ isInitialized: true;
97
+ };
98
+ type SWROrganization<TAlwaysInitialized extends boolean = boolean> = TAlwaysInitialized extends true ? SWROrganizationInitialized : SWROrganizationInitialized | SWROrganizationUninitialized;
99
+ declare const useIsMounted__INTERNAL: () => boolean;
100
+ declare const isUIClient: <TProjectConfiguration extends Partial<import("@stytch/core/public").StytchProjectConfiguration>>(client: StytchB2BClient<TProjectConfiguration>) => client is StytchB2BUIClient<TProjectConfiguration>;
101
+ /**
102
+ * Returns the active Member.
103
+ * The Stytch SDKs are used for client-side authentication and session management.
104
+ * Check the isInitialized property to determine if the SDK has completed initialization.
105
+ * Check the fromCache property to determine if the member data is from persistent storage.
106
+ * @example
107
+ * const {member, isInitialized, fromCache} = useStytchMember();
108
+ * if (!isInitialized) {
109
+ * return <p>Loading...</p>;
110
+ * }
111
+ * return (<h1>Welcome, {member.name}</h1>);
112
+ */
113
+ declare const useStytchMember: <TAssumeHydrated extends boolean = false>() => SWRMember<TAssumeHydrated>;
114
+ /**
115
+ * Returns the active member's Stytch member session.
116
+ * The Stytch SDKs are used for client-side authentication and session management.
117
+ * Check the isInitialized property to determine if the SDK has completed initialization.
118
+ * Check the fromCache property to determine if the session data is from persistent storage.
119
+ * @example
120
+ * const {session, isInitialized, fromCache} = useStytchMemberSession();
121
+ * useEffect(() => {
122
+ * if (!isInitialized) {
123
+ * return;
124
+ * }
125
+ * if (!session) {
126
+ * router.replace('/login')
127
+ * }
128
+ * }, [session, isInitialized]);
129
+ */
130
+ declare const useStytchMemberSession: <TAssumeHydrated extends boolean = false>() => SWRMemberSession<TAssumeHydrated>;
131
+ /**
132
+ * Returns the active Stytch organization.
133
+ * The Stytch SDKs are used for client-side authentication and session management.
134
+ * Check the isInitialized property to determine if the SDK has completed initialization.
135
+ * Check the fromCache property to determine if the organization data is from persistent storage.
136
+ * @example
137
+ * const {organization, isInitialized, fromCache} = useStytchOrganization();
138
+ * if (!isInitialized) {
139
+ * return <p>Loading...</p>;
140
+ * }
141
+ * return (<p>Welcome to {organization.organization_name}</p>);
142
+ */
143
+ declare const useStytchOrganization: <TAssumeHydrated extends boolean = false>() => SWROrganization<TAssumeHydrated>;
144
+ type SWRIsAuthorizedUninitialized = {
145
+ /**
146
+ * Whether the logged-in member is allowed to perform the specified action on the specified resource.
147
+ */
148
+ isAuthorized: false;
149
+ /**
150
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
151
+ */
152
+ fromCache: false;
153
+ /**
154
+ * If true, indicates that the SDK has completed initialization.
155
+ */
156
+ isInitialized: false;
157
+ };
158
+ type SWRIsAuthorizedInitialized = {
159
+ /**
160
+ * Whether the logged-in member is allowed to perform the specified action on the specified resource.
161
+ */
162
+ isAuthorized: boolean;
163
+ /**
164
+ * If true, indicates that the value returned is from the application cache and a state refresh is in progress.
165
+ */
166
+ fromCache: boolean;
167
+ /**
168
+ * If true, indicates that the SDK has completed initialization.
169
+ */
170
+ isInitialized: boolean;
171
+ };
172
+ type SWRIsAuthorized<TAlwaysInitialized extends boolean> = TAlwaysInitialized extends true ? SWRIsAuthorizedInitialized : SWRIsAuthorizedInitialized | SWRIsAuthorizedUninitialized;
173
+ /**
174
+ * Determines whether the logged-in member is allowed to perform the specified action on the specified resource.
175
+ * Returns `true` if the member can perform the action, `false` otherwise.
176
+ *
177
+ * If the member is not logged in, this method will always return false.
178
+ * If the resource or action provided are not valid for the configured RBAC policy, this method will return false.
179
+ *
180
+ * Remember - authorization checks for sensitive actions should always occur on the backend as well.
181
+ * @example
182
+ * const { isAuthorized } = useStytchIsAuthorized<Permissions>('documents', 'edit');
183
+ * return <button disabled={!isAuthorized}>Edit</button>
184
+ */
185
+ declare const useStytchIsAuthorized: <TAssumeHydrated extends boolean = false>(resourceId: string, action: string) => SWRIsAuthorized<TAssumeHydrated>;
186
+ /**
187
+ * Returns the Stytch B2B client stored in the Stytch context.
188
+ *
189
+ * @example
190
+ * const stytch = useStytchB2BClient();
191
+ * useEffect(() => {
192
+ * stytch.magicLinks.authenticate('...')
193
+ * }, [stytch]);
194
+ */
195
+ declare const useStytchB2BClient: <TProjectConfiguration extends Partial<import("@stytch/core/public").StytchProjectConfiguration> = Stytch.DefaultProjectConfiguration>() => StytchB2BHeadlessClient<TProjectConfiguration>;
196
+ declare const withStytchB2BClient: <T extends object, TProjectConfiguration extends Partial<import("@stytch/core/public").StytchProjectConfiguration>>(Component: React.ComponentType<T & {
197
+ stytch: StytchB2BHeadlessClient<TProjectConfiguration>;
198
+ }>) => React.ComponentType<T>;
199
+ declare const withStytchMember: <T extends object, TAssumeHydrated extends boolean = false>(Component: React.ComponentType<T & {
200
+ stytchMember: Member | null;
201
+ stytchMemberIsInitialized: boolean;
202
+ stytchMemberIsFromCache: boolean;
203
+ }>) => React.ComponentType<T>;
204
+ declare const withStytchMemberSession: <T extends object, TAssumeHydrated extends boolean = false>(Component: React.ComponentType<T & {
205
+ stytchMemberSession: MemberSession | null;
206
+ stytchMemberSessionIsInitialized: boolean;
207
+ stytchMemberSessionIsFromCache: boolean;
208
+ }>) => React.ComponentType<T>;
209
+ declare const withStytchOrganization: <T extends object, TAssumeHydrated extends boolean = false>(Component: React.ComponentType<T & {
210
+ stytchOrganization: Organization | null;
211
+ stytchOrganizationIsInitialized: boolean;
212
+ stytchOrganizationIsFromCache: boolean;
213
+ }>) => React.ComponentType<T>;
214
+ /**
215
+ * Wrap your component with this HOC in order to receive the permissions for the logged-in member.
216
+ * Evaluates all permissions granted to the logged-in member.
217
+ * Returns a Record<RoleId, Record<Action, boolean>> response indicating the member's permissions.
218
+ * Each boolean will be `true` if the member can perform the action, `false` otherwise.
219
+ *
220
+ * If the member is not logged in, all values will be false.
221
+ *
222
+ * Remember - authorization checks for sensitive actions should always occur on the backend as well.
223
+ * @example
224
+ * type Permissions = {
225
+ * document: 'create' | 'read' | 'write
226
+ * image: 'create' | 'read'
227
+ * }
228
+ *
229
+ * const MyComponent = (props) => {
230
+ * const canEditDocuments = props.stytchPermissions.document.edit;
231
+ * const canReadImages = props.stytchPermissions.image.read;
232
+ * }
233
+ * return withStytchPermissions<Permissions>(MyComponent)
234
+ */
235
+ declare const withStytchPermissions: <Permissions_1 extends Record<string, string>, T extends object>(Component: React.ComponentType<T & {
236
+ stytchPermissions: PermissionsMap<Permissions_1>;
237
+ }>) => React.ComponentType<T>;
238
+ interface StytchB2BProviderProps<TProjectConfiguration extends StytchProjectConfigurationInput = Stytch.DefaultProjectConfiguration> {
239
+ /**
240
+ * A Stytch client instance, created using either {@link createStytchHeadlessClient} or {@link createStytchUIClient}
241
+ */
242
+ stytch: StytchB2BClient<TProjectConfiguration>;
243
+ /**
244
+ * When true, the provider will assume that the component will only be
245
+ * rendered in a browser environment, either in a single-page application or
246
+ * after completing hydration of a server-rendered application. This allows
247
+ * cached values to be retrieved from the browser on the first render, meaning
248
+ * that the `isInitialized` value returned from Stytch hooks will be `true`
249
+ * starting from the first render.
250
+ *
251
+ * When `false`, the provider will defer initialization until after the first
252
+ * render, and `isInitialized` will initially be `false`.
253
+ *
254
+ * This value defaults to `false` in `@stytch/nextjs`.
255
+ */
256
+ assumeHydrated?: boolean;
257
+ children?: ReactNode;
258
+ }
259
+ /**
260
+ * The Stytch Context Provider.
261
+ * Wrap your application with this component in order to use Stytch everywhere in your app.
262
+ * @example
263
+ * const stytch = createStytchB2BHeadlessClient('public-token-<find yours in the stytch dashboard>')
264
+ *
265
+ * return (
266
+ * <StytchB2BProvider stytch={stytch}>
267
+ * <App />
268
+ * </StytchB2BProvider>
269
+ * )
270
+ */
271
+ declare const StytchB2BProvider: <TProjectConfiguration extends Partial<import("@stytch/core/public").StytchProjectConfiguration> = Stytch.DefaultProjectConfiguration>({ stytch, children, assumeHydrated, }: StytchB2BProviderProps<TProjectConfiguration>) => JSX.Element;
272
+ export { useIsMounted__INTERNAL, isUIClient, useStytchMember, useStytchMemberSession, useStytchOrganization, useStytchIsAuthorized, useStytchB2BClient, withStytchB2BClient, withStytchMember, withStytchMemberSession, withStytchOrganization, withStytchPermissions, StytchB2BProviderProps, StytchB2BProvider };
@@ -1,17 +1,21 @@
1
- import React, { createContext, useContext, useEffect, useMemo } from 'react';
2
- import { i as invariant, u as useAsyncState, m as mergeWithStableProps, n as noProviderError, B as B2BProviderMustBeUniqueError, a as noSSRError } from './invariant-568a7633.js';
1
+ import React, { createContext, useContext, useEffect, useMemo, useCallback, useRef } from 'react';
2
+ import { i as invariant, u as useAsyncState, m as mergeWithStableProps } from './useIsomorphicLayoutEffect-1babb81e.js';
3
+ import { n as noProviderError, i as isStytchSSRProxy } from './StytchSSRProxy-34c789b5.js';
3
4
 
4
5
  const initialMember = {
5
6
  member: null,
6
7
  fromCache: false,
8
+ isInitialized: false,
7
9
  };
8
10
  const initialMemberSession = {
9
11
  session: null,
10
12
  fromCache: false,
13
+ isInitialized: false,
11
14
  };
12
15
  const initialOrganization = {
13
16
  organization: null,
14
17
  fromCache: false,
18
+ isInitialized: false,
15
19
  };
16
20
  const StytchB2BContext = createContext({ isMounted: false });
17
21
  const StytchMemberContext = createContext(initialMember);
@@ -23,11 +27,15 @@ const isUIClient = (client) => {
23
27
  };
24
28
  /**
25
29
  * Returns the active Member.
30
+ * The Stytch SDKs are used for client-side authentication and session management.
31
+ * Check the isInitialized property to determine if the SDK has completed initialization.
26
32
  * Check the fromCache property to determine if the member data is from persistent storage.
27
33
  * @example
28
- * const {member} = useStytchMember();
34
+ * const {member, isInitialized, fromCache} = useStytchMember();
35
+ * if (!isInitialized) {
36
+ * return <p>Loading...</p>;
37
+ * }
29
38
  * return (<h1>Welcome, {member.name}</h1>);
30
- * @returns A {@link SWRUser}
31
39
  */
32
40
  const useStytchMember = () => {
33
41
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchMember', 'StytchB2BProvider'));
@@ -35,14 +43,19 @@ const useStytchMember = () => {
35
43
  };
36
44
  /**
37
45
  * Returns the active member's Stytch member session.
46
+ * The Stytch SDKs are used for client-side authentication and session management.
47
+ * Check the isInitialized property to determine if the SDK has completed initialization.
48
+ * Check the fromCache property to determine if the session data is from persistent storage.
38
49
  * @example
39
- * const { session } = useStytchMemberSession();
50
+ * const {session, isInitialized, fromCache} = useStytchMemberSession();
40
51
  * useEffect(() => {
52
+ * if (!isInitialized) {
53
+ * return;
54
+ * }
41
55
  * if (!session) {
42
56
  * router.replace('/login')
43
57
  * }
44
- * }, [session]);
45
- * @returns A {@link SWRMemberSession}
58
+ * }, [session, isInitialized]);
46
59
  */
47
60
  const useStytchMemberSession = () => {
48
61
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchMemberSession', 'StytchB2BProvider'));
@@ -50,10 +63,15 @@ const useStytchMemberSession = () => {
50
63
  };
51
64
  /**
52
65
  * Returns the active Stytch organization.
66
+ * The Stytch SDKs are used for client-side authentication and session management.
67
+ * Check the isInitialized property to determine if the SDK has completed initialization.
68
+ * Check the fromCache property to determine if the organization data is from persistent storage.
53
69
  * @example
54
- * const { organization } = useStytchOrganization();
55
- * return organization ? <p>Welcome to {organization.organization_name}</p> : <p>Log in to continue!</p>;
56
- * @returns A {@link SWROrganization}
70
+ * const {organization, isInitialized, fromCache} = useStytchOrganization();
71
+ * if (!isInitialized) {
72
+ * return <p>Loading...</p>;
73
+ * }
74
+ * return (<p>Welcome to {organization.organization_name}</p>);
57
75
  */
58
76
  const useStytchOrganization = () => {
59
77
  invariant(useIsMounted__INTERNAL(), noProviderError('useStytchOrganization', 'StytchB2BProvider'));
@@ -68,7 +86,7 @@ const useStytchOrganization = () => {
68
86
  *
69
87
  * Remember - authorization checks for sensitive actions should always occur on the backend as well.
70
88
  * @example
71
- * const isAuthorized = useStytchIsAuthorized<Permissions>('documents', 'edit');
89
+ * const { isAuthorized } = useStytchIsAuthorized<Permissions>('documents', 'edit');
72
90
  * return <button disabled={!isAuthorized}>Edit</button>
73
91
  */
74
92
  const useStytchIsAuthorized = (resourceId, action) => {
@@ -76,14 +94,27 @@ const useStytchIsAuthorized = (resourceId, action) => {
76
94
  const client = useStytchB2BClient();
77
95
  const { session } = useStytchMemberSession();
78
96
  const [isAuthorized, setIsAuthorized] = useAsyncState({
79
- fromCache: true,
80
- isAuthorized: client.rbac.isAuthorizedSync(resourceId, action),
97
+ isInitialized: false,
98
+ fromCache: false,
99
+ isAuthorized: false,
81
100
  });
82
101
  useEffect(() => {
83
- client.rbac.isAuthorized(resourceId, action).then((isAuthorized) => setIsAuthorized({
84
- fromCache: false,
85
- isAuthorized,
86
- }));
102
+ if (isStytchSSRProxy(client)) {
103
+ return;
104
+ }
105
+ setIsAuthorized({
106
+ isInitialized: true,
107
+ fromCache: true,
108
+ isAuthorized: client.rbac.isAuthorizedSync(resourceId, action),
109
+ });
110
+ }, [action, client, resourceId, setIsAuthorized]);
111
+ useEffect(() => {
112
+ if (isStytchSSRProxy(client)) {
113
+ return;
114
+ }
115
+ client.rbac.isAuthorized(resourceId, action).then((isAuthorized) => {
116
+ setIsAuthorized({ isAuthorized, fromCache: false, isInitialized: true });
117
+ });
87
118
  }, [client, session === null || session === void 0 ? void 0 : session.roles, resourceId, action, setIsAuthorized]);
88
119
  return isAuthorized;
89
120
  };
@@ -112,26 +143,26 @@ const withStytchB2BClient = (Component) => {
112
143
  const withStytchMember = (Component) => {
113
144
  const WithStytchUser = (props) => {
114
145
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchMember', 'StytchB2BProvider'));
115
- const { member, fromCache } = useStytchMember();
116
- return React.createElement(Component, Object.assign({}, props, { stytchMember: member, stytchMemberIsFromCache: fromCache }));
146
+ const { member, isInitialized, fromCache } = useStytchMember();
147
+ return (React.createElement(Component, Object.assign({}, props, { stytchMember: member, stytchMemberIsInitialized: isInitialized, stytchMemberIsFromCache: fromCache })));
117
148
  };
118
- WithStytchUser.displayName = `withStytchUser(${Component.displayName || Component.name || 'Component'})`;
149
+ WithStytchUser.displayName = `withStytchMember(${Component.displayName || Component.name || 'Component'})`;
119
150
  return WithStytchUser;
120
151
  };
121
152
  const withStytchMemberSession = (Component) => {
122
153
  const WithStytchSession = (props) => {
123
154
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchMemberSession', 'StytchB2BProvider'));
124
- const { session, fromCache } = useStytchMemberSession();
125
- return React.createElement(Component, Object.assign({}, props, { stytchMemberSession: session, stytchMemberSessionIsFromCache: fromCache }));
155
+ const { session, isInitialized, fromCache } = useStytchMemberSession();
156
+ return (React.createElement(Component, Object.assign({}, props, { stytchMemberSession: session, stytchMemberSessionIsInitialized: isInitialized, stytchMemberSessionIsFromCache: fromCache })));
126
157
  };
127
- WithStytchSession.displayName = `withStytchSession(${Component.displayName || Component.name || 'Component'})`;
158
+ WithStytchSession.displayName = `withStytchMemberSession(${Component.displayName || Component.name || 'Component'})`;
128
159
  return WithStytchSession;
129
160
  };
130
161
  const withStytchOrganization = (Component) => {
131
162
  const WithStytchOrganization = (props) => {
132
163
  invariant(useIsMounted__INTERNAL(), noProviderError('withStytchOrganization', 'StytchB2BProvider'));
133
- const { organization, fromCache } = useStytchOrganization();
134
- return React.createElement(Component, Object.assign({}, props, { stytchOrganization: organization, stytchOrganizationIsFromCache: fromCache }));
164
+ const { organization, isInitialized, fromCache } = useStytchOrganization();
165
+ return (React.createElement(Component, Object.assign({}, props, { stytchOrganization: organization, stytchOrganizationIsInitialized: isInitialized, stytchOrganizationIsFromCache: fromCache })));
135
166
  };
136
167
  WithStytchOrganization.displayName = `withStytchOrganization(${Component.displayName || Component.name || 'Component'})`;
137
168
  return WithStytchOrganization;
@@ -178,40 +209,54 @@ const withStytchPermissions = (Component) => {
178
209
  };
179
210
  /**
180
211
  * The Stytch Context Provider.
181
- * Wrap your application with this component in the root file in order to use Stytch everywhere in your app.
212
+ * Wrap your application with this component in order to use Stytch everywhere in your app.
182
213
  * @example
183
- * const stytch = new StytchB2BHeadlessClient('public-token-<find yours in the stytch dashboard>')
214
+ * const stytch = createStytchB2BHeadlessClient('public-token-<find yours in the stytch dashboard>')
184
215
  *
185
- * ReactDOM.render(
216
+ * return (
186
217
  * <StytchB2BProvider stytch={stytch}>
187
218
  * <App />
188
- * </StytchProvider>,
189
- * document.getElementById('root'),
219
+ * </StytchB2BProvider>
190
220
  * )
191
221
  */
192
- const StytchB2BProvider = ({ stytch, children, }) => {
193
- invariant(!useIsMounted__INTERNAL(), B2BProviderMustBeUniqueError);
194
- invariant(typeof window !== 'undefined', noSSRError);
222
+ const StytchB2BProvider = ({ stytch, children, assumeHydrated = false, }) => {
223
+ invariant(!useIsMounted__INTERNAL(), 'You cannot render a <StytchB2BProvider> inside another <StytchB2BProvider>.');
224
+ invariant(!assumeHydrated || typeof window !== 'undefined', 'The `assumeHydrated` prop must be set to `false` when using StytchB2BProvider in a server environment.');
195
225
  const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
196
- const [{ member, session, organization }, setClientState] = useAsyncState({
197
- session: stytch.session.getInfo(),
198
- member: stytch.self.getInfo(),
199
- organization: stytch.organization.getInfo(),
200
- });
201
- useEffect(() => stytch.onStateChange(() => {
202
- setClientState((oldState) => {
203
- const newState = {
204
- session: stytch.session.getInfo(),
205
- member: stytch.self.getInfo(),
206
- organization: stytch.organization.getInfo(),
207
- };
208
- return mergeWithStableProps(oldState, newState);
209
- });
210
- }), [setClientState, stytch]);
226
+ const getHydratedState = useCallback(() => {
227
+ return {
228
+ member: Object.assign(Object.assign({}, stytch.self.getInfo()), { isInitialized: true }),
229
+ session: Object.assign(Object.assign({}, stytch.session.getInfo()), { isInitialized: true }),
230
+ organization: Object.assign(Object.assign({}, stytch.organization.getInfo()), { isInitialized: true }),
231
+ };
232
+ }, [stytch]);
233
+ const getInitialState = () => {
234
+ return {
235
+ member: initialMember,
236
+ session: initialMemberSession,
237
+ organization: initialOrganization,
238
+ };
239
+ };
240
+ const [{ member, session, organization }, setClientState] = useAsyncState(() => assumeHydrated ? getHydratedState() : getInitialState());
241
+ // Store the initial value of `assumeHydrated` in a ref, because it is
242
+ // logically only relevant for the first render
243
+ const assumeHydratedRef = useRef(assumeHydrated);
244
+ useEffect(() => {
245
+ if (isStytchSSRProxy(stytch)) {
246
+ return;
247
+ }
248
+ const updateState = () => {
249
+ setClientState((oldState) => mergeWithStableProps(oldState, getHydratedState()));
250
+ };
251
+ if (!assumeHydratedRef.current) {
252
+ updateState();
253
+ }
254
+ return stytch.onStateChange(updateState);
255
+ }, [getHydratedState, setClientState, stytch]);
211
256
  return (React.createElement(StytchB2BContext.Provider, { value: ctx },
212
257
  React.createElement(StytchOrganizationContext.Provider, { value: organization },
213
258
  React.createElement(StytchMemberContext.Provider, { value: member },
214
259
  React.createElement(StytchMemberSessionContext.Provider, { value: session }, children)))));
215
260
  };
216
261
 
217
- export { StytchB2BProvider as S, useStytchB2BClient as a, useStytchMember as b, useStytchMemberSession as c, useStytchIsAuthorized as d, useStytchOrganization as e, withStytchMemberSession as f, withStytchMember as g, withStytchOrganization as h, isUIClient as i, withStytchPermissions as j, useIsMounted__INTERNAL as u, withStytchB2BClient as w };
262
+ export { StytchB2BProvider as S, useStytchB2BClient as a, useStytchMember as b, useStytchMemberSession as c, useStytchOrganization as d, useStytchIsAuthorized as e, withStytchMemberSession as f, withStytchOrganization as g, withStytchB2BClient as h, isUIClient as i, withStytchPermissions as j, useIsMounted__INTERNAL as u, withStytchMember as w };