Skip to content

Commit 2799c91

Browse files
authored
fix(web): sync session state on auth events (#1835)
1 parent 32a93bc commit 2799c91

2 files changed

Lines changed: 49 additions & 14 deletions

File tree

packages/web/src/auth/compass/session/SessionProvider.test.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { waitFor } from "@testing-library/react";
1+
import { act, renderHook, waitFor } from "@testing-library/react";
22
import { Subject } from "rxjs";
33
import { authSlice } from "@web/ducks/auth/slices/auth.slice";
44
import { userMetadataSlice } from "@web/ducks/auth/slices/user-metadata.slice";
@@ -101,8 +101,9 @@ const { session } = require("@web/common/classes/Session") as {
101101
};
102102
};
103103

104-
const { sessionInit } =
104+
const { SessionProvider, sessionInit } =
105105
require("./SessionProvider") as typeof import("./SessionProvider");
106+
const { useSession } = require("./useSession") as typeof import("./useSession");
106107

107108
describe("SessionProvider sessionInit", () => {
108109
beforeEach(() => {
@@ -164,6 +165,28 @@ describe("SessionProvider sessionInit", () => {
164165
);
165166
expect(closeStream).toHaveBeenCalledTimes(2);
166167
});
168+
169+
it("updates session consumers when SuperTokens creates a session", async () => {
170+
getStream.mockReturnValue({} as EventSource);
171+
doesSessionExist.mockResolvedValue(false);
172+
173+
const { result } = renderHook(() => useSession(), {
174+
wrapper: SessionProvider,
175+
});
176+
177+
act(() => {
178+
result.current.setAuthenticated(false);
179+
});
180+
181+
expect(result.current.authenticated).toBe(false);
182+
183+
sessionInit();
184+
act(() => {
185+
session.emit("SESSION_CREATED", { action: "SESSION_CREATED" });
186+
});
187+
188+
expect(result.current.authenticated).toBe(true);
189+
});
167190
});
168191

169192
afterAll(() => {

packages/web/src/auth/compass/session/SessionProvider.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,25 @@ export const SessionContext = createContext<CompassSession>({
6060
const authenticated$ = new BehaviorSubject(false);
6161
let isCheckingSession = false;
6262
let isSessionInitialized = false;
63+
let sessionEventVersion = 0;
6364

6465
const $authenticated = authenticated$.pipe(skip(1), distinctUntilChanged());
6566

66-
const handleSessionExists = async () => {
67+
const handleAuthenticatedSession = () => {
68+
authenticated$.next(true);
6769
markUserAsAuthenticated(getLastKnownEmail());
68-
await refreshUserMetadata();
70+
void refreshUserMetadata();
71+
};
72+
73+
const handleSessionExists = () => {
74+
handleAuthenticatedSession();
75+
if (!sse.getStream()) {
76+
sse.openStream();
77+
}
6978
};
7079

7180
const handleSessionMissing = () => {
81+
authenticated$.next(false);
7282
store.dispatch(authSlice.actions.resetAuth());
7383
store.dispatch(userMetadataSlice.actions.clear(undefined));
7484
clearGoogleSyncIndicatorOverride();
@@ -81,23 +91,24 @@ async function checkIfSessionExists(): Promise<boolean> {
8191
return false;
8292
}
8393

84-
if (isCheckingSession) return false;
94+
if (isCheckingSession) return authenticated$.value;
8595

8696
isCheckingSession = true;
97+
const eventVersionAtCheckStart = sessionEventVersion;
8798

8899
try {
89100
const exists = await session.doesSessionExist();
90101

102+
if (sessionEventVersion !== eventVersionAtCheckStart) {
103+
return authenticated$.value;
104+
}
105+
91106
if (exists) {
92107
handleSessionExists();
93-
if (!sse.getStream()) {
94-
sse.openStream();
95-
}
96108
} else {
97109
handleSessionMissing();
98110
}
99111

100-
authenticated$.next(exists);
101112
return exists;
102113
} catch (error) {
103114
console.error("Error checking auth status:", error);
@@ -118,22 +129,23 @@ export function sessionInit() {
118129

119130
// No need to unsubscribe as this runs for the lifetime of the app
120131
session.events.pipe(distinctUntilKeyChanged("action")).subscribe((e) => {
121-
void checkIfSessionExists();
122-
123132
switch (e.action) {
124133
case "REFRESH_SESSION":
125134
case "SESSION_CREATED":
135+
sessionEventVersion += 1;
126136
// Mark user as authenticated when session is created or refreshed
127137
// This ensures the flag is set even if markUserAsAuthenticated wasn't called during OAuth
128-
markUserAsAuthenticated(getLastKnownEmail());
129-
void refreshUserMetadata();
138+
handleAuthenticatedSession();
130139
sse.closeStream();
131140
sse.openStream();
132141
break;
133142
case "SIGN_OUT":
134-
store.dispatch(userMetadataSlice.actions.clear(undefined));
143+
sessionEventVersion += 1;
144+
handleSessionMissing();
135145
sse.closeStream();
136146
break;
147+
default:
148+
void checkIfSessionExists();
137149
}
138150
});
139151
}

0 commit comments

Comments
 (0)