From 32c4e52e40d40e15d843605f452e85188bfcde60 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Tue, 23 Jan 2024 15:31:23 +0200 Subject: [PATCH 1/5] feat: Add SSR support for SessionAuth (initialSessionAuthContext) --- lib/build/index2.js | 25 ++++++++++++++--------- lib/build/recipe/session/sessionAuth.d.ts | 5 +++++ lib/build/recipe/session/types.d.ts | 1 + lib/ts/recipe/session/sessionAuth.tsx | 19 ++++++++++++----- lib/ts/recipe/session/types.ts | 1 + package-lock.json | 12 +++++------ 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/lib/build/index2.js b/lib/build/index2.js index 084c5ad42..56632c50f 100644 --- a/lib/build/index2.js +++ b/lib/build/index2.js @@ -853,7 +853,7 @@ var SessionContext = React__default.default.createContext({ }); var SessionAuth = function (_a) { - var _b; + var _b, _c; var children = _a.children, props = genericComponentOverrideContext.__rest(_a, ["children"]); var requireAuth = React.useRef(props.requireAuth); @@ -865,20 +865,20 @@ var SessionAuth = function (_a) { } // Reusing the parent context was removed because it caused a redirect loop in an edge case // because it'd also reuse the invalid claims part until it loaded. - var _c = React.useState({ loading: true }), - context = _c[0], - setContext = _c[1]; + var _d = React.useState((_b = props.initialSessionAuthContext) !== null && _b !== void 0 ? _b : { loading: true }), + context = _d[0], + setContext = _d[1]; var session = React.useRef(); // We store this here, to prevent the list of called hooks changing even if a navigate hook is added later to SuperTokens. var navigateHookRef = React.useRef( - (_b = UI.getReactRouterDomWithCustomHistory()) === null || _b === void 0 ? void 0 : _b.useHistoryCustom + (_c = UI.getReactRouterDomWithCustomHistory()) === null || _c === void 0 ? void 0 : _c.useHistoryCustom ); var navigate; try { if (navigateHookRef.current) { navigate = navigateHookRef.current(); } - } catch (_d) { + } catch (_e) { // We catch and ignore errors here, because this is may throw if // the app is using react-router-dom but added a session auth outside of the router. } @@ -912,6 +912,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -952,6 +953,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -962,6 +964,7 @@ var SessionAuth = function (_a) { case 6: _b.trys.push([6, 9, , 11]); _a = { + preloaded: false, loading: false, doesSessionExist: true, invalidClaims: invalidClaims, @@ -999,6 +1002,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -1138,7 +1142,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { loading: false, invalidClaims: invalidClaims } + { preloaded: false, loading: false, invalidClaims: invalidClaims } ) ); return [ @@ -1165,6 +1169,7 @@ var SessionAuth = function (_a) { genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), { + preloaded: false, loading: false, invalidClaims: invalidClaims, accessDeniedValidatorError: failureRedirectInfo.failedClaim, @@ -1178,7 +1183,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { loading: false, invalidClaims: invalidClaims } + { preloaded: false, loading: false, invalidClaims: invalidClaims } ) ); return [2 /*return*/]; @@ -1186,7 +1191,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { loading: false, invalidClaims: [] } + { preloaded: false, loading: false, invalidClaims: [] } ) ); return [2 /*return*/]; @@ -1194,7 +1199,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { loading: false, invalidClaims: [] } + { preloaded: false, loading: false, invalidClaims: [] } ) ); if (props.onSessionExpired !== undefined) { diff --git a/lib/build/recipe/session/sessionAuth.d.ts b/lib/build/recipe/session/sessionAuth.d.ts index 95cdd4607..dd4ffe48f 100644 --- a/lib/build/recipe/session/sessionAuth.d.ts +++ b/lib/build/recipe/session/sessionAuth.d.ts @@ -1,8 +1,13 @@ import React from "react"; +import type { SessionContextType } from "./types"; import type { Navigate, ReactComponentClass, SessionClaimValidator, UserContext } from "../../types"; import type { PropsWithChildren } from "react"; import type { ClaimValidationError } from "supertokens-web-js/recipe/session"; export declare type SessionAuthProps = { + /** + * Initial context that is rendered on a server side (SSR). + */ + initialSessionAuthContext?: SessionContextType; /** * For a detailed explanation please see https://github.com/supertokens/supertokens-auth-react/issues/570 */ diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index 84028d572..e8def43c0 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -31,6 +31,7 @@ export declare type SessionContextUpdate = { accessTokenPayload: any; }; export declare type LoadedSessionContext = { + preloaded: boolean; loading: false; invalidClaims: ClaimValidationError[]; accessDeniedValidatorError?: ClaimValidationError; diff --git a/lib/ts/recipe/session/sessionAuth.tsx b/lib/ts/recipe/session/sessionAuth.tsx index 18e8043ae..eb235b0b5 100644 --- a/lib/ts/recipe/session/sessionAuth.tsx +++ b/lib/ts/recipe/session/sessionAuth.tsx @@ -34,6 +34,10 @@ import type { PropsWithChildren } from "react"; import type { ClaimValidationError } from "supertokens-web-js/recipe/session"; export type SessionAuthProps = { + /** + * Initial context that is rendered on a server side (SSR). + */ + initialSessionAuthContext?: SessionContextType; /** * For a detailed explanation please see https://github.com/supertokens/supertokens-auth-react/issues/570 */ @@ -67,7 +71,7 @@ const SessionAuth: React.FC> = ({ children, // Reusing the parent context was removed because it caused a redirect loop in an edge case // because it'd also reuse the invalid claims part until it loaded. - const [context, setContext] = useState({ loading: true }); + const [context, setContext] = useState(props.initialSessionAuthContext ?? { loading: true }); const session = useRef(); @@ -101,6 +105,7 @@ const SessionAuth: React.FC> = ({ children, if (sessionExists === false) { return { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -128,6 +133,7 @@ const SessionAuth: React.FC> = ({ children, throw err; } return { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -138,6 +144,7 @@ const SessionAuth: React.FC> = ({ children, try { return { + preloaded: false, loading: false, doesSessionExist: true, invalidClaims, @@ -159,6 +166,7 @@ const SessionAuth: React.FC> = ({ children, // This means that loading the access token or the userId failed // This may happen if the server cleared the error since the validation was done which should be extremely rare return { + preloaded: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -246,7 +254,7 @@ const SessionAuth: React.FC> = ({ children, userContext, }); if (failureRedirectInfo.redirectPath) { - setContext({ ...event.sessionContext, loading: false, invalidClaims }); + setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims }); return await SuperTokens.getInstanceOrThrow().redirectToUrl( failureRedirectInfo.redirectPath, navigate @@ -259,21 +267,22 @@ const SessionAuth: React.FC> = ({ children, }); return setContext({ ...event.sessionContext, + preloaded: false, loading: false, invalidClaims, accessDeniedValidatorError: failureRedirectInfo.failedClaim, }); } } - setContext({ ...event.sessionContext, loading: false, invalidClaims }); + setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims }); return; } case "SIGN_OUT": - setContext({ ...event.sessionContext, loading: false, invalidClaims: [] }); + setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims: [] }); return; case "UNAUTHORISED": - setContext({ ...event.sessionContext, loading: false, invalidClaims: [] }); + setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims: [] }); if (props.onSessionExpired !== undefined) { props.onSessionExpired(); } else if (props.requireAuth !== false && props.doRedirection !== false) { diff --git a/lib/ts/recipe/session/types.ts b/lib/ts/recipe/session/types.ts index 4050d4bd9..b2b0c3eb6 100644 --- a/lib/ts/recipe/session/types.ts +++ b/lib/ts/recipe/session/types.ts @@ -50,6 +50,7 @@ export type SessionContextUpdate = { }; export type LoadedSessionContext = { + preloaded: boolean; loading: false; invalidClaims: ClaimValidationError[]; accessDeniedValidatorError?: ClaimValidationError; diff --git a/package-lock.json b/package-lock.json index 9e01540a7..bae114388 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15917,9 +15917,9 @@ } }, "node_modules/supertokens-website": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.4.tgz", - "integrity": "sha512-ayWhEFvspUe26YhM1bq11ssEpnFCZIsoHZtJwJHgHsoflfMUKdgrzOix/bboI0PWJeNTUphHyZebw0ApctaS1Q==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.5.tgz", + "integrity": "sha512-NBOiKO3NV2VBAFgO+ZEmpOPVde2BwOjB6T0qjj2XaZX4jh+6yDGhrckJMwF5R0ucpTgOQXmBrpDnUJ5kFZlgiQ==", "peer": true, "dependencies": { "browser-tabs-lock": "^1.3.0", @@ -28941,9 +28941,9 @@ } }, "supertokens-website": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.4.tgz", - "integrity": "sha512-ayWhEFvspUe26YhM1bq11ssEpnFCZIsoHZtJwJHgHsoflfMUKdgrzOix/bboI0PWJeNTUphHyZebw0ApctaS1Q==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.5.tgz", + "integrity": "sha512-NBOiKO3NV2VBAFgO+ZEmpOPVde2BwOjB6T0qjj2XaZX4jh+6yDGhrckJMwF5R0ucpTgOQXmBrpDnUJ5kFZlgiQ==", "peer": true, "requires": { "browser-tabs-lock": "^1.3.0", From 4b51bb856093a9128209673485973f6fbce132c4 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Tue, 23 Jan 2024 15:39:50 +0200 Subject: [PATCH 2/5] Add preloaded to sessionAuth test file --- test/unit/recipe/session/sessionAuth.test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/recipe/session/sessionAuth.test.tsx b/test/unit/recipe/session/sessionAuth.test.tsx index f76daa20f..308cc1e0a 100644 --- a/test/unit/recipe/session/sessionAuth.test.tsx +++ b/test/unit/recipe/session/sessionAuth.test.tsx @@ -90,6 +90,7 @@ describe("SessionAuth", () => { doesSessionExist: true, invalidClaims: [], loading: false, + preloaded: false, }); }); @@ -216,6 +217,7 @@ describe("SessionAuth", () => { userId: "mock-id", invalidClaims: [], loading: false, + preloaded: false, }); // when @@ -508,6 +510,7 @@ describe("SessionAuth", () => { userId: "mock-id", invalidClaims: [{ validatorId: "st-test-claim", reason: "test-reason" }], loading: false, + preloaded: false, }); await act(() => @@ -664,6 +667,7 @@ describe("SessionAuth", () => { userId: "", invalidClaims: [], loading: false, + preloaded: false, }); // when @@ -692,6 +696,7 @@ describe("SessionAuth", () => { userId: "", invalidClaims: [], loading: false, + preloaded: false, }); // when From 9db1ee0ddcfb7493eae3f78cdffda63c7ae17f27 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Wed, 24 Jan 2024 11:00:44 +0200 Subject: [PATCH 3/5] Rename preloaded to isContextFromSSR --- lib/build/index2.js | 18 +++++++-------- lib/build/recipe/session/types.d.ts | 2 +- lib/ts/recipe/session/sessionAuth.tsx | 23 +++++++++++-------- lib/ts/recipe/session/types.ts | 2 +- test/unit/recipe/session/sessionAuth.test.tsx | 10 ++++---- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/build/index2.js b/lib/build/index2.js index 56632c50f..972347ef6 100644 --- a/lib/build/index2.js +++ b/lib/build/index2.js @@ -912,7 +912,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -953,7 +953,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -964,7 +964,7 @@ var SessionAuth = function (_a) { case 6: _b.trys.push([6, 9, , 11]); _a = { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: true, invalidClaims: invalidClaims, @@ -1002,7 +1002,7 @@ var SessionAuth = function (_a) { return [ 2 /*return*/, { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -1142,7 +1142,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { preloaded: false, loading: false, invalidClaims: invalidClaims } + { isContextFromSSR: false, loading: false, invalidClaims: invalidClaims } ) ); return [ @@ -1169,7 +1169,7 @@ var SessionAuth = function (_a) { genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), { - preloaded: false, + isContextFromSSR: false, loading: false, invalidClaims: invalidClaims, accessDeniedValidatorError: failureRedirectInfo.failedClaim, @@ -1183,7 +1183,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { preloaded: false, loading: false, invalidClaims: invalidClaims } + { isContextFromSSR: false, loading: false, invalidClaims: invalidClaims } ) ); return [2 /*return*/]; @@ -1191,7 +1191,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { preloaded: false, loading: false, invalidClaims: [] } + { isContextFromSSR: false, loading: false, invalidClaims: [] } ) ); return [2 /*return*/]; @@ -1199,7 +1199,7 @@ var SessionAuth = function (_a) { setContext( genericComponentOverrideContext.__assign( genericComponentOverrideContext.__assign({}, event.sessionContext), - { preloaded: false, loading: false, invalidClaims: [] } + { isContextFromSSR: false, loading: false, invalidClaims: [] } ) ); if (props.onSessionExpired !== undefined) { diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index e8def43c0..d200c6bd0 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -31,7 +31,7 @@ export declare type SessionContextUpdate = { accessTokenPayload: any; }; export declare type LoadedSessionContext = { - preloaded: boolean; + isContextFromSSR: boolean; loading: false; invalidClaims: ClaimValidationError[]; accessDeniedValidatorError?: ClaimValidationError; diff --git a/lib/ts/recipe/session/sessionAuth.tsx b/lib/ts/recipe/session/sessionAuth.tsx index eb235b0b5..e3637efe8 100644 --- a/lib/ts/recipe/session/sessionAuth.tsx +++ b/lib/ts/recipe/session/sessionAuth.tsx @@ -105,7 +105,7 @@ const SessionAuth: React.FC> = ({ children, if (sessionExists === false) { return { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -133,7 +133,7 @@ const SessionAuth: React.FC> = ({ children, throw err; } return { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -144,7 +144,7 @@ const SessionAuth: React.FC> = ({ children, try { return { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: true, invalidClaims, @@ -166,7 +166,7 @@ const SessionAuth: React.FC> = ({ children, // This means that loading the access token or the userId failed // This may happen if the server cleared the error since the validation was done which should be extremely rare return { - preloaded: false, + isContextFromSSR: false, loading: false, doesSessionExist: false, accessTokenPayload: {}, @@ -254,7 +254,12 @@ const SessionAuth: React.FC> = ({ children, userContext, }); if (failureRedirectInfo.redirectPath) { - setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims }); + setContext({ + ...event.sessionContext, + isContextFromSSR: false, + loading: false, + invalidClaims, + }); return await SuperTokens.getInstanceOrThrow().redirectToUrl( failureRedirectInfo.redirectPath, navigate @@ -267,22 +272,22 @@ const SessionAuth: React.FC> = ({ children, }); return setContext({ ...event.sessionContext, - preloaded: false, + isContextFromSSR: false, loading: false, invalidClaims, accessDeniedValidatorError: failureRedirectInfo.failedClaim, }); } } - setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims }); + setContext({ ...event.sessionContext, isContextFromSSR: false, loading: false, invalidClaims }); return; } case "SIGN_OUT": - setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims: [] }); + setContext({ ...event.sessionContext, isContextFromSSR: false, loading: false, invalidClaims: [] }); return; case "UNAUTHORISED": - setContext({ ...event.sessionContext, preloaded: false, loading: false, invalidClaims: [] }); + setContext({ ...event.sessionContext, isContextFromSSR: false, loading: false, invalidClaims: [] }); if (props.onSessionExpired !== undefined) { props.onSessionExpired(); } else if (props.requireAuth !== false && props.doRedirection !== false) { diff --git a/lib/ts/recipe/session/types.ts b/lib/ts/recipe/session/types.ts index b2b0c3eb6..f39baf6a9 100644 --- a/lib/ts/recipe/session/types.ts +++ b/lib/ts/recipe/session/types.ts @@ -50,7 +50,7 @@ export type SessionContextUpdate = { }; export type LoadedSessionContext = { - preloaded: boolean; + isContextFromSSR: boolean; loading: false; invalidClaims: ClaimValidationError[]; accessDeniedValidatorError?: ClaimValidationError; diff --git a/test/unit/recipe/session/sessionAuth.test.tsx b/test/unit/recipe/session/sessionAuth.test.tsx index 308cc1e0a..bf1118143 100644 --- a/test/unit/recipe/session/sessionAuth.test.tsx +++ b/test/unit/recipe/session/sessionAuth.test.tsx @@ -90,7 +90,7 @@ describe("SessionAuth", () => { doesSessionExist: true, invalidClaims: [], loading: false, - preloaded: false, + isContextFromSSR: false, }); }); @@ -217,7 +217,7 @@ describe("SessionAuth", () => { userId: "mock-id", invalidClaims: [], loading: false, - preloaded: false, + isContextFromSSR: false, }); // when @@ -510,7 +510,7 @@ describe("SessionAuth", () => { userId: "mock-id", invalidClaims: [{ validatorId: "st-test-claim", reason: "test-reason" }], loading: false, - preloaded: false, + isContextFromSSR: false, }); await act(() => @@ -667,7 +667,7 @@ describe("SessionAuth", () => { userId: "", invalidClaims: [], loading: false, - preloaded: false, + isContextFromSSR: false, }); // when @@ -696,7 +696,7 @@ describe("SessionAuth", () => { userId: "", invalidClaims: [], loading: false, - preloaded: false, + isContextFromSSR: false, }); // when From 6038587c3c2829781bf1e36b7ffc3ab616155bed Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Thu, 25 Jan 2024 13:27:15 +0200 Subject: [PATCH 4/5] changes to SSRSessionContextType --- lib/build/index2.js | 18 ++++++++++++------ lib/build/recipe/session/sessionAuth.d.ts | 4 ++-- lib/build/recipe/session/types.d.ts | 1 + lib/ts/recipe/session/sessionAuth.tsx | 18 +++++++++++++++--- lib/ts/recipe/session/types.ts | 2 ++ recipe/session/index.js | 1 + 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/build/index2.js b/lib/build/index2.js index 972347ef6..98fa2fe3d 100644 --- a/lib/build/index2.js +++ b/lib/build/index2.js @@ -853,7 +853,7 @@ var SessionContext = React__default.default.createContext({ }); var SessionAuth = function (_a) { - var _b, _c; + var _b; var children = _a.children, props = genericComponentOverrideContext.__rest(_a, ["children"]); var requireAuth = React.useRef(props.requireAuth); @@ -863,22 +863,28 @@ var SessionAuth = function (_a) { 'requireAuth prop should not change. If you are seeing this, it probably means that you are using SessionAuth in multiple routes with different values for requireAuth. To solve this, try adding the "key" prop to all uses of SessionAuth like ' ); } + var initialContext = props.initialSessionAuthContext + ? genericComponentOverrideContext.__assign( + genericComponentOverrideContext.__assign({}, props.initialSessionAuthContext), + { invalidClaims: [] } + ) + : { loading: true }; // Reusing the parent context was removed because it caused a redirect loop in an edge case // because it'd also reuse the invalid claims part until it loaded. - var _d = React.useState((_b = props.initialSessionAuthContext) !== null && _b !== void 0 ? _b : { loading: true }), - context = _d[0], - setContext = _d[1]; + var _c = React.useState(initialContext), + context = _c[0], + setContext = _c[1]; var session = React.useRef(); // We store this here, to prevent the list of called hooks changing even if a navigate hook is added later to SuperTokens. var navigateHookRef = React.useRef( - (_c = UI.getReactRouterDomWithCustomHistory()) === null || _c === void 0 ? void 0 : _c.useHistoryCustom + (_b = UI.getReactRouterDomWithCustomHistory()) === null || _b === void 0 ? void 0 : _b.useHistoryCustom ); var navigate; try { if (navigateHookRef.current) { navigate = navigateHookRef.current(); } - } catch (_e) { + } catch (_d) { // We catch and ignore errors here, because this is may throw if // the app is using react-router-dom but added a session auth outside of the router. } diff --git a/lib/build/recipe/session/sessionAuth.d.ts b/lib/build/recipe/session/sessionAuth.d.ts index dd4ffe48f..c9e7f60da 100644 --- a/lib/build/recipe/session/sessionAuth.d.ts +++ b/lib/build/recipe/session/sessionAuth.d.ts @@ -1,5 +1,5 @@ import React from "react"; -import type { SessionContextType } from "./types"; +import type { SSRSessionContextType } from "./types"; import type { Navigate, ReactComponentClass, SessionClaimValidator, UserContext } from "../../types"; import type { PropsWithChildren } from "react"; import type { ClaimValidationError } from "supertokens-web-js/recipe/session"; @@ -7,7 +7,7 @@ export declare type SessionAuthProps = { /** * Initial context that is rendered on a server side (SSR). */ - initialSessionAuthContext?: SessionContextType; + initialSessionAuthContext?: SSRSessionContextType; /** * For a detailed explanation please see https://github.com/supertokens/supertokens-auth-react/issues/570 */ diff --git a/lib/build/recipe/session/types.d.ts b/lib/build/recipe/session/types.d.ts index d200c6bd0..55894626e 100644 --- a/lib/build/recipe/session/types.d.ts +++ b/lib/build/recipe/session/types.d.ts @@ -41,6 +41,7 @@ export declare type SessionContextType = | { loading: true; }; +export declare type SSRSessionContextType = Omit; export declare type AccessDeniedThemeProps = { recipe: Session; navigate: Navigate; diff --git a/lib/ts/recipe/session/sessionAuth.tsx b/lib/ts/recipe/session/sessionAuth.tsx index e3637efe8..b6a27be3b 100644 --- a/lib/ts/recipe/session/sessionAuth.tsx +++ b/lib/ts/recipe/session/sessionAuth.tsx @@ -28,7 +28,12 @@ import Session from "./recipe"; import SessionContext from "./sessionContext"; import { getFailureRedirectionInfo } from "./utils"; -import type { LoadedSessionContext, RecipeEventWithSessionContext, SessionContextType } from "./types"; +import type { + LoadedSessionContext, + RecipeEventWithSessionContext, + SessionContextType, + SSRSessionContextType, +} from "./types"; import type { Navigate, ReactComponentClass, SessionClaimValidator, UserContext } from "../../types"; import type { PropsWithChildren } from "react"; import type { ClaimValidationError } from "supertokens-web-js/recipe/session"; @@ -37,7 +42,7 @@ export type SessionAuthProps = { /** * Initial context that is rendered on a server side (SSR). */ - initialSessionAuthContext?: SessionContextType; + initialSessionAuthContext?: SSRSessionContextType; /** * For a detailed explanation please see https://github.com/supertokens/supertokens-auth-react/issues/570 */ @@ -69,9 +74,16 @@ const SessionAuth: React.FC> = ({ children, ); } + const initialContext: SessionContextType = props.initialSessionAuthContext + ? { + ...props.initialSessionAuthContext, + invalidClaims: [], // invalidClaims is currently unsupported on server (SSR) + } + : { loading: true }; + // Reusing the parent context was removed because it caused a redirect loop in an edge case // because it'd also reuse the invalid claims part until it loaded. - const [context, setContext] = useState(props.initialSessionAuthContext ?? { loading: true }); + const [context, setContext] = useState(initialContext); const session = useRef(); diff --git a/lib/ts/recipe/session/types.ts b/lib/ts/recipe/session/types.ts index f39baf6a9..9214117a8 100644 --- a/lib/ts/recipe/session/types.ts +++ b/lib/ts/recipe/session/types.ts @@ -62,6 +62,8 @@ export type SessionContextType = loading: true; }; +export type SSRSessionContextType = Omit; + export type AccessDeniedThemeProps = { recipe: Session; navigate: Navigate; diff --git a/recipe/session/index.js b/recipe/session/index.js index cdb7217bd..23cbbbd3b 100644 --- a/recipe/session/index.js +++ b/recipe/session/index.js @@ -13,6 +13,7 @@ * under the License. */ "use strict"; +"use client"; // Important for NextJS support (SessionAuth is a client component) function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } From 5c080ef0d924df59558e73c051da8f7889730575 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Fri, 26 Jan 2024 10:11:58 +0200 Subject: [PATCH 5/5] add missing initialSessionAuthContext test --- lib/ts/recipe/session/index.ts | 3 +- test/unit/componentOverrides.test.tsx | 1 + .../recipe/emailpassword/signInUp.test.tsx | 1 + .../recipe/passwordless/signInUp.test.tsx | 1 + test/unit/recipe/session/sessionAuth.test.tsx | 30 ++++++++++++++++++- test/unit/recipe/thirdparty/signInUp.test.tsx | 1 + .../thirdpartyemailpassword/signInUp.test.tsx | 1 + .../thirdpartypasswordless/signInUp.test.tsx | 1 + 8 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/ts/recipe/session/index.ts b/lib/ts/recipe/session/index.ts index 4888ef6c3..c3f5a424a 100644 --- a/lib/ts/recipe/session/index.ts +++ b/lib/ts/recipe/session/index.ts @@ -25,7 +25,7 @@ import { RecipeComponentsOverrideContextProvider } from "./componentOverrideCont import Session from "./recipe"; import SessionAuthWrapper from "./sessionAuth"; import SessionContext from "./sessionContext"; -import { InputType, SessionContextType } from "./types"; +import { InputType, SessionContextType, SSRSessionContextType } from "./types"; import { useClaimValue as useClaimValueFunc } from "./useClaimValue"; import useSessionContextFunc from "./useSessionContext"; @@ -150,6 +150,7 @@ export { InputType, SessionContext, SessionContextType, + SSRSessionContextType, BooleanClaim, ClaimValidationError, ClaimValidationResult, diff --git a/test/unit/componentOverrides.test.tsx b/test/unit/componentOverrides.test.tsx index 11adfbfc0..75ac05cd6 100644 --- a/test/unit/componentOverrides.test.tsx +++ b/test/unit/componentOverrides.test.tsx @@ -214,6 +214,7 @@ describe("Components override per recipe provider", () => { ], }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [], diff --git a/test/unit/recipe/emailpassword/signInUp.test.tsx b/test/unit/recipe/emailpassword/signInUp.test.tsx index f6bc5b8ae..9bf27eb83 100644 --- a/test/unit/recipe/emailpassword/signInUp.test.tsx +++ b/test/unit/recipe/emailpassword/signInUp.test.tsx @@ -56,6 +56,7 @@ describe("EmailPassword.SignInAndUp", () => { }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [], diff --git a/test/unit/recipe/passwordless/signInUp.test.tsx b/test/unit/recipe/passwordless/signInUp.test.tsx index 53be0c7c8..104412f2b 100644 --- a/test/unit/recipe/passwordless/signInUp.test.tsx +++ b/test/unit/recipe/passwordless/signInUp.test.tsx @@ -57,6 +57,7 @@ describe("Passwordless.SingInUp", () => { }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [], diff --git a/test/unit/recipe/session/sessionAuth.test.tsx b/test/unit/recipe/session/sessionAuth.test.tsx index bf1118143..495d9af90 100644 --- a/test/unit/recipe/session/sessionAuth.test.tsx +++ b/test/unit/recipe/session/sessionAuth.test.tsx @@ -5,7 +5,7 @@ import SuperTokens from "../../../../lib/ts/superTokens"; import Session from "../../../../lib/ts/recipe/session/recipe"; import SessionAuth from "../../../../lib/ts/recipe/session/sessionAuth"; import SessionContext from "../../../../lib/ts/recipe/session/sessionContext"; -import { SessionContextType } from "../../../../lib/ts/recipe/session"; +import { SessionContextType, SSRSessionContextType } from "../../../../lib/ts/recipe/session"; import { PrimitiveClaim, SessionClaim, useClaimValue } from "../../../../lib/ts/recipe/session"; import * as utils from "supertokens-web-js/utils"; @@ -156,6 +156,34 @@ describe("SessionAuth", () => { result.unmount(); }); + + test("set initial SSR context (initialSessionAuthContext)", async () => { + const initialSessionAuthContext: SSRSessionContextType = { + isContextFromSSR: true, + loading: false, + doesSessionExist: true, + accessTokenPayload: { + foo: "bar", + }, + userId: "mock-ssr-user-id", + }; + + // when + const result = render( + + + + ); + + // then + expect(await result.findByText(/^userId:/)).toHaveTextContent(`userId: mock-ssr-user-id`); + expect(await result.findByText(/^accessTokenPayload:/)).toHaveTextContent( + `accessTokenPayload: ${JSON.stringify({ + foo: "bar", + })}` + ); + }); + test("set initial context", async () => { // when const result = render( diff --git a/test/unit/recipe/thirdparty/signInUp.test.tsx b/test/unit/recipe/thirdparty/signInUp.test.tsx index 3814af60b..a88ddd625 100644 --- a/test/unit/recipe/thirdparty/signInUp.test.tsx +++ b/test/unit/recipe/thirdparty/signInUp.test.tsx @@ -68,6 +68,7 @@ describe("ThirdParty.SignInAndUp", () => { }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [], diff --git a/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx b/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx index b249b8562..de22f745e 100644 --- a/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx +++ b/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx @@ -56,6 +56,7 @@ describe("ThirdPartyEmailPassword.SignInAndUp", () => { }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [], diff --git a/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx b/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx index 6fd489232..a84ffb557 100644 --- a/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx +++ b/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx @@ -57,6 +57,7 @@ describe("ThirdPartyPasswordless.SignInAndUp", () => { }); setMockResolvesSession({ + isContextFromSSR: false, userId: "mock-user-id", accessTokenPayload: {}, invalidClaims: [],