1- import { nanoid } from "nanoid" ;
2- import { z } from "zod" ;
3-
41import { APIError } from "./api" ;
2+ import { STORAGE_KEY_PREFIX } from "./persist" ;
53import { urlForName } from "./router" ;
64import appState , { AppStateService } from "./state" ;
75
86import type { APIUser } from "@/index" ;
97import type { Auth } from "@/types/auth" ;
108
11- const sessionIdSchema = z . string ( ) . nanoid ( ) ;
12- type SessionId = z . infer < typeof sessionIdSchema > ;
139type AuthState = Auth | null ;
1410type JWT = {
1511 user_id : string ;
@@ -32,21 +28,16 @@ export type LogOutEventDetail = {
3228 redirect ?: boolean ;
3329} ;
3430
35- type AuthMessage = {
36- responderId ?: SessionId ;
37- requesterId ?: SessionId ;
38- } ;
39-
40- type AuthRequestEventDetail = AuthMessage & {
31+ type AuthRequestEventDetail = {
4132 name : "requesting_auth" ;
4233} ;
4334
44- type AuthResponseEventDetail = AuthMessage & {
35+ type AuthResponseEventDetail = {
4536 name : "responding_auth" ;
4637 auth : AuthState ;
4738} ;
4839
49- export type AuthReceivedEventDetail = AuthMessage & {
40+ export type AuthReceivedEventDetail = {
5041 name : "auth_received" ;
5142} ;
5243
@@ -66,8 +57,6 @@ const FRESHNESS_TIMER_INTERVAL = 60 * 1000 * 5;
6657
6758export default class AuthService {
6859 private timerId ?: number ;
69- private readonly sessionId : SessionId = nanoid ( ) ;
70- private broadcastChannel ?: BroadcastChannel ;
7160
7261 static storageKey = "btrix.auth" ;
7362 static unsupportedAuthErrorCode = "UNSUPPORTED_AUTH_TYPE" ;
@@ -90,6 +79,9 @@ export default class AuthService {
9079 } ,
9180 } ;
9281
82+ private broadcastChannel ?: BroadcastChannel ;
83+ private readonly finalizeController = new AbortController ( ) ;
84+
9385 get authState ( ) {
9486 return appState . auth ;
9587 }
@@ -221,7 +213,6 @@ export default class AuthService {
221213 const broadcastPromise = new Promise < AuthState > ( ( resolve ) => {
222214 // Check if there's any authenticated tabs
223215 this . broadcastChannel ?. postMessage ( {
224- requesterId : this . sessionId ,
225216 name : "requesting_auth" ,
226217 } satisfies AuthRequestEventDetail ) ;
227218 // Wait for another tab to respond
@@ -231,10 +222,9 @@ export default class AuthService {
231222
232223 // Confirm receipt
233224 this . broadcastChannel ?. postMessage ( {
234- requesterId : this . sessionId ,
235- responderId : data . responderId ,
236225 name : "auth_received" ,
237226 } satisfies AuthReceivedEventDetail ) ;
227+
238228 resolve ( data . auth ) ;
239229 }
240230 } ;
@@ -265,17 +255,48 @@ export default class AuthService {
265255 }
266256
267257 constructor ( ) {
258+ const { signal } = this . finalizeController ;
259+
268260 this . broadcastChannel = new BroadcastChannel ( AuthService . storageKey ) ;
269261
270262 // Only have freshness check run in visible tab(s)
271- document . addEventListener ( "visibilitychange" , ( ) => {
272- if ( ! this . authState ) return ;
273- if ( document . visibilityState === "visible" ) {
274- this . startFreshnessCheck ( ) ;
275- } else {
276- this . cancelFreshnessCheck ( ) ;
277- }
278- } ) ;
263+ document . addEventListener (
264+ "visibilitychange" ,
265+ ( ) => {
266+ if ( ! this . authState ) return ;
267+ if ( document . visibilityState === "visible" ) {
268+ this . startFreshnessCheck ( ) ;
269+ } else {
270+ this . cancelFreshnessCheck ( ) ;
271+ }
272+ } ,
273+ { signal } ,
274+ ) ;
275+
276+ /**
277+ * Assume another tab has logged out if `orgSlug` preference is removed
278+ * See FIXME note in state.ts
279+ */
280+ window . addEventListener (
281+ "storage" ,
282+ ( e : StorageEvent ) => {
283+ if ( ! this . authState ) return ;
284+
285+ if (
286+ e . key === `${ STORAGE_KEY_PREFIX } .orgSlug` &&
287+ e . oldValue &&
288+ ! e . newValue
289+ ) {
290+ this . revoke ( ) ;
291+ }
292+ } ,
293+ { signal } ,
294+ ) ;
295+ }
296+
297+ finalize ( ) {
298+ this . finalizeController . abort ( ) ;
299+ this . stopSharingSession ( ) ;
279300 }
280301
281302 saveLogin ( auth : Auth ) {
@@ -327,7 +348,6 @@ export default class AuthService {
327348 if ( auth ) {
328349 // A new tab/window opened and is requesting shared auth
329350 this . broadcastChannel ?. postMessage ( {
330- responderId : this . sessionId ,
331351 name : "responding_auth" ,
332352 auth : AuthService . getCurrentTabAuth ( ) ,
333353 } satisfies AuthResponseEventDetail ) ;
0 commit comments