@talex-touch/utils 1.0.42 → 1.0.45

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.
Files changed (234) hide show
  1. package/.eslintcache +1 -0
  2. package/__tests__/cloud-sync-sdk.test.ts +442 -0
  3. package/__tests__/icons/icons.test.ts +84 -0
  4. package/__tests__/plugin-sdk-lifecycle.test.ts +130 -0
  5. package/__tests__/power-sdk.test.ts +143 -0
  6. package/__tests__/preset-export-types.test.ts +108 -0
  7. package/__tests__/search/fuzzy-match.test.ts +137 -0
  8. package/__tests__/transport/port-policy.test.ts +44 -0
  9. package/__tests__/transport-domain-sdks.test.ts +152 -0
  10. package/__tests__/types/update.test.ts +67 -0
  11. package/account/account-sdk.ts +915 -0
  12. package/account/index.ts +2 -0
  13. package/account/types.ts +321 -0
  14. package/analytics/client.ts +136 -0
  15. package/analytics/index.ts +2 -0
  16. package/analytics/types.ts +156 -0
  17. package/animation/auto-resize.ts +322 -0
  18. package/animation/window-node.ts +26 -19
  19. package/auth/clerk-types.ts +12 -30
  20. package/auth/index.ts +0 -2
  21. package/auth/useAuthState.ts +6 -14
  22. package/base/index.ts +2 -0
  23. package/base/log-level.ts +105 -0
  24. package/channel/index.ts +170 -69
  25. package/cloud-sync/cloud-sync-sdk.ts +450 -0
  26. package/cloud-sync/index.ts +1 -0
  27. package/common/file-scan-utils.ts +17 -9
  28. package/common/index.ts +4 -0
  29. package/common/logger/index.ts +46 -0
  30. package/common/logger/logger-manager.ts +303 -0
  31. package/common/logger/module-logger.ts +270 -0
  32. package/common/logger/transport-logger.ts +234 -0
  33. package/common/logger/types.ts +93 -0
  34. package/common/search/gather.ts +48 -6
  35. package/common/search/index.ts +8 -0
  36. package/common/storage/constants.ts +13 -0
  37. package/common/storage/entity/app-settings.ts +245 -0
  38. package/common/storage/entity/index.ts +3 -0
  39. package/common/storage/entity/layout-atom-types.ts +147 -0
  40. package/common/storage/entity/openers.ts +1 -0
  41. package/common/storage/entity/preset-cloud-api.ts +132 -0
  42. package/common/storage/entity/preset-export-types.ts +256 -0
  43. package/common/storage/entity/shortcut-settings.ts +1 -0
  44. package/common/storage/shortcut-storage.ts +11 -0
  45. package/common/utils/clone-diagnostics.ts +105 -0
  46. package/common/utils/file.ts +16 -8
  47. package/common/utils/index.ts +6 -2
  48. package/common/utils/payload-preview.ts +173 -0
  49. package/common/utils/polling.ts +167 -13
  50. package/common/utils/safe-path.ts +103 -0
  51. package/common/utils/safe-shell.ts +115 -0
  52. package/common/utils/task-queue.ts +4 -1
  53. package/core-box/builder/tuff-builder.ts +0 -1
  54. package/core-box/index.ts +1 -1
  55. package/core-box/recommendation.ts +38 -1
  56. package/core-box/tuff/tuff-dsl.ts +32 -0
  57. package/electron/download-manager.ts +10 -7
  58. package/electron/env-tool.ts +42 -40
  59. package/electron/index.ts +0 -1
  60. package/env/index.ts +156 -0
  61. package/eslint.config.js +55 -0
  62. package/i18n/index.ts +62 -0
  63. package/i18n/locales/en.json +226 -0
  64. package/i18n/locales/zh.json +226 -0
  65. package/i18n/message-keys.ts +236 -0
  66. package/i18n/resolver.ts +181 -0
  67. package/icons/index.ts +257 -0
  68. package/icons/svg.ts +69 -0
  69. package/index.ts +9 -1
  70. package/intelligence/client.ts +72 -42
  71. package/market/constants.ts +9 -5
  72. package/market/index.ts +1 -1
  73. package/market/types.ts +19 -4
  74. package/package.json +15 -5
  75. package/permission/index.ts +143 -46
  76. package/permission/legacy.ts +26 -0
  77. package/permission/registry.ts +304 -0
  78. package/permission/types.ts +164 -0
  79. package/plugin/channel.ts +68 -39
  80. package/plugin/index.ts +80 -7
  81. package/plugin/install.ts +3 -0
  82. package/plugin/log/types.ts +22 -5
  83. package/plugin/node/logger-manager.ts +11 -3
  84. package/plugin/node/logger.ts +24 -17
  85. package/plugin/preload.ts +25 -2
  86. package/plugin/providers/index.ts +4 -4
  87. package/plugin/providers/market-client.ts +6 -3
  88. package/plugin/providers/npm-provider.ts +22 -7
  89. package/plugin/providers/tpex-provider.ts +22 -8
  90. package/plugin/sdk/box-items.ts +14 -0
  91. package/plugin/sdk/box-sdk.ts +64 -0
  92. package/plugin/sdk/channel.ts +119 -4
  93. package/plugin/sdk/clipboard.ts +26 -12
  94. package/plugin/sdk/cloud-sync.ts +113 -0
  95. package/plugin/sdk/common.ts +19 -11
  96. package/plugin/sdk/core-box.ts +6 -15
  97. package/plugin/sdk/division-box.ts +160 -65
  98. package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
  99. package/plugin/sdk/feature-sdk.ts +111 -76
  100. package/plugin/sdk/flow.ts +146 -45
  101. package/plugin/sdk/hooks/bridge.ts +13 -6
  102. package/plugin/sdk/hooks/life-cycle.ts +35 -16
  103. package/plugin/sdk/index.ts +14 -3
  104. package/plugin/sdk/intelligence.ts +87 -0
  105. package/plugin/sdk/meta/README.md +179 -0
  106. package/plugin/sdk/meta-sdk.ts +244 -0
  107. package/plugin/sdk/notification.ts +9 -0
  108. package/plugin/sdk/plugin-info.ts +64 -0
  109. package/plugin/sdk/power.ts +155 -0
  110. package/plugin/sdk/recommend.ts +21 -0
  111. package/plugin/sdk/service/index.ts +12 -8
  112. package/plugin/sdk/sqlite.ts +141 -0
  113. package/plugin/sdk/storage.ts +2 -6
  114. package/plugin/sdk/system.ts +2 -9
  115. package/plugin/sdk/temp-files.ts +41 -0
  116. package/plugin/sdk/touch-sdk.ts +18 -0
  117. package/plugin/sdk/types.ts +44 -4
  118. package/plugin/sdk/window/index.ts +12 -9
  119. package/plugin/sdk-version.ts +231 -0
  120. package/preload/renderer.ts +3 -2
  121. package/renderer/hooks/arg-mapper.ts +16 -2
  122. package/renderer/hooks/index.ts +13 -0
  123. package/renderer/hooks/initialize.ts +2 -1
  124. package/renderer/hooks/use-agent-market-sdk.ts +7 -0
  125. package/renderer/hooks/use-agent-market.ts +106 -0
  126. package/renderer/hooks/use-agents-sdk.ts +7 -0
  127. package/renderer/hooks/use-app-sdk.ts +7 -0
  128. package/renderer/hooks/use-channel.ts +33 -4
  129. package/renderer/hooks/use-download-sdk.ts +21 -0
  130. package/renderer/hooks/use-intelligence-sdk.ts +7 -0
  131. package/renderer/hooks/use-intelligence-stats.ts +290 -0
  132. package/renderer/hooks/use-intelligence.ts +55 -214
  133. package/renderer/hooks/use-market-sdk.ts +16 -0
  134. package/renderer/hooks/use-notification-sdk.ts +7 -0
  135. package/renderer/hooks/use-permission-sdk.ts +7 -0
  136. package/renderer/hooks/use-permission.ts +325 -0
  137. package/renderer/hooks/use-platform-sdk.ts +7 -0
  138. package/renderer/hooks/use-plugin-sdk.ts +16 -0
  139. package/renderer/hooks/use-settings-sdk.ts +7 -0
  140. package/renderer/hooks/use-update-sdk.ts +21 -0
  141. package/renderer/index.ts +1 -0
  142. package/renderer/ref.ts +19 -10
  143. package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
  144. package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
  145. package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
  146. package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
  147. package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
  148. package/renderer/shared/components/index.ts +5 -0
  149. package/renderer/shared/components/shims-vue.d.ts +5 -0
  150. package/renderer/shared/index.ts +2 -0
  151. package/renderer/shared/plugin-detail.ts +62 -0
  152. package/renderer/storage/app-settings.ts +3 -1
  153. package/renderer/storage/base-storage.ts +508 -82
  154. package/renderer/storage/intelligence-storage.ts +31 -40
  155. package/renderer/storage/openers.ts +3 -1
  156. package/renderer/storage/storage-subscription.ts +126 -42
  157. package/renderer/touch-sdk/env.ts +10 -10
  158. package/renderer/touch-sdk/index.ts +114 -18
  159. package/renderer/touch-sdk/terminal.ts +24 -13
  160. package/search/feature-matcher.ts +279 -0
  161. package/search/fuzzy-match.ts +64 -34
  162. package/search/index.ts +10 -0
  163. package/search/levenshtein-utils.ts +17 -11
  164. package/transport/errors.ts +310 -0
  165. package/transport/event/builder.ts +378 -0
  166. package/transport/event/index.ts +7 -0
  167. package/transport/event/types.ts +292 -0
  168. package/transport/events/index.ts +2690 -0
  169. package/transport/events/meta-overlay.ts +79 -0
  170. package/transport/events/types/agents.ts +177 -0
  171. package/transport/events/types/app-index.ts +20 -0
  172. package/transport/events/types/app.ts +475 -0
  173. package/transport/events/types/box-item.ts +222 -0
  174. package/transport/events/types/clipboard.ts +80 -0
  175. package/transport/events/types/core-box.ts +534 -0
  176. package/transport/events/types/device-idle.ts +7 -0
  177. package/transport/events/types/division-box.ts +99 -0
  178. package/transport/events/types/download.ts +115 -0
  179. package/transport/events/types/file-index.ts +84 -0
  180. package/transport/events/types/flow.ts +149 -0
  181. package/transport/events/types/index.ts +70 -0
  182. package/transport/events/types/market.ts +39 -0
  183. package/transport/events/types/meta-overlay.ts +184 -0
  184. package/transport/events/types/notification.ts +140 -0
  185. package/transport/events/types/permission.ts +90 -0
  186. package/transport/events/types/platform.ts +8 -0
  187. package/transport/events/types/plugin.ts +631 -0
  188. package/transport/events/types/sentry.ts +20 -0
  189. package/transport/events/types/storage.ts +208 -0
  190. package/transport/events/types/transport.ts +60 -0
  191. package/transport/events/types/tray.ts +16 -0
  192. package/transport/events/types/update.ts +78 -0
  193. package/transport/index.ts +141 -0
  194. package/transport/main.ts +2 -0
  195. package/transport/prelude.ts +208 -0
  196. package/transport/sdk/constants.ts +29 -0
  197. package/transport/sdk/domains/agents-market.ts +47 -0
  198. package/transport/sdk/domains/agents.ts +62 -0
  199. package/transport/sdk/domains/app.ts +48 -0
  200. package/transport/sdk/domains/disposable.ts +35 -0
  201. package/transport/sdk/domains/download.ts +139 -0
  202. package/transport/sdk/domains/index.ts +13 -0
  203. package/transport/sdk/domains/intelligence.ts +616 -0
  204. package/transport/sdk/domains/market.ts +35 -0
  205. package/transport/sdk/domains/notification.ts +62 -0
  206. package/transport/sdk/domains/permission.ts +85 -0
  207. package/transport/sdk/domains/platform.ts +19 -0
  208. package/transport/sdk/domains/plugin.ts +144 -0
  209. package/transport/sdk/domains/settings.ts +102 -0
  210. package/transport/sdk/domains/update.ts +64 -0
  211. package/transport/sdk/index.ts +60 -0
  212. package/transport/sdk/main-transport.ts +710 -0
  213. package/transport/sdk/main.ts +9 -0
  214. package/transport/sdk/plugin-transport.ts +654 -0
  215. package/transport/sdk/port-policy.ts +38 -0
  216. package/transport/sdk/renderer-transport.ts +1165 -0
  217. package/transport/types.ts +605 -0
  218. package/types/agent.ts +399 -0
  219. package/types/cloud-sync.ts +157 -0
  220. package/types/division-box.ts +31 -31
  221. package/types/download.ts +1 -0
  222. package/types/flow.ts +63 -12
  223. package/types/icon.ts +2 -1
  224. package/types/index.ts +5 -0
  225. package/types/intelligence.ts +166 -173
  226. package/types/modules/base.ts +2 -0
  227. package/types/path-browserify.d.ts +5 -0
  228. package/types/platform.ts +12 -0
  229. package/types/startup-info.ts +32 -0
  230. package/types/touch-app-core.ts +8 -8
  231. package/types/update.ts +94 -1
  232. package/vitest.config.ts +25 -0
  233. package/auth/useClerkConfig.ts +0 -40
  234. package/auth/useClerkProvider.ts +0 -52
