@talex-touch/utils 1.0.21 → 1.0.22
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/index.ts +1 -0
- package/plugin/install.ts +43 -0
- package/plugin/node/logger-manager.ts +27 -9
- package/plugin/providers/types.ts +2 -0
- package/plugin/sdk/channel.ts +48 -0
- package/plugin/sdk/clipboard.ts +56 -0
- package/plugin/sdk/hooks/bridge.ts +3 -0
- package/plugin/sdk/index.ts +3 -1
- package/plugin/sdk/types.ts +109 -0
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type PluginInstallTaskStage =
|
|
2
|
+
| 'queued'
|
|
3
|
+
| 'downloading'
|
|
4
|
+
| 'awaiting-confirmation'
|
|
5
|
+
| 'installing'
|
|
6
|
+
| 'completed'
|
|
7
|
+
| 'failed'
|
|
8
|
+
| 'cancelled'
|
|
9
|
+
|
|
10
|
+
export interface PluginInstallProgressEvent {
|
|
11
|
+
taskId: string
|
|
12
|
+
stage: PluginInstallTaskStage
|
|
13
|
+
progress?: number
|
|
14
|
+
message?: string
|
|
15
|
+
error?: string
|
|
16
|
+
/** 安装源标识,例如下载 URL。 */
|
|
17
|
+
source?: string
|
|
18
|
+
/** 官方插件标记。 */
|
|
19
|
+
official?: boolean
|
|
20
|
+
/** 插件唯一标识或名称(由客户端提供)。 */
|
|
21
|
+
pluginId?: string
|
|
22
|
+
pluginName?: string
|
|
23
|
+
/** 队列中的剩余任务数量(包含当前任务)。 */
|
|
24
|
+
remaining?: number
|
|
25
|
+
/** 当前任务在队列中的位置(0 表示正在处理)。 */
|
|
26
|
+
position?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PluginInstallConfirmRequest {
|
|
30
|
+
taskId: string
|
|
31
|
+
pluginName?: string
|
|
32
|
+
pluginId?: string
|
|
33
|
+
source?: string
|
|
34
|
+
official?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type PluginInstallConfirmDecision = 'accept' | 'reject'
|
|
38
|
+
|
|
39
|
+
export interface PluginInstallConfirmResponse {
|
|
40
|
+
taskId: string
|
|
41
|
+
decision: PluginInstallConfirmDecision
|
|
42
|
+
reason?: string
|
|
43
|
+
}
|
|
@@ -12,6 +12,7 @@ export class PluginLoggerManager {
|
|
|
12
12
|
private readonly pluginLogDir: string
|
|
13
13
|
private readonly sessionLogPath: string
|
|
14
14
|
private readonly pluginInfoPath: string
|
|
15
|
+
private readonly sessionStart: string
|
|
15
16
|
private buffer: LogItem[] = []
|
|
16
17
|
private flushInterval: NodeJS.Timeout
|
|
17
18
|
private onLogAppend?: (log: LogItem) => void
|
|
@@ -25,15 +26,15 @@ export class PluginLoggerManager {
|
|
|
25
26
|
constructor(baseDir: string, pluginInfo: ITouchPlugin, onLogAppend?: (log: LogItem) => void) {
|
|
26
27
|
this.pluginInfo = pluginInfo
|
|
27
28
|
this.onLogAppend = onLogAppend
|
|
28
|
-
|
|
29
|
+
this.sessionStart = new Date().toISOString()
|
|
30
|
+
const timestamp = this.sessionStart.replace(/[:.]/g, '-')
|
|
29
31
|
const sessionFolder = `${timestamp}_${pluginInfo.name.replace(/[^a-zA-Z0-9-_]/g, '_')}`
|
|
30
32
|
|
|
31
33
|
this.pluginLogDir = path.resolve(baseDir, 'logs', sessionFolder)
|
|
32
34
|
this.sessionLogPath = path.resolve(this.pluginLogDir, 'session.log')
|
|
33
35
|
this.pluginInfoPath = path.resolve(this.pluginLogDir, 'touch-plugin.info')
|
|
34
36
|
|
|
35
|
-
this.
|
|
36
|
-
this.createPluginInfoFile()
|
|
37
|
+
this.ensureLogEnvironment(true)
|
|
37
38
|
this.flushInterval = setInterval(() => this.flush(), 5000)
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -52,8 +53,17 @@ export class PluginLoggerManager {
|
|
|
52
53
|
flush(): void {
|
|
53
54
|
if (this.buffer.length === 0) return
|
|
54
55
|
const lines = this.buffer.map((item) => structuredStrictStringify(item)).join('\n') + '\n'
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
try {
|
|
57
|
+
this.ensureLogEnvironment()
|
|
58
|
+
fs.appendFileSync(this.sessionLogPath, lines)
|
|
59
|
+
this.buffer = []
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error
|
|
62
|
+
// Directory or file was removed; rebuild and retry once.
|
|
63
|
+
this.ensureLogEnvironment(true)
|
|
64
|
+
fs.appendFileSync(this.sessionLogPath, lines)
|
|
65
|
+
this.buffer = []
|
|
66
|
+
}
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
/**
|
|
@@ -83,12 +93,12 @@ export class PluginLoggerManager {
|
|
|
83
93
|
/**
|
|
84
94
|
* Creates the touch-plugin.info file with plugin information.
|
|
85
95
|
*/
|
|
86
|
-
private
|
|
96
|
+
private writePluginInfoFile(): void {
|
|
87
97
|
const pluginInfo = {
|
|
88
98
|
name: this.pluginInfo.name,
|
|
89
99
|
version: this.pluginInfo.version,
|
|
90
100
|
description: this.pluginInfo.desc,
|
|
91
|
-
sessionStart:
|
|
101
|
+
sessionStart: this.sessionStart,
|
|
92
102
|
icon: this.pluginInfo.icon,
|
|
93
103
|
platforms: this.pluginInfo.platforms,
|
|
94
104
|
status: this.pluginInfo.status,
|
|
@@ -103,11 +113,19 @@ export class PluginLoggerManager {
|
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
106
|
-
* Ensures the log directory
|
|
116
|
+
* Ensures the log directory and related files exist.
|
|
107
117
|
*/
|
|
108
|
-
private
|
|
118
|
+
private ensureLogEnvironment(forceInfo = false): void {
|
|
109
119
|
if (!fs.existsSync(this.pluginLogDir)) {
|
|
110
120
|
fs.mkdirSync(this.pluginLogDir, { recursive: true })
|
|
111
121
|
}
|
|
122
|
+
|
|
123
|
+
if (!fs.existsSync(this.sessionLogPath)) {
|
|
124
|
+
fs.writeFileSync(this.sessionLogPath, '')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (forceInfo || !fs.existsSync(this.pluginInfoPath)) {
|
|
128
|
+
this.writePluginInfoFile()
|
|
129
|
+
}
|
|
112
130
|
}
|
|
113
131
|
}
|
|
@@ -17,6 +17,8 @@ export interface PluginInstallRequest {
|
|
|
17
17
|
hintType?: PluginProviderType
|
|
18
18
|
/** 可选元数据,在调用链中透传。 */
|
|
19
19
|
metadata?: Record<string, unknown>
|
|
20
|
+
/** 客户端附加信息,仅用于 UI 状态同步。 */
|
|
21
|
+
clientMetadata?: Record<string, unknown>
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface PluginProviderContext {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ITouchClientChannel } from '@talex-touch/utils/channel';
|
|
2
|
+
import { genChannel } from '../channel';
|
|
3
|
+
import type { IPluginRendererChannel, PluginChannelHandler } from './types';
|
|
4
|
+
|
|
5
|
+
const ensureClientChannel = (): ITouchClientChannel => genChannel();
|
|
6
|
+
|
|
7
|
+
export function createPluginRendererChannel(): IPluginRendererChannel {
|
|
8
|
+
const client = ensureClientChannel();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
send(eventName, payload) {
|
|
12
|
+
return client.send(eventName, payload);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
sendSync(eventName, payload) {
|
|
16
|
+
return client.sendSync(eventName, payload);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
on(eventName, handler) {
|
|
20
|
+
return client.regChannel(eventName, handler);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
once(eventName, handler) {
|
|
24
|
+
let dispose: () => void = () => void 0;
|
|
25
|
+
const wrapped: PluginChannelHandler = (event) => {
|
|
26
|
+
dispose();
|
|
27
|
+
handler(event);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
dispose = client.regChannel(eventName, wrapped);
|
|
31
|
+
return dispose;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
get raw() {
|
|
35
|
+
return client;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let cachedRendererChannel: IPluginRendererChannel | null = null;
|
|
41
|
+
|
|
42
|
+
export function usePluginRendererChannel(): IPluginRendererChannel {
|
|
43
|
+
if (!cachedRendererChannel) {
|
|
44
|
+
cachedRendererChannel = createPluginRendererChannel();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return cachedRendererChannel;
|
|
48
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { PluginClipboardHistoryResponse, PluginClipboardItem } from './types'
|
|
2
|
+
|
|
3
|
+
const 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
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ClipboardHistoryOptions {
|
|
12
|
+
page?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClipboardFavoriteOptions {
|
|
16
|
+
id: number
|
|
17
|
+
isFavorite: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ClipboardDeleteOptions {
|
|
21
|
+
id: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useClipboardHistory() {
|
|
25
|
+
const channel = ensurePluginChannel()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
async getLatest(): Promise<PluginClipboardItem | null> {
|
|
29
|
+
return channel.send('clipboard:get-latest')
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async getHistory(options: ClipboardHistoryOptions = {}): Promise<PluginClipboardHistoryResponse> {
|
|
33
|
+
const { page = 1 } = options
|
|
34
|
+
return channel.send('clipboard:get-history', { page })
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async setFavorite(options: ClipboardFavoriteOptions): Promise<void> {
|
|
38
|
+
await channel.send('clipboard:set-favorite', options)
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async deleteItem(options: ClipboardDeleteOptions): Promise<void> {
|
|
42
|
+
await channel.send('clipboard:delete-item', options)
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async clearHistory(): Promise<void> {
|
|
46
|
+
await channel.send('clipboard:clear-history')
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
|
|
50
|
+
return channel.regChannel('core-box:clipboard-change', ({ data }) => {
|
|
51
|
+
const item = (data && 'item' in data ? data.item : data) as PluginClipboardItem
|
|
52
|
+
callback(item)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -10,6 +10,7 @@ export type BridgeHook<T = any> = (data: T) => void
|
|
|
10
10
|
|
|
11
11
|
const __hooks: Record<BridgeEvent, Array<BridgeHook>> = {
|
|
12
12
|
[BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE]: [],
|
|
13
|
+
[BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE]: []
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -66,3 +67,5 @@ export const createBridgeHook = <T>(type: BridgeEvent) => (hook: BridgeHook<T>)
|
|
|
66
67
|
* @param data The input change data (string).
|
|
67
68
|
*/
|
|
68
69
|
export const onCoreBoxInputChange = createBridgeHook<{ query: string }>(BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE)
|
|
70
|
+
|
|
71
|
+
export const onCoreBoxClipboardChange = createBridgeHook<{ item: any }>(BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE)
|
package/plugin/sdk/index.ts
CHANGED
package/plugin/sdk/types.ts
CHANGED
|
@@ -4,6 +4,109 @@
|
|
|
4
4
|
* @version 1.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { ITouchChannel, ITouchClientChannel, StandardChannelData } from '@talex-touch/utils/channel';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handler signature for plugin channel events.
|
|
11
|
+
*/
|
|
12
|
+
export type PluginChannelHandler = (event: StandardChannelData) => any;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bridge exposed to plugin backends for channel-based communication.
|
|
16
|
+
*/
|
|
17
|
+
export interface IPluginChannelBridge {
|
|
18
|
+
/**
|
|
19
|
+
* Sends a payload to the main renderer process.
|
|
20
|
+
* @param eventName - Channel event name.
|
|
21
|
+
* @param payload - Optional data payload.
|
|
22
|
+
*/
|
|
23
|
+
sendToMain<T = any>(eventName: string, payload?: any): Promise<T>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sends a payload to this plugin's renderer view.
|
|
27
|
+
* @param eventName - Channel event name.
|
|
28
|
+
* @param payload - Optional data payload.
|
|
29
|
+
*/
|
|
30
|
+
sendToRenderer<T = any>(eventName: string, payload?: any): Promise<T>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Registers a handler for main renderer messages.
|
|
34
|
+
* @param eventName - Channel event name to listen for.
|
|
35
|
+
* @param handler - Handler invoked with the raw channel event.
|
|
36
|
+
* @returns Unsubscribe function.
|
|
37
|
+
*/
|
|
38
|
+
onMain(eventName: string, handler: PluginChannelHandler): () => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Registers a handler for renderer-originated messages scoped to this plugin.
|
|
42
|
+
* @param eventName - Channel event name to listen for.
|
|
43
|
+
* @param handler - Handler invoked with the raw channel event.
|
|
44
|
+
* @returns Unsubscribe function.
|
|
45
|
+
*/
|
|
46
|
+
onRenderer(eventName: string, handler: PluginChannelHandler): () => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Access to the underlying channel implementation for advanced scenarios.
|
|
50
|
+
*/
|
|
51
|
+
readonly raw: ITouchChannel;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Renderer-side helper for plugin webviews to interact with the bridge channel.
|
|
56
|
+
*/
|
|
57
|
+
export interface IPluginRendererChannel {
|
|
58
|
+
/**
|
|
59
|
+
* Sends a message asynchronously and resolves with the reply payload.
|
|
60
|
+
*/
|
|
61
|
+
send<T = any>(eventName: string, payload?: any): Promise<T>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sends a message synchronously and returns the reply payload.
|
|
65
|
+
*/
|
|
66
|
+
sendSync<T = any>(eventName: string, payload?: any): T;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Registers a handler for renderer channel events.
|
|
70
|
+
* @returns Unsubscribe function.
|
|
71
|
+
*/
|
|
72
|
+
on(eventName: string, handler: PluginChannelHandler): () => void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Registers a one-off handler for a renderer channel event.
|
|
76
|
+
* @returns Unsubscribe function (no-op after invocation).
|
|
77
|
+
*/
|
|
78
|
+
once(eventName: string, handler: PluginChannelHandler): () => void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Provides access to the raw client channel.
|
|
82
|
+
*/
|
|
83
|
+
readonly raw: ITouchClientChannel;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clipboard history item shared with plugin renderers.
|
|
88
|
+
*/
|
|
89
|
+
export interface PluginClipboardItem {
|
|
90
|
+
id?: number
|
|
91
|
+
type: 'text' | 'image' | 'files'
|
|
92
|
+
content: string
|
|
93
|
+
thumbnail?: string | null
|
|
94
|
+
rawContent?: string | null
|
|
95
|
+
sourceApp?: string | null
|
|
96
|
+
timestamp?: string | number | Date | null
|
|
97
|
+
isFavorite?: boolean | null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clipboard history pagination response structure.
|
|
102
|
+
*/
|
|
103
|
+
export interface PluginClipboardHistoryResponse {
|
|
104
|
+
history: PluginClipboardItem[]
|
|
105
|
+
total: number
|
|
106
|
+
page: number
|
|
107
|
+
pageSize: number
|
|
108
|
+
}
|
|
109
|
+
|
|
7
110
|
/**
|
|
8
111
|
* Plugin utilities interface providing core functionality for plugin development
|
|
9
112
|
*
|
|
@@ -28,6 +131,12 @@ export interface IPluginUtils {
|
|
|
28
131
|
*/
|
|
29
132
|
clipboard: IClipboardManager;
|
|
30
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Channel bridge for communicating with renderer and main processes
|
|
136
|
+
* @see {@link IPluginChannelBridge}
|
|
137
|
+
*/
|
|
138
|
+
channel: IPluginChannelBridge;
|
|
139
|
+
|
|
31
140
|
/**
|
|
32
141
|
* Search result manager for handling search operations
|
|
33
142
|
* @see {@link ISearchManager}
|