Skip to content

Commit 5b1fe5b

Browse files
committed
feat: semi
1 parent df73190 commit 5b1fe5b

File tree

26 files changed

+943
-609
lines changed

26 files changed

+943
-609
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"@emotion/babel-plugin": "^11.13.5",
3333
"@emotion/css": "^11.13.5",
3434
"@rsdoctor/rspack-plugin": "^1.5.2",
35-
"@rspack/cli": "^1.7.5",
36-
"@rspack/core": "^1.7.5",
35+
"@rspack/cli": "^1.7.6",
36+
"@rspack/core": "^1.7.6",
3737
"@swc/core": "^1.15.11",
3838
"@swc/jest": "^0.2.39",
3939
"@types/chrome": "0.1.36",
@@ -66,10 +66,10 @@
6666
"element-plus": "2.13.2",
6767
"punycode": "^2.3.1",
6868
"typescript-guard": "^0.2.1",
69-
"vue": "^3.5.27",
69+
"vue": "^3.5.28",
7070
"vue-router": "^5.0.2"
7171
},
7272
"engines": {
7373
"node": ">=22"
7474
}
75-
}
75+
}

src/background/track-server/group.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import itemService from '@service/item-service'
22

33
function handleTabGroupRemove(group: chrome.tabGroups.TabGroup) {
4-
itemService.batchDeleteGroupById(group.id)
4+
itemService.deleteByGroup(group.id)
55
}
66

77
export function handleTabGroupEnabled() {
Lines changed: 133 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,28 @@
1-
// Copy from chrome.storage, but not exported
2-
type NoInferX<T> = T[][T extends any ? 0 : never]
3-
41
const ALL_TABLES = ['stat'] as const
52

6-
export type IndexedTable = typeof ALL_TABLES[number]
7-
8-
const DB_NAME = `tt4b_${chrome.runtime.id}`
3+
export type Table = typeof ALL_TABLES[number]
94

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-
}
5+
export type Key<T = Record<string, number>> = keyof T & string
216

22-
const currentVersion = db.version
23-
db.close()
7+
type IndexConfig<T = Record<string, unknown>> = {
8+
key: Key<T> | Key<T>[]
9+
unique?: boolean
10+
}
2411

25-
const upgradeRequest = factory.open(DB_NAME, currentVersion + 1)
12+
export type Index<T = Record<string, unknown>> = Key<T> | Key<T>[] | IndexConfig<T>
2613

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-
}
14+
const DB_NAME = `tt4b_${chrome.runtime.id}`
4015

41-
upgradeRequest.onerror = () => reject(upgradeRequest.error)
42-
}
16+
function normalizeIndex<T = Record<string, number>>(index: Index<T>): IndexConfig<T> {
17+
return typeof index === 'string' || Array.isArray(index) ? { key: index } : index
18+
}
4319

44-
request.onerror = (ev) => {
45-
console.error("IndexedDB initialized error: ", ev)
46-
reject(ev)
47-
}
48-
})
20+
function formatIdxName<T = Record<string, number>>(key: IndexConfig<T>['key']): string {
21+
const keyStr = Array.isArray(key) ? key.sort().join('_') : key
22+
return `idx_${keyStr}`
4923
}
5024

