@@ -93,7 +93,42 @@ export const getDb = (version = VERSION) => {
9393 return dbPromise ;
9494} ;
9595
96- // Export db object with the same API as before
96+ // On iOS Safari PWA after a push subscription, `readwrite` requests on the
97+ // `Options` object store can stall indefinitely (no success/error/abort).
98+ // Other stores and reads are unaffected, and reopening the DB doesn't help.
99+ // Without this guard, `OneSignal.init()` hangs until WebKit's watchdog
100+ // eventually aborts the transaction (~30 minutes). Workaround: cap Options
101+ // writes with a short timeout, then trip a page-scoped circuit breaker so
102+ // subsequent writes short-circuit. The values that fail to persist are
103+ // session metadata the SW reads with sensible fallbacks. Remove if WebKit
104+ // ever fixes the underlying bug: https://bugs.webkit.org/show_bug.cgi?id=315804
105+ const OPTIONS_WRITE_TIMEOUT_MS = 1500 ;
106+ let optionsWriteWedged = false ;
107+
108+ export const isOptionsWriteWedged = ( ) => optionsWriteWedged ;
109+
110+ // `op` is invoked synchronously (callers await `dbPromise` first), so the
111+ // timeout scopes only to the readwrite request, not DB open/upgrade. Once a
112+ // write times out we trip a page-scoped circuit breaker so the rest of init's
113+ // Options writes short-circuit instead of each paying the full timeout.
114+ function guardOptionsWrite < T > (
115+ storeName : IDBStoreName ,
116+ label : string ,
117+ op : ( ) => Promise < T > ,
118+ ) : Promise < T | undefined > {
119+ if ( storeName !== 'Options' ) return op ( ) ;
120+ if ( optionsWriteWedged ) return Promise . resolve ( undefined ) ;
121+ let timer : ReturnType < typeof setTimeout > ;
122+ const timeout = new Promise < undefined > ( ( resolve ) => {
123+ timer = setTimeout ( ( ) => {
124+ optionsWriteWedged = true ;
125+ Log . _warn ( `db.${ label } timed out` ) ;
126+ resolve ( undefined ) ;
127+ } , OPTIONS_WRITE_TIMEOUT_MS ) ;
128+ } ) ;
129+ return Promise . race ( [ op ( ) , timeout ] ) . finally ( ( ) => clearTimeout ( timer ) ) ;
130+ }
131+
97132export const db = {
98133 async get < K extends IDBStoreName > (
99134 storeName : K ,
@@ -105,10 +140,14 @@ export const db = {
105140 return ( await dbPromise ) . getAll ( storeName ) ;
106141 } ,
107142 async put < K extends IDBStoreName > ( storeName : K , value : IndexedDBSchema [ K ] [ 'value' ] ) {
108- return ( await dbPromise ) . put ( storeName , value ) ;
143+ const _db = await dbPromise ;
144+ return guardOptionsWrite ( storeName , `put(${ storeName } )` , ( ) => _db . put ( storeName , value ) ) ;
109145 } ,
110146 async delete < K extends IDBStoreName > ( storeName : K , key : IndexedDBSchema [ K ] [ 'key' ] ) {
111- return ( await dbPromise ) . delete ( storeName , key ) ;
147+ const _db = await dbPromise ;
148+ return guardOptionsWrite ( storeName , `delete(${ storeName } /${ key } )` , ( ) =>
149+ _db . delete ( storeName , key ) ,
150+ ) ;
112151 } ,
113152} ;
114153
0 commit comments