@talex-touch/utils 1.0.22 → 1.0.24

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.
@@ -0,0 +1,494 @@
1
+ export type TimingMeta = Record<string, unknown>
2
+
3
+ export interface TimingRecord {
4
+ label: string
5
+ durationMs: number
6
+ startedAt: number
7
+ endedAt: number
8
+ iteration?: number
9
+ meta?: TimingMeta
10
+ error?: unknown
11
+ logLevel?: TimingLogLevel
12
+ }
13
+
14
+ export type TimingLogLevel = 'none' | 'info' | 'warn' | 'error'
15
+
16
+ export type TimingLogThresholdOverrides = Partial<Record<Exclude<TimingLogLevel, 'error'>, number>>
17
+
18
+ type ResolvedTimingLogThresholds = Record<Exclude<TimingLogLevel, 'error'>, number>
19
+
20
+ const DEFAULT_AUTO_LOG = true
21
+ const DEFAULT_STORE_HISTORY = true
22
+ const DEFAULT_HISTORY_LIMIT = 50
23
+
24
+ export const DEFAULT_TIMING_LOG_THRESHOLDS: Readonly<ResolvedTimingLogThresholds> = Object.freeze({
25
+ none: 16.7,
26
+ info: 200,
27
+ warn: 500
28
+ })
29
+
30
+ export const DEFAULT_TIMING_OPTIONS: Readonly<Required<Pick<TimingOptions, 'autoLog' | 'storeHistory'>> & {
31
+ logThresholds: Readonly<ResolvedTimingLogThresholds>
32
+ }> = Object.freeze({
33
+ autoLog: DEFAULT_AUTO_LOG,
34
+ storeHistory: DEFAULT_STORE_HISTORY,
35
+ logThresholds: DEFAULT_TIMING_LOG_THRESHOLDS
36
+ })
37
+
38
+ export const DEFAULT_TIMING_MANAGER_CONFIG: Readonly<
39
+ Required<Pick<TimingManagerConfig, 'autoLog' | 'storeHistory' | 'historyLimit'>> & {
40
+ logThresholds: Readonly<ResolvedTimingLogThresholds>
41
+ }
42
+ > = Object.freeze({
43
+ autoLog: DEFAULT_AUTO_LOG,
44
+ storeHistory: DEFAULT_STORE_HISTORY,
45
+ historyLimit: DEFAULT_HISTORY_LIMIT,
46
+ logThresholds: DEFAULT_TIMING_LOG_THRESHOLDS
47
+ })
48
+
49
+ export interface TimingStats {
50
+ label: string
51
+ count: number
52
+ totalMs: number
53
+ avgMs: number
54
+ maxMs: number
55
+ minMs: number
56
+ lastMs: number
57
+ lastStartedAt?: number
58
+ lastEndedAt?: number
59
+ errorCount: number
60
+ lastError?: unknown
61
+ lastLogLevel?: TimingLogLevel
62
+ }
63
+
64
+ export interface TimingSummary extends TimingStats {
65
+ history: TimingRecord[]
66
+ }
67
+
68
+ export interface TimingManagerConfig {
69
+ autoLog?: boolean
70
+ storeHistory?: boolean
71
+ historyLimit?: number
72
+ logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
73
+ formatter?: (entry: TimingRecord, stats: TimingStats) => string
74
+ logThresholds?: TimingLogThresholdOverrides
75
+ }
76
+
77
+ export interface TimingOptions {
78
+ autoLog?: boolean
79
+ storeHistory?: boolean
80
+ logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
81
+ formatter?: (entry: TimingRecord, stats: TimingStats) => string
82
+ historyLimit?: number
83
+ logThresholds?: TimingLogThresholdOverrides
84
+ }
85
+
86
+ type ResolvedTimingOptions = {
87
+ autoLog: boolean
88
+ storeHistory: boolean
89
+ historyLimit: number
90
+ logThresholds: ResolvedTimingLogThresholds
91
+ formatter: (entry: TimingRecord, stats: TimingStats) => string
92
+ logger: (message: string, entry: TimingRecord, stats: TimingStats) => void
93
+ }
94
+
95
+ export class TimingManager {
96
+ private readonly stats = new Map<string, TimingStats>()
97
+ private readonly history = new Map<string, TimingRecord[]>()
98
+ private readonly moduleStats = new Map<string, TimingStats>()
99
+ private readonly config: TimingManagerConfig
100
+
101
+ constructor(config: TimingManagerConfig = {}) {
102
+ const mergedThresholds: ResolvedTimingLogThresholds = {
103
+ ...DEFAULT_TIMING_LOG_THRESHOLDS,
104
+ ...(config.logThresholds ?? {})
105
+ }
106
+
107
+ this.config = {
108
+ ...DEFAULT_TIMING_MANAGER_CONFIG,
109
+ ...config,
110
+ logThresholds: mergedThresholds
111
+ }
112
+ }
113
+
114
+ createTiming(label: string, options: TimingOptions = {}): TimingScope {
115
+ return new TimingScope(this, label, options)
116
+ }
117
+
118
+ record(label: string, record: TimingRecord, options: TimingOptions = {}): void {
119
+ const resolved = this.resolveOptions(options)
120
+ const logLevel = determineLogLevel(record.durationMs, resolved.logThresholds)
121
+ const recordWithLevel: TimingRecord = { ...record, logLevel }
122
+
123
+ const stats = this.updateStats(this.stats, label, recordWithLevel)
124
+ const moduleKey = this.extractModuleKey(label)
125
+ this.updateStats(this.moduleStats, moduleKey, { ...recordWithLevel, label: moduleKey })
126
+
127
+ if (resolved.storeHistory) {
128
+ const limit = resolved.historyLimit
129
+ const list = this.history.get(label) ?? []
130
+ list.push(recordWithLevel)
131
+ if (list.length > limit) {
132
+ list.splice(0, list.length - limit)
133
+ }
134
+ this.history.set(label, list)
135
+ }
136
+
137
+ if (resolved.autoLog && logLevel !== 'none') {
138
+ const message = resolved.formatter(recordWithLevel, stats)
139
+ resolved.logger(message, recordWithLevel, stats)
140
+ }
141
+ }
142
+
143
+ getStats(label: string): TimingStats | undefined {
144
+ const stats = this.stats.get(label)
145
+ if (!stats) return undefined
146
+ return { ...stats }
147
+ }
148
+
149
+ getAllStats(): TimingStats[] {
150
+ return Array.from(this.stats.values()).map((s) => ({ ...s }))
151
+ }
152
+
153
+ getHistory(label: string): TimingRecord[] {
154
+ return [...(this.history.get(label) ?? [])]
155
+ }
156
+
157
+ getModuleStats(moduleKey?: string): TimingStats[] | TimingStats | undefined {
158
+ if (moduleKey) {
159
+ const stats = this.moduleStats.get(moduleKey)
160
+ return stats ? { ...stats } : undefined
161
+ }
162
+ return Array.from(this.moduleStats.values()).map((s) => ({ ...s }))
163
+ }
164
+
165
+ reset(label?: string): void {
166
+ if (!label) {
167
+ this.stats.clear()
168
+ this.history.clear()
169
+ this.moduleStats.clear()
170
+ return
171
+ }
172
+ this.stats.delete(label)
173
+ this.history.delete(label)
174
+ }
175
+
176
+ private updateStats(target: Map<string, TimingStats>, label: string, record: TimingRecord): TimingStats {
177
+ const { durationMs, error } = record
178
+ const next = target.get(label) ?? {
179
+ label,
180
+ count: 0,
181
+ totalMs: 0,
182
+ avgMs: 0,
183
+ maxMs: Number.NEGATIVE_INFINITY,
184
+ minMs: Number.POSITIVE_INFINITY,
185
+ lastMs: 0,
186
+ errorCount: 0
187
+ }
188
+
189
+ next.count += 1
190
+ next.totalMs += durationMs
191
+ next.avgMs = next.totalMs / next.count
192
+ next.maxMs = Math.max(next.maxMs, durationMs)
193
+ next.minMs = Math.min(next.minMs, durationMs)
194
+ next.lastMs = durationMs
195
+ next.lastStartedAt = record.startedAt
196
+ next.lastEndedAt = record.endedAt
197
+ next.lastLogLevel = record.logLevel
198
+
199
+ if (error) {
200
+ next.errorCount += 1
201
+ next.lastError = error
202
+ }
203
+
204
+ target.set(label, next)
205
+ return next
206
+ }
207
+
208
+ private extractModuleKey(label: string): string {
209
+ const [moduleKey] = label.split(':')
210
+ return moduleKey || label
211
+ }
212
+
213
+ private resolveOptions(options: TimingOptions = {}): ResolvedTimingOptions {
214
+ const logThresholds: ResolvedTimingLogThresholds = {
215
+ ...DEFAULT_TIMING_LOG_THRESHOLDS,
216
+ ...(this.config.logThresholds ?? {}),
217
+ ...(options.logThresholds ?? {})
218
+ }
219
+
220
+ return {
221
+ autoLog: options.autoLog ?? this.config.autoLog ?? DEFAULT_AUTO_LOG,
222
+ storeHistory: options.storeHistory ?? this.config.storeHistory ?? DEFAULT_STORE_HISTORY,
223
+ historyLimit: options.historyLimit ?? this.config.historyLimit ?? DEFAULT_HISTORY_LIMIT,
224
+ logThresholds,
225
+ formatter: options.formatter ?? this.config.formatter ?? defaultFormatter,
226
+ logger: options.logger ?? this.config.logger ?? defaultLogger
227
+ }
228
+ }
229
+ }
230
+
231
+ export class TimingScope {
232
+ constructor(
233
+ private readonly manager: TimingManager,
234
+ private readonly label: string,
235
+ private readonly options: TimingOptions
236
+ ) {}
237
+
238
+ async cost<T>(fn: () => Promise<T> | T, meta: TimingMeta = {}, overrides: TimingOptions = {}): Promise<T> {
239
+ const startedAt = now()
240
+ try {
241
+ const result = await fn()
242
+ this.finish(startedAt, meta, undefined, overrides)
243
+ return result
244
+ } catch (error) {
245
+ this.finish(startedAt, meta, error, overrides)
246
+ throw error
247
+ }
248
+ }
249
+
250
+ async count<T>(iterations: number, fn: (iteration: number) => Promise<T> | T, meta: TimingMeta | ((iteration: number) => TimingMeta) = {}, overrides: TimingOptions = {}): Promise<T[]> {
251
+ const results: T[] = []
252
+ for (let index = 0; index < iterations; index++) {
253
+ const iterationMeta = typeof meta === 'function' ? meta(index) : meta
254
+ const startedAt = now()
255
+ try {
256
+ const value = await fn(index)
257
+ this.finish(startedAt, { ...iterationMeta, iteration: index }, undefined, overrides)
258
+ results.push(value)
259
+ } catch (error) {
260
+ this.finish(startedAt, { ...iterationMeta, iteration: index }, error, overrides)
261
+ throw error
262
+ }
263
+ }
264
+ return results
265
+ }
266
+
267
+ mark(durationMs: number, meta: TimingMeta = {}, overrides: TimingOptions = {}): void {
268
+ const endedAt = now()
269
+ const startedAt = endedAt - durationMs
270
+ this.manager.record(
271
+ this.label,
272
+ {
273
+ label: this.label,
274
+ durationMs,
275
+ startedAt,
276
+ endedAt,
277
+ meta
278
+ },
279
+ { ...this.options, ...overrides }
280
+ )
281
+ }
282
+
283
+ getStats(): TimingStats | undefined {
284
+ return this.manager.getStats(this.label)
285
+ }
286
+
287
+ getHistory(): TimingRecord[] {
288
+ return this.manager.getHistory(this.label)
289
+ }
290
+
291
+ private finish(startedAt: number, meta: TimingMeta, error: unknown, overrides: TimingOptions): void {
292
+ const endedAt = now()
293
+ const record: TimingRecord = {
294
+ label: this.label,
295
+ durationMs: endedAt - startedAt,
296
+ startedAt,
297
+ endedAt,
298
+ meta,
299
+ error
300
+ }
301
+
302
+ this.manager.record(this.label, record, { ...this.options, ...overrides })
303
+ }
304
+ }
305
+
306
+ function defaultFormatter(record: TimingRecord, stats: TimingStats): string {
307
+ const duration = record.durationMs.toFixed(2)
308
+ const levelTag =
309
+ record.logLevel && record.logLevel !== 'info' ? ` [${record.logLevel.toUpperCase()}]` : ''
310
+ return `⏱ [${record.label}] ${duration} ms${levelTag} (avg: ${stats.avgMs.toFixed(
311
+ 2
312
+ )} ms, max: ${stats.maxMs.toFixed(2)} ms, count: ${stats.count})`
313
+ }
314
+
315
+ function defaultLogger(message: string, entry: TimingRecord, _stats: TimingStats): void {
316
+ if (entry.logLevel === 'none') {
317
+ return
318
+ }
319
+
320
+ if (entry.logLevel === 'warn') {
321
+ console.warn(message)
322
+ return
323
+ }
324
+
325
+ if (entry.logLevel === 'error') {
326
+ console.error(message)
327
+ return
328
+ }
329
+
330
+ if (typeof console.info === 'function') {
331
+ console.info(message)
332
+ return
333
+ }
334
+
335
+ console.log(message)
336
+ }
337
+
338
+ function determineLogLevel(durationMs: number, thresholds: ResolvedTimingLogThresholds): TimingLogLevel {
339
+ if (durationMs <= thresholds.none) {
340
+ return 'none'
341
+ }
342
+ if (durationMs <= thresholds.info) {
343
+ return 'info'
344
+ }
345
+ if (durationMs <= thresholds.warn) {
346
+ return 'warn'
347
+ }
348
+ return 'error'
349
+ }
350
+
351
+ const now = (() => {
352
+ if (typeof globalThis !== 'undefined') {
353
+ const perf = (globalThis as typeof globalThis & { performance?: { now?: () => number } }).performance
354
+ if (perf?.now) {
355
+ return () => perf.now()
356
+ }
357
+ }
358
+ return () => Date.now()
359
+ })()
360
+
361
+ const timingManagerInstance = new TimingManager()
362
+
363
+ export const timingManager = timingManagerInstance
364
+
365
+ export function startTiming(): number {
366
+ return now()
367
+ }
368
+
369
+ export function completeTiming(
370
+ label: string,
371
+ startedAt: number,
372
+ meta: TimingMeta = {},
373
+ options: TimingOptions = {}
374
+ ): number {
375
+ const endedAt = now()
376
+ const durationMs = endedAt - startedAt
377
+ timingManagerInstance.record(
378
+ label,
379
+ {
380
+ label,
381
+ durationMs,
382
+ startedAt,
383
+ endedAt,
384
+ meta
385
+ },
386
+ options
387
+ )
388
+ return durationMs
389
+ }
390
+
391
+ export function logTiming(
392
+ label: string,
393
+ durationMs: number,
394
+ meta: TimingMeta = {},
395
+ options: TimingOptions = {}
396
+ ): void {
397
+ const endedAt = now()
398
+ const startedAt = endedAt - durationMs
399
+ timingManagerInstance.record(
400
+ label,
401
+ {
402
+ label,
403
+ durationMs,
404
+ startedAt,
405
+ endedAt,
406
+ meta
407
+ },
408
+ options
409
+ )
410
+ }
411
+
412
+ export interface TimingLoggerToken {
413
+ label: string
414
+ startedAt: number
415
+ meta: TimingMeta
416
+ options: TimingOptions
417
+ }
418
+
419
+ export const timingLogger = {
420
+ start(label: string, meta: TimingMeta = {}, options: TimingOptions = {}): TimingLoggerToken {
421
+ return {
422
+ label,
423
+ startedAt: startTiming(),
424
+ meta,
425
+ options
426
+ }
427
+ },
428
+
429
+ finish(
430
+ token: TimingLoggerToken,
431
+ meta: TimingMeta = {},
432
+ overrides: TimingOptions = {}
433
+ ): number {
434
+ const mergedMeta = { ...token.meta, ...meta }
435
+ const mergedOptions = mergeTimingOptions(token.options, overrides)
436
+ return completeTiming(token.label, token.startedAt, mergedMeta, mergedOptions)
437
+ },
438
+
439
+ print(
440
+ label: string,
441
+ durationMs: number,
442
+ meta: TimingMeta = {},
443
+ options: TimingOptions = {}
444
+ ): number {
445
+ logTiming(label, durationMs, meta, options)
446
+ return durationMs
447
+ },
448
+
449
+ async cost<T>(
450
+ label: string,
451
+ fn: () => Promise<T> | T,
452
+ meta: TimingMeta = {},
453
+ options: TimingOptions = {}
454
+ ): Promise<T> {
455
+ const scope = createTiming(label, options)
456
+ return scope.cost(fn, meta)
457
+ },
458
+
459
+ mark(
460
+ label: string,
461
+ durationMs: number,
462
+ meta: TimingMeta = {},
463
+ options: TimingOptions = {}
464
+ ): number {
465
+ logTiming(label, durationMs, meta, options)
466
+ return durationMs
467
+ }
468
+ }
469
+
470
+ export function createTiming(label: string, options: TimingOptions = {}): TimingScope {
471
+ return timingManagerInstance.createTiming(label, options)
472
+ }
473
+
474
+ function mergeTimingOptions(
475
+ base: TimingOptions = {},
476
+ override: TimingOptions = {}
477
+ ): TimingOptions {
478
+ if (!base && !override) return {}
479
+ if (!override || Object.keys(override).length === 0) {
480
+ return { ...base }
481
+ }
482
+ if (!base || Object.keys(base).length === 0) {
483
+ return { ...override }
484
+ }
485
+
486
+ return {
487
+ ...base,
488
+ ...override,
489
+ logThresholds: {
490
+ ...(base.logThresholds ?? {}),
491
+ ...(override.logThresholds ?? {})
492
+ }
493
+ }
494
+ }
@@ -863,12 +863,29 @@ export interface TuffMeta {
863
863
  * @description The ID of the feature.
864
864
  */
865
865
  featureId?: string
866
+
867
+ /**
868
+ * For plugin feature items, this holds the interaction configuration.
869
+ * @description Defines how the feature should be rendered (widget, webcontent, or index).
870
+ */
871
+ interaction?: {
872
+ type: 'webcontent' | 'widget' | 'index'
873
+ path?: string
874
+ }
875
+
866
876
  /**
867
877
  * Defines the default action to be taken when the item is executed (e.g., by pressing Enter).
868
878
  * This is used to distinguish simple actions (like 'copy') from feature activations.
869
879
  * @description The default action type.
870
880
  */
871
881
  defaultAction?: string;
882
+
883
+ /**
884
+ * Priority of the item for sorting in search results
885
+ * Higher numbers have higher priority (displayed first)
886
+ * @description Priority value for search result ordering
887
+ */
888
+ priority?: number;
872
889
  /**
873
890
  * 原始数据
874
891
  * @description 项目的原始数据对象,用于特殊处理
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "module": "./index.ts",
6
6
  "license": "MPL-2.0",
7
7
  "private": false,
8
- "version": "1.0.22",
8
+ "version": "1.0.24",
9
9
  "scripts": {
10
10
  "publish": "npm publish --access public"
11
11
  },
package/plugin/index.ts CHANGED
@@ -98,6 +98,12 @@ export interface IPluginFeature {
98
98
  platform: IPlatform
99
99
  commands: IFeatureCommand[]
100
100
  interaction?: IFeatureInteraction
101
+ /**
102
+ * Priority of the feature for sorting in search results
103
+ * Higher numbers have higher priority (displayed first)
104
+ * Default is 0
105
+ */
106
+ priority?: number
101
107
  }
