@@ -29,25 +29,47 @@ function normalizeMeta(value: unknown): HeadersConfigMeta {
2929 return { seq : 0 , updatedAt : 0 } ;
3030}
3131
32- export async function bumpHeadersConfigMeta ( ) : Promise < HeadersConfigMeta > {
32+ /**
33+ * Queue to serialize storage writes and prevent race conditions.
34+ * Without this queue, rapid toggles can cause:
35+ * 1. Multiple concurrent reads of the same `seq` value
36+ * 2. Multiple writes with the same incremented `seq`
37+ * 3. Background script skipping updates because `isNewerMeta` returns false
38+ */
39+ let writeQueue : Promise < void > = Promise . resolve ( ) ;
40+
41+ async function bumpHeadersConfigMeta ( ) : Promise < HeadersConfigMeta > {
3342 const current = await browser . storage . local . get ( [ BrowserStorageKey . HeadersConfigMeta ] ) ;
3443 const prev = normalizeMeta ( current [ BrowserStorageKey . HeadersConfigMeta ] ) ;
3544
3645 // Ensure monotonicity even if system clock moves backwards or updates are very close.
3746 const now = Date . now ( ) ;
3847 const nextUpdatedAt = Math . max ( now , prev . updatedAt + 1 ) ;
3948
40- // Best-effort monotonic seq. It can race across concurrent writers; updatedAt remains authoritative .
49+ // Monotonic seq - now guaranteed by queue serialization .
4150 const nextSeq = prev . seq + 1 ;
4251
4352 return { seq : nextSeq , updatedAt : nextUpdatedAt } ;
4453}
4554
4655export async function setWithBumpedHeadersConfigMeta ( patch : Record < string , unknown > ) {
47- const meta = await bumpHeadersConfigMeta ( ) ;
48- await browser . storage . local . set ( {
49- ...patch ,
50- [ BrowserStorageKey . HeadersConfigMeta ] : meta ,
56+ // Chain this write operation onto the queue to ensure serialization.
57+ // This prevents race conditions where concurrent calls read the same `seq`
58+ // and write the same incremented value.
59+ const result = writeQueue . then ( async ( ) => {
60+ const meta = await bumpHeadersConfigMeta ( ) ;
61+ await browser . storage . local . set ( {
62+ ...patch ,
63+ [ BrowserStorageKey . HeadersConfigMeta ] : meta ,
64+ } ) ;
65+ return meta ;
5166 } ) ;
52- return meta ;
67+
68+ // Update queue to wait for this operation (ignore errors for queue chaining)
69+ writeQueue = result . then (
70+ ( ) => { } ,
71+ ( ) => { } ,
72+ ) ;
73+
74+ return result ;
5375}
0 commit comments