@stytch/react 15.0.0 → 17.0.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.
- package/CHANGELOG.md +19 -0
- package/dist/b2b/index.d.ts +3 -3
- package/dist/b2b/index.esm.d.ts +3 -3
- package/dist/b2b/index.esm.js +71 -40
- package/dist/b2b/index.js +71 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.d.ts +1 -1
- package/dist/index.esm.js +79 -40
- package/dist/index.js +79 -40
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @stytch/react
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- a0fbe9f: Update minimum peer dependency on `@stytch/vanilla-js` to `^4.9.0`
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [a0fbe9f]
|
|
12
|
+
- Updated dependencies [a0fbe9f]
|
|
13
|
+
- @stytch/vanilla-js@4.9.0
|
|
14
|
+
|
|
15
|
+
## 16.0.0
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [f604dcb]
|
|
20
|
+
- @stytch/vanilla-js@4.7.0
|
|
21
|
+
|
|
3
22
|
## 15.0.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/dist/b2b/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Callbacks, StyleConfig, StytchB2BUIConfig } from "@stytch/vanilla-js";
|
|
|
11
11
|
type StytchB2BClient = StytchB2BHeadlessClient | StytchB2BUIClient;
|
|
12
12
|
type SWRMember = {
|
|
13
13
|
/**
|
|
14
|
-
* Either the active {@link Member} object, or null if the
|
|
14
|
+
* Either the active {@link Member} object, or null if the member is not logged in.
|
|
15
15
|
*/
|
|
16
16
|
member: Member | null;
|
|
17
17
|
/**
|
|
@@ -21,7 +21,7 @@ type SWRMember = {
|
|
|
21
21
|
};
|
|
22
22
|
type SWRMemberSession = {
|
|
23
23
|
/**
|
|
24
|
-
* Either the active {@link MemberSession} object, or null if the
|
|
24
|
+
* Either the active {@link MemberSession} object, or null if the member is not logged in.
|
|
25
25
|
*/
|
|
26
26
|
session: MemberSession | null;
|
|
27
27
|
/**
|
|
@@ -31,7 +31,7 @@ type SWRMemberSession = {
|
|
|
31
31
|
};
|
|
32
32
|
type SWROrganization = {
|
|
33
33
|
/**
|
|
34
|
-
* Either the active {@link Organization} object, or null if the
|
|
34
|
+
* Either the active {@link Organization} object, or null if the member is not logged in.
|
|
35
35
|
*/
|
|
36
36
|
organization: Organization | null;
|
|
37
37
|
/**
|
package/dist/b2b/index.esm.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Callbacks, StyleConfig, StytchB2BUIConfig } from "@stytch/vanilla-js";
|
|
|
11
11
|
type StytchB2BClient = StytchB2BHeadlessClient | StytchB2BUIClient;
|
|
12
12
|
type SWRMember = {
|
|
13
13
|
/**
|
|
14
|
-
* Either the active {@link Member} object, or null if the
|
|
14
|
+
* Either the active {@link Member} object, or null if the member is not logged in.
|
|
15
15
|
*/
|
|
16
16
|
member: Member | null;
|
|
17
17
|
/**
|
|
@@ -21,7 +21,7 @@ type SWRMember = {
|
|
|
21
21
|
};
|
|
22
22
|
type SWRMemberSession = {
|
|
23
23
|
/**
|
|
24
|
-
* Either the active {@link MemberSession} object, or null if the
|
|
24
|
+
* Either the active {@link MemberSession} object, or null if the member is not logged in.
|
|
25
25
|
*/
|
|
26
26
|
session: MemberSession | null;
|
|
27
27
|
/**
|
|
@@ -31,7 +31,7 @@ type SWRMemberSession = {
|
|
|
31
31
|
};
|
|
32
32
|
type SWROrganization = {
|
|
33
33
|
/**
|
|
34
|
-
* Either the active {@link Organization} object, or null if the
|
|
34
|
+
* Either the active {@link Organization} object, or null if the member is not logged in.
|
|
35
35
|
*/
|
|
36
36
|
organization: Organization | null;
|
|
37
37
|
/**
|
package/dist/b2b/index.esm.js
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
|
|
2
2
|
|
|
3
|
+
const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
|
|
4
|
+
// If comparing functions, this may need some work. Not sure the
|
|
5
|
+
// best path for this: compare instance (what it currently does),
|
|
6
|
+
// stringify and compare, etc.
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
const deepEqual = (a, b) => {
|
|
9
|
+
// Ensures type is the same
|
|
10
|
+
if (typeof a !== typeof b)
|
|
11
|
+
return false;
|
|
12
|
+
// arrays, null, and objects all have type 'object'
|
|
13
|
+
if (a === null || b === null)
|
|
14
|
+
return a === b;
|
|
15
|
+
if (typeof a === 'object') {
|
|
16
|
+
if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
|
|
17
|
+
return false;
|
|
18
|
+
return Object.entries(a)
|
|
19
|
+
.filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
|
|
20
|
+
.every(([k, v]) => deepEqual(v, b[k]));
|
|
21
|
+
}
|
|
22
|
+
// boolean, string, number, undefined
|
|
23
|
+
return a === b;
|
|
24
|
+
};
|
|
25
|
+
return deepEqual;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const deepEqual = createDeepEqual();
|
|
29
|
+
/**
|
|
30
|
+
* Returns a version of `newValue` whose properties that are deeply equal to
|
|
31
|
+
* those in `oldValue` are replaced with those from `oldValue`. This provides a
|
|
32
|
+
* limited form of "structural sharing" that provides a stable reference for
|
|
33
|
+
* unchanged slices of the object.
|
|
34
|
+
*
|
|
35
|
+
* If `oldValue` and `newValue` are referentially equal, the same value is
|
|
36
|
+
* returned.
|
|
37
|
+
*
|
|
38
|
+
* @param oldValue The old value
|
|
39
|
+
* @param newValue The new value
|
|
40
|
+
*/
|
|
41
|
+
const mergeWithStableProps = (oldValue, newValue) => {
|
|
42
|
+
// If the values are already referentially the same, just return the new value
|
|
43
|
+
if (oldValue === newValue) {
|
|
44
|
+
return newValue;
|
|
45
|
+
}
|
|
46
|
+
return Object.keys(oldValue).reduce((acc, key) => {
|
|
47
|
+
if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
|
|
48
|
+
acc[key] = oldValue[key];
|
|
49
|
+
}
|
|
50
|
+
return acc;
|
|
51
|
+
}, Object.assign({}, newValue));
|
|
52
|
+
};
|
|
53
|
+
|
|
3
54
|
const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
|
|
4
55
|
const B2BProviderMustBeUniqueError = 'You cannot render a <StytchB2BProvider> inside another <StytchB2BProvider>.';
|
|
5
56
|
const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
|
|
@@ -116,7 +167,7 @@ const useStytchIsAuthorized = (resourceId, action) => {
|
|
|
116
167
|
fromCache: false,
|
|
117
168
|
isAuthorized,
|
|
118
169
|
}));
|
|
119
|
-
}, [client, session === null || session === void 0 ? void 0 : session.roles, resourceId, action]);
|
|
170
|
+
}, [client, session === null || session === void 0 ? void 0 : session.roles, resourceId, action, setIsAuthorized]);
|
|
120
171
|
return isAuthorized;
|
|
121
172
|
};
|
|
122
173
|
/**
|
|
@@ -199,7 +250,7 @@ const withStytchPermissions = (Component) => {
|
|
|
199
250
|
client.rbac
|
|
200
251
|
.allPermissions()
|
|
201
252
|
.then((permissions) => setPermissions({ loaded: true, value: permissions }));
|
|
202
|
-
}, [client, session === null || session === void 0 ? void 0 : session.roles]);
|
|
253
|
+
}, [client, session === null || session === void 0 ? void 0 : session.roles, setPermissions]);
|
|
203
254
|
if (!permissions.loaded) {
|
|
204
255
|
return null;
|
|
205
256
|
}
|
|
@@ -225,46 +276,25 @@ const StytchB2BProvider = ({ stytch, children }) => {
|
|
|
225
276
|
invariant(!useIsMounted__INTERNAL(), B2BProviderMustBeUniqueError);
|
|
226
277
|
invariant(typeof window !== 'undefined', noSSRError);
|
|
227
278
|
const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
|
|
228
|
-
const [member,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const [session, setMemberSession] = useAsyncState({
|
|
233
|
-
session: stytch.session.getSync(),
|
|
234
|
-
fromCache: true,
|
|
235
|
-
});
|
|
236
|
-
const [organization, setOrganization] = useAsyncState({
|
|
237
|
-
organization: stytch.organization.getSync(),
|
|
238
|
-
fromCache: true,
|
|
279
|
+
const [{ member, session, organization }, setClientState] = useAsyncState({
|
|
280
|
+
session: stytch.session.getInfo(),
|
|
281
|
+
member: stytch.self.getInfo(),
|
|
282
|
+
organization: stytch.organization.getInfo(),
|
|
239
283
|
});
|
|
240
|
-
useEffect(() => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
organization,
|
|
251
|
-
fromCache: false,
|
|
252
|
-
}));
|
|
253
|
-
return () => {
|
|
254
|
-
unsubscribeMember();
|
|
255
|
-
unsubscribeSession();
|
|
256
|
-
unsubscribeOrganization();
|
|
257
|
-
};
|
|
258
|
-
}, [stytch, setMember, setMemberSession, setOrganization]);
|
|
259
|
-
// TODO (SDK-813): Remove this when we have a single top-level onChange handler
|
|
260
|
-
const allValuesReady = !!member.member === !!session.session && !!session.session === !!organization.organization;
|
|
261
|
-
const finalValues = allValuesReady
|
|
262
|
-
? { member, session, organization }
|
|
263
|
-
: { member: initialMember, session: initialMemberSession, organization: initialOrganization };
|
|
284
|
+
useEffect(() => stytch.onStateChange(() => {
|
|
285
|
+
setClientState((oldState) => {
|
|
286
|
+
const newState = {
|
|
287
|
+
session: stytch.session.getInfo(),
|
|
288
|
+
member: stytch.self.getInfo(),
|
|
289
|
+
organization: stytch.organization.getInfo(),
|
|
290
|
+
};
|
|
291
|
+
return mergeWithStableProps(oldState, newState);
|
|
292
|
+
});
|
|
293
|
+
}), [setClientState, stytch]);
|
|
264
294
|
return (React.createElement(StytchB2BContext.Provider, { value: ctx },
|
|
265
|
-
React.createElement(StytchOrganizationContext.Provider, { value:
|
|
266
|
-
React.createElement(StytchMemberContext.Provider, { value:
|
|
267
|
-
React.createElement(StytchMemberSessionContext.Provider, { value:
|
|
295
|
+
React.createElement(StytchOrganizationContext.Provider, { value: organization },
|
|
296
|
+
React.createElement(StytchMemberContext.Provider, { value: member },
|
|
297
|
+
React.createElement(StytchMemberSessionContext.Provider, { value: session }, children)))));
|
|
268
298
|
};
|
|
269
299
|
|
|
270
300
|
/**
|
|
@@ -322,6 +352,7 @@ const StytchB2B = ({ styles, callbacks, config }) => {
|
|
|
322
352
|
elementId: `#${containerEl.current.id}`,
|
|
323
353
|
styles,
|
|
324
354
|
});
|
|
355
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- SDK-1354
|
|
325
356
|
}, [stytchClient, styles, callbacks]);
|
|
326
357
|
return React.createElement("div", { ref: containerEl });
|
|
327
358
|
};
|
package/dist/b2b/index.js
CHANGED
|
@@ -8,6 +8,57 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
8
8
|
|
|
9
9
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
10
10
|
|
|
11
|
+
const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
|
|
12
|
+
// If comparing functions, this may need some work. Not sure the
|
|
13
|
+
// best path for this: compare instance (what it currently does),
|
|
14
|
+
// stringify and compare, etc.
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const deepEqual = (a, b) => {
|
|
17
|
+
// Ensures type is the same
|
|
18
|
+
if (typeof a !== typeof b)
|
|
19
|
+
return false;
|
|
20
|
+
// arrays, null, and objects all have type 'object'
|
|
21
|
+
if (a === null || b === null)
|
|
22
|
+
return a === b;
|
|
23
|
+
if (typeof a === 'object') {
|
|
24
|
+
if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
|
|
25
|
+
return false;
|
|
26
|
+
return Object.entries(a)
|
|
27
|
+
.filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
|
|
28
|
+
.every(([k, v]) => deepEqual(v, b[k]));
|
|
29
|
+
}
|
|
30
|
+
// boolean, string, number, undefined
|
|
31
|
+
return a === b;
|
|
32
|
+
};
|
|
33
|
+
return deepEqual;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const deepEqual = createDeepEqual();
|
|
37
|
+
/**
|
|
38
|
+
* Returns a version of `newValue` whose properties that are deeply equal to
|
|
39
|
+
* those in `oldValue` are replaced with those from `oldValue`. This provides a
|
|
40
|
+
* limited form of "structural sharing" that provides a stable reference for
|
|
41
|
+
* unchanged slices of the object.
|
|
42
|
+
*
|
|
43
|
+
* If `oldValue` and `newValue` are referentially equal, the same value is
|
|
44
|
+
* returned.
|
|
45
|
+
*
|
|
46
|
+
* @param oldValue The old value
|
|
47
|
+
* @param newValue The new value
|
|
48
|
+
*/
|
|
49
|
+
const mergeWithStableProps = (oldValue, newValue) => {
|
|
50
|
+
// If the values are already referentially the same, just return the new value
|
|
51
|
+
if (oldValue === newValue) {
|
|
52
|
+
return newValue;
|
|
53
|
+
}
|
|
54
|
+
return Object.keys(oldValue).reduce((acc, key) => {
|
|
55
|
+
if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
|
|
56
|
+
acc[key] = oldValue[key];
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}, Object.assign({}, newValue));
|
|
60
|
+
};
|
|
61
|
+
|
|
11
62
|
const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
|
|
12
63
|
const B2BProviderMustBeUniqueError = 'You cannot render a <StytchB2BProvider> inside another <StytchB2BProvider>.';
|
|
13
64
|
const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
|
|
@@ -124,7 +175,7 @@ const useStytchIsAuthorized = (resourceId, action) => {
|
|
|
124
175
|
fromCache: false,
|
|
125
176
|
isAuthorized,
|
|
126
177
|
}));
|
|
127
|
-
}, [client, session === null || session === void 0 ? void 0 : session.roles, resourceId, action]);
|
|
178
|
+
}, [client, session === null || session === void 0 ? void 0 : session.roles, resourceId, action, setIsAuthorized]);
|
|
128
179
|
return isAuthorized;
|
|
129
180
|
};
|
|
130
181
|
/**
|
|
@@ -207,7 +258,7 @@ const withStytchPermissions = (Component) => {
|
|
|
207
258
|
client.rbac
|
|
208
259
|
.allPermissions()
|
|
209
260
|
.then((permissions) => setPermissions({ loaded: true, value: permissions }));
|
|
210
|
-
}, [client, session === null || session === void 0 ? void 0 : session.roles]);
|
|
261
|
+
}, [client, session === null || session === void 0 ? void 0 : session.roles, setPermissions]);
|
|
211
262
|
if (!permissions.loaded) {
|
|
212
263
|
return null;
|
|
213
264
|
}
|
|
@@ -233,46 +284,25 @@ const StytchB2BProvider = ({ stytch, children }) => {
|
|
|
233
284
|
invariant(!useIsMounted__INTERNAL(), B2BProviderMustBeUniqueError);
|
|
234
285
|
invariant(typeof window !== 'undefined', noSSRError);
|
|
235
286
|
const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
|
|
236
|
-
const [member,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const [session, setMemberSession] = useAsyncState({
|
|
241
|
-
session: stytch.session.getSync(),
|
|
242
|
-
fromCache: true,
|
|
243
|
-
});
|
|
244
|
-
const [organization, setOrganization] = useAsyncState({
|
|
245
|
-
organization: stytch.organization.getSync(),
|
|
246
|
-
fromCache: true,
|
|
287
|
+
const [{ member, session, organization }, setClientState] = useAsyncState({
|
|
288
|
+
session: stytch.session.getInfo(),
|
|
289
|
+
member: stytch.self.getInfo(),
|
|
290
|
+
organization: stytch.organization.getInfo(),
|
|
247
291
|
});
|
|
248
|
-
React.useEffect(() => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
organization,
|
|
259
|
-
fromCache: false,
|
|
260
|
-
}));
|
|
261
|
-
return () => {
|
|
262
|
-
unsubscribeMember();
|
|
263
|
-
unsubscribeSession();
|
|
264
|
-
unsubscribeOrganization();
|
|
265
|
-
};
|
|
266
|
-
}, [stytch, setMember, setMemberSession, setOrganization]);
|
|
267
|
-
// TODO (SDK-813): Remove this when we have a single top-level onChange handler
|
|
268
|
-
const allValuesReady = !!member.member === !!session.session && !!session.session === !!organization.organization;
|
|
269
|
-
const finalValues = allValuesReady
|
|
270
|
-
? { member, session, organization }
|
|
271
|
-
: { member: initialMember, session: initialMemberSession, organization: initialOrganization };
|
|
292
|
+
React.useEffect(() => stytch.onStateChange(() => {
|
|
293
|
+
setClientState((oldState) => {
|
|
294
|
+
const newState = {
|
|
295
|
+
session: stytch.session.getInfo(),
|
|
296
|
+
member: stytch.self.getInfo(),
|
|
297
|
+
organization: stytch.organization.getInfo(),
|
|
298
|
+
};
|
|
299
|
+
return mergeWithStableProps(oldState, newState);
|
|
300
|
+
});
|
|
301
|
+
}), [setClientState, stytch]);
|
|
272
302
|
return (React__default['default'].createElement(StytchB2BContext.Provider, { value: ctx },
|
|
273
|
-
React__default['default'].createElement(StytchOrganizationContext.Provider, { value:
|
|
274
|
-
React__default['default'].createElement(StytchMemberContext.Provider, { value:
|
|
275
|
-
React__default['default'].createElement(StytchMemberSessionContext.Provider, { value:
|
|
303
|
+
React__default['default'].createElement(StytchOrganizationContext.Provider, { value: organization },
|
|
304
|
+
React__default['default'].createElement(StytchMemberContext.Provider, { value: member },
|
|
305
|
+
React__default['default'].createElement(StytchMemberSessionContext.Provider, { value: session }, children)))));
|
|
276
306
|
};
|
|
277
307
|
|
|
278
308
|
/**
|
|
@@ -330,6 +360,7 @@ const StytchB2B = ({ styles, callbacks, config }) => {
|
|
|
330
360
|
elementId: `#${containerEl.current.id}`,
|
|
331
361
|
styles,
|
|
332
362
|
});
|
|
363
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- SDK-1354
|
|
333
364
|
}, [stytchClient, styles, callbacks]);
|
|
334
365
|
return React__default['default'].createElement("div", { ref: containerEl });
|
|
335
366
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
// We need to import the StytchUIClient type to give the TSDoc parser a hint as to where it is from
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
-
import { Callbacks, StytchLoginConfig, StyleConfig,
|
|
6
|
+
import { Callbacks, StytchLoginConfig, StyleConfig, Session, StytchUIClient, User } from "@stytch/vanilla-js";
|
|
7
7
|
import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
|
|
8
8
|
interface StytchProps {
|
|
9
9
|
/**
|
package/dist/index.esm.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
// We need to import the StytchUIClient type to give the TSDoc parser a hint as to where it is from
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
-
import { Callbacks, StytchLoginConfig, StyleConfig,
|
|
6
|
+
import { Callbacks, StytchLoginConfig, StyleConfig, Session, StytchUIClient, User } from "@stytch/vanilla-js";
|
|
7
7
|
import { StytchHeadlessClient } from "@stytch/vanilla-js/headless";
|
|
8
8
|
interface StytchProps {
|
|
9
9
|
/**
|
package/dist/index.esm.js
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect, useCallback, createContext, useContext, useMemo, useLayoutEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
|
|
4
|
+
// If comparing functions, this may need some work. Not sure the
|
|
5
|
+
// best path for this: compare instance (what it currently does),
|
|
6
|
+
// stringify and compare, etc.
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
const deepEqual = (a, b) => {
|
|
9
|
+
// Ensures type is the same
|
|
10
|
+
if (typeof a !== typeof b)
|
|
11
|
+
return false;
|
|
12
|
+
// arrays, null, and objects all have type 'object'
|
|
13
|
+
if (a === null || b === null)
|
|
14
|
+
return a === b;
|
|
15
|
+
if (typeof a === 'object') {
|
|
16
|
+
if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
|
|
17
|
+
return false;
|
|
18
|
+
return Object.entries(a)
|
|
19
|
+
.filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
|
|
20
|
+
.every(([k, v]) => deepEqual(v, b[k]));
|
|
21
|
+
}
|
|
22
|
+
// boolean, string, number, undefined
|
|
23
|
+
return a === b;
|
|
24
|
+
};
|
|
25
|
+
return deepEqual;
|
|
26
|
+
};
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
const deepEqual = createDeepEqual();
|
|
29
|
+
/**
|
|
30
|
+
* Returns a version of `newValue` whose properties that are deeply equal to
|
|
31
|
+
* those in `oldValue` are replaced with those from `oldValue`. This provides a
|
|
32
|
+
* limited form of "structural sharing" that provides a stable reference for
|
|
33
|
+
* unchanged slices of the object.
|
|
34
|
+
*
|
|
35
|
+
* If `oldValue` and `newValue` are referentially equal, the same value is
|
|
36
|
+
* returned.
|
|
37
|
+
*
|
|
38
|
+
* @param oldValue The old value
|
|
39
|
+
* @param newValue The new value
|
|
40
|
+
*/
|
|
41
|
+
const mergeWithStableProps = (oldValue, newValue) => {
|
|
42
|
+
// If the values are already referentially the same, just return the new value
|
|
43
|
+
if (oldValue === newValue) {
|
|
44
|
+
return newValue;
|
|
45
|
+
}
|
|
46
|
+
return Object.keys(oldValue).reduce((acc, key) => {
|
|
47
|
+
if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
|
|
48
|
+
acc[key] = oldValue[key];
|
|
49
|
+
}
|
|
50
|
+
return acc;
|
|
51
|
+
}, Object.assign({}, newValue));
|
|
52
|
+
};
|
|
18
53
|
|
|
19
54
|
// useState can cause memory leaks if it is set after the component unmounted. For example, if it is
|
|
20
55
|
// set after `await`, or in a `then`, `catch`, or `finally`, or in a setTimout/setInterval.
|
|
@@ -33,6 +68,22 @@ const useAsyncState = (initialState) => {
|
|
|
33
68
|
return [state, setStateAction];
|
|
34
69
|
};
|
|
35
70
|
|
|
71
|
+
const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
|
|
72
|
+
const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
|
|
73
|
+
const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
|
|
74
|
+
Use the @stytch/nextjs library instead -
|
|
75
|
+
npm remove @stytch/react && npm install @stytch/nextjs
|
|
76
|
+
`;
|
|
77
|
+
const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
|
|
78
|
+
You must use the UI SDK to use UI elements.
|
|
79
|
+
Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.`;
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
function invariant(cond, message) {
|
|
83
|
+
if (!cond)
|
|
84
|
+
throw new Error(message);
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
const initialUser = {
|
|
37
88
|
user: null,
|
|
38
89
|
fromCache: false,
|
|
@@ -132,34 +183,22 @@ const StytchProvider = ({ stytch, children }) => {
|
|
|
132
183
|
invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
|
|
133
184
|
invariant(typeof window !== 'undefined', noSSRError);
|
|
134
185
|
const ctx = useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
|
|
135
|
-
const [user,
|
|
136
|
-
|
|
137
|
-
|
|
186
|
+
const [{ user, session }, setClientState] = useAsyncState({
|
|
187
|
+
session: stytch.session.getInfo(),
|
|
188
|
+
user: stytch.user.getInfo(),
|
|
138
189
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const unsubscribeSession = stytch.session.onChange((session) => setSession({
|
|
149
|
-
session,
|
|
150
|
-
fromCache: false,
|
|
151
|
-
}));
|
|
152
|
-
return () => {
|
|
153
|
-
unsubscribeUser();
|
|
154
|
-
unsubscribeSession();
|
|
155
|
-
};
|
|
156
|
-
}, [stytch, setUser, setSession]);
|
|
157
|
-
// TODO (SDK-813): Remove this when we have a single top-level onChange handler
|
|
158
|
-
const finalSess = !!session.session === !!user.user ? session : initialSession;
|
|
159
|
-
const finalUser = !!session.session === !!user.user ? user : initialUser;
|
|
190
|
+
useEffect(() => stytch.onStateChange(() => {
|
|
191
|
+
setClientState((oldState) => {
|
|
192
|
+
const newState = {
|
|
193
|
+
session: stytch.session.getInfo(),
|
|
194
|
+
user: stytch.user.getInfo(),
|
|
195
|
+
};
|
|
196
|
+
return mergeWithStableProps(oldState, newState);
|
|
197
|
+
});
|
|
198
|
+
}), [setClientState, stytch]);
|
|
160
199
|
return (React.createElement(StytchContext.Provider, { value: ctx },
|
|
161
|
-
React.createElement(StytchUserContext.Provider, { value:
|
|
162
|
-
React.createElement(StytchSessionContext.Provider, { value:
|
|
200
|
+
React.createElement(StytchUserContext.Provider, { value: user },
|
|
201
|
+
React.createElement(StytchSessionContext.Provider, { value: session }, children))));
|
|
163
202
|
};
|
|
164
203
|
|
|
165
204
|
/**
|
package/dist/index.js
CHANGED
|
@@ -8,21 +8,56 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
8
8
|
|
|
9
9
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
const createDeepEqual = ({ KEYS_TO_EXCLUDE = [] } = {}) => {
|
|
12
|
+
// If comparing functions, this may need some work. Not sure the
|
|
13
|
+
// best path for this: compare instance (what it currently does),
|
|
14
|
+
// stringify and compare, etc.
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const deepEqual = (a, b) => {
|
|
17
|
+
// Ensures type is the same
|
|
18
|
+
if (typeof a !== typeof b)
|
|
19
|
+
return false;
|
|
20
|
+
// arrays, null, and objects all have type 'object'
|
|
21
|
+
if (a === null || b === null)
|
|
22
|
+
return a === b;
|
|
23
|
+
if (typeof a === 'object') {
|
|
24
|
+
if (Object.keys(a).length !== Object.keys(b).length || Object.keys(a).some((k) => !(k in b)))
|
|
25
|
+
return false;
|
|
26
|
+
return Object.entries(a)
|
|
27
|
+
.filter(([k]) => !KEYS_TO_EXCLUDE.includes(k))
|
|
28
|
+
.every(([k, v]) => deepEqual(v, b[k]));
|
|
29
|
+
}
|
|
30
|
+
// boolean, string, number, undefined
|
|
31
|
+
return a === b;
|
|
32
|
+
};
|
|
33
|
+
return deepEqual;
|
|
34
|
+
};
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
const deepEqual = createDeepEqual();
|
|
37
|
+
/**
|
|
38
|
+
* Returns a version of `newValue` whose properties that are deeply equal to
|
|
39
|
+
* those in `oldValue` are replaced with those from `oldValue`. This provides a
|
|
40
|
+
* limited form of "structural sharing" that provides a stable reference for
|
|
41
|
+
* unchanged slices of the object.
|
|
42
|
+
*
|
|
43
|
+
* If `oldValue` and `newValue` are referentially equal, the same value is
|
|
44
|
+
* returned.
|
|
45
|
+
*
|
|
46
|
+
* @param oldValue The old value
|
|
47
|
+
* @param newValue The new value
|
|
48
|
+
*/
|
|
49
|
+
const mergeWithStableProps = (oldValue, newValue) => {
|
|
50
|
+
// If the values are already referentially the same, just return the new value
|
|
51
|
+
if (oldValue === newValue) {
|
|
52
|
+
return newValue;
|
|
53
|
+
}
|
|
54
|
+
return Object.keys(oldValue).reduce((acc, key) => {
|
|
55
|
+
if (key in newValue && deepEqual(oldValue[key], newValue[key])) {
|
|
56
|
+
acc[key] = oldValue[key];
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}, Object.assign({}, newValue));
|
|
60
|
+
};
|
|
26
61
|
|
|
27
62
|
// useState can cause memory leaks if it is set after the component unmounted. For example, if it is
|
|
28
63
|
// set after `await`, or in a `then`, `catch`, or `finally`, or in a setTimout/setInterval.
|
|
@@ -41,6 +76,22 @@ const useAsyncState = (initialState) => {
|
|
|
41
76
|
return [state, setStateAction];
|
|
42
77
|
};
|
|
43
78
|
|
|
79
|
+
const noProviderError = (item, provider = 'StytchProvider') => `${item} can only be used inside <${provider}>.`;
|
|
80
|
+
const providerMustBeUniqueError = 'You cannot render a <StytchProvider> inside another <StytchProvider>.';
|
|
81
|
+
const noSSRError = `The @stytch/react library is not meant for use with serverside environments like NextJS.
|
|
82
|
+
Use the @stytch/nextjs library instead -
|
|
83
|
+
npm remove @stytch/react && npm install @stytch/nextjs
|
|
84
|
+
`;
|
|
85
|
+
const noHeadlessClientError = `Tried to create a Stytch Login UI element using the Stytch Headless SDK.
|
|
86
|
+
You must use the UI SDK to use UI elements.
|
|
87
|
+
Please make sure you are importing from @stytch/vanilla-js and not from the @stytch/vanilla-js/headless.`;
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
function invariant(cond, message) {
|
|
91
|
+
if (!cond)
|
|
92
|
+
throw new Error(message);
|
|
93
|
+
}
|
|
94
|
+
|
|
44
95
|
const initialUser = {
|
|
45
96
|
user: null,
|
|
46
97
|
fromCache: false,
|
|
@@ -140,34 +191,22 @@ const StytchProvider = ({ stytch, children }) => {
|
|
|
140
191
|
invariant(!useIsMounted__INTERNAL(), providerMustBeUniqueError);
|
|
141
192
|
invariant(typeof window !== 'undefined', noSSRError);
|
|
142
193
|
const ctx = React.useMemo(() => ({ client: stytch, isMounted: true }), [stytch]);
|
|
143
|
-
const [user,
|
|
144
|
-
|
|
145
|
-
|
|
194
|
+
const [{ user, session }, setClientState] = useAsyncState({
|
|
195
|
+
session: stytch.session.getInfo(),
|
|
196
|
+
user: stytch.user.getInfo(),
|
|
146
197
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const unsubscribeSession = stytch.session.onChange((session) => setSession({
|
|
157
|
-
session,
|
|
158
|
-
fromCache: false,
|
|
159
|
-
}));
|
|
160
|
-
return () => {
|
|
161
|
-
unsubscribeUser();
|
|
162
|
-
unsubscribeSession();
|
|
163
|
-
};
|
|
164
|
-
}, [stytch, setUser, setSession]);
|
|
165
|
-
// TODO (SDK-813): Remove this when we have a single top-level onChange handler
|
|
166
|
-
const finalSess = !!session.session === !!user.user ? session : initialSession;
|
|
167
|
-
const finalUser = !!session.session === !!user.user ? user : initialUser;
|
|
198
|
+
React.useEffect(() => stytch.onStateChange(() => {
|
|
199
|
+
setClientState((oldState) => {
|
|
200
|
+
const newState = {
|
|
201
|
+
session: stytch.session.getInfo(),
|
|
202
|
+
user: stytch.user.getInfo(),
|
|
203
|
+
};
|
|
204
|
+
return mergeWithStableProps(oldState, newState);
|
|
205
|
+
});
|
|
206
|
+
}), [setClientState, stytch]);
|
|
168
207
|
return (React__default['default'].createElement(StytchContext.Provider, { value: ctx },
|
|
169
|
-
React__default['default'].createElement(StytchUserContext.Provider, { value:
|
|
170
|
-
React__default['default'].createElement(StytchSessionContext.Provider, { value:
|
|
208
|
+
React__default['default'].createElement(StytchUserContext.Provider, { value: user },
|
|
209
|
+
React__default['default'].createElement(StytchSessionContext.Provider, { value: session }, children))));
|
|
171
210
|
};
|
|
172
211
|
|
|
173
212
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stytch/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "Stytch's official React Library",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.esm.js",
|
|
@@ -33,15 +33,19 @@
|
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@babel/runtime": "7.18.6",
|
|
36
|
-
"@stytch/
|
|
36
|
+
"@stytch/js-utils": "0.0.0",
|
|
37
|
+
"@stytch/vanilla-js": "4.9.0",
|
|
37
38
|
"@testing-library/react": "14.0.0",
|
|
38
39
|
"eslint-config-custom": "0.0.1",
|
|
40
|
+
"react": "18.2.0",
|
|
41
|
+
"react-dom": "18.2.0",
|
|
39
42
|
"react-test-renderer": "18.0.0",
|
|
40
43
|
"rollup": "2.56.3",
|
|
41
|
-
"
|
|
44
|
+
"type-fest": "4.15.0",
|
|
45
|
+
"typescript": "5.3.3"
|
|
42
46
|
},
|
|
43
47
|
"peerDependencies": {
|
|
44
|
-
"@stytch/vanilla-js": "^4.
|
|
48
|
+
"@stytch/vanilla-js": "^4.9.0",
|
|
45
49
|
"react": ">= 17.0.2",
|
|
46
50
|
"react-dom": ">= 17.0.2"
|
|
47
51
|
}
|