102
108
 
103
109
  export type IFeatureInteraction = {
@@ -113,6 +119,11 @@ export type IFeatureInteraction = {
113
119
  * These hooks are triggered based on real user interaction and system events.
114
120
  */
115
121
  export interface IFeatureLifeCycle {
122
+ /**
123
+ * onInit is called when the feature is initialized.
124
+ * Can be used to prepare data or UI specific to this session.
125
+ */
126
+ onInit?(): void
116
127
  /**
117
128
  * Called when a feature is actively launched from the launcher.
118
129
  * Can be used to prepare data or UI specific to this session.
@@ -156,8 +167,16 @@ export interface IFeatureLifeCycle {
156
167
  * This is used for handling actions on the items themselves,
157
168
  * rather than triggering a new feature.
158
169
  * @param item The TuffItem that was executed.
170
+ * @returns Object indicating whether to activate the feature and any activation data
159
171
  */
160
- onItemAction?(item: any): Promise<any>
172
+ onItemAction?(item: any): Promise<{
173
+ /** Whether the action executed an external operation (e.g., opened browser) */
174
+ externalAction?: boolean
175
+ /** Whether the feature should be activated after this action */
176
+ shouldActivate?: boolean
177
+ /** Activation data if shouldActivate is true */
178
+ activation?: any
179
+ } | void>
161
180
  }
162
181
 
163
182
  /**
@@ -206,8 +225,16 @@ export interface ITargetFeatureLifeCycle {
206
225
  * This is used for handling actions on the items themselves,
207
226
  * rather than triggering a new feature.
208
227
  * @param item The TuffItem that was executed.
228
+ * @returns Object indicating whether to activate the feature and any activation data
209
229
  */
210
- onItemAction?(item: any): Promise<any>
230
+ onItemAction?(item: any): Promise<{
231
+ /** Whether the action executed an external operation (e.g., opened browser) */
232
+ externalAction?: boolean
233
+ /** Whether the feature should be activated after this action */
234
+ shouldActivate?: boolean
235
+ /** Activation data if shouldActivate is true */
236
+ activation?: any
237
+ } | void>
211
238
  }
212
239
 
213
240
  /**
@@ -8,6 +8,19 @@ const ensurePluginChannel = () => {
8
8
  return channel
9
9
  }
10
10
 
11
+ const normalizeItem = (item: PluginClipboardItem | null): PluginClipboardItem | null => {
12
+ if (!item) return item
13
+ if (!item.meta && typeof item.metadata === 'string') {
14
+ try {
15
+ const parsed = JSON.parse(item.metadata)
16
+ return { ...item, meta: parsed }
17
+ } catch {
18
+ return { ...item, meta: null }
19
+ }
20
+ }
21
+ return item
22
+ }
23
+
11
24
  export interface ClipboardHistoryOptions {
12
25
  page?: number
13
26
  }
@@ -21,17 +34,35 @@ export interface ClipboardDeleteOptions {
21
34
  id: number
22
35
  }
23
36
 
37
+ export interface ClipboardApplyOptions {
38
+ item?: PluginClipboardItem
39
+ text?: string
40
+ html?: string | null
41
+ files?: string[]
42
+ delayMs?: number
43
+ hideCoreBox?: boolean
44
+ type?: PluginClipboardItem['type']
45
+ }
46
+
24
47
  export function useClipboardHistory() {
25
48
  const channel = ensurePluginChannel()
26
49
 
27
50
  return {
28
51
  async getLatest(): Promise<PluginClipboardItem | null> {
29
- return channel.send('clipboard:get-latest')
52
+ const result = await channel.send('clipboard:get-latest')
53
+ return normalizeItem(result)
30
54
  },
31
55
 
32
56
  async getHistory(options: ClipboardHistoryOptions = {}): Promise<PluginClipboardHistoryResponse> {
33
57
  const { page = 1 } = options
34
- return channel.send('clipboard:get-history', { page })
58
+ const response = await channel.send('clipboard:get-history', { page })
59
+ const history = Array.isArray(response?.history)
60
+ ? response.history.map((item: PluginClipboardItem) => normalizeItem(item) ?? item)
61
+ : []
62
+ return {
63
+ ...response,
64
+ history
65
+ }
35
66
  },
36
67
 
37
68
  async setFavorite(options: ClipboardFavoriteOptions): Promise<void> {
@@ -49,8 +80,20 @@ export function useClipboardHistory() {
49
80
  onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
50
81
  return channel.regChannel('core-box:clipboard-change', ({ data }) => {
51
82
  const item = (data && 'item' in data ? data.item : data) as PluginClipboardItem
52
- callback(item)
83
+ callback(normalizeItem(item) ?? item)
53
84
  })
85
+ },
86
+
87
+ /**
88
+ * Writes the provided clipboard payload to the system clipboard and issues a paste command
89
+ * to the foreground application.
90
+ */
91
+ async applyToActiveApp(options: ClipboardApplyOptions = {}): Promise<boolean> {
92
+ const response = await channel.send('clipboard:apply-to-active-app', options)
93
+ if (typeof response === 'object' && response) {
94
+ return Boolean((response as any).success)
95
+ }
96
+ return true
54
97
  }
55
98
  }
56
99
  }
@@ -0,0 +1,27 @@
1
+ import type { IPluginRendererChannel } from './types'
2
+
3
+ const ensurePluginContext = (): { channel: IPluginRendererChannel; pluginName: string } => {
4
+ const plugin = (window as any)?.$plugin
5
+ if (!plugin?.name) {
6
+ throw new Error('[TouchSDK] Unable to resolve plugin name inside renderer context.')
7
+ }
8
+
9
+ const channel = (window as any)?.$channel as IPluginRendererChannel | undefined
10
+ if (!channel) {
11
+ throw new Error('[TouchSDK] Channel bridge is not available for the current plugin renderer.')
12
+ }
13
+
14
+ return {
15
+ channel,
16
+ pluginName: plugin.name as string
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Clears all CoreBox items associated with the current plugin.
22
+ */
23
+ export async function clearCoreBoxItems(): Promise<void> {
24
+ const { channel, pluginName } = ensurePluginContext()
25
+ await channel.send('core-box:clear-items', { pluginName })
26
+ }
27
+