Skip to content

Commit 728cd22

Browse files
authored
feat: track local files in background script for FF (#628)
1 parent 285c873 commit 728cd22

File tree

13 files changed

+163
-88
lines changed

13 files changed

+163
-88
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
"@crowdin/crowdin-api-client": "^1.49.0",
3232
"@emotion/babel-plugin": "^11.13.5",
3333
"@emotion/css": "^11.13.5",
34-
"@rsdoctor/rspack-plugin": "^1.3.9",
35-
"@rspack/cli": "^1.6.3",
36-
"@rspack/core": "^1.6.3",
37-
"@swc/core": "^1.15.2",
34+
"@rsdoctor/rspack-plugin": "^1.3.11",
35+
"@rspack/cli": "^1.6.4",
36+
"@rspack/core": "^1.6.4",
37+
"@swc/core": "^1.15.3",
3838
"@swc/jest": "^0.2.39",
39-
"@types/chrome": "0.1.30",
39+
"@types/chrome": "0.1.31",
4040
"@types/decompress": "^4.2.7",
4141
"@types/jest": "^30.0.0",
4242
"@types/node": "^24.10.1",
@@ -54,7 +54,7 @@
5454
"postcss": "^8.5.6",
5555
"postcss-loader": "^8.2.0",
5656
"postcss-rtlcss": "^5.7.1",
57-
"puppeteer": "^24.30.0",
57+
"puppeteer": "^24.31.0",
5858
"ts-loader": "^9.5.4",
5959
"ts-node": "^10.9.2",
6060
"tsconfig-paths": "^4.2.0",

src/api/chrome/window.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IS_ANDROID } from "@util/constant/environment"
1+
import { IS_ANDROID, IS_FIREFOX } from "@util/constant/environment"
22
import { handleError } from "./common"
33

