@talex-touch/utils 1.0.31 → 1.0.32
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 +87 -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 +3 -6
- package/core-box/preview/index.ts +1 -0
- package/core-box/preview/types.ts +43 -0
- package/core-box/tuff/index.ts +1 -1
- package/core-box/tuff/tuff-dsl.ts +328 -266
- package/electron/clipboard-helper.ts +20 -12
- 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 +1 -1
- 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/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/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 +8 -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 +147 -141
- 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 +215 -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 +413 -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 +79 -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/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
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
interface PollingTask {
|
|
9
|
-
id: string
|
|
10
|
-
callback: () => void | Promise<void
|
|
11
|
-
intervalMs: number
|
|
12
|
-
nextRunMs: number
|
|
9
|
+
id: string
|
|
10
|
+
callback: () => void | Promise<void>
|
|
11
|
+
intervalMs: number
|
|
12
|
+
nextRunMs: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
type TimeUnit = 'seconds' | 'minutes' | 'hours'
|
|
15
|
+
type TimeUnit = 'seconds' | 'minutes' | 'hours'
|
|
16
16
|
|
|
17
17
|
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
|
|
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
|
|
23
23
|
|
|
24
24
|
private constructor() {
|
|
25
25
|
// Private constructor to enforce singleton pattern
|
|
@@ -27,21 +27,21 @@ export class PollingService {
|
|
|
27
27
|
|
|
28
28
|
public static getInstance(): PollingService {
|
|
29
29
|
if (!PollingService.instance) {
|
|
30
|
-
PollingService.instance = new PollingService()
|
|
30
|
+
PollingService.instance = new PollingService()
|
|
31
31
|
}
|
|
32
|
-
return PollingService.instance
|
|
32
|
+
return PollingService.instance
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
private convertToMs(interval: number, unit: TimeUnit): number {
|
|
36
36
|
switch (unit) {
|
|
37
37
|
case 'seconds':
|
|
38
|
-
return interval * 1000
|
|
38
|
+
return interval * 1000
|
|
39
39
|
case 'minutes':
|
|
40
|
-
return interval * 60 * 1000
|
|
40
|
+
return interval * 60 * 1000
|
|
41
41
|
case 'hours':
|
|
42
|
-
return interval * 60 * 60 * 1000
|
|
42
|
+
return interval * 60 * 60 * 1000
|
|
43
43
|
default:
|
|
44
|
-
throw new Error(`Invalid time unit: ${unit}`)
|
|
44
|
+
throw new Error(`Invalid time unit: ${unit}`)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -54,31 +54,31 @@ export class PollingService {
|
|
|
54
54
|
public register(
|
|
55
55
|
id: string,
|
|
56
56
|
callback: () => void | Promise<void>,
|
|
57
|
-
options: { interval: number
|
|
57
|
+
options: { interval: number, unit: TimeUnit, runImmediately?: boolean },
|
|
58
58
|
): void {
|
|
59
59
|
if (this.tasks.has(id)) {
|
|
60
|
-
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
60
|
+
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
63
|
+
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
64
64
|
if (intervalMs <= 0) {
|
|
65
|
-
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
66
|
-
return
|
|
65
|
+
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
66
|
+
return
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
69
|
+
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
70
70
|
|
|
71
71
|
this.tasks.set(id, {
|
|
72
72
|
id,
|
|
73
73
|
callback,
|
|
74
74
|
intervalMs,
|
|
75
75
|
nextRunMs,
|
|
76
|
-
})
|
|
76
|
+
})
|
|
77
77
|
|
|
78
|
-
console.log(`[PollingService] Task '${id}' registered to run every ${options.interval} ${options.unit}.`)
|
|
78
|
+
console.log(`[PollingService] Task '${id}' registered to run every ${options.interval} ${options.unit}.`)
|
|
79
79
|
|
|
80
80
|
if (this.isRunning) {
|
|
81
|
-
this._reschedule()
|
|
81
|
+
this._reschedule()
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -88,12 +88,13 @@ export class PollingService {
|
|
|
88
88
|
*/
|
|
89
89
|
public unregister(id: string): void {
|
|
90
90
|
if (this.tasks.delete(id)) {
|
|
91
|
-
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
91
|
+
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
92
92
|
if (this.isRunning) {
|
|
93
|
-
this._reschedule()
|
|
93
|
+
this._reschedule()
|
|
94
94
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.warn(`[PollingService] Attempted to unregister a non-existent task with ID '${id}'.`)
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -103,7 +104,7 @@ export class PollingService {
|
|
|
103
104
|
* @returns - True if the task is registered, false otherwise.
|
|
104
105
|
*/
|
|
105
106
|
public isRegistered(id: string): boolean {
|
|
106
|
-
return this.tasks.has(id)
|
|
107
|
+
return this.tasks.has(id)
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/**
|
|
@@ -112,13 +113,13 @@ export class PollingService {
|
|
|
112
113
|
*/
|
|
113
114
|
public start(): void {
|
|
114
115
|
if (this.isRunning) {
|
|
115
|
-
console.warn('[PollingService] Already running, skipping start.')
|
|
116
|
-
return
|
|
116
|
+
console.warn('[PollingService] Already running, skipping start.')
|
|
117
|
+
return
|
|
117
118
|
}
|
|
118
|
-
this.isRunning = true
|
|
119
|
-
console.log('[PollingService] Polling service started')
|
|
120
|
-
this._setupQuitListener()
|
|
121
|
-
this._reschedule()
|
|
119
|
+
this.isRunning = true
|
|
120
|
+
console.log('[PollingService] Polling service started')
|
|
121
|
+
this._setupQuitListener()
|
|
122
|
+
this._reschedule()
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
/**
|
|
@@ -130,25 +131,26 @@ export class PollingService {
|
|
|
130
131
|
try {
|
|
131
132
|
// Use dynamic require to avoid hard dependency on Electron
|
|
132
133
|
// Similar to the approach used in packages/utils/plugin/channel.ts
|
|
133
|
-
const electron = (globalThis as any)?.electron
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const electron = (globalThis as any)?.electron
|
|
135
|
+
?? (typeof require !== 'undefined' ? require('electron') : null)
|
|
136
|
+
|
|
136
137
|
if (electron?.app) {
|
|
137
|
-
const app = electron.app
|
|
138
|
-
|
|
138
|
+
const app = electron.app
|
|
139
|
+
|
|
139
140
|
// Listen to before-quit event
|
|
140
141
|
const quitHandler = () => {
|
|
141
|
-
this.stop('app quit')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
app.on('before-quit', quitHandler)
|
|
145
|
-
|
|
142
|
+
this.stop('app quit')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
app.on('before-quit', quitHandler)
|
|
146
|
+
|
|
146
147
|
// Store cleanup function
|
|
147
148
|
this.quitListenerCleanup = () => {
|
|
148
|
-
app.removeListener('before-quit', quitHandler)
|
|
149
|
-
}
|
|
149
|
+
app.removeListener('before-quit', quitHandler)
|
|
150
|
+
}
|
|
150
151
|
}
|
|
151
|
-
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
152
154
|
// Not in Electron environment or Electron not available
|
|
153
155
|
// This is fine, just skip the quit listener setup
|
|
154
156
|
}
|
|
@@ -160,73 +162,74 @@ export class PollingService {
|
|
|
160
162
|
*/
|
|
161
163
|
public stop(reason?: string): void {
|
|
162
164
|
if (!this.isRunning) {
|
|
163
|
-
return
|
|
165
|
+
return
|
|
164
166
|
}
|
|
165
|
-
this.isRunning = false
|
|
167
|
+
this.isRunning = false
|
|
166
168
|
if (this.timerId) {
|
|
167
|
-
clearTimeout(this.timerId)
|
|
168
|
-
this.timerId = null
|
|
169
|
+
clearTimeout(this.timerId)
|
|
170
|
+
this.timerId = null
|
|
169
171
|
}
|
|
170
|
-
|
|
172
|
+
|
|
171
173
|
// Clean up quit listener
|
|
172
174
|
if (this.quitListenerCleanup) {
|
|
173
|
-
this.quitListenerCleanup()
|
|
174
|
-
this.quitListenerCleanup = undefined
|
|
175
|
+
this.quitListenerCleanup()
|
|
176
|
+
this.quitListenerCleanup = undefined
|
|
175
177
|
}
|
|
176
|
-
|
|
178
|
+
|
|
177
179
|
if (reason) {
|
|
178
|
-
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
179
|
-
}
|
|
180
|
-
|
|
180
|
+
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log('[PollingService] Polling service stopped')
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
private _reschedule(): void {
|
|
185
188
|
if (this.timerId) {
|
|
186
|
-
clearTimeout(this.timerId)
|
|
187
|
-
this.timerId = null
|
|
189
|
+
clearTimeout(this.timerId)
|
|
190
|
+
this.timerId = null
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
if (!this.isRunning || this.tasks.size === 0) {
|
|
191
|
-
return
|
|
194
|
+
return
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
const now = Date.now()
|
|
197
|
+
const now = Date.now()
|
|
195
198
|
const nextTask = Array.from(this.tasks.values()).reduce((prev, curr) =>
|
|
196
|
-
prev.nextRunMs < curr.nextRunMs ? prev : curr
|
|
197
|
-
)
|
|
199
|
+
prev.nextRunMs < curr.nextRunMs ? prev : curr,
|
|
200
|
+
)
|
|
198
201
|
|
|
199
|
-
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
200
|
-
this.timerId = setTimeout(() => this._tick(), delay)
|
|
202
|
+
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
203
|
+
this.timerId = setTimeout(() => this._tick(), delay)
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
private async _tick(): Promise<void> {
|
|
204
|
-
const now = Date.now()
|
|
205
|
-
const tasksToRun: PollingTask[] = []
|
|
207
|
+
const now = Date.now()
|
|
208
|
+
const tasksToRun: PollingTask[] = []
|
|
206
209
|
|
|
207
210
|
for (const task of this.tasks.values()) {
|
|
208
211
|
if (task.nextRunMs <= now) {
|
|
209
|
-
tasksToRun.push(task)
|
|
212
|
+
tasksToRun.push(task)
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
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;
|
|
217
|
+
// console.debug(`[PollingService] Executing ${tasksToRun.length} tasks.`);
|
|
218
|
+
for (const task of tasksToRun) {
|
|
219
|
+
try {
|
|
220
|
+
await task.callback()
|
|
224
221
|
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error(`[PollingService] Error executing task '${task.id}':`, error)
|
|
224
|
+
}
|
|
225
|
+
// Update next run time based on its last scheduled run time, not 'now'.
|
|
226
|
+
// This prevents drift if a task takes a long time to execute.
|
|
227
|
+
task.nextRunMs += task.intervalMs
|
|
228
|
+
}
|
|
225
229
|
}
|
|
226
230
|
|
|
227
|
-
|
|
228
|
-
this._reschedule();
|
|
231
|
+
this._reschedule()
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
232
|
-
export const pollingService = PollingService.getInstance()
|
|
235
|
+
export const pollingService = PollingService.getInstance()
|