@talex-touch/utils 1.0.39 → 1.0.42

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.
@@ -3,53 +3,109 @@ import { ensureRendererChannel } from '../channel'
3
3
 
4
4
  export type BridgeEvent = BridgeEventForCoreBox
5
5
 
6
- /**
7
- * Defines the shape of a bridge hook function.
8
- * @template T The type of data the hook will receive.
9
- */
10
- export type BridgeHook<T = any> = (data: T) => void
6
+ export interface BridgeEventMeta {
7
+ timestamp: number
8
+ fromCache: boolean
9
+ }
10
+
11
+ export interface BridgeEventPayload<T = any> {
12
+ data: T
13
+ meta: BridgeEventMeta
14
+ }
15
+
16
+ /** @template T The type of data the hook will receive. */
17
+ export type BridgeHook<T = any> = (payload: BridgeEventPayload<T>) => void
18
+
19
+ interface CachedEvent<T = any> {
20
+ data: T
21
+ timestamp: number
22
+ }
11
23
 
12
24
  const __hooks: Record<BridgeEvent, Array<BridgeHook>> = {
13
25
  [BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE]: [],
14
26
  [BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE]: [],
27
+ [BridgeEventForCoreBox.CORE_BOX_KEY_EVENT]: [],
15
28
  }
16
29
 
