@talex-touch/utils 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/animation/window-node.ts +15 -12
- package/animation/window.ts +19 -15
- package/auth/clerk-types.ts +1 -1
- package/auth/index.ts +1 -1
- package/auth/useAuthState.ts +6 -5
- package/auth/useClerkConfig.ts +4 -4
- package/auth/useClerkProvider.ts +3 -2
- package/channel/index.ts +23 -22
- package/common/file-scan-constants.ts +137 -121
- package/common/file-scan-utils.ts +48 -27
- package/common/index.ts +3 -3
- package/common/search/gather.ts +1 -1
- package/common/search/index.ts +5 -6
- package/common/storage/constants.ts +3 -2
- package/common/storage/entity/app-settings.ts +5 -3
- package/common/storage/entity/shortcut-settings.ts +10 -10
- package/common/storage/shortcut-storage.ts +6 -4
- package/common/utils/file.ts +14 -6
- package/common/utils/index.ts +62 -52
- package/common/utils/polling.ts +88 -84
- package/common/utils/task-queue.ts +11 -10
- package/common/utils/time.ts +50 -47
- package/common/utils/timing.ts +41 -37
- package/core-box/builder/index.ts +1 -1
- package/core-box/builder/tuff-builder.ts +254 -229
- package/core-box/index.ts +4 -6
- package/core-box/preview/index.ts +1 -0
- package/core-box/preview/types.ts +43 -0
- package/core-box/recommendation.ts +77 -0
- package/core-box/tuff/index.ts +1 -1
- package/core-box/tuff/tuff-dsl.ts +328 -266
- package/electron/download-manager.ts +43 -42
- package/electron/env-tool.ts +19 -18
- package/electron/file-parsers/index.ts +2 -2
- package/electron/file-parsers/parsers/text-parser.ts +15 -14
- package/electron/file-parsers/registry.ts +9 -7
- package/electron/file-parsers/types.ts +4 -4
- package/electron/index.ts +2 -2
- package/eventbus/index.ts +11 -11
- package/index.ts +5 -4
- package/intelligence/client.ts +87 -0
- package/intelligence/index.ts +1 -0
- package/package.json +14 -14
- package/permission/index.ts +8 -8
- package/plugin/channel.ts +77 -68
- package/plugin/index.ts +96 -82
- package/plugin/install.ts +8 -8
- package/plugin/log/types.ts +5 -5
- package/plugin/node/index.ts +1 -1
- package/plugin/node/logger-manager.ts +14 -11
- package/plugin/node/logger.ts +8 -8
- package/plugin/plugin-source.ts +11 -11
- package/plugin/preload.ts +1 -1
- package/plugin/providers/registry.ts +8 -7
- package/plugin/providers/types.ts +6 -6
- package/plugin/sdk/README.md +216 -0
- package/plugin/sdk/box-sdk.ts +219 -0
- package/plugin/sdk/channel.ts +20 -20
- package/plugin/sdk/clipboard.ts +8 -6
- package/plugin/sdk/common.ts +10 -6
- package/plugin/sdk/core-box.ts +2 -3
- package/plugin/sdk/division-box.ts +266 -0
- package/plugin/sdk/enum/bridge-event.ts +1 -1
- package/plugin/sdk/examples/storage-onDidChange-example.js +1 -1
- package/plugin/sdk/feature-sdk.ts +235 -0
- package/plugin/sdk/features.ts +34 -26
- package/plugin/sdk/hooks/bridge.ts +3 -6
- package/plugin/sdk/hooks/index.ts +1 -1
- package/plugin/sdk/hooks/life-cycle.ts +4 -10
- package/plugin/sdk/index.ts +10 -7
- package/plugin/sdk/service/index.ts +3 -3
- package/plugin/sdk/storage.ts +4 -4
- package/plugin/sdk/system.ts +1 -1
- package/plugin/sdk/types.ts +165 -146
- package/plugin/sdk/window/index.ts +8 -5
- package/preload/loading.ts +6 -6
- package/preload/renderer.ts +4 -2
- package/renderer/hooks/arg-mapper.ts +1 -2
- package/renderer/hooks/index.ts +2 -0
- package/renderer/hooks/initialize.ts +10 -8
- package/renderer/hooks/performance.ts +4 -4
- package/renderer/hooks/use-channel.ts +150 -0
- package/renderer/hooks/use-intelligence.ts +236 -0
- package/renderer/index.ts +6 -2
- package/renderer/ref.ts +32 -36
- package/renderer/slots.ts +29 -26
- package/renderer/storage/app-settings.ts +16 -6
- package/renderer/storage/base-storage.ts +222 -114
- package/renderer/storage/index.ts +3 -0
- package/renderer/storage/intelligence-storage.ts +218 -0
- package/renderer/storage/openers.ts +13 -3
- package/renderer/touch-sdk/env.ts +41 -41
- package/renderer/touch-sdk/index.ts +1 -1
- package/renderer/touch-sdk/terminal.ts +5 -5
- package/renderer/touch-sdk/utils.ts +4 -3
- package/search/levenshtein-utils.ts +11 -11
- package/search/types.ts +102 -102
- package/service/index.ts +11 -11
- package/service/protocol/index.ts +217 -14
- package/types/division-box.ts +248 -0
- package/types/download.ts +72 -34
- package/types/index.ts +3 -1
- package/types/intelligence.ts +607 -0
- package/types/modules/base.ts +16 -16
- package/types/modules/index.ts +1 -1
- package/types/modules/module-lifecycle.ts +21 -21
- package/types/modules/module-manager.ts +11 -11
- package/types/modules/module.ts +16 -16
- package/types/storage.ts +0 -1
- package/types/touch-app-core.ts +32 -32
- package/types/update.ts +91 -21
- package/core-box/README.md +0 -218
- package/core-box/builder/tuff-builder.example.ts.bak +0 -258
- package/core-box/run-tests.sh +0 -7
- package/core-box/search.ts +0 -1
- package/electron/clipboard-helper.ts +0 -199
package/common/search/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISearchProvider, TuffQuery, TuffSearchResult } from '@talex-touch/utils'
|
|
1
|
+
import type { ISearchProvider, TuffQuery, TuffSearchResult } from '@talex-touch/utils'
|
|
2
2
|
|
|
3
3
|
export * from './gather'
|
|
4
4
|
|
|
@@ -30,7 +30,6 @@ export interface TuffUpdate {
|
|
|
30
30
|
sourceStats?: TuffSearchResult['sources']
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
33
|
/**
|
|
35
34
|
* Search Engine Interface (formerly ISearchEngine)
|
|
36
35
|
*
|
|
@@ -41,13 +40,13 @@ export interface ISearchEngine<C> {
|
|
|
41
40
|
* Registers a search provider with the engine.
|
|
42
41
|
* @param provider - An instance of ISearchProvider.
|
|
43
42
|
*/
|
|
44
|
-
registerProvider(provider: ISearchProvider<C>)
|
|
43
|
+
registerProvider: (provider: ISearchProvider<C>) => void
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
46
|
* Unregisters a search provider by its unique ID.
|
|
48
47
|
* @param providerId - The unique ID of the provider to remove.
|
|
49
48
|
*/
|
|
50
|
-
unregisterProvider(providerId: string)
|
|
49
|
+
unregisterProvider: (providerId: string) => void
|
|
51
50
|
|
|
52
51
|
/**
|
|
53
52
|
* Executes a search across all registered and relevant providers.
|
|
@@ -57,11 +56,11 @@ export interface ISearchEngine<C> {
|
|
|
57
56
|
* @returns A promise that resolves to a TuffSearchResult object,
|
|
58
57
|
* containing the ranked items and metadata about the search operation.
|
|
59
58
|
*/
|
|
60
|
-
search(query: TuffQuery)
|
|
59
|
+
search: (query: TuffQuery) => Promise<TuffSearchResult>
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
62
|
* Performs background maintenance tasks, such as pre-heating caches,
|
|
64
63
|
* refreshing indexes, etc.
|
|
65
64
|
*/
|
|
66
|
-
maintain()
|
|
65
|
+
maintain: () => void
|
|
67
66
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export enum StorageList {
|
|
2
2
|
APP_SETTING = 'app-setting.ini',
|
|
3
3
|
SHORTCUT_SETTING = 'shortcut-setting.ini',
|
|
4
|
-
OPENERS = 'openers.json'
|
|
4
|
+
OPENERS = 'openers.json',
|
|
5
|
+
IntelligenceConfig = 'aisdk-config',
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -13,5 +14,5 @@ export enum ConfigKeys {
|
|
|
13
14
|
* Stores the timestamp of the last successful full application scan using mdfind.
|
|
14
15
|
* This is used to schedule the next comprehensive scan.
|
|
15
16
|
*/
|
|
16
|
-
APP_PROVIDER_LAST_MDFIND_SCAN = 'app_provider_last_mdfind_scan'
|
|
17
|
+
APP_PROVIDER_LAST_MDFIND_SCAN = 'app_provider_last_mdfind_scan',
|
|
17
18
|
}
|
|
@@ -51,9 +51,11 @@ const _appSettingOriginData = {
|
|
|
51
51
|
showTray: true,
|
|
52
52
|
adminPrivileges: false,
|
|
53
53
|
hideDock: false,
|
|
54
|
+
runAsAdmin: false,
|
|
55
|
+
customDesktop: false,
|
|
54
56
|
},
|
|
55
57
|
layout: 'simple',
|
|
56
|
-
}
|
|
58
|
+
}
|
|
57
59
|
|
|
58
60
|
export const appSettingOriginData = Object.freeze(_appSettingOriginData)
|
|
59
61
|
|
|
@@ -63,5 +65,5 @@ export const appSettingOriginData = Object.freeze(_appSettingOriginData)
|
|
|
63
65
|
* Combines the default configuration with support for dynamic additional properties.
|
|
64
66
|
*/
|
|
65
67
|
export type AppSetting = typeof _appSettingOriginData & {
|
|
66
|
-
[key: string]: any
|
|
67
|
-
}
|
|
68
|
+
[key: string]: any
|
|
69
|
+
}
|
|
@@ -4,19 +4,19 @@ export enum ShortcutType {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export interface ShortcutMeta {
|
|
7
|
-
creationTime: number
|
|
8
|
-
modificationTime: number
|
|
9
|
-
author: string
|
|
10
|
-
description?: string
|
|
7
|
+
creationTime: number
|
|
8
|
+
modificationTime: number
|
|
9
|
+
author: string
|
|
10
|
+
description?: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface Shortcut {
|
|
14
|
-
id: string
|
|
15
|
-
accelerator: string
|
|
16
|
-
type: ShortcutType
|
|
17
|
-
meta: ShortcutMeta
|
|
14
|
+
id: string
|
|
15
|
+
accelerator: string
|
|
16
|
+
type: ShortcutType
|
|
17
|
+
meta: ShortcutMeta
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export type ShortcutSetting = Shortcut[]
|
|
20
|
+
export type ShortcutSetting = Shortcut[]
|
|
21
21
|
|
|
22
|
-
export const shortcutSettingOriginData: ShortcutSetting = []
|
|
22
|
+
export const shortcutSettingOriginData: ShortcutSetting = []
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import type { Shortcut, ShortcutSetting } from './entity/shortcut-settings'
|
|
1
2
|
import { StorageList } from './constants'
|
|
2
|
-
import { shortcutSettingOriginData
|
|
3
|
+
import { shortcutSettingOriginData } from './entity/shortcut-settings'
|
|
3
4
|
|
|
4
5
|
class ShortcutStorage {
|
|
5
6
|
private _config: ShortcutSetting = []
|
|
6
7
|
|
|
7
8
|
constructor(private readonly storage: {
|
|
8
|
-
getConfig: (name: string) => any
|
|
9
|
-
saveConfig: (name: string, content?: string) => void
|
|
9
|
+
getConfig: (name: string) => any
|
|
10
|
+
saveConfig: (name: string, content?: string) => void
|
|
10
11
|
}) {
|
|
11
12
|
this.init()
|
|
12
13
|
}
|
|
@@ -16,7 +17,8 @@ class ShortcutStorage {
|
|
|
16
17
|
if (!config || !Array.isArray(config) || config.length === 0) {
|
|
17
18
|
this._config = [...shortcutSettingOriginData]
|
|
18
19
|
this._save()
|
|
19
|
-
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
20
22
|
this._config = config
|
|
21
23
|
}
|
|
22
24
|
}
|
package/common/utils/file.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2
|
-
const path =
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const path = (() => {
|
|
3
|
+
if (typeof window === 'undefined') {
|
|
4
|
+
return require('node:path')
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
return require('path-browserify')
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return require('node:path')
|
|
11
|
+
}
|
|
12
|
+
})()
|
|
5
13
|
|
|
6
14
|
/**
|
|
7
15
|
* Enum for various file types.
|
|
@@ -21,7 +29,7 @@ export enum FileType {
|
|
|
21
29
|
Spreadsheet = 'Spreadsheet',
|
|
22
30
|
Presentation = 'Presentation',
|
|
23
31
|
Ebook = 'Ebook',
|
|
24
|
-
Other = 'Other'
|
|
32
|
+
Other = 'Other',
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
const extensionMap: Map<FileType, Set<string>> = new Map([
|
|
@@ -37,8 +45,8 @@ const extensionMap: Map<FileType, Set<string>> = new Map([
|
|
|
37
45
|
[FileType.Font, new Set(['.ttf', '.otf', '.woff', '.woff2'])],
|
|
38
46
|
[FileType.Spreadsheet, new Set(['.xls', '.xlsx', '.csv', '.numbers'])],
|
|
39
47
|
[FileType.Presentation, new Set(['.ppt', '.pptx', '.key'])],
|
|
40
|
-
[FileType.Ebook, new Set(['.epub', '.mobi', '.azw'])]
|
|
41
|
-
])
|
|
48
|
+
[FileType.Ebook, new Set(['.epub', '.mobi', '.azw'])],
|
|
49
|
+
])
|
|
42
50
|
|
|
43
51
|
/**
|
|
44
52
|
* Get the file type from a file path.
|
package/common/utils/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
12
|
export async function sleep(time: number): Promise<number> {
|
|
13
|
-
|
|
13
|
+
return new Promise(resolve => setTimeout(() => resolve(time), time))
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -28,14 +28,14 @@ export async function sleep(time: number): Promise<number> {
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export function anyStr2Num(str: string): bigint {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const codes = Array.from(str).map(ch => ch.charCodeAt(0))
|
|
32
|
+
const minCode = Math.min(...codes)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const encoded = codes
|
|
35
|
+
.map(code => (code - minCode).toString().padStart(2, '0'))
|
|
36
|
+
.join('')
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
return BigInt(`${minCode}000${encoded}`)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -51,16 +51,16 @@ export function anyStr2Num(str: string): bigint {
|
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
export function num2anyStr(num: bigint): string {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const [baseStr, encoded] = num.toString().split('000')
|
|
55
|
+
const base = Number(baseStr)
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
let result = ''
|
|
58
|
+
for (let i = 0; i < encoded.length; i += 2) {
|
|
59
|
+
const offset = Number(encoded.slice(i, i + 2))
|
|
60
|
+
result += String.fromCharCode(base + offset)
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
return result
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const LOCALHOST_KEYS = ['localhost', '127.0.0.1']
|
|
@@ -78,7 +78,7 @@ const LOCALHOST_KEYS = ['localhost', '127.0.0.1']
|
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
80
|
export function isLocalhostUrl(urlStr: string): boolean {
|
|
81
|
-
|
|
81
|
+
return LOCALHOST_KEYS.includes(new URL(urlStr).hostname)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
@@ -90,85 +90,95 @@ export function isLocalhostUrl(urlStr: string): boolean {
|
|
|
90
90
|
* @throws Error if an unsupported value is found (reports path and type).
|
|
91
91
|
*/
|
|
92
92
|
export function structuredStrictStringify(value: unknown): string {
|
|
93
|
-
const seen = new WeakMap<object, string>()
|
|
93
|
+
const seen = new WeakMap<object, string>()
|
|
94
94
|
const badTypes = [
|
|
95
|
-
'symbol'
|
|
96
|
-
]
|
|
95
|
+
'symbol',
|
|
96
|
+
]
|
|
97
97
|
// Support Map/Set, but not Error, DOM, Proxy, WeakMap, WeakSet
|
|
98
98
|
function getType(val: any): string {
|
|
99
|
-
if (val === null)
|
|
100
|
-
|
|
99
|
+
if (val === null)
|
|
100
|
+
return 'null'
|
|
101
|
+
if (Array.isArray(val))
|
|
102
|
+
return 'Array'
|
|
101
103
|
if (typeof Document !== 'undefined') {
|
|
102
|
-
if (val instanceof Node)
|
|
104
|
+
if (val instanceof Node)
|
|
105
|
+
return 'DOMNode'
|
|
103
106
|
}
|
|
104
107
|
// if (val instanceof Error) return 'Error';
|
|
105
|
-
if (val instanceof WeakMap)
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if (val instanceof WeakMap)
|
|
109
|
+
return 'WeakMap'
|
|
110
|
+
if (val instanceof WeakSet)
|
|
111
|
+
return 'WeakSet'
|
|
112
|
+
if (typeof val === 'object' && val !== null && val.constructor?.name === 'Proxy')
|
|
113
|
+
return 'Proxy'
|
|
114
|
+
if (typeof val === 'bigint')
|
|
115
|
+
return 'BigInt'
|
|
116
|
+
return typeof val
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
function serialize(val: any, path: string): any {
|
|
113
|
-
const type = getType(val)
|
|
120
|
+
const type = getType(val)
|
|
114
121
|
// Block disallowed/unsafe types and edge cases for structured-clone
|
|
115
122
|
if (badTypes.includes(typeof val) || type === 'DOMNode' || type === 'Proxy' || type === 'WeakMap' || type === 'WeakSet' || type === 'BigInt') {
|
|
116
|
-
throw new Error(`Cannot serialize property at path "${path}": type "${type}"`)
|
|
123
|
+
throw new Error(`Cannot serialize property at path "${path}": type "${type}"`)
|
|
117
124
|
}
|
|
118
125
|
// JSON doesn't support undefined, skip it for values in objects, preserve in arrays as null
|
|
119
|
-
if (typeof val === 'undefined')
|
|
126
|
+
if (typeof val === 'undefined')
|
|
127
|
+
return null
|
|
120
128
|
// Simple value
|
|
121
129
|
if (
|
|
122
|
-
val === null
|
|
123
|
-
typeof val === 'number'
|
|
124
|
-
typeof val === 'boolean'
|
|
125
|
-
typeof val === 'string'
|
|
126
|
-
)
|
|
130
|
+
val === null
|
|
131
|
+
|| typeof val === 'number'
|
|
132
|
+
|| typeof val === 'boolean'
|
|
133
|
+
|| typeof val === 'string'
|
|
134
|
+
) {
|
|
135
|
+
return val
|
|
136
|
+
}
|
|
127
137
|
// Cycle check
|
|
128
138
|
if (typeof val === 'object') {
|
|
129
139
|
if (seen.has(val)) {
|
|
130
|
-
return `[Circular ~${seen.get(val)}]
|
|
140
|
+
return `[Circular ~${seen.get(val)}]` // You could just throw if you dislike this fallback!
|
|
131
141
|
}
|
|
132
|
-
seen.set(val, path)
|
|
142
|
+
seen.set(val, path)
|
|
133
143
|
if (val instanceof Error) {
|
|
134
144
|
return {
|
|
135
145
|
name: val.name,
|
|
136
146
|
message: val.message,
|
|
137
147
|
stack: val.stack,
|
|
138
|
-
}
|
|
148
|
+
}
|
|
139
149
|
}
|
|
140
150
|
if (Array.isArray(val)) {
|
|
141
|
-
return val.map((item, idx) => serialize(item, `${path}[${idx}]`))
|
|
151
|
+
return val.map((item, idx) => serialize(item, `${path}[${idx}]`))
|
|
142
152
|
}
|
|
143
153
|
if (val instanceof Date) {
|
|
144
|
-
return val.toISOString()
|
|
154
|
+
return val.toISOString()
|
|
145
155
|
}
|
|
146
156
|
if (val instanceof Map) {
|
|
147
|
-
const obj: Record<string, any> = {}
|
|
157
|
+
const obj: Record<string, any> = {}
|
|
148
158
|
for (const [k, v] of val.entries()) {
|
|
149
|
-
obj[typeof k === 'string' ? k : JSON.stringify(k)] = serialize(v, `${path}[Map(${typeof k === 'string' ? k : JSON.stringify(k)})]`)
|
|
159
|
+
obj[typeof k === 'string' ? k : JSON.stringify(k)] = serialize(v, `${path}[Map(${typeof k === 'string' ? k : JSON.stringify(k)})]`)
|
|
150
160
|
}
|
|
151
|
-
return obj
|
|
161
|
+
return obj
|
|
152
162
|
}
|
|
153
163
|
if (val instanceof Set) {
|
|
154
|
-
return Array.from(val).map((item, idx) => serialize(item, `${path}[SetEntry${idx}]`))
|
|
164
|
+
return Array.from(val).map((item, idx) => serialize(item, `${path}[SetEntry${idx}]`))
|
|
155
165
|
}
|
|
156
166
|
// General object
|
|
157
|
-
const res: any = {}
|
|
167
|
+
const res: any = {}
|
|
158
168
|
for (const key of Object.keys(val)) {
|
|
159
|
-
res[key] = serialize(val[key], `${path}.${key}`)
|
|
169
|
+
res[key] = serialize(val[key], `${path}.${key}`)
|
|
160
170
|
}
|
|
161
|
-
return res
|
|
171
|
+
return res
|
|
162
172
|
}
|
|
163
|
-
throw new Error(`Cannot serialize property at path "${path}": unknown type`)
|
|
173
|
+
throw new Error(`Cannot serialize property at path "${path}": unknown type`)
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
return JSON.stringify(serialize(value, 'root'))
|
|
176
|
+
return JSON.stringify(serialize(value, 'root'))
|
|
167
177
|
}
|
|
168
178
|
|
|
169
|
-
export * from './
|
|
179
|
+
export * from './file'
|
|
170
180
|
|
|
171
|
-
export {
|
|
181
|
+
export { type AdaptiveTaskQueueOptions, runAdaptiveTaskQueue } from './task-queue'
|
|
172
182
|
|
|
173
183
|
export * from './time'
|
|
174
|
-
export * from './
|
|
184
|
+
export * from './timing'
|
package/common/utils/polling.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
/**
|
|
2
3
|
* @module polling
|
|
3
4
|
* A high-precision, efficient, singleton polling service for scheduling periodic tasks.
|
|
@@ -6,20 +7,20 @@
|
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
interface PollingTask {
|
|
9
|
-
id: string
|
|
10
|
-
callback: () => void | Promise<void
|
|
11
|
-
intervalMs: number
|
|
12
|
-
nextRunMs: number
|
|
10
|
+
id: string
|
|
11
|
+
callback: () => void | Promise<void>
|
|
12
|
+
intervalMs: number
|
|
13
|
+
nextRunMs: number
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
type TimeUnit = 'seconds' | 'minutes' | 'hours'
|
|
16
|
+
type TimeUnit = 'seconds' | 'minutes' | 'hours'
|
|
16
17
|
|
|
17
18
|
export class PollingService {
|
|
18
|
-
private static instance: PollingService
|
|
19
|
-
private tasks = new Map<string, PollingTask>()
|
|
20
|
-
private timerId: NodeJS.Timeout | null = null
|
|
21
|
-
private isRunning = false
|
|
22
|
-
private quitListenerCleanup?: () => void
|
|
19
|
+
private static instance: PollingService
|
|
20
|
+
private tasks = new Map<string, PollingTask>()
|
|
21
|
+
private timerId: NodeJS.Timeout | null = null
|
|
22
|
+
private isRunning = false
|
|
23
|
+
private quitListenerCleanup?: () => void
|
|
23
24
|
|
|
24
25
|
private constructor() {
|
|
25
26
|
// Private constructor to enforce singleton pattern
|
|
@@ -27,21 +28,21 @@ export class PollingService {
|
|
|
27
28
|
|
|
28
29
|
public static getInstance(): PollingService {
|
|
29
30
|
if (!PollingService.instance) {
|
|
30
|
-
PollingService.instance = new PollingService()
|
|
31
|
+
PollingService.instance = new PollingService()
|
|
31
32
|
}
|
|
32
|
-
return PollingService.instance
|
|
33
|
+
return PollingService.instance
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
private convertToMs(interval: number, unit: TimeUnit): number {
|
|
36
37
|
switch (unit) {
|
|
37
38
|
case 'seconds':
|
|
38
|
-
return interval * 1000
|
|
39
|
+
return interval * 1000
|
|
39
40
|
case 'minutes':
|
|
40
|
-
return interval * 60 * 1000
|
|
41
|
+
return interval * 60 * 1000
|
|
41
42
|
case 'hours':
|
|
42
|
-
return interval * 60 * 60 * 1000
|
|
43
|
+
return interval * 60 * 60 * 1000
|
|
43
44
|
default:
|
|
44
|
-
throw new Error(`Invalid time unit: ${unit}`)
|
|
45
|
+
throw new Error(`Invalid time unit: ${unit}`)
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -54,31 +55,31 @@ export class PollingService {
|
|
|
54
55
|
public register(
|
|
55
56
|
id: string,
|
|
56
57
|
callback: () => void | Promise<void>,
|
|
57
|
-
options: { interval: number
|
|
58
|
+
options: { interval: number, unit: TimeUnit, runImmediately?: boolean },
|
|
58
59
|
): void {
|
|
59
60
|
if (this.tasks.has(id)) {
|
|
60
|
-
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
61
|
+
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
64
|
+
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
64
65
|
if (intervalMs <= 0) {
|
|
65
|
-
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
66
|
-
return
|
|
66
|
+
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
67
|
+
return
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
70
|
+
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
70
71
|
|
|
71
72
|
this.tasks.set(id, {
|
|
72
73
|
id,
|
|
73
74
|
callback,
|
|
74
75
|
intervalMs,
|
|
75
76
|
nextRunMs,
|
|
76
|
-
})
|
|
77
|
+
})
|
|
77
78
|
|
|
78
|
-
console.
|
|
79
|
+
console.debug(`[PollingService] Task '${id}' registered to run every ${options.interval} ${options.unit}.`)
|
|
79
80
|
|
|
80
81
|
if (this.isRunning) {
|
|
81
|
-
this._reschedule()
|
|
82
|
+
this._reschedule()
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -88,12 +89,13 @@ export class PollingService {
|
|
|
88
89
|
*/
|
|
89
90
|
public unregister(id: string): void {
|
|
90
91
|
if (this.tasks.delete(id)) {
|
|
91
|
-
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
92
|
+
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
92
93
|
if (this.isRunning) {
|
|
93
|
-
this._reschedule()
|
|
94
|
+
this._reschedule()
|
|
94
95
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.warn(`[PollingService] Attempted to unregister a non-existent task with ID '${id}'.`)
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -103,7 +105,7 @@ export class PollingService {
|
|
|
103
105
|
* @returns - True if the task is registered, false otherwise.
|
|
104
106
|
*/
|
|
105
107
|
public isRegistered(id: string): boolean {
|
|
106
|
-
return this.tasks.has(id)
|
|
108
|
+
return this.tasks.has(id)
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
@@ -112,13 +114,13 @@ export class PollingService {
|
|
|
112
114
|
*/
|
|
113
115
|
public start(): void {
|
|
114
116
|
if (this.isRunning) {
|
|
115
|
-
console.warn('[PollingService] Already running, skipping start.')
|
|
116
|
-
return
|
|
117
|
+
console.warn('[PollingService] Already running, skipping start.')
|
|
118
|
+
return
|
|
117
119
|
}
|
|
118
|
-
this.isRunning = true
|
|
119
|
-
console.log('[PollingService] Polling service started')
|
|
120
|
-
this._setupQuitListener()
|
|
121
|
-
this._reschedule()
|
|
120
|
+
this.isRunning = true
|
|
121
|
+
console.log('[PollingService] Polling service started')
|
|
122
|
+
this._setupQuitListener()
|
|
123
|
+
this._reschedule()
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
/**
|
|
@@ -130,25 +132,26 @@ export class PollingService {
|
|
|
130
132
|
try {
|
|
131
133
|
// Use dynamic require to avoid hard dependency on Electron
|
|
132
134
|
// Similar to the approach used in packages/utils/plugin/channel.ts
|
|
133
|
-
const electron = (globalThis as any)?.electron
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
const electron = (globalThis as any)?.electron
|
|
136
|
+
?? (typeof require !== 'undefined' ? require('electron') : null)
|
|
137
|
+
|
|
136
138
|
if (electron?.app) {
|
|
137
|
-
const app = electron.app
|
|
138
|
-
|
|
139
|
+
const app = electron.app
|
|
140
|
+
|
|
139
141
|
// Listen to before-quit event
|
|
140
142
|
const quitHandler = () => {
|
|
141
|
-
this.stop('app quit')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
app.on('before-quit', quitHandler)
|
|
145
|
-
|
|
143
|
+
this.stop('app quit')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
app.on('before-quit', quitHandler)
|
|
147
|
+
|
|
146
148
|
// Store cleanup function
|
|
147
149
|
this.quitListenerCleanup = () => {
|
|
148
|
-
app.removeListener('before-quit', quitHandler)
|
|
149
|
-
}
|
|
150
|
+
app.removeListener('before-quit', quitHandler)
|
|
151
|
+
}
|
|
150
152
|
}
|
|
151
|
-
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
152
155
|
// Not in Electron environment or Electron not available
|
|
153
156
|
// This is fine, just skip the quit listener setup
|
|
154
157
|
}
|
|
@@ -160,73 +163,74 @@ export class PollingService {
|
|
|
160
163
|
*/
|
|
161
164
|
public stop(reason?: string): void {
|
|
162
165
|
if (!this.isRunning) {
|
|
163
|
-
return
|
|
166
|
+
return
|
|
164
167
|
}
|
|
165
|
-
this.isRunning = false
|
|
168
|
+
this.isRunning = false
|
|
166
169
|
if (this.timerId) {
|
|
167
|
-
clearTimeout(this.timerId)
|
|
168
|
-
this.timerId = null
|
|
170
|
+
clearTimeout(this.timerId)
|
|
171
|
+
this.timerId = null
|
|
169
172
|
}
|
|
170
|
-
|
|
173
|
+
|
|
171
174
|
// Clean up quit listener
|
|
172
175
|
if (this.quitListenerCleanup) {
|
|
173
|
-
this.quitListenerCleanup()
|
|
174
|
-
this.quitListenerCleanup = undefined
|
|
176
|
+
this.quitListenerCleanup()
|
|
177
|
+
this.quitListenerCleanup = undefined
|
|
175
178
|
}
|
|
176
|
-
|
|
179
|
+
|
|
177
180
|
if (reason) {
|
|
178
|
-
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
+
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
console.log('[PollingService] Polling service stopped')
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
private _reschedule(): void {
|
|
185
189
|
if (this.timerId) {
|
|
186
|
-
clearTimeout(this.timerId)
|
|
187
|
-
this.timerId = null
|
|
190
|
+
clearTimeout(this.timerId)
|
|
191
|
+
this.timerId = null
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
if (!this.isRunning || this.tasks.size === 0) {
|
|
191
|
-
return
|
|
195
|
+
return
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
const now = Date.now()
|
|
198
|
+
const now = Date.now()
|
|
195
199
|
const nextTask = Array.from(this.tasks.values()).reduce((prev, curr) =>
|
|
196
|
-
prev.nextRunMs < curr.nextRunMs ? prev : curr
|
|
197
|
-
)
|
|
200
|
+
prev.nextRunMs < curr.nextRunMs ? prev : curr,
|
|
201
|
+
)
|
|
198
202
|
|
|
199
|
-
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
200
|
-
this.timerId = setTimeout(() => this._tick(), delay)
|
|
203
|
+
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
204
|
+
this.timerId = setTimeout(() => this._tick(), delay)
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
private async _tick(): Promise<void> {
|
|
204
|
-
const now = Date.now()
|
|
205
|
-
const tasksToRun: PollingTask[] = []
|
|
208
|
+
const now = Date.now()
|
|
209
|
+
const tasksToRun: PollingTask[] = []
|
|
206
210
|
|
|
207
211
|
for (const task of this.tasks.values()) {
|
|
208
212
|
if (task.nextRunMs <= now) {
|
|
209
|
-
tasksToRun.push(task)
|
|
213
|
+
tasksToRun.push(task)
|
|
210
214
|
}
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
if (tasksToRun.length > 0) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error(`[PollingService] Error executing task '${task.id}':`, error);
|
|
220
|
-
}
|
|
221
|
-
// Update next run time based on its last scheduled run time, not 'now'.
|
|
222
|
-
// This prevents drift if a task takes a long time to execute.
|
|
223
|
-
task.nextRunMs += task.intervalMs;
|
|
218
|
+
// console.debug(`[PollingService] Executing ${tasksToRun.length} tasks.`);
|
|
219
|
+
for (const task of tasksToRun) {
|
|
220
|
+
try {
|
|
221
|
+
await task.callback()
|
|
224
222
|
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error(`[PollingService] Error executing task '${task.id}':`, error)
|
|
225
|
+
}
|
|
226
|
+
// Update next run time based on its last scheduled run time, not 'now'.
|
|
227
|
+
// This prevents drift if a task takes a long time to execute.
|
|
228
|
+
task.nextRunMs += task.intervalMs
|
|
229
|
+
}
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
this._reschedule();
|
|
232
|
+
this._reschedule()
|
|
229
233
|
}
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
export const pollingService = PollingService.getInstance()
|
|
236
|
+
export const pollingService = PollingService.getInstance()
|