@@ -8,7 +8,57 @@ import { S3Client } from "@aws-sdk/client-s3";
88import { detectAdBlockEnabled } from "./helpers.js" ;
99import { page } from "$app/state" ;
1010import { goto } from "$app/navigation" ;
11+ import type { User } from "oidc-client-ts" ;
12+ import { createModal } from "$lib/modal.js" ;
13+ import { signinRequest } from "$lib/authentication.js" ;
1114
15+
16+ interface Tokens {
17+ idToken : string ;
18+ accessToken : string ;
19+ refreshToken : string ;
20+ expiresAt ?: number ; // epoch ms
21+ }
22+ const TOKEN_STORAGE_KEY = 'ccported_tokens' ;
23+
24+ function readStoredTokens ( ) : Tokens | null {
25+ if ( ! browser ) return null ;
26+ try {
27+ const raw = localStorage . getItem ( TOKEN_STORAGE_KEY ) ;
28+ if ( ! raw ) return null ;
29+ return JSON . parse ( raw ) ;
30+ } catch {
31+ return null ;
32+ }
33+ }
34+
35+ function clearStoredTokens ( ) {
36+ if ( ! browser ) return ;
37+ try { localStorage . removeItem ( TOKEN_STORAGE_KEY ) ; } catch { }
38+ }
39+
40+ function isExpired ( expiresAt ?: number , skewSec = 60 ) : boolean {
41+ if ( ! expiresAt ) return true ;
42+ return Date . now ( ) >= ( expiresAt - skewSec * 1000 ) ;
43+ }
44+
45+ function decodeJwt < T = any > ( token ?: string ) : T | null {
46+ if ( ! token ) return null ;
47+ try {
48+ const parts = token . split ( '.' ) ;
49+ if ( parts . length !== 3 ) return null ;
50+ const payload = atob ( parts [ 1 ] . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ) ;
51+ const json = decodeURIComponent (
52+ payload
53+ . split ( '' )
54+ . map ( ( c ) => '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) )
55+ . join ( '' )
56+ ) ;
57+ return JSON . parse ( json ) ;
58+ } catch {
59+ return null ;
60+ }
61+ }
1262export const SessionState = {
1363 awsReady : false ,
1464 ssr : ! browser ,
@@ -20,7 +70,10 @@ export const SessionState = {
2070 devMode : ( browser && window . location . hostname === "localhost" ) ,
2171 serverResponses : [ ] as { server : Server ; success : boolean ; time : number , reason : string } [ ] ,
2272 plays : 0 ,
23- user : null as null | { name : string ; email : string ; tokens : any } | undefined ,
73+ user : null as null | {
74+ profile ?: any ;
75+ tokens ?: Tokens ;
76+ } ,
2477 loggedIn : false
2578}
2679
@@ -82,7 +135,7 @@ export async function initializeTooling() {
82135 return ;
83136 }
84137 initializingTooling = true ;
85-
138+
86139 // Handle SetServer query parameter
87140 if ( browser && window ) {
88141 const urlParams = new URLSearchParams ( window . location . search ) ;
@@ -108,8 +161,50 @@ export async function initializeTooling() {
108161 newUrl . search = urlParams . toString ( ) ;
109162 window . history . replaceState ( null , '' , newUrl . toString ( ) ) ;
110163 }
164+
165+ // Initialize auth state from persisted tokens (runs client-side only)
166+ const storedTokens = readStoredTokens ( ) ;
167+ if ( storedTokens ) {
168+ console . log ( "[initializeTooling] Found stored tokens." , storedTokens ) ;
169+ if ( isExpired ( storedTokens . expiresAt ) ) {
170+ // Tokens expired: inform the user and offer to log in again or continue without login
171+ createModal ( {
172+ title : 'Session expired' ,
173+ content : 'Your session has expired and you have been signed out. You can log in again or continue without logging in.' ,
174+ actions : [
175+ {
176+ label : 'Log in' ,
177+ onClick : ( ) => {
178+ // Attempt a fresh sign-in redirect
179+ try { signinRequest ( ) ; } catch { }
180+ }
181+ } ,
182+ {
183+ label : 'Continue without login' ,
184+ onClick : ( api ) => {
185+ clearStoredTokens ( ) ;
186+ SessionState . user = null as any ;
187+ SessionState . loggedIn = false ;
188+ api . close ( ) ;
189+ }
190+ }
191+ ]
192+ } ) ;
193+ } else {
194+ // Tokens valid: derive a user profile from the id token claims
195+ const profile = decodeJwt ( storedTokens . idToken ) ;
196+ if ( ! SessionState . user ) {
197+ SessionState . user = { } ;
198+ }
199+ SessionState . user . tokens = storedTokens ;
200+ if ( profile ) {
201+ SessionState . user . profile = profile as any ;
202+ SessionState . loggedIn = true ;
203+ }
204+ }
205+ }
111206 }
112-
207+
113208 const server = await findServer ( ) ;
114209 if ( ! server ) {
115210 console . error ( "No available servers found." ) ;
@@ -145,7 +240,7 @@ export async function initializeTooling() {
145240 } ) ;
146241 }
147242
148- const credentials = await initializeUnathenticated ( ) ;
243+ const credentials = await initializeUnauthenticated ( ) ;
149244 const dynamoDBClient = new DynamoDBClient ( {
150245 region : "us-west-2" ,
151246 credentials
@@ -562,7 +657,7 @@ export async function getAllAvailableServers(servers: Server[]): Promise<Server[
562657 return availableServers ;
563658}
564659
565- async function initializeUnathenticated ( ) {
660+ async function initializeUnauthenticated ( ) {
566661
567662 const identityPoolId = "us-west-2:8ffe94a1-9042-4509-8e65-4efe16e61e3e" ;
568663 const credentials = fromCognitoIdentityPool ( {
@@ -573,4 +668,3 @@ async function initializeUnathenticated() {
573668 SessionState . awsReady = true ;
574669 return credentials ;
575670}
576-
0 commit comments