@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 +1 -1
- package/plugin/channel.ts +66 -7
- package/plugin/sdk/README.md +5 -1
- package/plugin/sdk/box-sdk.ts +163 -53
- package/plugin/sdk/channel.ts +34 -0
- package/plugin/sdk/division-box.ts +15 -15
- package/plugin/sdk/feature-sdk.ts +3 -7
- package/plugin/sdk/hooks/bridge.ts +3 -1
- package/plugin/sdk/hooks/life-cycle.ts +4 -1
- package/plugin/sdk/storage.ts +2 -1
package/package.json
CHANGED
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:
|
|
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
|
-
|
|
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
|
-
|
|
289
|
+
try {
|
|
290
|
+
const res = this.__parse_raw_data(
|
|
291
|
+
void 0,
|
|
292
|
+
this.ipcRenderer.sendSync('@plugin-process-message', data),
|
|
293
|
+
)!
|
|
245
294
|
|
|
246
|
-
|
|
247
|
-
|
|
295
|
+
if (res.header.status === 'reply')
|
|
296
|
+
return res.data
|
|
248
297
|
|
|
249
|
-
|
|
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
|
|
package/plugin/sdk/README.md
CHANGED
|
@@ -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` - 请求输入值(主进程 → 渲染进程)
|
package/plugin/sdk/box-sdk.ts
CHANGED
|
@@ -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()
|
|
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()
|
|
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)
|
|
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()
|
|
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()
|
|
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()
|
|
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()
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
151
|
-
}
|
|
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
|
|
160
|
-
}
|
|
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
|
|
169
|
-
}
|
|
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
|
|
178
|
-
}
|
|
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
|
|
261
|
+
const result = await send('core-box:get-input')
|
|
187
262
|
return result?.data?.input || result?.input || ''
|
|
188
|
-
}
|
|
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
|
-
|
|
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
|
}
|
package/plugin/sdk/channel.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
222
|
+
// @ts-ignore - window.$boxItems is injected by the plugin system
|
|
222
223
|
const boxItemsAPI = window.$boxItems
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/plugin/sdk/storage.ts
CHANGED
|
@@ -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 =
|
|
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
|
/**
|