44
export function listAllWindows(): Promise<chrome.windows.Window[]> {
@@ -43,7 +43,11 @@ class FocusedWindowCtx {
4343
// init
4444
this.last = await this.getInner()
4545
if (!this.listened) {
46-
chrome.windows.onFocusChanged.addListener(wid => this.last = wid, { windowTypes: this.windowsTypes })
46+
// filter argument is not supported for Firefox
47+
// @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows/onFocusChanged#addlistener_syntax
48+
IS_FIREFOX
49+
? chrome.windows.onFocusChanged.addListener(wid => this.last = wid)
50+
: chrome.windows.onFocusChanged.addListener(wid => this.last = wid, { windowTypes: this.windowsTypes })
4751
this.listened = true
4852
}
4953
return this.last
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { getTab, onTabActivated } from '@api/chrome/tab'
2+
import optionHolder from '@service/components/option-holder'
3+
import { extractFileHost } from '@util/pattern'
4+
import { handleTrackTimeEvent } from './normal'
5+
6+
type Context = {
7+
host: string
8+
tab: ChromeTab
9+
// Start timestamp of this tick
10+
start: number
11+
}
12+
13+
async function convertContext(tabId: number): Promise<Context | null> {
14+
const tab = await getTab(tabId)
15+
const { active, url } = tab
16+
if (!active || !url) return null
17+
const fileHost = extractFileHost(url)
18+
if (!fileHost) return null
19+
return {
20+
host: fileHost, tab,
21+
start: Date.now(),
22+
}
23+
}
24+
25+
/**
26+
* Local file tracker for firefox
27+
*/
28+
class FileTracker {
29+
private enabled = false
30+
private current: Context | null = null
31+
32+
init() {
33+
optionHolder.get().then(v => this.enabled = v.countLocalFiles)
34+
optionHolder.addChangeListener(v => this.enabled = v.countLocalFiles)
35+
36+
onTabActivated(async tabId => {
37+
this.tick()
38+
this.current = await convertContext(tabId)
39+
})
40+
41+
// NOTE: if migrate to MV3, this line won't work expectedly
42+
setInterval(() => this.tick(), 1000)
43+
}
44+
45+
private tick() {
46+
if (!this.current) return
47+
const { host, tab, start } = this.current
48+
const end = Date.now()
49+
this.enabled && handleTrackTimeEvent({
50+
host, start, end,
51+
url: tab.url ?? '',
52+
ignoreTabCheck: false,
53+
}, tab)
54+
this.current.start = end
55+
}
56+
}
57+
58+
export default FileTracker
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import itemService from '@service/item-service'
2+
3+
function handleTabGroupRemove(group: chrome.tabGroups.TabGroup) {
4+
itemService.batchDeleteGroupById(group.id)
5+
}
6+
7+
export function handleTabGroupEnabled() {
8+
try {
9+
chrome.tabGroups.onRemoved.removeListener(handleTabGroupRemove)
10+
chrome.tabGroups.onRemoved.addListener(handleTabGroupRemove)
11+
} catch (e) {
12+
console.warn('failed to handle event: enableTabGroup', e)
13+
}
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import itemService from "@service/item-service"
2+
import { IS_ANDROID, IS_FIREFOX } from '@util/constant/environment'
3+
import { isFileUrl } from '@util/pattern'
4+
import type MessageDispatcher from "../message-dispatcher"
5+
import FileTracker from './file-tracker'
6+
import { handleTabGroupEnabled } from './group'
7+
import { handleIncVisitEvent, handleTrackTimeEvent } from './normal'
8+
import { handleTrackRunTimeEvent } from './runtime'
9+
10+
export default function initTrackServer(messageDispatcher: MessageDispatcher) {
11+
messageDispatcher
12+
.register<timer.core.Event, void>('cs.trackTime', (ev, sender) => {
13+
// not to process cs events from local files for FF
14+
if (IS_FIREFOX && isFileUrl(ev.url)) return
15+
16+
handleTrackTimeEvent(ev, sender.tab)
17+
})
18+
.register<timer.core.Event, void>('cs.trackRunTime', handleTrackRunTimeEvent)
19+
.register<{ host: string, url: string }, void>('cs.incVisitCount', handleIncVisitEvent)
20+
.register<string, timer.core.Result>('cs.getTodayInfo', host => itemService.getResult(host, new Date()))
21+
.register<void, void>('enableTabGroup', handleTabGroupEnabled)
22+
23+
// Track file time in background script for FF
24+
// Not accurate, since can't detect if the tabs are active or not
25+
IS_FIREFOX && !IS_ANDROID && new FileTracker().init()
26+
}
Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import periodThrottler from '@service/throttler/period-throttler'
77
import whitelistHolder from "@service/whitelist/holder"
88
import { IS_ANDROID } from "@util/constant/environment"
99
import { extractHostname } from "@util/pattern"
10-
import { formatTimeYMD, getStartOfDay, MILL_PER_DAY } from "@util/time"
11-
import badgeManager from "./badge-manager"
12-
import MessageDispatcher from "./message-dispatcher"
10+
import badgeManager from "../badge-manager"
1311

1412
async function handleTime(context: ItemIncContext, timeRange: [number, number], tabId: number | undefined): Promise<number> {
1513
const { host, url } = context
@@ -28,9 +26,9 @@ async function handleTime(context: ItemIncContext, timeRange: [number, number],
2826
return focusTime
2927
}
3028

31-
async function handleTrackTimeEvent(event: timer.core.Event, sender: ChromeMessageSender): Promise<void> {
29+
export async function handleTrackTimeEvent(event: timer.core.Event, senderTab: ChromeTab | undefined): Promise<void> {
3230
const { url, start, end, ignoreTabCheck } = event
33-
const { id: tabId, windowId, groupId } = sender?.tab || {}
31+
const { id: tabId, windowId, groupId } = senderTab ?? {}
3432
if (!ignoreTabCheck) {
3533
if (await windowNotFocused(windowId)) return
3634
if (await tabNotActive(tabId)) return
@@ -86,58 +84,12 @@ async function handleVisit(context: ItemIncContext) {
8684
metLimits?.length && sendLimitedMessage(metLimits)
8785
}
8886

89-
async function handleIncVisitEvent(param: { host: string, url: string }, sender: ChromeMessageSender): Promise<void> {
90-
const { host, url } = param || {}
87+
export async function handleIncVisitEvent(param: { host: string, url: string }, sender: ChromeMessageSender): Promise<void> {
88+
const { host, url } = param
9189
const { groupId } = sender?.tab ?? {}
92-
const { protocol } = extractHostname(url) || {}
90+
const { protocol } = extractHostname(url)
9391
const option = await optionHolder.get()
9492
if (protocol === "file" && !option.countLocalFiles) return
9593
await handleVisit({ host, url, groupId })
9694
}
9795

98-
function splitRunTime(start: number, end: number): Record<string, number> {
99-
const res: Record<string, number> = {}
100-
while (start < end) {
101-
const startOfNextDay = getStartOfDay(start).getTime() + MILL_PER_DAY
102-
const newStart = Math.min(end, startOfNextDay)
103-
const runTime = newStart - start
104-
runTime && (res[formatTimeYMD(start)] = runTime)
105-
start = newStart
106-
}
107-
return res
108-
}
109-
110-
const RUN_TIME_END_CACHE: { [host: string]: number } = {}
111-
112-
async function handleTrackRunTimeEvent(event: timer.core.Event): Promise<void> {
113-
const { start, end, url, host } = event || {}
114-
if (!host || !start || !end) return
115-
if (whitelistHolder.contains(host, url)) return
116-
const realStart = Math.max(RUN_TIME_END_CACHE[host] ?? 0, start)
117-
const byDate = splitRunTime(realStart, end)
118-
if (!Object.keys(byDate).length) return
119-
await itemService.addRunTime(host, byDate)
120-
RUN_TIME_END_CACHE[host] = Math.max(end, realStart)
121-
}
122-
123-
function handleTabGroupRemove(group: chrome.tabGroups.TabGroup) {
124-
itemService.batchDeleteGroupById(group.id)
125-
}
126-
127-
function handleTabGroupEnabled() {
128-
try {
129-
chrome.tabGroups.onRemoved.removeListener(handleTabGroupRemove)
130-
chrome.tabGroups.onRemoved.addListener(handleTabGroupRemove)
131-
} catch (e) {
132-
console.warn('failed to handle event: enableTabGroup', e)
133-
}
134-
}
135-
136-
export default function initTrackServer(messageDispatcher: MessageDispatcher) {
137-
messageDispatcher
138-
.register<timer.core.Event, void>('cs.trackTime', handleTrackTimeEvent)
139-
.register<timer.core.Event, void>('cs.trackRunTime', handleTrackRunTimeEvent)
140-
.register<{ host: string, url: string }, void>('cs.incVisitCount', handleIncVisitEvent)
141-
.register<string, timer.core.Result>('cs.getTodayInfo', host => itemService.getResult(host, new Date()))
142-
.register<void, void>('enableTabGroup', handleTabGroupEnabled)
143-
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import itemService from "@service/item-service"
2+
import whitelistHolder from "@service/whitelist/holder"
3+
import { formatTimeYMD, getStartOfDay, MILL_PER_DAY } from "@util/time"
4+
5+
function splitRunTime(start: number, end: number): Record<string, number> {
6+
const res: Record<string, number> = {}
7+
while (start < end) {
8+
const startOfNextDay = getStartOfDay(start).getTime() + MILL_PER_DAY
9+
const newStart = Math.min(end, startOfNextDay)
10+
const runTime = newStart - start
11+
runTime && (res[formatTimeYMD(start)] = runTime)
12+
start = newStart
13+
}
14+
return res
15+
}
16+
17+
const RUN_TIME_END_CACHE: { [host: string]: number } = {}
18+
19+
export async function handleTrackRunTimeEvent(event: timer.core.Event): Promise<void> {
20+
const { start, end, url, host } = event || {}
21+
if (!host || !start || !end) return
22+
if (whitelistHolder.contains(host, url)) return
23+
const realStart = Math.max(RUN_TIME_END_CACHE[host] ?? 0, start)
24+
const byDate = splitRunTime(realStart, end)
25+
if (!Object.keys(byDate).length) return
26+
await itemService.addRunTime(host, byDate)
27+
RUN_TIME_END_CACHE[host] = Math.max(end, realStart)
28+
}

src/i18n/message/app/option-resource.json

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"tabGroupInfo": "删除标签组后,数据也会被删除",
4444
"tabGroupsPermGrant": "该功能需要授予相关权限",
4545
"fileAccessDisabled": "目前不允许访问文件网址,请先在管理界面开启",
46-
"fileAccessFirefox": "很抱歉,该功能在 Firefox 中不支持",
4746
"weekStart": "每周的第一天 {input}",
4847
"weekStartAsNormal": "按照惯例"
4948
},
@@ -171,7 +170,6 @@
171170
"tabGroupInfo": "刪除分頁群組時,相關的時間追蹤數據也會一併清除。",
172171
"tabGroupsPermGrant": "此功能需要相關權限才能運作",
173172
"fileAccessDisabled": "目前不允許存取檔案 URL,請至管理頁面啟用",
174-
"fileAccessFirefox": "抱歉,Firefox 不支援此功能",
175173
"weekStart": "每週起始日 {input}",
176174
"weekStartAsNormal": "依慣例"
177175
},
@@ -300,7 +298,6 @@
300298
"tabGroupInfo": "When you delete a tag group, the data will also be deleted.",
301299
"tabGroupsPermGrant": "This feature requires relevant permissions",
302300
"fileAccessDisabled": "Access to file URLs is currently not allowed. Please enable it on the manage page first",
303-
"fileAccessFirefox": "Sorry, this feature is not supported in Firefox",
304301
"weekStart": "The first day for each week {input}",
305302
"weekStartAsNormal": "As Normal"
306303
},
@@ -426,7 +423,6 @@
426423
"localFileTime": "ローカルファイルの読み取り",
427424
"localFilesInfo": "PDF、画像、txt、jsonを含む",
428425
"fileAccessDisabled": "ファイル URL へのアクセスは現在許可されていません。まず管理ページで有効にしてください。",
429-
"fileAccessFirefox": "申し訳ありませんが、この機能はFirefoxではサポートされていません",
430426
"weekStart": "週の最初の日 {input}",
431427
"weekStartAsNormal": "いつものように"
432428
},
@@ -551,7 +547,6 @@
551547
"localFileTime": "ler ficheiros locais",
552548
"localFilesInfo": "Suporta PDF, imagens, txt e json.",
553549
"fileAccessDisabled": "Acesso a URLs de ficheiro não permitido. Ative na página de gestão.",
554-
"fileAccessFirefox": "Não suportado no Firefox",
555550
"weekStart": "Primeiro dia da semana {input}",
556551
"weekStartAsNormal": "Normal"
557552
},
@@ -672,7 +667,6 @@
672667
"tabGroupInfo": "Якщо видалити групу вкладок, дані також видаляться.",
673668
"tabGroupsPermGrant": "Ця функція потребує відповідних дозволів",
674669
"fileAccessDisabled": "Доступ до URL-адрес файлу наразі не дозволено. Спершу ввімкніть на сторінці керування",
675-
"fileAccessFirefox": "На жаль, ця функція не підтримується у Firefox",
676670
"weekStart": "Перший день тижня: {input}",
677671
"weekStartAsNormal": "Типово"
678672
},
@@ -800,7 +794,6 @@
800794
"tabGroupInfo": "Al eliminar un grupo de pestañas, sus datos también se borrarán.",
801795
"tabGroupsPermGrant": "Esta función requiere permisos pertinentes",
802796
"fileAccessDisabled": "Actualmente no se permite el acceso a las URL de archivos. Habilítelo primero en la página de administración",
803-
"fileAccessFirefox": "Lo sentimos, esta función no es compatible con Firefox",
804797
"weekStart": "El primer día de cada semana {input}",
805798
"weekStartAsNormal": "Como normalmente"
806799
},
@@ -928,7 +921,6 @@
928921
"tabGroupInfo": "Wenn Sie eine Tag-Gruppe löschen, werden auch die Daten gelöscht.",
929922
"tabGroupsPermGrant": "Dieses Feature benötigt entsprechende Berechtigungen",
930923
"fileAccessDisabled": "Der Zugriff auf Datei-URLs ist derzeit nicht erlaubt. Bitte aktiviere ihn zuerst auf der Verwaltungsseite",
931-
"fileAccessFirefox": "Leider wird diese Funktion in Firefox nicht unterstützt",
932924
"weekStart": "Erster Tag der Woche {input}",
933925
"weekStartAsNormal": "Wie normal"
934926
},
@@ -1057,7 +1049,6 @@
10571049
"tabGroupInfo": "Lorsque vous supprimez un groupe d'onglets, les données seront également supprimées.",
10581050
"tabGroupsPermGrant": "Cette fonctionnalité nécessite des autorisations pertinentes",
10591051
"fileAccessDisabled": "L'accès aux URL des fichiers n'est actuellement pas autorisé. Veuillez d'abord l'activer dans la page de gestion.",
1060-
"fileAccessFirefox": "Désolé, cette fonctionnalité n'est pas prise en charge dans Firefox",
10611052
"weekStart": "Le premier jour de chaque semaine {input}",
10621053
"weekStartAsNormal": "Comme d'habitude"
10631054
},
@@ -1227,7 +1218,6 @@
12271218
"tabGroupInfo": "عند حذف مجموعة علامات تبويب، سيتم حذف البيانات أيضًا.",
12281219
"tabGroupsPermGrant": "هذه الميزة تتطلب أذونات ذات صلة",
12291220
"fileAccessDisabled": "الوصول إلى عناوين الملفات غير مسموح به حاليًا. يرجى تفعيل الخاصية من صفحة الإدارة أولاً",
1230-
"fileAccessFirefox": "عذراً، هذه الميزة غير مدعومة في فايرفوكس",
12311221
"weekStart": "اليوم الأول لكل أسبوع {input}",
12321222
"weekStartAsNormal": "بشكل عادي"
12331223
},
@@ -1356,7 +1346,6 @@
13561346
"tabGroupInfo": "Bir etiket grubunu sildiğinizde, veriler de silinir.",
13571347
"tabGroupsPermGrant": "Bu özellik ilgili izinleri gerektirir",
13581348
"fileAccessDisabled": "Dosya URL'lerine erişim şu anda izin verilmiyor. Lütfen önce yönetim sayfasında bunu etkinleştirin",
1359-
"fileAccessFirefox": "Üzgünüz, bu özellik Firefox'ta desteklenmiyor",
13601349
"weekStart": "Her haftanın ilk günü {input}",
13611350
"weekStartAsNormal": "Normal"
13621351
},

src/i18n/message/app/option.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export type OptionMessage = {
4848
tabGroupInfo: string
4949
tabGroupsPermGrant: string
5050
fileAccessDisabled: string
51-
fileAccessFirefox: string
5251
weekStart: string
5352
weekStartAsNormal: string
5453
}

0 commit comments

Comments
 (0)