@talex-touch/utils 1.0.30 → 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 +205 -0
- 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 +6 -6
- package/auth/useClerkProvider.ts +3 -2
- package/channel/index.ts +28 -21
- package/common/file-scan-constants.ts +137 -121
- package/common/file-scan-utils.ts +49 -25
- 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 +19 -3
- package/common/storage/entity/shortcut-settings.ts +10 -10
- package/common/storage/shortcut-storage.ts +6 -4
- package/common/utils/file.ts +15 -4
- package/common/utils/index.ts +62 -52
- package/common/utils/polling.ts +114 -63
- 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 +255 -230
- 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 +419 -253
- 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 +6 -5
- 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 +113 -84
- 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 +6 -3
- 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 +9 -13
- 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 +169 -143
- 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 -1
- 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 +236 -88
- 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 -103
- 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/icon.ts +2 -1
- 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
|
}
|
|
@@ -39,7 +39,23 @@ const _appSettingOriginData = {
|
|
|
39
39
|
searchEngine: {
|
|
40
40
|
logsEnabled: false,
|
|
41
41
|
},
|
|
42
|
-
|
|
42
|
+
window: {
|
|
43
|
+
closeToTray: true,
|
|
44
|
+
startMinimized: false,
|
|
45
|
+
startSilent: false,
|
|
46
|
+
},
|
|
47
|
+
setup: {
|
|
48
|
+
accessibility: false,
|
|
49
|
+
notifications: false,
|
|
50
|
+
autoStart: false,
|
|
51
|
+
showTray: true,
|
|
52
|
+
adminPrivileges: false,
|
|
53
|
+
hideDock: false,
|
|
54
|
+
runAsAdmin: false,
|
|
55
|
+
customDesktop: false,
|
|
56
|
+
},
|
|
57
|
+
layout: 'simple',
|
|
58
|
+
}
|
|
43
59
|
|
|
44
60
|
export const appSettingOriginData = Object.freeze(_appSettingOriginData)
|
|
45
61
|
|
|
@@ -49,5 +65,5 @@ export const appSettingOriginData = Object.freeze(_appSettingOriginData)
|
|
|
49
65
|
* Combines the default configuration with support for dynamic additional properties.
|
|
50
66
|
*/
|
|
51
67
|
export type AppSetting = typeof _appSettingOriginData & {
|
|
52
|
-
[key: string]: any
|
|
53
|
-
}
|
|
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,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
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
|
+
})()
|
|
2
13
|
|
|
3
14
|
/**
|
|
4
15
|
* Enum for various file types.
|
|
@@ -18,7 +29,7 @@ export enum FileType {
|
|
|
18
29
|
Spreadsheet = 'Spreadsheet',
|
|
19
30
|
Presentation = 'Presentation',
|
|
20
31
|
Ebook = 'Ebook',
|
|
21
|
-
Other = 'Other'
|
|
32
|
+
Other = 'Other',
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
const extensionMap: Map<FileType, Set<string>> = new Map([
|
|
@@ -34,8 +45,8 @@ const extensionMap: Map<FileType, Set<string>> = new Map([
|
|
|
34
45
|
[FileType.Font, new Set(['.ttf', '.otf', '.woff', '.woff2'])],
|
|
35
46
|
[FileType.Spreadsheet, new Set(['.xls', '.xlsx', '.csv', '.numbers'])],
|
|
36
47
|
[FileType.Presentation, new Set(['.ppt', '.pptx', '.key'])],
|
|
37
|
-
[FileType.Ebook, new Set(['.epub', '.mobi', '.azw'])]
|
|
38
|
-
])
|
|
48
|
+
[FileType.Ebook, new Set(['.epub', '.mobi', '.azw'])],
|
|
49
|
+
])
|
|
39
50
|
|
|
40
51
|
/**
|
|
41
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,19 +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
|
|
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
|
|
22
23
|
|
|
23
24
|
private constructor() {
|
|
24
25
|
// Private constructor to enforce singleton pattern
|
|
@@ -26,21 +27,21 @@ export class PollingService {
|
|
|
26
27
|
|
|
27
28
|
public static getInstance(): PollingService {
|
|
28
29
|
if (!PollingService.instance) {
|
|
29
|
-
PollingService.instance = new PollingService()
|
|
30
|
+
PollingService.instance = new PollingService()
|
|
30
31
|
}
|
|
31
|
-
return PollingService.instance
|
|
32
|
+
return PollingService.instance
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
private convertToMs(interval: number, unit: TimeUnit): number {
|
|
35
36
|
switch (unit) {
|
|
36
37
|
case 'seconds':
|
|
37
|
-
return interval * 1000
|
|
38
|
+
return interval * 1000
|
|
38
39
|
case 'minutes':
|
|
39
|
-
return interval * 60 * 1000
|
|
40
|
+
return interval * 60 * 1000
|
|
40
41
|
case 'hours':
|
|
41
|
-
return interval * 60 * 60 * 1000
|
|
42
|
+
return interval * 60 * 60 * 1000
|
|
42
43
|
default:
|
|
43
|
-
throw new Error(`Invalid time unit: ${unit}`)
|
|
44
|
+
throw new Error(`Invalid time unit: ${unit}`)
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -53,31 +54,31 @@ export class PollingService {
|
|
|
53
54
|
public register(
|
|
54
55
|
id: string,
|
|
55
56
|
callback: () => void | Promise<void>,
|
|
56
|
-
options: { interval: number
|
|
57
|
+
options: { interval: number, unit: TimeUnit, runImmediately?: boolean },
|
|
57
58
|
): void {
|
|
58
59
|
if (this.tasks.has(id)) {
|
|
59
|
-
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
60
|
+
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
63
|
+
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
63
64
|
if (intervalMs <= 0) {
|
|
64
|
-
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
65
|
-
return
|
|
65
|
+
console.error(`[PollingService] Task '${id}' has an invalid interval of ${intervalMs}ms. Registration aborted.`)
|
|
66
|
+
return
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
69
|
+
const nextRunMs = options.runImmediately ? Date.now() : Date.now() + intervalMs
|
|
69
70
|
|
|
70
71
|
this.tasks.set(id, {
|
|
71
72
|
id,
|
|
72
73
|
callback,
|
|
73
74
|
intervalMs,
|
|
74
75
|
nextRunMs,
|
|
75
|
-
})
|
|
76
|
+
})
|
|
76
77
|
|
|
77
|
-
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}.`)
|
|
78
79
|
|
|
79
80
|
if (this.isRunning) {
|
|
80
|
-
this._reschedule()
|
|
81
|
+
this._reschedule()
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
@@ -87,12 +88,13 @@ export class PollingService {
|
|
|
87
88
|
*/
|
|
88
89
|
public unregister(id: string): void {
|
|
89
90
|
if (this.tasks.delete(id)) {
|
|
90
|
-
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
91
|
+
console.log(`[PollingService] Task '${id}' unregistered.`)
|
|
91
92
|
if (this.isRunning) {
|
|
92
|
-
this._reschedule()
|
|
93
|
+
this._reschedule()
|
|
93
94
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.warn(`[PollingService] Attempted to unregister a non-existent task with ID '${id}'.`)
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -102,7 +104,7 @@ export class PollingService {
|
|
|
102
104
|
* @returns - True if the task is registered, false otherwise.
|
|
103
105
|
*/
|
|
104
106
|
public isRegistered(id: string): boolean {
|
|
105
|
-
return this.tasks.has(id)
|
|
107
|
+
return this.tasks.has(id)
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
/**
|
|
@@ -111,74 +113,123 @@ export class PollingService {
|
|
|
111
113
|
*/
|
|
112
114
|
public start(): void {
|
|
113
115
|
if (this.isRunning) {
|
|
114
|
-
|
|
116
|
+
console.warn('[PollingService] Already running, skipping start.')
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
this.isRunning = true
|
|
120
|
+
console.log('[PollingService] Polling service started')
|
|
121
|
+
this._setupQuitListener()
|
|
122
|
+
this._reschedule()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sets up Electron app quit listener if running in Electron environment
|
|
127
|
+
* Uses lazy resolution to avoid hard dependency on Electron
|
|
128
|
+
*/
|
|
129
|
+
private _setupQuitListener(): void {
|
|
130
|
+
// Check if we're in Electron environment
|
|
131
|
+
try {
|
|
132
|
+
// Use dynamic require to avoid hard dependency on Electron
|
|
133
|
+
// Similar to the approach used in packages/utils/plugin/channel.ts
|
|
134
|
+
const electron = (globalThis as any)?.electron
|
|
135
|
+
?? (typeof require !== 'undefined' ? require('electron') : null)
|
|
136
|
+
|
|
137
|
+
if (electron?.app) {
|
|
138
|
+
const app = electron.app
|
|
139
|
+
|
|
140
|
+
// Listen to before-quit event
|
|
141
|
+
const quitHandler = () => {
|
|
142
|
+
this.stop('app quit')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
app.on('before-quit', quitHandler)
|
|
146
|
+
|
|
147
|
+
// Store cleanup function
|
|
148
|
+
this.quitListenerCleanup = () => {
|
|
149
|
+
app.removeListener('before-quit', quitHandler)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// Not in Electron environment or Electron not available
|
|
155
|
+
// This is fine, just skip the quit listener setup
|
|
115
156
|
}
|
|
116
|
-
this.isRunning = true;
|
|
117
|
-
console.debug('[PollingService] Service started.');
|
|
118
|
-
this._reschedule();
|
|
119
157
|
}
|
|
120
158
|
|
|
121
159
|
/**
|
|
122
160
|
* Stops the polling service and clears all scheduled tasks.
|
|
161
|
+
* @param reason - Optional reason for stopping the service (for logging purposes)
|
|
123
162
|
*/
|
|
124
|
-
public stop(): void {
|
|
163
|
+
public stop(reason?: string): void {
|
|
125
164
|
if (!this.isRunning) {
|
|
126
|
-
return
|
|
165
|
+
return
|
|
127
166
|
}
|
|
128
|
-
this.isRunning = false
|
|
167
|
+
this.isRunning = false
|
|
129
168
|
if (this.timerId) {
|
|
130
|
-
clearTimeout(this.timerId)
|
|
131
|
-
this.timerId = null
|
|
169
|
+
clearTimeout(this.timerId)
|
|
170
|
+
this.timerId = null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Clean up quit listener
|
|
174
|
+
if (this.quitListenerCleanup) {
|
|
175
|
+
this.quitListenerCleanup()
|
|
176
|
+
this.quitListenerCleanup = undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (reason) {
|
|
180
|
+
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log('[PollingService] Polling service stopped')
|
|
132
184
|
}
|
|
133
|
-
console.log('[PollingService] Service stopped.');
|
|
134
185
|
}
|
|
135
186
|
|
|
136
187
|
private _reschedule(): void {
|
|
137
188
|
if (this.timerId) {
|
|
138
|
-
clearTimeout(this.timerId)
|
|
139
|
-
this.timerId = null
|
|
189
|
+
clearTimeout(this.timerId)
|
|
190
|
+
this.timerId = null
|
|
140
191
|
}
|
|
141
192
|
|
|
142
193
|
if (!this.isRunning || this.tasks.size === 0) {
|
|
143
|
-
return
|
|
194
|
+
return
|
|
144
195
|
}
|
|
145
196
|
|
|
146
|
-
const now = Date.now()
|
|
197
|
+
const now = Date.now()
|
|
147
198
|
const nextTask = Array.from(this.tasks.values()).reduce((prev, curr) =>
|
|
148
|
-
prev.nextRunMs < curr.nextRunMs ? prev : curr
|
|
149
|
-
)
|
|
199
|
+
prev.nextRunMs < curr.nextRunMs ? prev : curr,
|
|
200
|
+
)
|
|
150
201
|
|
|
151
|
-
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
152
|
-
this.timerId = setTimeout(() => this._tick(), delay)
|
|
202
|
+
const delay = Math.max(0, nextTask.nextRunMs - now)
|
|
203
|
+
this.timerId = setTimeout(() => this._tick(), delay)
|
|
153
204
|
}
|
|
154
205
|
|
|
155
206
|
private async _tick(): Promise<void> {
|
|
156
|
-
const now = Date.now()
|
|
157
|
-
const tasksToRun: PollingTask[] = []
|
|
207
|
+
const now = Date.now()
|
|
208
|
+
const tasksToRun: PollingTask[] = []
|
|
158
209
|
|
|
159
210
|
for (const task of this.tasks.values()) {
|
|
160
211
|
if (task.nextRunMs <= now) {
|
|
161
|
-
tasksToRun.push(task)
|
|
212
|
+
tasksToRun.push(task)
|
|
162
213
|
}
|
|
163
214
|
}
|
|
164
215
|
|
|
165
216
|
if (tasksToRun.length > 0) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
// Update next run time based on its last scheduled run time, not 'now'.
|
|
174
|
-
// This prevents drift if a task takes a long time to execute.
|
|
175
|
-
task.nextRunMs += task.intervalMs;
|
|
217
|
+
// console.debug(`[PollingService] Executing ${tasksToRun.length} tasks.`);
|
|
218
|
+
for (const task of tasksToRun) {
|
|
219
|
+
try {
|
|
220
|
+
await task.callback()
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error(`[PollingService] Error executing task '${task.id}':`, error)
|
|
176
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
|
+
}
|
|
177
229
|
}
|
|
178
230
|
|
|
179
|
-
|
|
180
|
-
this._reschedule();
|
|
231
|
+
this._reschedule()
|
|
181
232
|
}
|
|
182
233
|
}
|
|
183
234
|
|
|
184
|
-
export const pollingService = PollingService.getInstance()
|
|
235
|
+
export const pollingService = PollingService.getInstance()
|