@yushaw/sanqian-sdk 0.2.10 → 0.2.11
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/dist/index.browser.d.mts +430 -0
- package/dist/index.browser.mjs +1058 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +326 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -255
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -4
package/dist/index.mjs
CHANGED
|
@@ -1,273 +1,293 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
3
10
|
|
|
4
11
|
// src/discovery.ts
|
|
12
|
+
var discovery_exports = {};
|
|
13
|
+
__export(discovery_exports, {
|
|
14
|
+
DiscoveryManager: () => DiscoveryManager
|
|
15
|
+
});
|
|
5
16
|
import { existsSync, readFileSync, watch } from "fs";
|
|
6
17
|
import { homedir, platform } from "os";
|
|
7
18
|
import { join } from "path";
|
|
8
19
|
import { spawn, execSync } from "child_process";
|
|
9
|
-
var DiscoveryManager
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* Returns null if:
|
|
29
|
-
* - File doesn't exist
|
|
30
|
-
* - File is invalid JSON
|
|
31
|
-
* - Process is not running
|
|
32
|
-
*/
|
|
33
|
-
read() {
|
|
34
|
-
const filePath = this.getConnectionFilePath();
|
|
35
|
-
if (!existsSync(filePath)) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
const content = readFileSync(filePath, "utf-8");
|
|
40
|
-
const info = JSON.parse(content);
|
|
41
|
-
if (!info.port || !info.token || !info.pid) {
|
|
42
|
-
return null;
|
|
20
|
+
var DiscoveryManager;
|
|
21
|
+
var init_discovery = __esm({
|
|
22
|
+
"src/discovery.ts"() {
|
|
23
|
+
"use strict";
|
|
24
|
+
DiscoveryManager = class {
|
|
25
|
+
connectionInfo = null;
|
|
26
|
+
watcher = null;
|
|
27
|
+
onChange = null;
|
|
28
|
+
pollInterval = null;
|
|
29
|
+
/**
|
|
30
|
+
* Cached executable path from last successful connection.json read.
|
|
31
|
+
* Persists even when Sanqian is not running, for use in auto-launch.
|
|
32
|
+
*/
|
|
33
|
+
cachedExecutable = null;
|
|
34
|
+
/**
|
|
35
|
+
* Get the path to connection.json
|
|
36
|
+
*/
|
|
37
|
+
getConnectionFilePath() {
|
|
38
|
+
return join(homedir(), ".sanqian", "runtime", "connection.json");
|
|
43
39
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Read and validate connection info
|
|
42
|
+
*
|
|
43
|
+
* Returns null if:
|
|
44
|
+
* - File doesn't exist
|
|
45
|
+
* - File is invalid JSON
|
|
46
|
+
* - Process is not running
|
|
47
|
+
*/
|
|
48
|
+
read() {
|
|
49
|
+
const filePath = this.getConnectionFilePath();
|
|
50
|
+
if (!existsSync(filePath)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const content = readFileSync(filePath, "utf-8");
|
|
55
|
+
const info = JSON.parse(content);
|
|
56
|
+
if (!info.port || !info.token || !info.pid) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (info.executable) {
|
|
60
|
+
this.cachedExecutable = info.executable;
|
|
61
|
+
}
|
|
62
|
+
if (!this.isProcessRunning(info.pid)) {
|
|
63
|
+
return null;
|
|
68
64
|
}
|
|
69
|
-
this.
|
|
65
|
+
this.connectionInfo = info;
|
|
66
|
+
return info;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
70
69
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
this.onChange?.(newInfo);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start watching for connection file changes
|
|
73
|
+
*/
|
|
74
|
+
startWatching(onChange) {
|
|
75
|
+
this.onChange = onChange;
|
|
76
|
+
const dir = join(homedir(), ".sanqian", "runtime");
|
|
77
|
+
if (!existsSync(dir)) {
|
|
78
|
+
this.pollInterval = setInterval(() => {
|
|
79
|
+
if (existsSync(dir)) {
|
|
80
|
+
if (this.pollInterval) {
|
|
81
|
+
clearInterval(this.pollInterval);
|
|
82
|
+
this.pollInterval = null;
|
|
83
|
+
}
|
|
84
|
+
this.setupWatcher(dir);
|
|
87
85
|
}
|
|
88
|
-
},
|
|
86
|
+
}, 2e3);
|
|
87
|
+
return;
|
|
89
88
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Stop watching
|
|
97
|
-
*/
|
|
98
|
-
stopWatching() {
|
|
99
|
-
if (this.pollInterval) {
|
|
100
|
-
clearInterval(this.pollInterval);
|
|
101
|
-
this.pollInterval = null;
|
|
102
|
-
}
|
|
103
|
-
this.watcher?.close();
|
|
104
|
-
this.watcher = null;
|
|
105
|
-
this.onChange = null;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Check if a process is running by PID
|
|
109
|
-
*
|
|
110
|
-
* Cross-platform implementation:
|
|
111
|
-
* - macOS/Linux: process.kill(pid, 0) works correctly
|
|
112
|
-
* - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
|
|
113
|
-
* so we use tasklist command for reliable checking
|
|
114
|
-
*/
|
|
115
|
-
isProcessRunning(pid) {
|
|
116
|
-
try {
|
|
117
|
-
if (platform() === "win32") {
|
|
89
|
+
this.setupWatcher(dir);
|
|
90
|
+
}
|
|
91
|
+
setupWatcher(dir) {
|
|
118
92
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
93
|
+
this.watcher = watch(dir, (event, filename) => {
|
|
94
|
+
if (filename === "connection.json") {
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
const newInfo = this.read();
|
|
97
|
+
const oldInfoStr = JSON.stringify(this.connectionInfo);
|
|
98
|
+
const newInfoStr = JSON.stringify(newInfo);
|
|
99
|
+
if (oldInfoStr !== newInfoStr) {
|
|
100
|
+
this.connectionInfo = newInfo;
|
|
101
|
+
this.onChange?.(newInfo);
|
|
102
|
+
}
|
|
103
|
+
}, 100);
|
|
104
|
+
}
|
|
122
105
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.error("Failed to watch connection file:", e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stop watching
|
|
112
|
+
*/
|
|
113
|
+
stopWatching() {
|
|
114
|
+
if (this.pollInterval) {
|
|
115
|
+
clearInterval(this.pollInterval);
|
|
116
|
+
this.pollInterval = null;
|
|
117
|
+
}
|
|
118
|
+
this.watcher?.close();
|
|
119
|
+
this.watcher = null;
|
|
120
|
+
this.onChange = null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if a process is running by PID
|
|
124
|
+
*
|
|
125
|
+
* Cross-platform implementation:
|
|
126
|
+
* - macOS/Linux: process.kill(pid, 0) works correctly
|
|
127
|
+
* - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
|
|
128
|
+
* so we use tasklist command for reliable checking
|
|
129
|
+
*/
|
|
130
|
+
isProcessRunning(pid) {
|
|
131
|
+
try {
|
|
132
|
+
if (platform() === "win32") {
|
|
133
|
+
try {
|
|
134
|
+
const result = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
135
|
+
encoding: "utf-8",
|
|
136
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
137
|
+
});
|
|
138
|
+
const trimmed = result.trim();
|
|
139
|
+
if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
process.kill(pid, 0);
|
|
148
|
+
return true;
|
|
126
149
|
}
|
|
127
|
-
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
128
150
|
} catch {
|
|
129
151
|
return false;
|
|
130
152
|
}
|
|
131
|
-
} else {
|
|
132
|
-
process.kill(pid, 0);
|
|
133
|
-
return true;
|
|
134
153
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
* Get cached connection info (may be stale)
|
|
141
|
-
*/
|
|
142
|
-
getCached() {
|
|
143
|
-
return this.connectionInfo;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Build WebSocket URL from connection info
|
|
147
|
-
*/
|
|
148
|
-
buildWebSocketUrl(info) {
|
|
149
|
-
const wsPath = info.ws_path || "/ws/apps";
|
|
150
|
-
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Build HTTP base URL from connection info
|
|
154
|
-
*/
|
|
155
|
-
buildHttpUrl(info) {
|
|
156
|
-
return `http://127.0.0.1:${info.port}`;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Find Sanqian executable path
|
|
160
|
-
* Searches in standard installation locations for each platform
|
|
161
|
-
*/
|
|
162
|
-
findSanqianPath(customPath) {
|
|
163
|
-
if (customPath) {
|
|
164
|
-
if (existsSync(customPath)) {
|
|
165
|
-
return customPath;
|
|
154
|
+
/**
|
|
155
|
+
* Get cached connection info (may be stale)
|
|
156
|
+
*/
|
|
157
|
+
getCached() {
|
|
158
|
+
return this.connectionInfo;
|
|
166
159
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
searchPaths.push(
|
|
174
|
-
// Production: installed app
|
|
175
|
-
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
176
|
-
join(homedir(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
177
|
-
// Development: electron-builder output
|
|
178
|
-
join(homedir(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
179
|
-
join(homedir(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
180
|
-
);
|
|
181
|
-
} else if (os === "win32") {
|
|
182
|
-
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
183
|
-
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
184
|
-
const localAppData = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
185
|
-
searchPaths.push(
|
|
186
|
-
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
187
|
-
join(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
188
|
-
// Legacy/alternative paths with uppercase
|
|
189
|
-
join(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
190
|
-
join(programFiles, "Sanqian", "Sanqian.exe"),
|
|
191
|
-
join(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
192
|
-
// Development: electron-builder output
|
|
193
|
-
join(homedir(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
194
|
-
);
|
|
195
|
-
} else {
|
|
196
|
-
searchPaths.push(
|
|
197
|
-
"/usr/bin/sanqian",
|
|
198
|
-
"/usr/local/bin/sanqian",
|
|
199
|
-
join(homedir(), ".local/bin/sanqian"),
|
|
200
|
-
"/opt/Sanqian/sanqian",
|
|
201
|
-
// Development
|
|
202
|
-
join(homedir(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
for (const path of searchPaths) {
|
|
206
|
-
if (existsSync(path)) {
|
|
207
|
-
return path;
|
|
160
|
+
/**
|
|
161
|
+
* Build WebSocket URL from connection info
|
|
162
|
+
*/
|
|
163
|
+
buildWebSocketUrl(info) {
|
|
164
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
165
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
208
166
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (customPath) {
|
|
224
|
-
sanqianPath = this.findSanqianPath(customPath);
|
|
225
|
-
} else if (this.cachedExecutable && existsSync(this.cachedExecutable)) {
|
|
226
|
-
sanqianPath = this.cachedExecutable;
|
|
227
|
-
console.log(`[SDK] Using cached executable: ${sanqianPath}`);
|
|
228
|
-
} else {
|
|
229
|
-
sanqianPath = this.findSanqianPath();
|
|
230
|
-
}
|
|
231
|
-
if (!sanqianPath) {
|
|
232
|
-
console.error("[SDK] Sanqian executable not found");
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
236
|
-
try {
|
|
237
|
-
const os = platform();
|
|
238
|
-
if (os === "darwin") {
|
|
239
|
-
const appPath = sanqianPath.replace(
|
|
240
|
-
"/Contents/MacOS/Sanqian",
|
|
241
|
-
""
|
|
242
|
-
);
|
|
243
|
-
spawn("open", ["-g", "-a", appPath, "--args", "--hidden"], {
|
|
244
|
-
detached: true,
|
|
245
|
-
stdio: "ignore"
|
|
246
|
-
}).unref();
|
|
247
|
-
} else {
|
|
248
|
-
spawn(sanqianPath, ["--hidden"], {
|
|
249
|
-
detached: true,
|
|
250
|
-
stdio: "ignore",
|
|
251
|
-
// On Windows, hide the console window
|
|
252
|
-
...os === "win32" && {
|
|
253
|
-
windowsHide: true,
|
|
254
|
-
shell: false
|
|
167
|
+
/**
|
|
168
|
+
* Build HTTP base URL from connection info
|
|
169
|
+
*/
|
|
170
|
+
buildHttpUrl(info) {
|
|
171
|
+
return `http://127.0.0.1:${info.port}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find Sanqian executable path
|
|
175
|
+
* Searches in standard installation locations for each platform
|
|
176
|
+
*/
|
|
177
|
+
findSanqianPath(customPath) {
|
|
178
|
+
if (customPath) {
|
|
179
|
+
if (existsSync(customPath)) {
|
|
180
|
+
return customPath;
|
|
255
181
|
}
|
|
256
|
-
|
|
182
|
+
console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const os = platform();
|
|
186
|
+
const searchPaths = [];
|
|
187
|
+
if (os === "darwin") {
|
|
188
|
+
searchPaths.push(
|
|
189
|
+
// Production: installed app
|
|
190
|
+
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
191
|
+
join(homedir(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
192
|
+
// Development: electron-builder output
|
|
193
|
+
join(homedir(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
194
|
+
join(homedir(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
195
|
+
);
|
|
196
|
+
} else if (os === "win32") {
|
|
197
|
+
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
198
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
199
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
200
|
+
searchPaths.push(
|
|
201
|
+
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
202
|
+
join(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
203
|
+
// Legacy/alternative paths with uppercase
|
|
204
|
+
join(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
205
|
+
join(programFiles, "Sanqian", "Sanqian.exe"),
|
|
206
|
+
join(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
207
|
+
// Development: electron-builder output
|
|
208
|
+
join(homedir(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
209
|
+
);
|
|
210
|
+
} else {
|
|
211
|
+
searchPaths.push(
|
|
212
|
+
"/usr/bin/sanqian",
|
|
213
|
+
"/usr/local/bin/sanqian",
|
|
214
|
+
join(homedir(), ".local/bin/sanqian"),
|
|
215
|
+
"/opt/Sanqian/sanqian",
|
|
216
|
+
// Development
|
|
217
|
+
join(homedir(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
for (const path of searchPaths) {
|
|
221
|
+
if (existsSync(path)) {
|
|
222
|
+
return path;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
257
226
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Launch Sanqian in hidden/tray mode
|
|
229
|
+
* Returns true if launch was initiated successfully
|
|
230
|
+
*
|
|
231
|
+
* Priority for finding Sanqian executable:
|
|
232
|
+
* 1. customPath parameter (if provided)
|
|
233
|
+
* 2. Cached executable from connection.json (most reliable)
|
|
234
|
+
* 3. Search in standard installation locations (fallback)
|
|
235
|
+
*/
|
|
236
|
+
launchSanqian(customPath) {
|
|
237
|
+
let sanqianPath = null;
|
|
238
|
+
if (customPath) {
|
|
239
|
+
sanqianPath = this.findSanqianPath(customPath);
|
|
240
|
+
} else if (this.cachedExecutable && existsSync(this.cachedExecutable)) {
|
|
241
|
+
sanqianPath = this.cachedExecutable;
|
|
242
|
+
console.log(`[SDK] Using cached executable: ${sanqianPath}`);
|
|
243
|
+
} else {
|
|
244
|
+
sanqianPath = this.findSanqianPath();
|
|
245
|
+
}
|
|
246
|
+
if (!sanqianPath) {
|
|
247
|
+
console.error("[SDK] Sanqian executable not found");
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
251
|
+
try {
|
|
252
|
+
const os = platform();
|
|
253
|
+
if (os === "darwin") {
|
|
254
|
+
const appPath = sanqianPath.replace(
|
|
255
|
+
"/Contents/MacOS/Sanqian",
|
|
256
|
+
""
|
|
257
|
+
);
|
|
258
|
+
spawn("open", ["-g", "-a", appPath, "--args", "--hidden"], {
|
|
259
|
+
detached: true,
|
|
260
|
+
stdio: "ignore"
|
|
261
|
+
}).unref();
|
|
262
|
+
} else {
|
|
263
|
+
spawn(sanqianPath, ["--hidden"], {
|
|
264
|
+
detached: true,
|
|
265
|
+
stdio: "ignore",
|
|
266
|
+
// On Windows, hide the console window
|
|
267
|
+
...os === "win32" && {
|
|
268
|
+
windowsHide: true,
|
|
269
|
+
shell: false
|
|
270
|
+
}
|
|
271
|
+
}).unref();
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.error("[SDK] Failed to launch Sanqian:", e);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if Sanqian is running
|
|
281
|
+
*/
|
|
282
|
+
isSanqianRunning() {
|
|
283
|
+
return this.read() !== null;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
269
286
|
}
|
|
270
|
-
};
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// src/client.ts
|
|
290
|
+
import WebSocket from "isomorphic-ws";
|
|
271
291
|
|
|
272
292
|
// src/errors.ts
|
|
273
293
|
var SANQIAN_WEBSITE = "https://sanqian.io";
|
|
@@ -438,7 +458,8 @@ function createSDKError(code, details) {
|
|
|
438
458
|
// src/client.ts
|
|
439
459
|
var SanqianSDK = class _SanqianSDK {
|
|
440
460
|
config;
|
|
441
|
-
|
|
461
|
+
// DiscoveryManager is only initialized in Node.js environments (when connectionInfo is not provided)
|
|
462
|
+
discovery = null;
|
|
442
463
|
ws = null;
|
|
443
464
|
connectionInfo = null;
|
|
444
465
|
state = {
|
|
@@ -494,11 +515,10 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
494
515
|
autoLaunchSanqian: true,
|
|
495
516
|
...config
|
|
496
517
|
};
|
|
497
|
-
this.discovery = new DiscoveryManager();
|
|
498
518
|
for (const tool of config.tools) {
|
|
499
519
|
this.toolHandlers.set(tool.name, tool.handler);
|
|
500
520
|
}
|
|
501
|
-
this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
|
|
521
|
+
this.launchedBySanqian = typeof process !== "undefined" && (process.env?.SANQIAN_LAUNCHED === "1" || process.env?.SANQIAN_NO_RECONNECT === "1");
|
|
502
522
|
if (this.launchedBySanqian) {
|
|
503
523
|
this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
|
|
504
524
|
setTimeout(() => {
|
|
@@ -508,6 +528,26 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
508
528
|
}, 0);
|
|
509
529
|
}
|
|
510
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Build WebSocket URL from connection info
|
|
533
|
+
* This is inlined to avoid importing DiscoveryManager in browser environments
|
|
534
|
+
*/
|
|
535
|
+
buildWebSocketUrl(info) {
|
|
536
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
537
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Lazily initialize DiscoveryManager (only in Node.js environments)
|
|
541
|
+
* This uses dynamic import to avoid bundling Node.js APIs in browser builds
|
|
542
|
+
*/
|
|
543
|
+
async getDiscovery() {
|
|
544
|
+
if (this.discovery) {
|
|
545
|
+
return this.discovery;
|
|
546
|
+
}
|
|
547
|
+
const { DiscoveryManager: DiscoveryManager2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
548
|
+
this.discovery = new DiscoveryManager2();
|
|
549
|
+
return this.discovery;
|
|
550
|
+
}
|
|
511
551
|
// ============================================
|
|
512
552
|
// Lifecycle
|
|
513
553
|
// ============================================
|
|
@@ -531,7 +571,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
531
571
|
*/
|
|
532
572
|
async connectWithInfo(info) {
|
|
533
573
|
this.connectionInfo = info;
|
|
534
|
-
const url = this.
|
|
574
|
+
const url = this.buildWebSocketUrl(info);
|
|
535
575
|
return new Promise((resolve, reject) => {
|
|
536
576
|
this.log(`Connecting to ${url}`);
|
|
537
577
|
this.ws = new WebSocket(url);
|
|
@@ -581,7 +621,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
581
621
|
async disconnect() {
|
|
582
622
|
this.stopHeartbeat();
|
|
583
623
|
this.stopReconnect();
|
|
584
|
-
this.discovery
|
|
624
|
+
this.discovery?.stopWatching();
|
|
585
625
|
if (this.ws) {
|
|
586
626
|
this.ws.close(1e3, "Client disconnect");
|
|
587
627
|
this.ws = null;
|
|
@@ -962,11 +1002,12 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
962
1002
|
this.log("Using pre-configured connection info (browser mode)");
|
|
963
1003
|
info = this.config.connectionInfo;
|
|
964
1004
|
} else {
|
|
965
|
-
|
|
1005
|
+
const discovery = await this.getDiscovery();
|
|
1006
|
+
info = discovery.read();
|
|
966
1007
|
if (!info) {
|
|
967
1008
|
if (this.config.autoLaunchSanqian) {
|
|
968
1009
|
this.log("Sanqian not running, attempting to launch...");
|
|
969
|
-
const launched =
|
|
1010
|
+
const launched = discovery.launchSanqian(this.config.sanqianPath);
|
|
970
1011
|
if (!launched) {
|
|
971
1012
|
throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
|
|
972
1013
|
}
|
|
@@ -985,8 +1026,9 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
985
1026
|
async waitForSanqianStartup(timeout = 12e4) {
|
|
986
1027
|
const startTime = Date.now();
|
|
987
1028
|
const pollInterval = 500;
|
|
1029
|
+
const discovery = await this.getDiscovery();
|
|
988
1030
|
while (Date.now() - startTime < timeout) {
|
|
989
|
-
const info =
|
|
1031
|
+
const info = discovery.read();
|
|
990
1032
|
if (info) {
|
|
991
1033
|
this.log("Sanqian started, connection info available");
|
|
992
1034
|
return info;
|
|
@@ -1335,6 +1377,20 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1335
1377
|
});
|
|
1336
1378
|
return this.on(event, onceWrapper);
|
|
1337
1379
|
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Remove all event listeners
|
|
1382
|
+
*
|
|
1383
|
+
* Call this to prevent memory leaks when disposing the SDK instance.
|
|
1384
|
+
* If event is specified, only removes listeners for that event.
|
|
1385
|
+
*/
|
|
1386
|
+
removeAllListeners(event) {
|
|
1387
|
+
if (event) {
|
|
1388
|
+
this.eventListeners.delete(event);
|
|
1389
|
+
} else {
|
|
1390
|
+
this.eventListeners.clear();
|
|
1391
|
+
}
|
|
1392
|
+
return this;
|
|
1393
|
+
}
|
|
1338
1394
|
emit(event, ...args) {
|
|
1339
1395
|
const listeners = this.eventListeners.get(event);
|
|
1340
1396
|
if (listeners) {
|
|
@@ -1435,6 +1491,9 @@ var Conversation = class {
|
|
|
1435
1491
|
});
|
|
1436
1492
|
}
|
|
1437
1493
|
};
|
|
1494
|
+
|
|
1495
|
+
// src/index.ts
|
|
1496
|
+
init_discovery();
|
|
1438
1497
|
export {
|
|
1439
1498
|
Conversation,
|
|
1440
1499
|
DiscoveryManager,
|