1+ // Copy from chrome.storage, but not exported
2+ type NoInferX < T > = T [ ] [ T extends any ? 0 : never ]
3+
4+ const ALL_TABLES = [ 'stat' ] as const
5+
6+ export type IndexedTable = typeof ALL_TABLES [ number ]
7+
8+ const DB_NAME = `tt4b_${ chrome . runtime . id } `
9+
10+ const initDb = ( ) : Promise < IDBDatabase > => {
11+ const factory = typeof window === 'undefined' ? self . indexedDB : window . indexedDB
12+ const request = factory . open ( DB_NAME )
13+ return new Promise ( ( resolve , reject ) => {
14+ request . onsuccess = ( ) => {
15+ const db = request . result
16+ const existingStores = Array . from ( db . objectStoreNames )
17+ const missingStores = ALL_TABLES . filter ( table => ! existingStores . includes ( table ) )
18+ if ( ! missingStores . length ) {
19+ return resolve ( db )
20+ }
21+
22+ const currentVersion = db . version
23+ db . close ( )
24+
25+ const upgradeRequest = factory . open ( DB_NAME , currentVersion + 1 )
26+
27+ upgradeRequest . onupgradeneeded = ( ) => {
28+ const upgradeDb = upgradeRequest . result
29+ for ( const table of missingStores ) {
30+ if ( ! upgradeDb . objectStoreNames . contains ( table ) ) {
31+ upgradeDb . createObjectStore ( table )
32+ }
33+ }
34+ }
35+
36+ upgradeRequest . onsuccess = ( ) => {
37+ console . log ( "IndexedDB upgraded" )
38+ resolve ( upgradeRequest . result )
39+ }
40+
41+ upgradeRequest . onerror = ( ) => reject ( upgradeRequest . error )
42+ }
43+
44+ request . onerror = ( ev ) => {
45+ console . error ( "IndexedDB initialized error: " , ev )
46+ reject ( ev )
47+ }
48+ } )
49+ }
50+
51+ function promiseRequest < T > ( req : IDBRequest < T > ) : Promise < T > {
52+ return new Promise ( ( resolve , reject ) => {
53+ req . onsuccess = ( ) => resolve ( req . result )
54+ req . onerror = ( ev ) => {
55+ console . error ( "Failed to request indexed-db" , ev , req . error )
56+ reject ( req . error )
57+ }
58+ } )
59+ }
60+
61+ function cvtDbKey2Str ( dbKey : IDBValidKey ) : string {
62+ if ( typeof dbKey === 'string' ) {
63+ return dbKey
64+ } else if ( typeof dbKey === 'number' ) {
65+ return `${ dbKey } `
66+ } else if ( dbKey instanceof Date ) {
67+ return `${ dbKey . getTime ( ) } `
68+ } else {
69+ return dbKey . toString ( )
70+ }
71+ }
72+
73+ export class IndexedDbStorage implements chrome . storage . StorageArea {
74+ private db : IDBDatabase | undefined
75+
76+ onChanged : chrome . events . Event < ( changes : { [ key : string ] : chrome . storage . StorageChange } ) => void > = {
77+ addListener : function ( callback : ( changes : { [ key : string ] : chrome . storage . StorageChange } ) => void ) : void {
78+ throw new Error ( 'Function not implemented.' )
79+ } ,
80+ getRules : function ( ruleIdentifiers : string [ ] | ArgCallback < chrome . events . Rule [ ] > , callback ?: ArgCallback < chrome . events . Rule [ ] > ) : void {
81+ // not unsupported
82+ } ,
83+ hasListener : function ( callback : ( changes : { [ key : string ] : chrome . storage . StorageChange } ) => void ) : boolean {
84+ return false
85+ } ,
86+ removeRules : function ( ruleIdentifiers : string [ ] | undefined | ArgCallback < chrome . events . Rule [ ] > , callback ?: ArgCallback < chrome . events . Rule [ ] > ) : void {
87+ // not unsupported
88+ } ,
89+ addRules : function ( rules : chrome . events . Rule [ ] , callback ?: ( rules : chrome . events . Rule [ ] ) => void ) : void {
90+ // not unsupported
91+ } ,
92+ removeListener : function ( callback : ( changes : { [ key : string ] : chrome . storage . StorageChange } ) => void ) : void {
93+ // not unsupported
94+ } ,
95+ hasListeners : function ( ) : boolean {
96+ return false
97+ }
98+ }
99+
100+ constructor ( private table : IndexedTable ) { }
101+
102+ private async withTransaction < T > ( operation : ( store : IDBObjectStore ) => Promise < T > , mode ?: IDBTransactionMode ) : Promise < T > {
103+ if ( ! this . db ) {
104+ ( self as any ) . _db = this . db = await initDb ( )
105+ }
106+ const transaction = this . db . transaction ( [ this . table ] , mode ?? 'readwrite' )
107+ const store = transaction . objectStore ( this . table )
108+ try {
109+ return await operation ( store )
110+ } finally {
111+ transaction . commit ( )
112+ }
113+ }
114+
115+ async clear ( callback ?: NoArgCallback ) : Promise < void > {
116+ await this . withTransaction ( async store => {
117+ const req = store . clear ( )
118+ await promiseRequest ( req )
119+ } )
120+ callback ?.( )
121+ }
122+
123+ async set < T = { [ key : string ] : any } > ( items : Partial < T > , callback ?: NoArgCallback ) : Promise < void > {
124+ await this . withTransaction ( async store => {
125+ const promises : Promise < unknown > [ ] = [ ]
126+
127+ for ( const [ key , value ] of Object . entries ( items ) ) {
128+ const promise = promiseRequest ( store . put ( value , key ) )
129+ promises . push ( promise )
130+ }
131+ await Promise . all ( promises )
132+ } )
133+
134+ callback ?.( )
135+ }
136+
137+ async remove < T = { [ key : string ] : any } > ( keys : keyof T | Array < keyof T > , callback ?: ( ) => void ) : Promise < void > {
138+ await this . withTransaction ( async store => {
139+ keys = Array . isArray ( keys ) ? keys : [ keys ]
140+ await Promise . all ( keys . map ( key => typeof key !== 'symbol' && promiseRequest ( store . delete ( key ) ) ) )
141+ } )
142+ callback ?.( )
143+ }
144+
145+ async get < T = { [ key : string ] : unknown } > (
146+ keys : NoInferX < keyof T > | Array < NoInferX < keyof T > > | Partial < NoInferX < T > > | null | undefined | ArgCallback < T > ,
147+ callback ?: ArgCallback < T > ,
148+ ) : Promise < T > {
149+ if ( typeof keys === 'function' ) {
150+ callback = keys
151+ keys = null
152+ }
153+ const data = await this . getData ( keys )
154+ callback ?.( data )
155+ return data
156+ }
157+
158+ private async getData < T = { [ key : string ] : unknown } > ( keys ?: NoInferX < keyof T > | Array < NoInferX < keyof T > > | Partial < NoInferX < T > > | null ) : Promise < T > {
159+ const result = { } as { [ key : string ] : unknown }
160+
161+ await this . withTransaction ( async store => {
162+ const fill = async ( key : keyof NoInferX < T > ) => {
163+ if ( typeof key === 'symbol' ) return
164+ const value = await promiseRequest < unknown > ( store . get ( key ) )
165+ result [ cvtDbKey2Str ( key ) ] = value
166+ }
167+
168+ if ( keys == null ) {
169+ const allKeys = await promiseRequest ( store . getAllKeys ( ) )
170+ const allValues = await promiseRequest ( store . getAll ( ) )
171+ allKeys . forEach ( ( key , index ) => result [ cvtDbKey2Str ( key ) ] = allValues [ index ] )
172+ } else if ( Array . isArray ( keys ) ) {
173+ await Promise . all ( keys . map ( fill ) )
174+ } else if ( typeof keys === 'object' ) {
175+ for ( const key in keys ) {
176+ await fill ( key )
177+ }
178+ } else {
179+ await fill ( keys )
180+ }
181+ } , 'readonly' )
182+
183+ return result as T
184+ }
185+
186+ async setAccessLevel ( accessOptions : { accessLevel : `${chrome . storage . AccessLevel } ` } , callback ?: NoArgCallback ) : Promise < void > {
187+ // no nothing
188+ console . info ( "Invoked setAccessLevel()" , accessOptions )
189+ callback ?.( )
190+ }
191+
192+ async getKeys ( callback ?: ArgCallback < string [ ] > ) : Promise < string [ ] > {
193+ const keys = await this . withTransaction ( async store => {
194+ const dbKeys = await promiseRequest ( store . getAllKeys ( ) )
195+ return dbKeys . map ( cvtDbKey2Str )
196+ } , 'readonly' )
197+ callback ?.( keys )
198+ return keys
199+ }
200+
201+ async getBytesInUse < T = { [ key : string ] : any } > (
202+ keys ?: keyof T | Array < keyof T > | null | ArgCallback < number > ,
203+ callback ?: ArgCallback < number >
204+ ) : Promise < number > {
205+ [ keys , callback ] = typeof keys === 'function' ? [ null , keys ] : [ keys , callback ]
206+ const value = 0
207+ callback ?.( value )
208+ return value
209+ }
210+ }
0 commit comments