@@ -11,11 +11,8 @@ const chatTransportContractPath = new URL(
1111
1212const flushAsyncWork = ( ) => new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
1313
14- function runBootstrap ( source , authResults ) {
15- const values = new Map ( ) ;
16- const invocations = [ ] ;
17- const intervals = [ ] ;
18- const localStorage = {
14+ function createStorage ( values ) {
15+ return {
1916 getItem ( key ) {
2017 return values . has ( key ) ? values . get ( key ) : null ;
2118 } ,
@@ -29,12 +26,28 @@ function runBootstrap(source, authResults) {
2926 values . clear ( ) ;
3027 } ,
3128 } ;
29+ }
30+
31+ function runBootstrap ( source , authResults , sharedState = { } ) {
32+ const localValues = sharedState . localValues || new Map ( ) ;
33+ const sessionValues = sharedState . sessionValues || new Map ( ) ;
34+ const navigation = sharedState . navigation || { reloads : 0 } ;
35+ sharedState . localValues = localValues ;
36+ sharedState . sessionValues = sessionValues ;
37+ sharedState . navigation = navigation ;
38+ const invocations = [ ] ;
39+ const intervals = [ ] ;
40+ const localStorage = createStorage ( localValues ) ;
41+ const sessionStorage = createStorage ( sessionValues ) ;
3242 const location = {
3343 href : 'http://127.0.0.1:6185/#/auth/login' ,
3444 origin : 'http://127.0.0.1:6185' ,
3545 hash : '#/auth/login' ,
3646 assign ( ) { } ,
3747 replace ( ) { } ,
48+ reload ( ) {
49+ navigation . reloads += 1 ;
50+ } ,
3851 toString ( ) {
3952 return this . href ;
4053 } ,
@@ -60,6 +73,7 @@ function runBootstrap(source, authResults) {
6073 } ,
6174 } ,
6275 localStorage,
76+ sessionStorage,
6377 location,
6478 open : ( ) => null ,
6579 setInterval ( handler , delay ) {
@@ -69,7 +83,17 @@ function runBootstrap(source, authResults) {
6983 } ;
7084 class MockElement { }
7185 class MockAnchor extends MockElement { }
72- const document = { addEventListener ( ) { } } ;
86+ const document = {
87+ addEventListener ( ) { } ,
88+ getElementById ( id ) {
89+ if ( id !== 'app' ) return null ;
90+ return {
91+ hasChildNodes ( ) {
92+ return sharedState . appMounted === true ;
93+ } ,
94+ } ;
95+ } ,
96+ } ;
7397 const quietConsole = { warn ( ) { } , error ( ) { } , log ( ) { } } ;
7498
7599 runInNewContext (
@@ -88,7 +112,14 @@ function runBootstrap(source, authResults) {
88112 } ,
89113 ) ;
90114
91- return { window, localStorage, invocations, intervals } ;
115+ return {
116+ window,
117+ localStorage,
118+ sessionStorage,
119+ invocations,
120+ intervals,
121+ navigation,
122+ } ;
92123}
93124
94125test ( 'bridge bootstrap defines astrbotAppUpdater methods' , async ( ) => {
@@ -115,27 +146,47 @@ test('bridge bootstrap owns desktop passwordless authentication lifecycle', asyn
115146 ) ;
116147} ) ;
117148
118- test ( 'bridge bootstrap automatically authenticates and reacquires a removed token' , async ( ) => {
149+ test ( 'bridge bootstrap reloads once after initial auth and reacquires a removed token' , async ( ) => {
119150 const source = await readFile ( bootstrapPath , 'utf8' ) ;
120- const runtime = runBootstrap ( source , [
121- { ok : true , token : 'first-jwt' , username : 'astrbot' } ,
122- { ok : true , token : 'second-jwt' , username : 'astrbot' } ,
123- ] ) ;
151+ const sharedState = { } ;
152+ const firstRuntime = runBootstrap (
153+ source ,
154+ [ { ok : true , token : 'first-jwt' , username : 'astrbot' } ] ,
155+ sharedState ,
156+ ) ;
124157
125158 await flushAsyncWork ( ) ;
126159 await flushAsyncWork ( ) ;
127- assert . equal ( runtime . localStorage . getItem ( 'token' ) , 'first-jwt' ) ;
128- assert . equal ( runtime . localStorage . getItem ( 'user' ) , 'astrbot' ) ;
129- assert . equal ( runtime . window . location . hash , '/welcome' ) ;
130- assert . equal ( runtime . intervals . length , 1 ) ;
131- assert . equal ( runtime . intervals [ 0 ] . delay , 6 * 60 * 60 * 1000 ) ;
160+ assert . equal ( firstRuntime . localStorage . getItem ( 'token' ) , 'first-jwt' ) ;
161+ assert . equal ( firstRuntime . localStorage . getItem ( 'user' ) , 'astrbot' ) ;
162+ assert . equal ( firstRuntime . navigation . reloads , 1 ) ;
163+ assert . equal ( firstRuntime . window . location . hash , '#/auth/login' ) ;
164+ assert . equal ( firstRuntime . intervals . length , 1 ) ;
165+ assert . equal ( firstRuntime . intervals [ 0 ] . delay , 6 * 60 * 60 * 1000 ) ;
166+
167+ const reloadedRuntime = runBootstrap (
168+ source ,
169+ [
170+ { ok : true , token : 'second-jwt' , username : 'astrbot' } ,
171+ { ok : true , token : 'third-jwt' , username : 'astrbot' } ,
172+ ] ,
173+ sharedState ,
174+ ) ;
175+ await flushAsyncWork ( ) ;
176+ await flushAsyncWork ( ) ;
177+ assert . equal ( reloadedRuntime . localStorage . getItem ( 'token' ) , 'second-jwt' ) ;
178+ assert . equal ( reloadedRuntime . navigation . reloads , 1 ) ;
179+ assert . equal ( reloadedRuntime . window . location . hash , '#/auth/login' ) ;
132180
133- runtime . localStorage . removeItem ( 'token' ) ;
181+ sharedState . appMounted = true ;
182+ reloadedRuntime . localStorage . removeItem ( 'token' ) ;
134183 await flushAsyncWork ( ) ;
135184 await flushAsyncWork ( ) ;
136- assert . equal ( runtime . localStorage . getItem ( 'token' ) , 'second-jwt' ) ;
185+ assert . equal ( reloadedRuntime . localStorage . getItem ( 'token' ) , 'third-jwt' ) ;
186+ assert . equal ( reloadedRuntime . navigation . reloads , 1 ) ;
187+ assert . equal ( reloadedRuntime . window . location . hash , '/welcome' ) ;
137188 assert . ok (
138- runtime . invocations . filter (
189+ reloadedRuntime . invocations . filter (
139190 ( { command } ) => command === 'desktop_bridge_get_auth_token' ,
140191 ) . length >= 2 ,
141192 ) ;
0 commit comments