Skip to content

Commit 1da8408

Browse files
committed
feat: semi
1 parent 7b095c1 commit 1da8408

File tree

27 files changed

+363
-223
lines changed

27 files changed

+363
-223
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { createServer } from "http"
2+
import md5 from 'md5'
3+
4+
const AUTH_TOKEN: string | undefined = process.env.AUTH_TOKEN
5+
6+
function genSign(meta: any, auth: string): string {
7+
const data = { ...meta, _auth: auth }
8+
const sortedKeys = Object.keys(data).sort()
9+
const query = sortedKeys
10+
.map(key => `${key}=${encodeURIComponent(String(data[key as keyof typeof data]))}`)
11+
.join('&')
12+
return md5(query)
13+
}
14+
15+
function verifySign(meta: any, receivedSign: string | string[] | undefined): boolean {
16+
if (!AUTH_TOKEN) return true
17+
if (!receivedSign) return false
18+
return genSign(meta, AUTH_TOKEN) === receivedSign
19+
}
20+
21+
function main() {
22+
const server = createServer(async (req, res) => {
23+
if (req.method === 'HEAD') {
24+
res.writeHead(200).end()
25+
return
26+
}
27+
28+
if (req.method === 'POST') {
29+
try {
30+
const body = await new Promise<string>((resolve, reject) => {
31+
let data = ''
32+
req.on('data', (chunk: Buffer) => { data += chunk.toString() })
33+
req.on('end', () => resolve(data))
34+
req.on('error', reject)
35+
})
36+
37+
const { meta } = JSON.parse(body)
38+
const sign = req.headers['tt4b-sign']
39+
if (!verifySign(meta, sign)) {
40+
res.writeHead(401).end('Unauthorized')
41+
return
42+
}
43+
44+
res.writeHead(200, { 'Content-Type': 'application/json' })
45+
res.end("Thanks!!")
46+
} catch (e) {
47+
res.writeHead(400).end('Bad Request')
48+
}
49+
return
50+
}
51+
52+
res.writeHead(405).end('Method Not Allowed')
53+
})
54+
55+
const port = 3000
56+
server.listen(port, () => console.log(`Notification server listening on port ${port}`))
57+
}
58+
59+
main()

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@
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.6",
36-
"@rspack/core": "^1.7.6",
35+
"@rspack/cli": "^1.7.7",
36+
"@rspack/core": "^1.7.7",
3737
"@swc/core": "^1.15.18",
3838
"@swc/jest": "^0.2.39",
3939
"@types/chrome": "0.1.37",
4040
"@types/decompress": "^4.2.7",
4141
"@types/jest": "^30.0.0",
42+
"@types/md5": "^2.3.6",
4243
"@types/node": "^25.3.3",
4344
"@types/punycode": "^2.1.4",
4445
"@vue/babel-plugin-jsx": "^2.0.1",
4546
"babel-loader": "^10.0.0",
46-
"commitlint": "^20.4.2",
47+
"commitlint": "^20.4.3",
4748
"css-loader": "^7.1.4",
4849
"decompress": "^4.2.1",
4950
"fake-indexeddb": "^6.2.5",
@@ -52,7 +53,7 @@
5253
"jest-environment-jsdom": "^30.2.0",
5354
"jest-junit": "^16.0.0",
5455
"jszip": "^3.10.1",
55-
"postcss": "^8.5.6",
56+
"postcss": "^8.5.8",
5657
"postcss-loader": "^8.2.1",
5758
"postcss-rtlcss": "^5.7.1",
5859
"puppeteer": "^24.37.5",
@@ -65,6 +66,7 @@
6566
"@element-plus/icons-vue": "^2.3.2",
6667
"echarts": "^6.0.0",
6768
"element-plus": "2.13.3",
69+
"md5": "^2.3.0",
6870
"punycode": "^2.3.1",
6971
"typescript-guard": "^0.2.1",
7072
"vue": "^3.5.29",

src/api/chrome/alarm.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ export function createAlarm(name: string, when: number): Promise<void> {
2828
}))
2929
}
3030

