@talex-touch/utils 1.0.34 → 1.0.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talex-touch/utils",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "private": false,
5
5
  "description": "Tuff series utils",
6
6
  "author": "TalexDreamSoul",
package/plugin/channel.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  DataCode,
11
11
  } from '../channel'
12
12
 
13
+ const CHANNEL_DEFAULT_TIMEOUT = 10_000
14
+
13
15
  let cachedIpcRenderer: IpcRenderer | null = null
14
16
 
15
17
  // 使用惰性解析避免在打包阶段静态引入 electron
@@ -197,6 +199,19 @@ class TouchChannel implements ITouchClientChannel {
197
199
  return true
198
200
  }
199
201
 
202
+ private formatPayloadPreview(payload: unknown): string {
203
+ if (payload === null || payload === undefined)
204
+ return String(payload)
205
+ if (typeof payload === 'string')
206
+ return payload.length > 200 ? `${payload.slice(0, 200)}…` : payload
207
+ try {
208
+ return JSON.stringify(payload)
209
+ }
210
+ catch {
211
+ return '[unserializable]'
212
+ }
213
+ }
214
+
200
215
  send(eventName: string, arg: any): Promise<any> {
201
216
  const uniqueId = `${new Date().getTime()}#${eventName}@${Math.random().toString(
202
217
  12,
@@ -207,7 +222,7 @@ class TouchChannel implements ITouchClientChannel {
207
222
  data: arg,
208
223
  sync: {
209
224
  timeStamp: new Date().getTime(),
210
- timeout: 10000,
225
+ timeout: CHANNEL_DEFAULT_TIMEOUT,
211
226
  id: uniqueId,
212
227
  },
213
228
  name: eventName,
@@ -218,10 +233,40 @@ class TouchChannel implements ITouchClientChannel {
218
233
  },
219
234
  } as RawStandardChannelData
220
235
 
221
- return new Promise((resolve) => {
222
- this.ipcRenderer.send('@plugin-process-message', data)
236
+ return new Promise((resolve, reject) => {
237
+ try {
238
+ this.ipcRenderer.send('@plugin-process-message', data)
239
+ }
240
+ catch (error) {
241
+ const errorMessage = error instanceof Error ? error.message : String(error)
242
+ console.error(
243
+ `[PluginChannel] Failed to send "${eventName}": ${errorMessage}`,
244
+ { payloadPreview: this.formatPayloadPreview(arg) },
245
+ )
246
+ reject(
247
+ Object.assign(
248
+ new Error(`Failed to send plugin channel message "${eventName}": ${errorMessage}`),
249
+ { code: 'plugin_channel_send_failed' },
250
+ ),
251
+ )
252
+ return
253
+ }
254
+
255
+ const timeoutMs = data.sync?.timeout ?? CHANNEL_DEFAULT_TIMEOUT
256
+ const timeoutHandle = setTimeout(() => {
257
+ if (!this.pendingMap.has(uniqueId))
258
+ return
259
+ this.pendingMap.delete(uniqueId)
260
+ const timeoutError = Object.assign(
261
+ new Error(`Plugin channel request "${eventName}" timed out after ${timeoutMs}ms`),
262
+ { code: 'plugin_channel_timeout' },
263
+ )
264
+ console.warn(timeoutError.message)
265
+ reject(timeoutError)
266
+ }, timeoutMs)
223
267
 
224
268
  this.pendingMap.set(uniqueId, (res: any) => {
269
+ clearTimeout(timeoutHandle)
225
270
  this.pendingMap.delete(uniqueId)
226
271
 
227
272
  resolve(res.data)
@@ -241,12 +286,26 @@ class TouchChannel implements ITouchClientChannel {
241
286
  },
242
287
  } as RawStandardChannelData
243
288
 
244
- const res = this.__parse_raw_data(void 0, this.ipcRenderer.sendSync('@plugin-process-message', data))!
289
+ try {
290
+ const res = this.__parse_raw_data(
291
+ void 0,
292
+ this.ipcRenderer.sendSync('@plugin-process-message', data),
293
+ )!
245
294
 
246
- if (res.header.status === 'reply')
247
- return res.data
295
+ if (res.header.status === 'reply')
296
+ return res.data
248
297
 
249
- return res
298
+ return res
299
+ }
300
+ catch (error) {
301
+ const errorMessage = error instanceof Error ? error.message : String(error)
302
+ console.error('[PluginChannel] Failed to sendSync message', {
303
+ eventName,
304
+ error: errorMessage,
305
+ payloadPreview: this.formatPayloadPreview(arg),
306
+ })
307
+ throw new Error(`Failed to sendSync plugin channel message "${eventName}": ${errorMessage}`)
308
+ }
250
309
  }
251
310
  }
252
311
 
@@ -26,8 +26,10 @@ plugin.box.shrink()
26
26
  plugin.box.hideInput()
27
27
  plugin.box.showInput()
28
28
 
29
- // 获取当前输入
29
+ // 获取与设置输入
30
30
  const input = await plugin.box.getInput()
31
+ await plugin.box.setInput('Hello Touch!')
32
+ await plugin.box.clearInput()
31
33
  ```
32
34
 
33
35
  ### 2. FeatureSDK - 搜索结果管理
@@ -203,6 +205,8 @@ export default {
203
205
  - `core-box:hide-input` - 隐藏输入框
204
206
  - `core-box:show-input` - 显示输入框
205
207
  - `core-box:get-input` - 获取当前输入值
208
+ - `core-box:set-input` - 设置输入框内容
209
+ - `core-box:clear-input` - 清空输入框
206
210
  - `core-box:input-changed` - 输入变化广播(主进程 → 插件)
207
211
  - `core-box:set-input-visibility` - 设置输入框可见性(主进程 → 渲染进程)
208
212
  - `core-box:request-input-value` - 请求输入值(主进程 → 渲染进程)
@@ -1,9 +1,28 @@
1
1
  /**
2
2
  * Box SDK for Plugin Development
3
- *
3
+ *
4
4
  * Provides a unified API for plugins to control the CoreBox window behavior,
5
5
  * including visibility, size, input field control, and input value access.
6
6
  */
7
+ import { ensureRendererChannel } from './channel'
8
+
9
+ /**
10
+ * Clipboard content type flags for binary combination
11
+ */
12
+ export enum ClipboardType {
13
+ TEXT = 0b0001,
14
+ IMAGE = 0b0010,
15
+ FILE = 0b0100,
16
+ }
17
+
18
+ /**
19
+ * Preset clipboard type combinations
20
+ */
21
+ export const ClipboardTypePresets = {
22
+ TEXT_ONLY: ClipboardType.TEXT,
23
+ TEXT_AND_IMAGE: ClipboardType.TEXT | ClipboardType.IMAGE,
24
+ ALL: ClipboardType.TEXT | ClipboardType.IMAGE | ClipboardType.FILE,
25
+ } as const
7
26
 
8
27
  /**
9
28
  * Expand options for CoreBox window
@@ -17,18 +36,18 @@ export interface BoxExpandOptions {
17
36
 
18
37
  /**
19
38
  * Box SDK interface for plugins
20
- *
39
+ *
21
40
  * @example
22
41
  * ```typescript
23
42
  * // Hide CoreBox
24
43
  * plugin.box.hide()
25
- *
44
+ *
26
45
  * // Show CoreBox
27
46
  * plugin.box.show()
28
- *
47
+ *
29
48
  * // Expand to show 10 items
30
49
  * plugin.box.expand({ length: 10 })
31
- *
50
+ *
32
51
  * // Get current input
33
52
  * const input = plugin.box.getInput()
34
53
  * ```
@@ -36,119 +55,172 @@ export interface BoxExpandOptions {
36
55
  export interface BoxSDK {
37
56
  /**
38
57
  * Hides the CoreBox window
39
- *
58
+ *
40
59
  * @example
41
60
  * ```typescript
42
61
  * plugin.box.hide()
43
62
  * ```
44
63
  */
45
- hide(): void
64
+ hide: () => void
46
65
 
47
66
  /**
48
67
  * Shows the CoreBox window
49
- *
68
+ *
50
69
  * @example
51
70
  * ```typescript
52
71
  * plugin.box.show()
53
72
  * ```
54
73
  */
55
- show(): void
74
+ show: () => void
56
75
 
57
76
  /**
58
77
  * Expands the CoreBox window
59
- *
78
+ *
60
79
  * @param options - Optional expansion configuration
61
- *
80
+ *
62
81
  * @example
63
82
  * ```typescript
64
83
  * // Expand to show 10 items
65
84
  * plugin.box.expand({ length: 10 })
66
- *
85
+ *
67
86
  * // Force maximum expansion
68
87
  * plugin.box.expand({ forceMax: true })
69
- *
88
+ *
70
89
  * // Default expansion
71
90
  * plugin.box.expand()
72
91
  * ```
73
92
  */
74
- expand(options?: BoxExpandOptions): Promise<void>
93
+ expand: (options?: BoxExpandOptions) => Promise<void>
75
94
 
76
95
  /**
77
96
  * Shrinks the CoreBox window to compact size
78
- *
97
+ *
79
98
  * @example
80
99
  * ```typescript
81
100
  * plugin.box.shrink()
82
101
  * ```
83
102
  */
84
- shrink(): Promise<void>
103
+ shrink: () => Promise<void>
85
104
 
86
105
  /**
87
106
  * Hides the input field in CoreBox
88
- *
107
+ *
89
108
  * @example
90
109
  * ```typescript
91
110
  * plugin.box.hideInput()
92
111
  * ```
93
112
  */
94
- hideInput(): Promise<void>
113
+ hideInput: () => Promise<void>
95
114
 
96
115
  /**
97
116
  * Shows the input field in CoreBox
98
- *
117
+ *
99
118
  * @example
100
119
  * ```typescript
101
120
  * plugin.box.showInput()
102
121
  * ```
103
122
  */
104
- showInput(): Promise<void>
123
+ showInput: () => Promise<void>
105
124
 
106
125
  /**
107
126
  * Gets the current input value from CoreBox search field
108
- *
127
+ *
109
128
  * @returns Promise resolving to the current input string
110
- *
129
+ *
111
130
  * @example
112
131
  * ```typescript
113
132
  * const input = await plugin.box.getInput()
114
133
  * console.log('Current input:', input)
115
134
  * ```
116
135
  */
117
- getInput(): Promise<string>
136
+ getInput: () => Promise<string>
137
+
138
+ /**
139
+ * Sets the CoreBox search input to the specified value
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * await plugin.box.setInput('hello world')
144
+ * ```
145
+ */
146
+ setInput: (value: string) => Promise<void>
147
+
148
+ /**
149
+ * Clears the CoreBox search input
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * await plugin.box.clearInput()
154
+ * ```
155
+ */
156
+ clearInput: () => Promise<void>
157
+
158
+ /**
159
+ * Enable input monitoring for attached UI view
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * await plugin.box.allowInput()
164
+ * plugin.channel.regChannel('core-box:input-change', ({ data }) => {
165
+ * console.log('Input changed:', data.input)
166
+ * })
167
+ * ```
168
+ */
169
+ allowInput: () => Promise<void>
170
+
171
+ /**
172
+ * Enable clipboard monitoring for specified type combination
173
+ *
174
+ * @param types - Binary combination of ClipboardType flags
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * // Allow text and images
179
+ * await plugin.box.allowClipboard(ClipboardType.TEXT | ClipboardType.IMAGE)
180
+ *
181
+ * // Or use presets
182
+ * await plugin.box.allowClipboard(ClipboardTypePresets.TEXT_AND_IMAGE)
183
+ * ```
184
+ */
185
+ allowClipboard: (types: number) => Promise<void>
118
186
  }
119
187
 
120
188
  /**
121
189
  * Creates a Box SDK instance for plugin use
122
- *
190
+ *
123
191
  * @param channel - The plugin channel bridge for IPC communication
124
192
  * @returns Configured Box SDK instance
125
- *
193
+ *
126
194
  * @internal
127
195
  */
128
196
  export function createBoxSDK(channel: any): BoxSDK {
129
- const sendFn = channel.sendToMain || channel.send
130
-
131
- if (!sendFn) {
132
- throw new Error('[Box SDK] Channel send function not available')
133
- }
197
+ const send: (eventName: string, payload?: any) => Promise<any> =
198
+ typeof channel?.sendToMain === 'function'
199
+ ? channel.sendToMain.bind(channel)
200
+ : typeof channel?.send === 'function'
201
+ ? channel.send.bind(channel)
202
+ : (() => {
203
+ throw new Error('[Box SDK] Channel send function not available')
204
+ })()
134
205
 
135
206
  return {
136
207
  hide(): void {
137
- sendFn('core-box:hide').catch((error: any) => {
208
+ send('core-box:hide').catch((error: any) => {
138
209
  console.error('[Box SDK] Failed to hide CoreBox:', error)
139
210
  })
140
211
  },
141
212
 
142
213
  show(): void {
143
- sendFn('core-box:show').catch((error: any) => {
214
+ send('core-box:show').catch((error: any) => {
144
215
  console.error('[Box SDK] Failed to show CoreBox:', error)
145
216
  })
146
217
  },
147
218
 
148
219
  async expand(options?: BoxExpandOptions): Promise<void> {
149
220
  try {
150
- await sendFn('core-box:expand', options || {})
151
- } catch (error) {
221
+ await send('core-box:expand', options || {})
222
+ }
223
+ catch (error) {
152
224
  console.error('[Box SDK] Failed to expand CoreBox:', error)
153
225
  throw error
154
226
  }
@@ -156,8 +228,9 @@ export function createBoxSDK(channel: any): BoxSDK {
156
228
 
157
229
  async shrink(): Promise<void> {
158
230
  try {
159
- await sendFn('core-box:expand', { mode: 'collapse' })
160
- } catch (error) {
231
+ await send('core-box:expand', { mode: 'collapse' })
232
+ }
233
+ catch (error) {
161
234
  console.error('[Box SDK] Failed to shrink CoreBox:', error)
162
235
  throw error
163
236
  }
@@ -165,8 +238,9 @@ export function createBoxSDK(channel: any): BoxSDK {
165
238
 
166
239
  async hideInput(): Promise<void> {
167
240
  try {
168
- await sendFn('core-box:hide-input')
169
- } catch (error) {
241
+ await send('core-box:hide-input')
242
+ }
243
+ catch (error) {
170
244
  console.error('[Box SDK] Failed to hide input:', error)
171
245
  throw error
172
246
  }
@@ -174,8 +248,9 @@ export function createBoxSDK(channel: any): BoxSDK {
174
248
 
175
249
  async showInput(): Promise<void> {
176
250
  try {
177
- await sendFn('core-box:show-input')
178
- } catch (error) {
251
+ await send('core-box:show-input')
252
+ }
253
+ catch (error) {
179
254
  console.error('[Box SDK] Failed to show input:', error)
180
255
  throw error
181
256
  }
@@ -183,37 +258,72 @@ export function createBoxSDK(channel: any): BoxSDK {
183
258
 
184
259
  async getInput(): Promise<string> {
185
260
  try {
186
- const result = await sendFn('core-box:get-input')
261
+ const result = await send('core-box:get-input')
187
262
  return result?.data?.input || result?.input || ''
188
- } catch (error) {
263
+ }
264
+ catch (error) {
189
265
  console.error('[Box SDK] Failed to get input:', error)
190
266
  throw error
191
267
  }
192
- }
268
+ },
269
+
270
+ async setInput(value: string): Promise<void> {
271
+ try {
272
+ await send('core-box:set-input', { value })
273
+ }
274
+ catch (error) {
275
+ console.error('[Box SDK] Failed to set input:', error)
276
+ throw error
277
+ }
278
+ },
279
+
280
+ async clearInput(): Promise<void> {
281
+ try {
282
+ await send('core-box:clear-input')
283
+ }
284
+ catch (error) {
285
+ console.error('[Box SDK] Failed to clear input:', error)
286
+ throw error
287
+ }
288
+ },
289
+
290
+ async allowInput(): Promise<void> {
291
+ try {
292
+ await send('core-box:allow-input')
293
+ }
294
+ catch (error) {
295
+ console.error('[Box SDK] Failed to enable input monitoring:', error)
296
+ throw error
297
+ }
298
+ },
299
+
300
+ async allowClipboard(types: number): Promise<void> {
301
+ try {
302
+ await send('core-box:allow-clipboard', types)
303
+ }
304
+ catch (error) {
305
+ console.error('[Box SDK] Failed to enable clipboard monitoring:', error)
306
+ throw error
307
+ }
308
+ },
193
309
  }
194
310
  }
195
311
 
196
312
  /**
197
313
  * Hook for using Box SDK in plugin context
198
- *
314
+ *
199
315
  * @returns Box SDK instance
200
- *
316
+ *
201
317
  * @example
202
318
  * ```typescript
203
319
  * const box = useBox()
204
- *
320
+ *
205
321
  * box.hide()
206
322
  * box.expand({ length: 10 })
207
323
  * const input = await box.getInput()
208
324
  * ```
209
325
  */
210
326
  export function useBox(): BoxSDK {
211
- // @ts-ignore - window.$channel is injected by the plugin system
212
- const channel = window.$channel
213
-
214
- if (!channel) {
215
- throw new Error('[Box SDK] Channel not available. Make sure this is called in a plugin context.')
216
- }
217
-
327
+ const channel = ensureRendererChannel('[Box SDK] Channel not available. Make sure this is called in a plugin context.')
218
328
  return createBoxSDK(channel)
219
329
  }
@@ -4,6 +4,34 @@ import { genChannel } from '../channel'
4
4
 
5
5
  const ensureClientChannel = (): ITouchClientChannel => genChannel()
6
6
 
7
+ const DEFAULT_CHANNEL_ERROR = '[Plugin SDK] Channel not available. Make sure this code runs inside a plugin renderer context.'
8
+
9
+ let cachedWindowChannel: ITouchClientChannel | null = null
10
+
11
+ /**
12
+ * Ensures that the renderer-side plugin channel (window.$channel) exists and returns it.
13
+ *
14
+ * @param errorMessage - Optional custom error message when the channel is unavailable
15
+ */
16
+ export function ensureRendererChannel(errorMessage = DEFAULT_CHANNEL_ERROR): ITouchClientChannel {
17
+ const globalWindow = typeof window === 'undefined' ? undefined : window
18
+ const channel = globalWindow?.$channel ?? cachedWindowChannel
19
+
20
+ if (!channel) {
21
+ throw new Error(errorMessage)
22
+ }
23
+
24
+ cachedWindowChannel = channel
25
+ return channel
26
+ }
27
+
28
+ /**
29
+ * Convenience hook for accessing window.$channel in plugin renderers.
30
+ */
31
+ export function useChannel(errorMessage?: string): ITouchClientChannel {
32
+ return ensureRendererChannel(errorMessage)
33
+ }
34
+
7
35
  export function createPluginRendererChannel(): IPluginRendererChannel {
8
36
  const client = ensureClientChannel()
9
37
 
@@ -46,3 +74,9 @@ export function usePluginRendererChannel(): IPluginRendererChannel {
46
74
 
47
75
  return cachedRendererChannel
48
76
  }
77
+
78
+ declare global {
79
+ interface Window {
80
+ $channel: ITouchClientChannel
81
+ }
82
+ }
@@ -11,6 +11,7 @@ import type {
11
11
  DivisionBoxState,
12
12
  SessionInfo,
13
13
  } from '../../types/division-box'
14
+ import { ensureRendererChannel } from './channel'
14
15
 
15
16
  /**
16
17
  * State change event handler
@@ -180,11 +181,19 @@ export function createDivisionBoxSDK(channel: any): DivisionBoxSDK {
180
181
 
181
182
  registerListener()
182
183
 
184
+ const send: (eventName: string, payload?: any) => Promise<any> =
185
+ typeof channel?.sendToMain === 'function'
186
+ ? channel.sendToMain.bind(channel)
187
+ : typeof channel?.send === 'function'
188
+ ? channel.send.bind(channel)
189
+ : (() => {
190
+ throw new Error('[DivisionBox SDK] Channel send function not available')
191
+ })()
192
+
183
193
  return {
184
194
  async open(config: DivisionBoxConfig): Promise<SessionInfo> {
185
195
  // Send to main process
186
- const sendFn = channel.sendToMain || channel.send
187
- const result = await sendFn('division-box:open', config)
196
+ const result = await send('division-box:open', config)
188
197
 
189
198
  if (!result.success) {
190
199
  throw new Error(result.error?.message || 'Failed to open DivisionBox')
@@ -194,8 +203,7 @@ export function createDivisionBoxSDK(channel: any): DivisionBoxSDK {
194
203
  },
195
204
 
196
205
  async close(sessionId: string, options?: CloseOptions): Promise<void> {
197
- const sendFn = channel.sendToMain || channel.send
198
- const result = await sendFn('division-box:close', { sessionId, options })
206
+ const result = await send('division-box:close', { sessionId, options })
199
207
 
200
208
  if (!result.success) {
201
209
  throw new Error(result.error?.message || 'Failed to close DivisionBox')
@@ -211,8 +219,7 @@ export function createDivisionBoxSDK(channel: any): DivisionBoxSDK {
211
219
  },
212
220
 
213
221
  async updateState(sessionId: string, key: string, value: any): Promise<void> {
214
- const sendFn = channel.sendToMain || channel.send
215
- const result = await sendFn('division-box:update-state', {
222
+ const result = await send('division-box:update-state', {
216
223
  sessionId,
217
224
  key,
218
225
  value
@@ -224,8 +231,7 @@ export function createDivisionBoxSDK(channel: any): DivisionBoxSDK {
224
231
  },
225
232
 
226
233
  async getState(sessionId: string, key: string): Promise<any> {
227
- const sendFn = channel.sendToMain || channel.send
228
- const result = await sendFn('division-box:get-state', {
234
+ const result = await send('division-box:get-state', {
229
235
  sessionId,
230
236
  key
231
237
  })
@@ -255,12 +261,6 @@ export function createDivisionBoxSDK(channel: any): DivisionBoxSDK {
255
261
  * ```
256
262
  */
257
263
  export function useDivisionBox(): DivisionBoxSDK {
258
- // @ts-ignore - window.$channel is injected by the plugin system
259
- const channel = window.$channel
260
-
261
- if (!channel) {
262
- throw new Error('[DivisionBox SDK] Channel not available. Make sure this is called in a plugin context.')
263
- }
264
-
264
+ const channel = ensureRendererChannel('[DivisionBox SDK] Channel not available. Make sure this is called in a plugin context.')
265
265
  return createDivisionBoxSDK(channel)
266
266
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { TuffItem } from '../../core-box/tuff'
10
+ import { ensureRendererChannel } from './channel'
10
11
 
11
12
  /**
12
13
  * Input change event handler
@@ -218,18 +219,13 @@ export function createFeatureSDK(boxItemsAPI: any, channel: any): FeatureSDK {
218
219
  * ```
219
220
  */
220
221
  export function useFeature(): FeatureSDK {
221
- // @ts-ignore - window.$boxItems and window.$channel are injected by the plugin system
222
+ // @ts-ignore - window.$boxItems is injected by the plugin system
222
223
  const boxItemsAPI = window.$boxItems
223
- // @ts-ignore
224
- const channel = window.$channel
224
+ const channel = ensureRendererChannel('[Feature SDK] Channel not available. Make sure this is called in a plugin context.')
225
225
 
226
226
  if (!boxItemsAPI) {
227
227
  throw new Error('[Feature SDK] boxItems API not available. Make sure this is called in a plugin context.')
228
228
  }
229
229
 
230
- if (!channel) {
231
- throw new Error('[Feature SDK] Channel not available. Make sure this is called in a plugin context.')
232
- }
233
-
234
230
  return createFeatureSDK(boxItemsAPI, channel)
235
231
  }
@@ -1,4 +1,5 @@
1
1
  import { BridgeEventForCoreBox } from '../enum/bridge-event'
2
+ import { ensureRendererChannel } from '../channel'
2
3
 
3
4
  export type BridgeEvent = BridgeEventForCoreBox
4
5
 
@@ -26,7 +27,8 @@ export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
26
27
 
27
28
  // Only register the channel listener once per event type
28
29
  if (hooks.length === 0) {
29
- window.$channel.regChannel(type, ({ data }) => {
30
+ const channel = ensureRendererChannel('[TouchSDK] Bridge channel not available. Make sure hooks run in plugin renderer context.')
31
+ channel.regChannel(type, ({ data }) => {
30
32
  console.debug(`[TouchSDK] ${type} event received: `, data)
31
33
  // When the event is received, call all registered hooks for this type
32
34
  const registeredHooks = __hooks[type]
@@ -1,3 +1,5 @@
1
+ import { ensureRendererChannel } from '../channel'
2
+
1
3
  export enum LifecycleHooks {
2
4
  ENABLE = 'en',
3
5
  DISABLE = 'di',
@@ -22,7 +24,8 @@ export function injectHook(type: LifecycleHooks, hook: Function, processFunc = (
22
24
  const hooks: Array<Function> = __hooks[type] || (__hooks[type] = [])
23
25
 
24
26
  if (hooks.length === 0) {
25
- window.$channel.regChannel(`@lifecycle:${type}`, (obj: any) => {
27
+ const channel = ensureRendererChannel('[Lifecycle Hook] Channel not available. Make sure hooks run in plugin renderer context.')
28
+ channel.regChannel(`@lifecycle:${type}`, (obj: any) => {
26
29
  processFunc(obj)
27
30
 
28
31
  // @ts-ignore
@@ -1,4 +1,5 @@
1
1
  import type { FileDetails, StorageStats, StorageTreeNode } from '../../types/storage'
2
+ import { ensureRendererChannel } from './channel'
2
3
 
3
4
  /**
4
5
  * Get the storage for the current plugin.
@@ -15,7 +16,7 @@ export function usePluginStorage() {
15
16
  throw new Error('[Plugin SDK] Cannot determine plugin name. Make sure this is called in a plugin context.')
16
17
  }
17
18
 
18
- const channel = window.$channel
19
+ const channel = ensureRendererChannel('[Plugin Storage] Channel not available. Make sure this is called in a plugin context.')
19
20
 
20
21
  return {
21
22
  /**