17
- /**
18
- * Injects a hook for a given bridge event.
19
- * @param type The bridge event type.
20
- * @param hook The hook function to inject.
21
- * @returns The wrapped hook function.
22
- * @internal
23
- * @template T The type of data the hook will receive.
24
- */
25
- export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
26
- const hooks: Array<BridgeHook<T>> = __hooks[type] || (__hooks[type] = [])
30
+ const __eventCache: Map<BridgeEvent, CachedEvent[]> = new Map()
31
+ const __channelRegistered = new Set<BridgeEvent>()
32
+
33
+ const CACHE_MAX_SIZE: Record<BridgeEvent, number> = {
34
+ [BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE]: 1,
35
+ [BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE]: 1,
36
+ [BridgeEventForCoreBox.CORE_BOX_KEY_EVENT]: 10,
37
+ }
38
+
39
+ function invokeHook<T>(hook: BridgeHook<T>, data: T, fromCache: boolean, timestamp: number): void {
40
+ try {
41
+ hook({ data, meta: { timestamp, fromCache } })
42
+ }
43
+ catch (e) {
44
+ console.error('[TouchSDK] Bridge hook error:', e)
45
+ }
46
+ }
47
+
48
+ function registerEarlyListener(type: BridgeEvent): void {
49
+ if (__channelRegistered.has(type)) return
27
50
 
28
- // Only register the channel listener once per event type
29
- if (hooks.length === 0) {
30
- const channel = ensureRendererChannel('[TouchSDK] Bridge channel not available. Make sure hooks run in plugin renderer context.')
51
+ try {
52
+ const channel = ensureRendererChannel()
31
53
  channel.regChannel(type, ({ data }) => {
32
- console.debug(`[TouchSDK] ${type} event received: `, data)
33
- // When the event is received, call all registered hooks for this type
34
- const registeredHooks = __hooks[type]
35
- if (registeredHooks) {
36
- registeredHooks.forEach(h => h(data))
54
+ const timestamp = Date.now()
55
+ const hooks = __hooks[type]
56
+
57
+ if (hooks && hooks.length > 0) {
58
+ hooks.forEach(h => invokeHook(h, data, false, timestamp))
59
+ }
60
+ else {
61
+ if (!__eventCache.has(type)) __eventCache.set(type, [])
62
+ const cache = __eventCache.get(type)!
63
+ const maxSize = CACHE_MAX_SIZE[type] ?? 1
64
+ cache.push({ data, timestamp })
65
+ while (cache.length > maxSize) cache.shift()
66
+ console.debug(`[TouchSDK] ${type} cached, size: ${cache.length}`)
37
67
  }
38
68
  })
69
+ __channelRegistered.add(type)
70
+ }
71
+ catch {
72
+ // Channel not ready yet
39
73
  }
74
+ }
40
75
 
41
- const wrappedHook = (data: T) => {
42
- try {
43
- hook(data)
44
- }
45
- catch (e) {
46
- console.error(`[TouchSDK] ${type} hook error: `, e)
47
- }
76
+ /** Clears the event cache for a specific event type or all types. */
77
+ export function clearBridgeEventCache(type?: BridgeEvent): void {
78
+ if (type) {
79
+ __eventCache.delete(type)
80
+ }
81
+ else {
82
+ __eventCache.clear()
48
83
  }
84
+ }
49
85
 
50
- hooks.push(wrappedHook)
86
+ // Auto-init on module load
87
+ ;(function initBridgeEventCache() {
88
+ setTimeout(() => {
89
+ Object.values(BridgeEventForCoreBox).forEach(e => registerEarlyListener(e as BridgeEvent))
90
+ }, 0)
91
+ })()
51
92
 
52
- return wrappedHook
93
+ /** @internal Injects a hook for a given bridge event with cache replay. */
94
+ export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
95
+ const hooks: Array<BridgeHook<T>> = __hooks[type] || (__hooks[type] = [])
96
+
97
+ // Ensure channel listener is registered
98
+ registerEarlyListener(type)
99
+
100
+ // Replay cached events to this new hook
101
+ const cached = __eventCache.get(type)
102
+ if (cached && cached.length > 0) {
103
+ cached.forEach(({ data, timestamp }) => invokeHook(hook, data as T, true, timestamp))
104
+ __eventCache.delete(type)
105
+ }
106
+
107
+ hooks.push(hook)
108
+ return hook
53
109
  }
54
110
 
55
111
  /**
@@ -60,11 +116,29 @@ export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
60
116
  */
61
117
  export const createBridgeHook = <T>(type: BridgeEvent) => (hook: BridgeHook<T>) => injectBridgeEvent<T>(type, hook)
62
118
 
63
- /**
64
- * Hook for when the core box input changes.
65
- * The hook receives the new input value as a string.
66
- * @param data The input change data (string).
67
- */
68
- export const onCoreBoxInputChange = createBridgeHook<{ query: string }>(BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE)
119
+ export interface CoreBoxInputData {
120
+ query: { inputs: Array<any>, text: string }
121
+ }
122
+
123
+ export interface CoreBoxKeyEventData {
124
+ key: string
125
+ code: string
126
+ metaKey: boolean
127
+ ctrlKey: boolean
128
+ altKey: boolean
129
+ shiftKey: boolean
130
+ repeat: boolean
131
+ }
132
+
133
+ export interface CoreBoxClipboardData {
134
+ item: any
135
+ }
136
+
137
+ /** Hook for CoreBox input changes. Payload includes `data` and `meta` (timestamp, fromCache). */
138
+ export const onCoreBoxInputChange = createBridgeHook<CoreBoxInputData>(BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE)
139
+
140
+ /** Hook for CoreBox clipboard changes. Payload includes `data` and `meta` (timestamp, fromCache). */
141
+ export const onCoreBoxClipboardChange = createBridgeHook<CoreBoxClipboardData>(BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE)
69
142
 
70
- export const onCoreBoxClipboardChange = createBridgeHook<{ item: any }>(BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE)
143
+ /** Hook for keyboard events forwarded from CoreBox. Payload includes `data` and `meta` (timestamp, fromCache). */
144
+ export const onCoreBoxKeyEvent = createBridgeHook<CoreBoxKeyEventData>(BridgeEventForCoreBox.CORE_BOX_KEY_EVENT)
@@ -13,9 +13,11 @@ export * from './clipboard'
13
13
  export * from './core-box'
14
14
  export * from './division-box'
15
15
  export * from './feature-sdk'
16
+ export * from './flow'
16
17
  export { createFeaturesManager, useFeatures } from './features'
17
18
 
18
19
  export * from './hooks/index'
20
+ export * from './performance'
19
21
  export * from './service/index'
20
22
  export * from './storage'
21
23
  export * from './system'
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Plugin Performance SDK
3
+ *
4
+ * Provides APIs for plugins to access their own performance metrics
5
+ * and storage statistics.
6
+ */
7
+ import type { ITouchClientChannel } from '@talex-touch/utils/channel'
8
+ import type { StorageStats } from '../../types/storage'
9
+ import { ensureRendererChannel } from './channel'
10
+
11
+ /**
12
+ * Performance metrics interface
13
+ */
14
+ export interface PerformanceMetrics {
15
+ /** Plugin load time in milliseconds */
16
+ loadTime: number
17
+ /** Estimated memory usage in bytes */
18
+ memoryUsage: number
19
+ /** CPU usage percentage (0-100) */
20
+ cpuUsage: number
21
+ /** Last active timestamp */
22
+ lastActiveTime: number
23
+ }
24
+
25
+ /**
26
+ * Plugin paths interface
27
+ */
28
+ export interface PluginPaths {
29
+ /** Plugin installation directory */
30
+ pluginPath: string
31
+ /** Plugin data directory */
32
+ dataPath: string
33
+ /** Plugin config directory */
34
+ configPath: string
35
+ /** Plugin logs directory */
36
+ logsPath: string
37
+ /** Plugin temp directory */
38
+ tempPath: string
39
+ }
40
+
41
+ /**
42
+ * Performance SDK interface
43
+ */
44
+ export interface PerformanceSDK {
45
+ /**
46
+ * Get storage statistics for the current plugin
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const stats = await plugin.performance.getStorageStats()
51
+ * console.log(`Using ${stats.usagePercent}% of storage`)
52
+ * ```
53
+ */
54
+ getStorageStats: () => Promise<StorageStats>
55
+
56
+ /**
57
+ * Get performance metrics for the current plugin
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const metrics = await plugin.performance.getMetrics()
62
+ * console.log(`Load time: ${metrics.loadTime}ms`)
63
+ * ```
64
+ */
65
+ getMetrics: () => Promise<PerformanceMetrics>
66
+
67
+ /**
68
+ * Get all paths for the current plugin
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const paths = await plugin.performance.getPaths()
73
+ * console.log(`Plugin installed at: ${paths.pluginPath}`)
74
+ * ```
75
+ */
76
+ getPaths: () => Promise<PluginPaths>
77
+
78
+ /**
79
+ * Get combined performance data (storage + metrics + paths)
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const data = await plugin.performance.getAll()
84
+ * console.log(data.storage, data.metrics, data.paths)
85
+ * ```
86
+ */
87
+ getAll: () => Promise<{
88
+ storage: StorageStats
89
+ metrics: PerformanceMetrics
90
+ paths: PluginPaths
91
+ }>
92
+ }
93
+
94
+ /**
95
+ * Creates a Performance SDK instance for plugin use
96
+ *
97
+ * @param channel - The plugin channel bridge for IPC communication
98
+ * @returns Configured Performance SDK instance
99
+ *
100
+ * @internal
101
+ */
102
+ export function createPerformanceSDK(channel: ITouchClientChannel): PerformanceSDK {
103
+ return {
104
+ async getStorageStats(): Promise<StorageStats> {
105
+ try {
106
+ const result = await channel.send('plugin:storage:get-stats')
107
+ return result?.data || {
108
+ totalSize: 0,
109
+ fileCount: 0,
110
+ dirCount: 0,
111
+ maxSize: 10 * 1024 * 1024,
112
+ usagePercent: 0,
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error('[Performance SDK] Failed to get storage stats:', error)
117
+ throw error
118
+ }
119
+ },
120
+
121
+ async getMetrics(): Promise<PerformanceMetrics> {
122
+ try {
123
+ const result = await channel.send('plugin:performance:get-metrics')
124
+ return result?.data || {
125
+ loadTime: 0,
126
+ memoryUsage: 0,
127
+ cpuUsage: 0,
128
+ lastActiveTime: 0,
129
+ }
130
+ }
131
+ catch (error) {
132
+ console.error('[Performance SDK] Failed to get performance metrics:', error)
133
+ throw error
134
+ }
135
+ },
136
+
137
+ async getPaths(): Promise<PluginPaths> {
138
+ try {
139
+ const result = await channel.send('plugin:performance:get-paths')
140
+ return result?.data || {
141
+ pluginPath: '',
142
+ dataPath: '',
143
+ configPath: '',
144
+ logsPath: '',
145
+ tempPath: '',
146
+ }
147
+ }
148
+ catch (error) {
149
+ console.error('[Performance SDK] Failed to get plugin paths:', error)
150
+ throw error
151
+ }
152
+ },
153
+
154
+ async getAll(): Promise<{
155
+ storage: StorageStats
156
+ metrics: PerformanceMetrics
157
+ paths: PluginPaths
158
+ }> {
159
+ const [storage, metrics, paths] = await Promise.all([
160
+ this.getStorageStats(),
161
+ this.getMetrics(),
162
+ this.getPaths(),
163
+ ])
164
+
165
+ return { storage, metrics, paths }
166
+ },
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Hook for using Performance SDK in plugin context
172
+ *
173
+ * @returns Performance SDK instance
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const performance = usePerformance()
178
+ *
179
+ * const stats = await performance.getStorageStats()
180
+ * const metrics = await performance.getMetrics()
181
+ * ```
182
+ */
183
+ export function usePerformance(): PerformanceSDK {
184
+ const channel = ensureRendererChannel('[Performance SDK] Channel not available. Make sure this is called in a plugin context.')
185
+ return createPerformanceSDK(channel)
186
+ }
package/plugin/widget.ts CHANGED
@@ -18,6 +18,11 @@ export interface WidgetRegistrationPayload {
18
18
  code: string
19
19
  styles: string
20
20
  hash: string
21
+ /**
22
+ * List of allowed module dependencies for widget sandbox
23
+ * Widget 沙箱允许的模块依赖列表
24
+ */
25
+ dependencies?: string[]
21
26
  }
22
27
 
23
28
  export function makeWidgetId(pluginName: string, featureId: string): string {
@@ -5,6 +5,8 @@
5
5
  export interface IArgMapperOptions {
6
6
  /** The type of touch window - either main window or core-box popup */
7
7
  touchType?: 'main' | 'core-box'
8
+ /** The sub-type for core-box windows (e.g., division-box) */
9
+ coreType?: 'division-box'
8
10
  /** User data directory path */
9
11
  userDataDir?: string
10
12
  /** Application path */
@@ -32,22 +34,17 @@ declare global {
32
34
  * @returns Mapped command line arguments as key-value pairs
33
35
  */
34
36
  export function useArgMapper(args: string[] = process.argv): IArgMapperOptions {
35
- if (window.$argMapper) {
36
- return window.$argMapper
37
- }
37
+ if (window.$argMapper) return window.$argMapper
38
38
 
39
39
  const mapper: IArgMapperOptions = {}
40
-
41
40
  for (const arg of args) {
42
41
  if (arg.startsWith('--') && arg.includes('=')) {
43
42
  const [key, ...valueParts] = arg.slice(2).split('=')
44
43
  const value = valueParts.join('=')
45
-
46
44
  const camelCaseKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
47
45
  mapper[camelCaseKey] = value
48
46
  }
49
47
  }
50
-
51
48
  return window.$argMapper = mapper
52
49
  }
53
50
 
@@ -76,3 +73,20 @@ export function isMainWindow() {
76
73
  export function isCoreBox() {
77
74
  return useTouchType() === 'core-box'
78
75
  }
76
+
77
+ /**
78
+ * Gets the core-box sub-type from command line arguments
79
+ * @returns The core type ('division-box') or undefined
80
+ */
81
+ export function useCoreType() {
82
+ const argMapper = useArgMapper()
83
+ return argMapper.coreType
84
+ }
85
+
86
+ /**
87
+ * Checks if the current window is a division-box window
88
+ * @returns True if the current window is a division-box
89
+ */
90
+ export function isDivisionBox() {
91
+ return isCoreBox() && useCoreType() === 'division-box'
92
+ }