@@ -5,6 +5,8 @@ import type {
5
5
  PluginProvider,
6
6
  PluginProviderContext,
7
7
  } from './types'
8
+ import { Buffer } from 'node:buffer'
9
+ import process from 'node:process'
8
10
  import { PluginProviderType } from './types'
9
11
 
10
12
  const NPM_REGISTRY = 'https://registry.npmjs.org'
@@ -30,12 +32,12 @@ export interface NpmPackageInfo {
30
32
  }
31
33
 
32
34
  export interface NpmPackageVersions {
33
- name: string
35
+ 'name': string
34
36
  'dist-tags': {
35
37
  latest: string
36
38
  [tag: string]: string
37
39
  }
38
- versions: Record<string, NpmPackageInfo>
40
+ 'versions': Record<string, NpmPackageInfo>
39
41
  }
40
42
 
41
43
  /**
@@ -51,17 +53,29 @@ export interface NpmPackageVersions {
51
53
  function parseNpmSource(source: string): { packageName: string, version?: string } | null {
52
54
  const npmMatch = source.match(/^npm:(@?[a-z0-9][\w\-.]*(?:\/[a-z0-9][\w\-.]*)?)(?:@(.+))?$/i)
53
55
  if (npmMatch) {
54
- return { packageName: npmMatch[1], version: npmMatch[2] }
56
+ const packageName = npmMatch[1]
57
+ if (!packageName)
58
+ return null
59
+ const version = npmMatch[2]
60
+ return version ? { packageName, version } : { packageName }
55
61
  }
56
62
 
57
63
  const scopedMatch = source.match(/^(@tuff\/[a-z0-9][\w\-.]*)(?:@(.+))?$/i)
58
64
  if (scopedMatch) {
59
- return { packageName: scopedMatch[1], version: scopedMatch[2] }
65
+ const packageName = scopedMatch[1]
66
+ if (!packageName)
67
+ return null
68
+ const version = scopedMatch[2]
69
+ return version ? { packageName, version } : { packageName }
60
70
  }
61
71
 
62
72
  const prefixMatch = source.match(/^(tuff-plugin-[a-z0-9][\w\-.]*)(?:@(.+))?$/i)
63
73
  if (prefixMatch) {
64
- return { packageName: prefixMatch[1], version: prefixMatch[2] }
74
+ const packageName = prefixMatch[1]
75
+ if (!packageName)
76
+ return null
77
+ const version = prefixMatch[2]
78
+ return version ? { packageName, version } : { packageName }
65
79
  }
66
80
 
67
81
  return null
@@ -123,7 +137,7 @@ export class NpmProvider implements PluginProvider {
123
137
  const fileName = `${safePackageName}-${packageInfo.version}.tgz`
124
138
  const filePath = `${tempDir}/${fileName}`
125
139
 
126
- if (typeof globalThis.process !== 'undefined') {
140
+ if (typeof process !== 'undefined') {
127
141
  const fs = await import('node:fs/promises')
128
142
  await fs.writeFile(filePath, Buffer.from(arrayBuffer))
129
143
  }
@@ -166,7 +180,8 @@ export class NpmProvider implements PluginProvider {
166
180
  const res = await fetch(`${this.registry}/${encodedName}`)
167
181
 
168
182
  if (!res.ok) {
169
- if (res.status === 404) return null
183
+ if (res.status === 404)
184
+ return null
170
185
  throw new Error(`Failed to fetch package info: ${res.statusText}`)
171
186
  }
172
187
 
@@ -5,9 +5,12 @@ import type {
5
5
  PluginProvider,
6
6
  PluginProviderContext,
7
7
  } from './types'
8
+ import { Buffer } from 'node:buffer'
9
+ import process from 'node:process'
10
+ import { NEXUS_BASE_URL } from '../../env'
8
11
  import { PluginProviderType } from './types'
9
12
 
10
- const DEFAULT_TPEX_API = 'https://tuff.tagzxia.com'
13
+ const DEFAULT_TPEX_API = NEXUS_BASE_URL
11
14
 
12
15
  /**
13
16
  * Check if source is a .tpex file path or URL
@@ -41,6 +44,7 @@ export interface TpexPluginInfo {
41
44
  channel: string
42
45
  packageUrl: string
43
46
  packageSize: number
47
+ signature?: string
44
48
  manifest?: Record<string, unknown> | null
45
49
  changelog?: string | null
46
50
  }
@@ -59,6 +63,7 @@ export interface TpexDetailResponse {
59
63
  channel: string
60
64
  packageUrl: string
61
65
  packageSize: number
66
+ signature?: string
62
67
  manifest?: Record<string, unknown> | null
63
68
  changelog?: string | null
64
69
  }>
@@ -70,14 +75,22 @@ export interface TpexDetailResponse {
70
75
  * Formats: "tpex:slug", "tpex:slug@version", "slug" (when hintType is TPEX)
71
76
  */