31-
export async function existAlarm(name: string): Promise<boolean> {
31+
export async function getAlarm(name: string): Promise<chrome.alarms.Alarm | undefined> {
3232
if (IS_MV3) {
33-
const alarm = await chrome.alarms.get(name)
34-
return !!alarm
33+
return chrome.alarms.get(name)
3534
}
3635
return new Promise(resolve => chrome.alarms.get(name, alarm => {
37-
handleError('existAlarm')
38-
resolve(!!alarm)
36+
handleError('getAlarm')
37+
resolve(alarm)
3938
}))
4039
}

src/api/chrome/notifications.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,22 @@
77
import { IS_MV3 } from "@util/constant/environment"
88
import { handleError } from "./common"
99

10-
export async function createNotification(options: chrome.notifications.NotificationOptions): Promise<string> {
11-
const notificationId = `timer-notification-${Date.now()}`
12-
13-
// Ensure all required fields are present
14-
const createOptions: chrome.notifications.NotificationCreateOptions = {
15-
type: (options.type || 'basic') as chrome.notifications.TemplateType,
16-
title: options.title ?? '',
17-
message: options.message ?? '',
18-
iconUrl: options.iconUrl ?? '',
19-
}
20-
10+
export type NotificationTopic = 'time'
11+
12+
export async function createNotification(
13+
topic: NotificationTopic,
14+
options: MakeRequired<chrome.notifications.NotificationOptions, 'type' | 'title' | 'message' | 'iconUrl'>
15+
): Promise<string> {
2116
if (IS_MV3) {
22-
return await chrome.notifications.create(notificationId, createOptions)
17+
return await chrome.notifications.create(topic, options)
2318
} else {
2419
return new Promise((resolve, reject) => {
25-
// In MV2, chrome.notifications.create can accept (id: string, options: NotificationCreateOptions, callback)
26-
;(chrome.notifications.create as any)(notificationId, createOptions, (id: string) => {
20+
chrome.notifications.create(topic, options, (id: string) => {
2721
const error = handleError('createNotification')
2822
if (error) {
2923
reject(new Error(error))
3024
} else {
31-
resolve(id || notificationId)
25+
resolve(id)
3226
}
3327
})
3428
})

src/background/alarm-manager.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { clearAlarm, createAlarm, existAlarm, onAlarm } from "@api/chrome/alarm"
1+
import { clearAlarm, createAlarm, getAlarm, onAlarm } from "@api/chrome/alarm"
22
import { getRuntimeId } from "@api/chrome/runtime"
33

44
type _AlarmConfig = {
55
handler: _Handler,
66
interval?: number,
7-
when?: () => number,
7+
when?: () => number | null,
88
}
99

1010
type _Handler = (alarm: ChromeAlarm) => void
@@ -15,7 +15,7 @@ const ALARM_PREFIX_LENGTH = ALARM_PREFIX.length
1515
const getInnerName = (outerName: string) => ALARM_PREFIX + outerName
1616
const getOuterName = (innerName: string) => innerName.substring(ALARM_PREFIX_LENGTH)
1717

18-
const calcNextTs = (config: _AlarmConfig): number => {
18+
const calcNextTs = (config: _AlarmConfig): number | null => {
1919
const { interval, when } = config
2020
if (interval) return Date.now() + interval
2121
if (when) return when()
@@ -53,10 +53,10 @@ class AlarmManager {
5353
} catch (e) {
5454
console.info("Failed to handle alarm event", e)
5555
} finally {
56-
const nextTs = calcNextTs(config)
5756
// Clear this one
5857
await clearAlarm(name)
59-
await createAlarm(name, nextTs)
58+
const nextTs = calcNextTs(config)
59+
nextTs && await createAlarm(name, nextTs)
6060
}
6161
})
6262
}
@@ -85,7 +85,7 @@ class AlarmManager {
8585
/**
8686
* Set a alarm to do sth if the time arrives
8787
*/
88-
async setWhen(outerName: string, when: () => number, handler: _Handler): Promise<void> {
88+
async setWhen(outerName: string, when: () => number | null, handler: _Handler): Promise<void> {
8989
if (!when || !handler) {
9090
return
9191
}
@@ -98,7 +98,8 @@ class AlarmManager {
9898
// Initialize config
9999
this.alarms[outerName] = config
100100
// Create new one alarm
101-
await createAlarm(getInnerName(outerName), when())
101+
const next = calcNextTs(config)
102+
next && await createAlarm(getInnerName(outerName), next)
102103
}
103104

104105
/**
@@ -112,9 +113,9 @@ class AlarmManager {
112113
/**
113114
* Judge if exist
114115
*/
115-
async exist(outerName: string): Promise<boolean> {
116+
async getAlarm(outerName: string): Promise<chrome.alarms.Alarm | undefined> {
116117
const innerName = getInnerName(outerName)
117-
const existed = await existAlarm(innerName)
118+
const existed = await getAlarm(innerName)
118119
if (!existed && this.alarms[outerName]) {
119120
delete this.alarms[outerName]
120121
}

src/background/backup-scheduler.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/background/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { isNoneWindowId, onNormalWindowFocusChanged } from "@api/chrome/window"
1010
import optionHolder from "@service/components/option-holder"
1111
import { isBrowserUrl } from "@util/pattern"
1212
import { openLog } from "../common/logger"
13-
import { initBackupScheduler } from './backup-scheduler'
1413
import badgeTextManager from "./badge-manager"
1514
import initBrowserAction from "./browser-action-manager"
1615
import initCsHandler from "./content-script-handler"
@@ -19,6 +18,7 @@ import handleInstall from './install-handler'
1918
import initLimitProcessor from "./limit-processor"
2019
import MessageDispatcher from "./message-dispatcher"
2120
import VersionMigrator from "./migrator"
21+
import { initScheduler } from './scheduler'
2222
import initSidePanel from "./side-panel"
2323
import TabListener from './tab-listener'
2424
import initTrackServer from "./track-server"
@@ -50,8 +50,8 @@ initTrackServer(messageDispatcher)
5050
// Process version
5151
new VersionMigrator().init()
5252

53-
// Backup scheduler
54-
initBackupScheduler(messageDispatcher)
53+
// scheduler
54+
initScheduler(messageDispatcher)
5555

5656
// Manage the context menus
5757
initWhitelistMenuManager()

src/background/scheduler.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Copyright (c) 2023 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
8+
import optionDatabase from '@db/option-database'
9+
import backupProcessor from "@service/backup/processor"
10+
import notificationProcessor from "@service/notification/processor"
11+
import { MILL_PER_MINUTE } from "@util/time"
12+
import alarmManager from "./alarm-manager"
13+
import type MessageDispatcher from './message-dispatcher'
14+
15+
const BACKUP_ALARM_NAME = 'auto-backup-data'
16+
const NOTIFICATION_ALARM_NAME = 'notification-data'
17+
18+
export async function initScheduler(dispatcher: MessageDispatcher): Promise<void> {
19+
dispatcher.register('resetBackupScheduler', resetBackup)
20+
.register('resetNotificationScheduler', resetNotification)
21+
22+
const existBackup = await alarmManager.getAlarm(BACKUP_ALARM_NAME)
23+
!existBackup && await resetBackup()
24+
25+
const existNotification = await alarmManager.getAlarm(NOTIFICATION_ALARM_NAME)
26+
!existNotification && await resetNotification()
27+
}
28+
29+
async function resetBackup(): Promise<void> {
30+
// MUST read latest option from database
31+
const option = await optionDatabase.getOption()
32+
33+
await alarmManager.remove(BACKUP_ALARM_NAME)
34+
35+
const { autoBackUp, backupType, autoBackUpInterval = 0 } = option
36+
if (backupType === 'none' || !autoBackUp || !autoBackUpInterval) {
37+
return
38+
}
39+
40+
const interval = autoBackUpInterval * MILL_PER_MINUTE
41+
await alarmManager.setInterval(BACKUP_ALARM_NAME, interval, async () => {
42+
const result = await backupProcessor.syncData()
43+
if (!result.success) {
44+
console.warn(`Failed to backup ts=${Date.now()}, msg=${result.errorMsg}`)
45+
}
46+
})
47+
}
48+
49+
type OffsetHandler = (offsetMin: number) => number
50+
const OFFSET_HANDLERS: Record<Exclude<timer.notification.Cycle, 'none'>, OffsetHandler> = {
51+
daily: offset => {
52+
const next = new Date()
53+
next.setHours(0, offset, 0, 0)
54+
const now = new Date()
55+
while (next.getTime() < now.getTime()) {
56+
next.setDate(next.getDate() + 1)
57+
}
58+
return next.getTime()
59+
},
60+
weekly: offset => {
61+
const next = new Date()
62+
const weekday = next.getDay()
63+
next.setDate(next.getDate() - weekday)
64+
next.setHours(0, offset, 0, 0)
65+
const now = new Date()
66+
while (next.getTime() < now.getTime()) {
67+
next.setDate(next.getDate() + 7)
68+
}
69+
return next.getTime()
70+
}
71+
}
72+
73+
async function resetNotification(): Promise<void> {
74+
await alarmManager.remove(NOTIFICATION_ALARM_NAME)
75+
76+
const option = await optionDatabase.getOption()
77+
const { notificationCycle: cycle, notificationOffset: offset } = option
78+
79+
if (cycle === 'none') return
80+
81+
await alarmManager.setWhen(
82+
NOTIFICATION_ALARM_NAME,
83+
() => OFFSET_HANDLERS[cycle](offset),
84+
async () => {
85+
const result = await notificationProcessor.doSend()
86+
if (!result.success) {
87+
console.warn(`Failed to send notification ts=${Date.now()}, msg=${result.errorMsg}`)
88+
}
89+
}
90+
)
91+
}

0 commit comments

Comments
 (0)