51-
function promiseRequest<T>(req: IDBRequest<T>): Promise<T> {
25+
export function req2Promise<T = unknown>(req: IDBRequest<T>): Promise<T | undefined> {
5226
return new Promise((resolve, reject) => {
5327
req.onsuccess = () => resolve(req.result)
5428
req.onerror = (ev) => {
@@ -58,153 +32,145 @@ function promiseRequest<T>(req: IDBRequest<T>): Promise<T> {
5832
})
5933
}
6034

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-
}
35+
export async function iterateCursor<T = unknown>(
36+
req: IDBRequest<IDBCursorWithValue | null>,
37+
processor?: (cursor: IDBCursorWithValue) => void | Promise<void>
38+
): Promise<T[]> {
39+
const results: T[] = []
40+
41+
return new Promise((resolve, reject) => {
42+
req.onsuccess = async () => {
43+
const cursor = req.result
44+
if (!cursor) return resolve(results)
45+
46+
try {
47+
results.push(cursor.value as T)
48+
await processor?.(cursor)
49+
cursor.continue()
50+
} catch (error) {
51+
reject(error)
52+
}
53+
}
54+
55+
req.onerror = () => reject(req.error)
56+
})
7157
}
7258

73-
export class IndexedDbStorage implements chrome.storage.StorageArea {
59+
export abstract class BaseIDBStorage<T = Record<string, unknown>> {
7460
private db: IDBDatabase | undefined
61+
abstract indexes: Index<T>[]
62+
abstract key: Key<T> | Key<T>[]
63+
abstract table: Table
7564

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-
}
65+
protected async initDb(): Promise<IDBDatabase> {
66+
if (this.db) return this.db
9967

100-
constructor(private table: IndexedTable) { }
68+
const factory = typeof window === 'undefined' ? self.indexedDB : window.indexedDB
69+
const checkRequest = factory.open(DB_NAME)
10170

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-
}
71+
return new Promise((resolve, reject) => {
72+
checkRequest.onsuccess = () => {
73+
const db = checkRequest.result
74+
const storeExisted = db.objectStoreNames.contains(this.table)
75+
const needUpgrade = !storeExisted || this.needUpgradeIndexes(db)
11476

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-
}
77+
if (!needUpgrade) {
78+
this.db = db
79+
return resolve(db)
80+
}
12281

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>[] = []
82+
const currentVersion = db.version
83+
db.close()
12684

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-
})
85+
const upgradeRequest = factory.open(DB_NAME, currentVersion + 1)
13386

134-
callback?.()
135-
}
87+
upgradeRequest.onupgradeneeded = () => {
88+
const upgradeDb = upgradeRequest.result
89+
const transaction = upgradeRequest.transaction
90+
if (!transaction) return reject("Failed to get transaction of upgrading request")
13691

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-
}
92+
let store = upgradeDb.objectStoreNames.contains(this.table)
93+
? transaction.objectStore(this.table)
94+
: upgradeDb.createObjectStore(this.table, { keyPath: this.key })
95+
this.createIndexes(store)
96+
}
15797

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 }
98+
upgradeRequest.onsuccess = () => {
99+
console.log("IndexedDB upgraded")
100+
this.db = upgradeRequest.result
101+
resolve(upgradeRequest.result)
102+
}
160103

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
104+
upgradeRequest.onerror = () => reject(upgradeRequest.error)
166105
}
167106

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)
107+
checkRequest.onerror = () => reject(checkRequest.error)
108+
})
109+
}
110+
111+
private needUpgradeIndexes(db: IDBDatabase): boolean {
112+
try {
113+
const transaction = db.transaction(this.table, 'readonly')
114+
const store = transaction.objectStore(this.table)
115+
const indexNames = store.indexNames
116+
117+
for (const index of this.indexes) {
118+
const { key } = normalizeIndex(index)
119+
const idxName = formatIdxName(key)
120+
if (!indexNames.contains(idxName)) {
121+
return true
177122
}
178-
} else {
179-
await fill(keys)
180123
}
181-
}, 'readonly')
182-
183-
return result as T
124+
return false
125+
} catch (e) {
126+
console.error("Failed to check indexes", e)
127+
return true
128+
}
184129
}
185130

186-
async setAccessLevel(accessOptions: { accessLevel: `${chrome.storage.AccessLevel}` }, callback?: NoArgCallback): Promise<void> {
187-
// no nothing
188-
console.info("Invoked setAccessLevel()", accessOptions)
189-
callback?.()
131+
private createIndexes(store: IDBObjectStore) {
132+
const existingIndexes = store.indexNames
133+
134+
for (const index of this.indexes) {
135+
const { key, unique } = normalizeIndex(index)
136+
const idxName = formatIdxName(key)
137+
if (!existingIndexes.contains(idxName)) {
138+
store.createIndex(idxName, key, { unique })
139+
}
140+
}
190141
}
191142

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
143+
protected async withStore<T = unknown>(operation: (store: IDBObjectStore) => T | Promise<T>, mode?: IDBTransactionMode): Promise<T> {
144+
const db = await this.initDb()
145+
const trans = db.transaction(this.table, mode ?? 'readwrite')
146+
try {
147+
const store = trans.objectStore(this.table)
148+
const result = await operation(store)
149+
// Waiting for transaction completed
150+
await new Promise<void>((resolve, reject) => {
151+
trans.oncomplete = () => resolve()
152+
trans.onerror = () => reject(trans.error)
153+
trans.onabort = () => reject(new Error('Transaction aborted'))
154+
})
155+
return result
156+
} catch (e) {
157+
console.error("Failed to process with transaction", e)
158+
if (!trans.error && trans.mode !== 'readonly') {
159+
try {
160+
trans.abort()
161+
} catch (ignored) { }
162+
}
163+
throw e
164+
}
199165
}
200166

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
167+
protected assertIndex(store: IDBObjectStore, key: Key<T> | Key<T>[]): IDBIndex {
168+
const idxName = formatIdxName(key)
169+
try {
170+
return store.index(idxName)
171+
} catch (err) {
172+
console.error(`Failed to query index: table=${this.table}`, err)
173+
throw err
174+
}
209175
}
210-
}
176+
}

0 commit comments

Comments
 (0)