72
77
  function parseTpexSource(source: string): { slug: string, version?: string } | null {
73
- const tpexMatch = source.match(/^tpex:([a-z0-9][a-z0-9\-_.]{1,62}[a-z0-9])(?:@(.+))?$/i)
78
+ const tpexMatch = source.match(/^tpex:([a-z0-9][\w\-.]{1,62}[a-z0-9])(?:@(.+))?$/i)
74
79
  if (tpexMatch) {
75
- return { slug: tpexMatch[1], version: tpexMatch[2] }
80
+ const slug = tpexMatch[1]
81
+ if (!slug)
82
+ return null
83
+ const version = tpexMatch[2]
84
+ return version ? { slug, version } : { slug }
76
85
  }
77
86
 
78
- const slugMatch = source.match(/^([a-z0-9][a-z0-9\-_.]{1,62}[a-z0-9])(?:@(.+))?$/i)
87
+ const slugMatch = source.match(/^([a-z0-9][\w\-.]{1,62}[a-z0-9])(?:@(.+))?$/i)
79
88
  if (slugMatch) {
80
- return { slug: slugMatch[1], version: slugMatch[2] }
89
+ const slug = slugMatch[1]
90
+ if (!slug)
91
+ return null
92
+ const version = slugMatch[2]
93
+ return version ? { slug, version } : { slug }
81
94
  }
82
95
 
83
96
  return null
@@ -138,7 +151,7 @@ export class TpexProvider implements PluginProvider {
138
151
  const fileName = `tpex-${Date.now()}.tpex`
139
152
  filePath = `${tempDir}/${fileName}`
140
153
 
141
- if (typeof globalThis.process !== 'undefined') {
154
+ if (typeof process !== 'undefined') {
142
155
  const fs = await import('node:fs/promises')
143
156
  await fs.writeFile(filePath, Buffer.from(arrayBuffer))
144
157
  }
@@ -204,7 +217,7 @@ export class TpexProvider implements PluginProvider {
204
217
  const fileName = `${slug}-${targetVersion.version}.tpex`
205
218
  const filePath = `${tempDir}/${fileName}`
206
219
 
207
- if (typeof globalThis.process !== 'undefined') {
220
+ if (typeof process !== 'undefined') {
208
221
  const fs = await import('node:fs/promises')
209
222
  await fs.writeFile(filePath, Buffer.from(arrayBuffer))
210
223
  }
@@ -257,7 +270,8 @@ export class TpexProvider implements PluginProvider {
257
270
  async getPlugin(slug: string): Promise<TpexDetailResponse['plugin'] | null> {
258
271
  const res = await fetch(`${this.apiBase}/api/market/plugins/${slug}`)
259
272
  if (!res.ok) {
260
- if (res.status === 404) return null
273
+ if (res.status === 404)
274
+ return null
261
275
  throw new Error(`Failed to fetch plugin: ${res.statusText}`)
262
276
  }
263
277
 
@@ -0,0 +1,14 @@
1
+ import { hasWindow } from '../../env'
2
+
3
+ export type BoxItemsAPI = Record<string, any>
4
+
5
+ const DEFAULT_BOX_ITEMS_ERROR
6
+ = '[Feature SDK] boxItems API not available. Make sure this is called in a plugin context.'
7
+
8
+ export function useBoxItems(errorMessage = DEFAULT_BOX_ITEMS_ERROR): BoxItemsAPI {
9
+ const boxItemsApi = hasWindow() ? (window as any)?.$boxItems as BoxItemsAPI | undefined : undefined
10
+ if (!boxItemsApi) {
11
+ throw new Error(errorMessage)
12
+ }
13
+ return boxItemsApi
14
+ }
@@ -184,6 +184,39 @@ export interface BoxSDK {
184
184
  * ```
185
185
  */
186
186
  allowClipboard: (types: number) => Promise<void>
187
+
188
+ /**
189
+ * Set CoreBox window to a specific height (60-800px)
190
+ *
191
+ * @param height - Target height in pixels (will be clamped to 60-800)
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * // Set to custom height
196
+ * await plugin.box.setHeight(400)
197
+ * ```
198
+ */
199
+ setHeight: (height: number) => Promise<void>
200
+
201
+ /**
202
+ * Set CoreBox vertical position as percentage from top (0.1-0.9)
203
+ *
204
+ * @param topPercent - Position from top (0.1 = 10%, 0.5 = 50%, etc.)
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * // Position at 40% from top
209
+ * await plugin.box.setPositionOffset(0.4)
210
+ * ```
211
+ */
212
+ setPositionOffset: (topPercent: number) => Promise<void>
213
+
214
+ /**
215
+ * Get current CoreBox window bounds
216
+ *
217
+ * @returns Window bounds { x, y, width, height }
218
+ */
219
+ getBounds: () => Promise<{ x: number, y: number, width: number, height: number }>
187
220
  }
188
221
 
189
222
  /**
@@ -298,6 +331,37 @@ export function createBoxSDK(channel: ITouchClientChannel): BoxSDK {
298
331
  throw error
299
332
  }
300
333
  },
334
+
335
+ async setHeight(height: number): Promise<void> {
336
+ try {
337
+ await channel.send('core-box:set-height', { height })
338
+ }
339
+ catch (error) {
340
+ console.error('[Box SDK] Failed to set height:', error)
341
+ throw error
342
+ }
343
+ },
344
+
345
+ async setPositionOffset(topPercent: number): Promise<void> {
346
+ try {
347
+ await channel.send('core-box:set-position-offset', { topPercent })
348
+ }
349
+ catch (error) {
350
+ console.error('[Box SDK] Failed to set position offset:', error)
351
+ throw error
352
+ }
353
+ },
354
+
355
+ async getBounds(): Promise<{ x: number, y: number, width: number, height: number }> {
356
+ try {
357
+ const result = await channel.send('core-box:get-bounds')
358
+ return result.bounds
359
+ }
360
+ catch (error) {
361
+ console.error('[Box SDK] Failed to get bounds:', error)
362
+ throw error
363
+ }
364
+ },
301
365
  }
302
366
  }
303
367
 
@@ -1,12 +1,94 @@
1
- import type { ITouchClientChannel } from '@talex-touch/utils/channel'
1
+ import type { ITouchClientChannel, StandardChannelData } from '@talex-touch/utils/channel'
2
+ import type { ITuffTransport } from '@talex-touch/utils/transport'
2
3
  import type { IPluginRendererChannel, PluginChannelHandler } from './types'
4
+ import { ChannelType, DataCode } from '@talex-touch/utils/channel'
5
+ import { hasWindow } from '@talex-touch/utils/env'
6
+ import { defineRawEvent } from '@talex-touch/utils/transport/event/builder'
3
7
  import { genChannel } from '../channel'
4
8
 
5
9
  const ensureClientChannel = (): ITouchClientChannel => genChannel()
10
+ function resolvePluginName(): string | undefined {
11
+ if (!hasWindow()) {
12
+ return undefined
13
+ }
14
+
15
+ return (window as { $plugin?: { name?: string } } | undefined)?.$plugin?.name
16
+ }
17
+ function buildStandardChannelEvent(eventName: string, payload: unknown, pluginName: string | undefined, reply: (code: DataCode, data: unknown) => void): StandardChannelData {
18
+ return {
19
+ name: eventName,
20
+ header: {
21
+ status: 'request',
22
+ type: ChannelType.PLUGIN,
23
+ plugin: pluginName,
24
+ },
25
+ code: DataCode.SUCCESS,
26
+ data: payload,
27
+ plugin: pluginName,
28
+ reply,
29
+ }
30
+ }
31
+ function createTransportClientChannel(transport: ITuffTransport, fallback: ITouchClientChannel | null): ITouchClientChannel {
32
+ const handlerMap = new Map<string, Map<(data: StandardChannelData) => any, () => void>>()
33
+
34
+ return {
35
+ regChannel: (eventName, callback) => {
36
+ const disposer = transport.on(defineRawEvent(eventName), async (payload) => {
37
+ let replied = false
38
+ let replyData: unknown
39
+ const event = buildStandardChannelEvent(
40
+ eventName,
41
+ payload,
42
+ resolvePluginName(),
43
+ (_code, data) => {
44
+ replied = true
45
+ replyData = data
46
+ },
47
+ )
48
+
49
+ const result = await callback(event)
50
+ return replied ? replyData : result
51
+ })
52
+
53
+ let handlers = handlerMap.get(eventName)
54
+ if (!handlers) {
55
+ handlers = new Map()
56
+ handlerMap.set(eventName, handlers)
57
+ }
58
+ handlers.set(callback, disposer)
59
+
60
+ return () => {
61
+ disposer()
62
+ handlers?.delete(callback)
63
+ }
64
+ },
65
+ unRegChannel: (eventName, callback) => {
66
+ const disposer = handlerMap.get(eventName)?.get(callback)
67
+ if (!disposer) {
68
+ return false
69
+ }
70
+ disposer()
71
+ handlerMap.get(eventName)?.delete(callback)
72
+ return true
73
+ },
74
+ send: (eventName, arg) => transport.send(defineRawEvent(eventName), arg),
75
+ sendSync: (eventName, arg) => {
76
+ if (fallback?.sendSync) {
77
+ return fallback.sendSync(eventName, arg)
78
+ }
79
+ throw new Error(`[Plugin SDK] sendSync is not supported without legacy channel: ${eventName}`)
80
+ },
81
+ }
82
+ }
83
+ function resolveRendererTransport(): ITuffTransport | null {
84
+ const globalWindow = hasWindow() ? window : undefined
85
+ return globalWindow?.$transport ?? null
86
+ }
6
87
 
7
88
  const DEFAULT_CHANNEL_ERROR = '[Plugin SDK] Channel not available. Make sure this code runs inside a plugin renderer context.'
8
89
 
9
90
  let cachedWindowChannel: ITouchClientChannel | null = null
91
+ let cachedTransportChannel: ITouchClientChannel | null = null
10
92
 
11
93
  /**
12
94
  * Ensures that the renderer-side plugin channel (window.$channel) exists and returns it.
@@ -14,7 +96,16 @@ let cachedWindowChannel: ITouchClientChannel | null = null
14
96
  * @param errorMessage - Optional custom error message when the channel is unavailable
15
97
  */
16
98
  export function ensureRendererChannel(errorMessage = DEFAULT_CHANNEL_ERROR): ITouchClientChannel {
17
- const globalWindow = typeof window === 'undefined' ? undefined : window
99
+ const globalWindow = hasWindow() ? window : undefined
100
+ const transport = globalWindow?.$transport ?? null
101
+ if (transport) {
102
+ if (!cachedTransportChannel) {
103
+ const fallback = globalWindow?.$channel ?? cachedWindowChannel ?? null
104
+ cachedTransportChannel = createTransportClientChannel(transport, fallback)
105
+ }
106
+ return cachedTransportChannel
107
+ }
108
+
18
109
  const channel = globalWindow?.$channel ?? cachedWindowChannel
19
110
 
20
111
  if (!channel) {
@@ -34,9 +125,13 @@ export function useChannel(errorMessage?: string): ITouchClientChannel {
34
125
 
35
126
  export function createPluginRendererChannel(): IPluginRendererChannel {
36
127
  const client = ensureClientChannel()
128
+ const transport = resolveRendererTransport()
37
129
 
38
130
  return {
39
131
  send(eventName, payload) {
132
+ if (transport) {
133
+ return transport.send(defineRawEvent(eventName), payload)
134
+ }
40
135
  return client.send(eventName, payload)
41
136
  },
42
137
 
@@ -45,7 +140,26 @@ export function createPluginRendererChannel(): IPluginRendererChannel {
45
140
  },
46
141
 
47
142
  on(eventName, handler) {
48
- return client.regChannel(eventName, handler)
143
+ if (!transport) {
144
+ return client.regChannel(eventName, handler)
145
+ }
146
+
147
+ return transport.on(defineRawEvent(eventName), async (payload) => {
148
+ let replied = false
149
+ let replyData: unknown
150
+ const event = buildStandardChannelEvent(
151
+ eventName,
152
+ payload,
153
+ resolvePluginName(),
154
+ (_code, data) => {
155
+ replied = true
156
+ replyData = data
157
+ },
158
+ )
159
+
160
+ const result = await handler(event)
161
+ return replied ? replyData : result
162
+ })
49
163
  },
50
164
 
51
165
  once(eventName, handler) {
@@ -55,7 +169,7 @@ export function createPluginRendererChannel(): IPluginRendererChannel {
55
169
  handler(event)
56
170
  }
57
171
 
58
- dispose = client.regChannel(eventName, wrapped)
172
+ dispose = this.on(eventName, wrapped)
59
173
  return dispose
60
174
  },
61
175
 
@@ -78,5 +192,6 @@ export function usePluginRendererChannel(): IPluginRendererChannel {
78
192
  declare global {
79
193
  interface Window {
80
194
  $channel: ITouchClientChannel
195
+ $transport?: ITuffTransport
81
196
  }
82
197
  }
@@ -1,12 +1,5 @@
1
1
  import type { PluginClipboardHistoryResponse, PluginClipboardItem, PluginClipboardSearchOptions, PluginClipboardSearchResponse } from './types'
2
-
3
- function ensurePluginChannel() {
4
- const channel = (window as any)?.$channel
5
- if (!channel) {
6
- throw new Error('[Plugin SDK] Clipboard channel requires plugin renderer context with $channel available.')
7
- }
8
- return channel
9
- }
2
+ import { useChannel } from './channel'
10
3
 
11
4
  function normalizeItem(item: PluginClipboardItem | null): PluginClipboardItem | null {
12
5
  if (!item)
@@ -63,6 +56,11 @@ export interface ClipboardImageResult {
63
56
  dataUrl: string
64
57
  width: number
65
58
  height: number
59
+ /**
60
+ * Original image as a local streamable URL (Electron only).
61
+ * Returned when calling `readImage({ preview: false })`.
62
+ */
63
+ tfileUrl?: string
66
64
  }
67
65
 
68
66
  export interface ClipboardCopyAndPasteOptions {
@@ -117,7 +115,7 @@ export function useClipboardHistory() {
117
115
  * ```
118
116
  */
119
117
  export function useClipboard() {
120
- const channel = ensurePluginChannel()
118
+ const channel = useChannel('[Plugin SDK] Clipboard channel requires plugin renderer context with $channel available.')
121
119
 
122
120
  const history = {
123
121
  /**
@@ -214,7 +212,8 @@ export function useClipboard() {
214
212
  * ```
215
213
  */
216
214
  onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
217
- return channel.regChannel('core-box:clipboard-change', ({ data }: { data: unknown }) => {
215
+ return channel.regChannel('core-box:clipboard-change', (event) => {
216
+ const data = event?.data
218
217
  const item = (data && typeof data === 'object' && 'item' in data ? (data as { item: PluginClipboardItem }).item : data) as PluginClipboardItem
219
218
  callback(normalizeItem(item) ?? item)
220
219
  })
@@ -264,8 +263,23 @@ export function useClipboard() {
264
263
  /**
265
264
  * Reads image from clipboard as data URL
266
265
  */
267
- async readImage(): Promise<ClipboardImageResult | null> {
268
- return await channel.send('clipboard:read-image')
266
+ async readImage(options?: { preview?: boolean }): Promise<ClipboardImageResult | null> {
267
+ return await channel.send('clipboard:read-image', { preview: options?.preview ?? true })
268
+ },
269
+
270
+ /**
271
+ * Resolves the original image URL for a clipboard history item (streamable via tfile://).
272
+ *
273
+ * @remarks
274
+ * - This avoids transferring large base64 payloads over IPC.
275
+ * - Returns null if the item is not an image or original asset is not available.
276
+ */
277
+ async getHistoryImageUrl(id: number): Promise<string | null> {
278
+ const res = await channel.send('clipboard:get-image-url', { id })
279
+ if (res && typeof res === 'object' && 'url' in res && typeof (res as any).url === 'string') {
280
+ return (res as any).url as string
281
+ }
282
+ return null
269
283
  },
270
284
 
271
285
  /**
@@ -0,0 +1,113 @@
1
+ import type { CloudSyncSDKOptions as ClientOptions } from '../../cloud-sync/cloud-sync-sdk'
2
+ import { CloudSyncSDK as CloudSyncClientSDK, CloudSyncError } from '../../cloud-sync/cloud-sync-sdk'
3
+ import type { HandshakeResponse, PullResponse, PushResponse, SyncItemInput } from '../../types/cloud-sync'
4
+ import { accountSDK } from '../../account'
5
+ import { ensureRendererChannel } from './channel'
6
+
7
+ export interface CloudSyncSDKOptions {
8
+ baseUrl?: string
9
+ serviceBaseUrl?: string
10
+ fetch?: typeof fetch
11
+ now?: () => number
12
+ syncTokenCache?: { token?: string, expiresAt?: string }
13
+ onSyncTokenUpdate?: (token: string, expiresAt: string) => void
14
+ onHandshake?: (response: HandshakeResponse) => void
15
+ onStepUpRequired?: () => string | null | Promise<string | null>
16
+ formDataFactory?: () => FormData
17
+ channelSend?: (event: string, data?: any) => Promise<any>
18
+ ignoreSyncPreferenceCheck?: boolean
19
+ }
20
+
21
+ let accountChannelBound = false
22
+
23
+ function bindAccountChannel(options?: CloudSyncSDKOptions) {
24
+ if (accountChannelBound)
25
+ return
26
+
27
+ if (options?.channelSend) {
28
+ accountSDK.setChannelSend(options.channelSend)
29
+ accountChannelBound = true
30
+ return
31
+ }
32
+
33
+ const channel = ensureRendererChannel('[CloudSyncSDK] Channel not available. Make sure this runs in plugin renderer context.')
34
+ accountSDK.setChannelSend(channel.send)
35
+ accountChannelBound = true
36
+ }
37
+
38
+ async function resolveAuthToken(options?: CloudSyncSDKOptions): Promise<string> {
39
+ bindAccountChannel(options)
40
+ if (!options?.ignoreSyncPreferenceCheck) {
41
+ const syncEnabled = await accountSDK.getSyncEnabled()
42
+ if (!syncEnabled) {
43
+ throw new CloudSyncError(
44
+ '[CloudSyncSDK] Sync is disabled by user preference.',
45
+ 403,
46
+ 'SYNC_DISABLED'
47
+ )
48
+ }
49
+ }
50
+ const token = await accountSDK.getAuthToken()
51
+ if (!token) {
52
+ throw new Error('[CloudSyncSDK] Auth token is not available. Ensure user is signed in and AccountSDK channel is configured.')
53
+ }
54
+ return token
55
+ }
56
+
57
+ async function resolveDeviceId(options?: CloudSyncSDKOptions): Promise<string> {
58
+ bindAccountChannel(options)
59
+ const deviceId = await accountSDK.getDeviceId()
60
+ if (!deviceId) {
61
+ throw new Error('[CloudSyncSDK] Device ID is not available. Ensure AccountSDK channel is configured.')
62
+ }
63
+ return deviceId
64
+ }
65
+
66
+ export class CloudSyncSDK extends CloudSyncClientSDK {
67
+ constructor(options: CloudSyncSDKOptions = {}) {
68
+ bindAccountChannel(options)
69
+ const clientOptions: ClientOptions = {
70
+ baseUrl: options.baseUrl ?? options.serviceBaseUrl,
71
+ fetch: options.fetch,
72
+ now: options.now,
73
+ syncTokenCache: options.syncTokenCache,
74
+ onSyncTokenUpdate: options.onSyncTokenUpdate,
75
+ onHandshake: options.onHandshake,
76
+ onStepUpRequired: options.onStepUpRequired,
77
+ formDataFactory: options.formDataFactory,
78
+ getAuthToken: () => resolveAuthToken(options),
79
+ getDeviceId: () => resolveDeviceId(options),
80
+ }
81
+ super(clientOptions)
82
+ }
83
+
84
+ async push(items: SyncItemInput[]): Promise<PushResponse> {
85
+ const response = await super.push(items)
86
+ await accountSDK.recordSyncActivity('push')
87
+ return response
88
+ }
89
+
90
+ async pull(params?: { cursor?: number, limit?: number }): Promise<PullResponse> {
91
+ const response = await super.pull(params)
92
+ await accountSDK.recordSyncActivity('pull')
93
+ return response
94
+ }
95
+ }
96
+
97
+ let cachedSDK: CloudSyncSDK | null = null
98
+
99
+ export function createCloudSyncSDK(options?: CloudSyncSDKOptions): CloudSyncSDK {
100
+ return new CloudSyncSDK(options)
101
+ }
102
+
103
+ export function useCloudSyncSDK(options?: CloudSyncSDKOptions): CloudSyncSDK {
104
+ if (!cachedSDK)
105
+ cachedSDK = new CloudSyncSDK(options)
106
+ return cachedSDK
107
+ }
108
+
109
+ export function resetCloudSyncSDK(): void {
110
+ cachedSDK = null
111
+ }
112
+
113
+ export { CloudSyncError }
@@ -5,7 +5,10 @@
5
5
  * 提供插件SDK的通用功能,包括通信、快捷键等
6
6
  */
7
7
 
8
- import { genChannel } from '../channel'
8
+ import { getLogger } from '../../common/logger'
9
+ import { useChannel } from './channel'
10
+
11
+ const sdkLog = getLogger('plugin-sdk')
9
12
 
10
13
  /**
11
14
  * Register a shortcut
@@ -13,16 +16,21 @@ import { genChannel } from '../channel'
13
16
  * @param func - The trigger function
14
17
  * @returns Whether the shortcut is registered successfully
15
18
  */
16
- export function regShortcut(key: string, func: Function): boolean {
17
- const channel = genChannel()
19
+ export async function regShortcut(key: string, func: () => void): Promise<boolean> {
20
+ const channel = useChannel('[Plugin SDK] Shortcut registration requires renderer channel.')
18
21
 
19
- const res = channel.sendSync('shortcon:reg', { key })
20
- if (res instanceof String)
22
+ const res = await channel.send('shortcon:reg', { key })
23
+ if (typeof res === 'string' || Object.prototype.toString.call(res) === '[object String]')
21
24
  throw new Error(String(res))
22
25
  if (res === false)
23
26
  return false
24
27
 
25
- channel.regChannel('shortcon:trigger', ({ data }) => key === data.key && func())
28
+ channel.regChannel('shortcon:trigger', ({ data }) => {
29
+ const payload = data as { key?: string, id?: string } | undefined
30
+ const triggerKey = payload?.key ?? payload?.id
31
+ if (triggerKey === key)
32
+ func()
33
+ })
26
34
 
27
35
  return true
28
36
  }
@@ -37,7 +45,7 @@ export async function communicateWithPlugin(
37
45
  key: string,
38
46
  info: any = {},
39
47
  ): Promise<any> {
40
- const channel = genChannel()
48
+ const channel = useChannel('[Plugin SDK] Communication requires renderer channel.')
41
49
 
42
50
  try {
43
51
  return await channel.send('index:communicate', {
@@ -46,7 +54,7 @@ export async function communicateWithPlugin(
46
54
  })
47
55
  }
48
56
  catch (error) {
49
- console.error(`[Plugin SDK] Failed to communicate`, error)
57
+ sdkLog.error('Failed to communicate', { error })
50
58
  throw error
51
59
  }
52
60
  }
@@ -58,13 +66,13 @@ export async function communicateWithPlugin(
58
66
  * @returns Promise<any> The message result
59
67
  */
60
68
  export async function sendMessage(message: string, data: any = {}): Promise<any> {
61
- const channel = genChannel()
69
+ const channel = useChannel('[Plugin SDK] Messaging requires renderer channel.')
62
70
 
63
71
  try {
64
72
  return await channel.send(`plugin:${message}`, data)
65
73
  }
66
74
  catch (error) {
67
- console.error(`[Plugin SDK] Failed to send message: ${message}`, error)
75
+ sdkLog.error(`Failed to send message: ${message}`, { error })
68
76
  throw error
69
77
  }
70
78
  }
@@ -75,7 +83,7 @@ export async function sendMessage(message: string, data: any = {}): Promise<any>
75
83
  * @returns The channel object for the plugin
76
84
  */
77
85
  export function getChannel() {
78
- return genChannel()
86
+ return useChannel('[Plugin SDK] Channel requires renderer context.')
79
87
  }
80
88
 
81
89
  export * from './window'