@gecho-ai/gecho-bridge 1.1.26 → 1.1.27

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@gecho-ai/gecho-bridge",
3
- "version": "1.1.26",
3
+ "version": "1.1.27",
4
4
  "description": "TikTok Search Tool for OpenClaw"
5
5
  }
@@ -33854,7 +33854,7 @@ var require_package = __commonJS({
33854
33854
  "package.json"(exports2, module2) {
33855
33855
  module2.exports = {
33856
33856
  name: "@gecho-ai/gecho-bridge",
33857
- version: "1.1.26",
33857
+ version: "1.1.27",
33858
33858
  description: "TikTok Search and Insight tools for OpenClaw, connecting LLMs to TikTok via Chrome Extension.",
33859
33859
  main: "mcp-client.js",
33860
33860
  bin: {
@@ -33868,6 +33868,7 @@ var require_package = __commonJS({
33868
33868
  ".mcp.json",
33869
33869
  "openclaw.plugin.json",
33870
33870
  "skills",
33871
+ "distribution-skills",
33871
33872
  "README.md",
33872
33873
  "README.zh.md",
33873
33874
  "qywx.jpg"
@@ -34017,12 +34018,19 @@ function requestShutdown() {
34017
34018
  });
34018
34019
  }
34019
34020
  function startServiceDetached() {
34020
- const child = spawn("node", [SERVICE_PATH], {
34021
- detached: true,
34022
- stdio: "ignore"
34023
- // 静默启动,不占用当前终端
34021
+ const isWindows = process.platform === "win32";
34022
+ const child = spawn(process.execPath, [SERVICE_PATH], {
34023
+ // On Windows, detached creates a separate process group. It is unnecessary
34024
+ // after unref() and has proved unreliable for the browser launched by this
34025
+ // service when the parent is a desktop MCP host. Keep the Unix behaviour.
34026
+ detached: !isWindows,
34027
+ stdio: "ignore",
34028
+ windowsHide: true,
34029
+ // Make this equivalent to running `node server.js` from the package.
34030
+ cwd: __dirname
34024
34031
  });
34025
34032
  child.unref();
34033
+ return child;
34026
34034
  }
34027
34035
  async function waitForServiceDown() {
34028
34036
  let retries = 10;
package/dist/server.cjs CHANGED
@@ -3640,7 +3640,7 @@ var require_package = __commonJS({
3640
3640
  "package.json"(exports2, module2) {
3641
3641
  module2.exports = {
3642
3642
  name: "@gecho-ai/gecho-bridge",
3643
- version: "1.1.26",
3643
+ version: "1.1.27",
3644
3644
  description: "TikTok Search and Insight tools for OpenClaw, connecting LLMs to TikTok via Chrome Extension.",
3645
3645
  main: "mcp-client.js",
3646
3646
  bin: {
@@ -3654,6 +3654,7 @@ var require_package = __commonJS({
3654
3654
  ".mcp.json",
3655
3655
  "openclaw.plugin.json",
3656
3656
  "skills",
3657
+ "distribution-skills",
3657
3658
  "README.md",
3658
3659
  "README.zh.md",
3659
3660
  "qywx.jpg"
@@ -3699,9 +3700,10 @@ var { WebSocketServer } = require_ws();
3699
3700
  var http = require("http");
3700
3701
  var fs = require("fs");
3701
3702
  var path = require("path");
3703
+ var { spawn } = require("child_process");
3702
3704
  var packageJson = require_package();
3703
- var WS_PORT = 18792;
3704
- var HTTP_PORT = 18793;
3705
+ var WS_PORT = Number(process["env"].GECHO_WS_PORT || 18792);
3706
+ var HTTP_PORT = Number(process["env"].GECHO_HTTP_PORT || 18793);
3705
3707
  var SERVICE_PROTOCOL_VERSION = 2;
3706
3708
  var SERVICE_STARTED_AT = Date.now();
3707
3709
  var DEFAULT_DATA_DIR = path.join(__dirname, "data");
@@ -3709,10 +3711,19 @@ var JOBS_DIR = process["env"].GECHO_DATA_DIR || DEFAULT_DATA_DIR;
3709
3711
  var JOBS_STORE_PATH = path.join(JOBS_DIR, ".async_jobs.json");
3710
3712
  var JOB_DETAILS_DIR = path.join(JOBS_DIR, "jobs");
3711
3713
  var REQUEST_INDEX_PATH = path.join(JOBS_DIR, ".async_request_index.json");
3714
+ var BROWSER_CONNECTION_PATH = path.join(JOBS_DIR, ".browser_connection.json");
3712
3715
  var JOB_TTL_MS = Number(process["env"].GECHO_JOB_TTL_MS || 3 * 24 * 60 * 60 * 1e3);
3713
3716
  var MAX_PERSISTED_JOBS = Number(process["env"].GECHO_MAX_PERSISTED_JOBS || 2e3);
3714
3717
  var CLEANUP_INTERVAL_MS = Number(process["env"].GECHO_CLEANUP_INTERVAL_MS || 10 * 60 * 1e3);
3718
+ var EXTENSION_CONNECT_TIMEOUT_MS = Math.max(1e3, Number(process["env"].GECHO_EXTENSION_CONNECT_TIMEOUT_MS || 3e4));
3719
+ var EXTENSION_CONNECT_POLL_MS = 500;
3720
+ var AUTO_LAUNCH_BROWSER = process["env"].GECHO_AUTO_LAUNCH_BROWSER !== "0";
3721
+ var AUTO_LAUNCH_BROWSER_DRY_RUN = process["env"].GECHO_AUTO_LAUNCH_BROWSER_DRY_RUN === "1";
3722
+ var AUTO_LAUNCH_BROWSER_COOLDOWN_MS = Math.max(0, Number(process["env"].GECHO_AUTO_LAUNCH_BROWSER_COOLDOWN_MS || 1e4));
3715
3723
  var extensionSocket = null;
3724
+ var lastBrowserConnection = null;
3725
+ var browserLaunchPromise = null;
3726
+ var lastBrowserLaunchAt = 0;
3716
3727
  var pendingRequests = /* @__PURE__ */ new Map();
3717
3728
  var requestIdCounter = 1;
3718
3729
  var shuttingDown = false;
@@ -3738,6 +3749,27 @@ function ensureJobDetailsDirReady() {
3738
3749
  function getJobDetailPath(jobId) {
3739
3750
  return path.join(JOB_DETAILS_DIR, `${jobId}.json`);
3740
3751
  }
3752
+ function writeFileAtomic(targetPath, content) {
3753
+ const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
3754
+ fs.writeFileSync(tmpPath, content, "utf8");
3755
+ try {
3756
+ fs.renameSync(tmpPath, targetPath);
3757
+ } catch (e) {
3758
+ if (e && (e.code === "EEXIST" || e.code === "EPERM")) {
3759
+ try {
3760
+ fs.unlinkSync(targetPath);
3761
+ } catch (_ignore) {
3762
+ }
3763
+ fs.renameSync(tmpPath, targetPath);
3764
+ } else {
3765
+ try {
3766
+ fs.unlinkSync(tmpPath);
3767
+ } catch (_ignore) {
3768
+ }
3769
+ throw e;
3770
+ }
3771
+ }
3772
+ }
3741
3773
  function enqueueWrite(task, label = "write_task") {
3742
3774
  writeQueue = writeQueue.then(async () => {
3743
3775
  await task();
@@ -3768,6 +3800,235 @@ async function writeFileAtomicAsync(targetPath, content) {
3768
3800
  }
3769
3801
  }
3770
3802
  }
3803
+ function isExtensionConnected() {
3804
+ return !!extensionSocket && extensionSocket.readyState === 1;
3805
+ }
3806
+ function normalizeBrowserName(value) {
3807
+ const normalized = String(value || "").trim().toLowerCase();
3808
+ if (normalized === "edge" || normalized === "microsoft-edge" || normalized === "msedge") return "edge";
3809
+ if (normalized === "chrome" || normalized === "google-chrome") return "chrome";
3810
+ return "";
3811
+ }
3812
+ function detectBrowserFromUserAgent(userAgent) {
3813
+ const value = String(userAgent || "");
3814
+ if (/\bEdg\//i.test(value)) return "edge";
3815
+ if (/\bChrome\//i.test(value) && !/\b(?:OPR|Edg)\//i.test(value)) return "chrome";
3816
+ return "";
3817
+ }
3818
+ function loadLastBrowserConnection() {
3819
+ try {
3820
+ if (!fs.existsSync(BROWSER_CONNECTION_PATH)) return null;
3821
+ const parsed = JSON.parse(fs.readFileSync(BROWSER_CONNECTION_PATH, "utf8") || "{}");
3822
+ const browser = normalizeBrowserName(parsed?.browser);
3823
+ if (!browser) return null;
3824
+ return {
3825
+ browser,
3826
+ detectedAt: Number(parsed.detectedAt || 0) || 0
3827
+ };
3828
+ } catch (e) {
3829
+ console.warn(`Failed to load last browser connection: ${e.message}`);
3830
+ return null;
3831
+ }
3832
+ }
3833
+ function persistLastBrowserConnection(browser) {
3834
+ const normalized = normalizeBrowserName(browser);
3835
+ if (!normalized) return;
3836
+ lastBrowserConnection = { browser: normalized, detectedAt: Date.now() };
3837
+ try {
3838
+ ensureJobsDirReady();
3839
+ writeFileAtomic(BROWSER_CONNECTION_PATH, JSON.stringify(lastBrowserConnection));
3840
+ } catch (e) {
3841
+ console.warn(`Failed to persist last browser connection: ${e.message}`);
3842
+ }
3843
+ }
3844
+ function findFirstExistingPath(paths) {
3845
+ return paths.find((candidate) => {
3846
+ try {
3847
+ return !!candidate && fs.existsSync(candidate);
3848
+ } catch (_e) {
3849
+ return false;
3850
+ }
3851
+ }) || "";
3852
+ }
3853
+ function findFirstCommand(commands) {
3854
+ const pathEntries = String(process["env"].PATH || "").split(path.delimiter).filter(Boolean);
3855
+ const extensions = process.platform === "win32" ? String(process["env"].PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";") : [""];
3856
+ for (const command of commands) {
3857
+ for (const entry of pathEntries) {
3858
+ for (const extension of extensions) {
3859
+ const executable = path.join(entry, process.platform === "win32" ? `${command}${extension}` : command);
3860
+ if (fs.existsSync(executable)) return executable;
3861
+ }
3862
+ }
3863
+ }
3864
+ return "";
3865
+ }
3866
+ function getWindowsBrowserExecutable(browser) {
3867
+ const normalized = normalizeBrowserName(browser);
3868
+ const executable = normalized === "edge" ? "msedge.exe" : "chrome.exe";
3869
+ const vendorPath = normalized === "edge" ? ["Microsoft", "Edge", "Application"] : ["Google", "Chrome", "Application"];
3870
+ const roots = [
3871
+ process["env"].LOCALAPPDATA,
3872
+ process["env"].PROGRAMFILES,
3873
+ process["env"]["PROGRAMFILES(X86)"]
3874
+ ].filter(Boolean);
3875
+ return findFirstExistingPath(roots.map((root) => path.join(root, ...vendorPath, executable)));
3876
+ }
3877
+ function isBrowserInstalled(browser) {
3878
+ const normalized = normalizeBrowserName(browser);
3879
+ if (!normalized) return false;
3880
+ if (process.platform === "darwin") {
3881
+ const appName = normalized === "edge" ? "Microsoft Edge.app" : "Google Chrome.app";
3882
+ return !!findFirstExistingPath([
3883
+ path.join("/Applications", appName),
3884
+ path.join("/System/Applications", appName)
3885
+ ]);
3886
+ }
3887
+ if (process.platform === "win32") {
3888
+ return !!getWindowsBrowserExecutable(normalized);
3889
+ }
3890
+ return !!findFirstCommand(
3891
+ normalized === "edge" ? ["microsoft-edge", "microsoft-edge-stable"] : ["google-chrome", "google-chrome-stable"]
3892
+ );
3893
+ }
3894
+ function getBrowserToLaunch() {
3895
+ const configured = normalizeBrowserName(process["env"].GECHO_BROWSER);
3896
+ if (configured) return configured;
3897
+ if (lastBrowserConnection?.browser) return lastBrowserConnection.browser;
3898
+ if (isBrowserInstalled("chrome")) return "chrome";
3899
+ if (isBrowserInstalled("edge")) return "edge";
3900
+ return "";
3901
+ }
3902
+ function getBrowserLaunchSpec(browser) {
3903
+ const normalized = normalizeBrowserName(browser);
3904
+ const appName = normalized === "edge" ? "Microsoft Edge" : "Google Chrome";
3905
+ if (!normalized) return null;
3906
+ if (process.platform === "darwin") {
3907
+ return {
3908
+ command: "open",
3909
+ args: ["-a", appName, "--args", "--new-window", "about:blank"]
3910
+ };
3911
+ }
3912
+ if (process.platform === "win32") {
3913
+ const browserPath = getWindowsBrowserExecutable(normalized);
3914
+ if (browserPath) {
3915
+ return {
3916
+ command: browserPath,
3917
+ args: ["--new-window", "about:blank"]
3918
+ };
3919
+ }
3920
+ const executable2 = normalized === "edge" ? "msedge" : "chrome";
3921
+ return {
3922
+ command: "cmd.exe",
3923
+ args: ["/d", "/s", "/c", "start", "", executable2, "--new-window", "about:blank"]
3924
+ };
3925
+ }
3926
+ const executable = findFirstCommand(
3927
+ normalized === "edge" ? ["microsoft-edge", "microsoft-edge-stable"] : ["google-chrome", "google-chrome-stable"]
3928
+ ) || (normalized === "edge" ? "microsoft-edge" : "google-chrome");
3929
+ return {
3930
+ command: executable,
3931
+ args: ["--new-window", "about:blank"]
3932
+ };
3933
+ }
3934
+ async function launchBrowserForExtension() {
3935
+ if (!AUTO_LAUNCH_BROWSER) {
3936
+ return { attempted: false, launched: false, reason: "disabled" };
3937
+ }
3938
+ const browser = getBrowserToLaunch();
3939
+ const spec = getBrowserLaunchSpec(browser);
3940
+ if (!spec) {
3941
+ return { attempted: false, launched: false, reason: "browser_unknown" };
3942
+ }
3943
+ if (browserLaunchPromise) return browserLaunchPromise;
3944
+ if (Date.now() - lastBrowserLaunchAt < AUTO_LAUNCH_BROWSER_COOLDOWN_MS) {
3945
+ return { attempted: false, launched: false, browser, reason: "cooldown" };
3946
+ }
3947
+ lastBrowserLaunchAt = Date.now();
3948
+ browserLaunchPromise = new Promise((resolve) => {
3949
+ if (AUTO_LAUNCH_BROWSER_DRY_RUN) {
3950
+ console.log(`\u{1F9EA} Browser launch dry run: ${browser}`);
3951
+ resolve({ attempted: true, launched: true, browser, dryRun: true });
3952
+ return;
3953
+ }
3954
+ let child;
3955
+ try {
3956
+ child = spawn(spec.command, spec.args, {
3957
+ // A detached Windows process starts in a separate process group. That
3958
+ // extra isolation is not needed for a GUI browser after unref(), and
3959
+ // can prevent an extension service worker from reconnecting when the
3960
+ // bridge itself was started by a desktop MCP host.
3961
+ detached: process.platform !== "win32",
3962
+ stdio: "ignore",
3963
+ windowsHide: true
3964
+ });
3965
+ } catch (e) {
3966
+ resolve({ attempted: true, launched: false, browser, reason: e.message });
3967
+ return;
3968
+ }
3969
+ child.once("error", (e) => {
3970
+ resolve({ attempted: true, launched: false, browser, reason: e.message });
3971
+ });
3972
+ child.once("spawn", () => {
3973
+ child.unref();
3974
+ console.log(`\u{1F310} Started ${browser} while waiting for the extension connection`);
3975
+ resolve({ attempted: true, launched: true, browser });
3976
+ });
3977
+ });
3978
+ try {
3979
+ return await browserLaunchPromise;
3980
+ } finally {
3981
+ browserLaunchPromise = null;
3982
+ }
3983
+ }
3984
+ function waitForExtensionConnection(timeoutMs = EXTENSION_CONNECT_TIMEOUT_MS) {
3985
+ return new Promise((resolve) => {
3986
+ if (isExtensionConnected()) return resolve(true);
3987
+ let waited = 0;
3988
+ const checkTimer = setInterval(() => {
3989
+ waited += EXTENSION_CONNECT_POLL_MS;
3990
+ if (isExtensionConnected()) {
3991
+ clearInterval(checkTimer);
3992
+ resolve(true);
3993
+ } else if (waited >= timeoutMs) {
3994
+ clearInterval(checkTimer);
3995
+ resolve(false);
3996
+ }
3997
+ }, EXTENSION_CONNECT_POLL_MS);
3998
+ });
3999
+ }
4000
+ async function ensureExtensionConnection(action) {
4001
+ if (isExtensionConnected()) {
4002
+ return { connected: true, launch: { attempted: false, launched: false, reason: "already_connected" } };
4003
+ }
4004
+ const launch = await launchBrowserForExtension();
4005
+ console.log(
4006
+ `\u23F3 Extension not connected yet. Waiting up to ${EXTENSION_CONNECT_TIMEOUT_MS / 1e3}s (action: ${action}, browser: ${launch.browser || "unknown"})`
4007
+ );
4008
+ const connected = await waitForExtensionConnection();
4009
+ return { connected, launch };
4010
+ }
4011
+ function extensionConnectionError(connection) {
4012
+ const launch = connection?.launch || {};
4013
+ const details = [];
4014
+ if (launch.reason === "browser_unknown") {
4015
+ details.push("Bridge has not identified a browser yet. Open Edge or Chrome with the Gecho extension once, then retry.");
4016
+ } else if (launch.reason === "disabled") {
4017
+ details.push("Automatic browser launch is disabled (GECHO_AUTO_LAUNCH_BROWSER=0).");
4018
+ } else if (launch.reason === "cooldown") {
4019
+ details.push(`${launch.browser} was started recently; it did not reconnect in time.`);
4020
+ } else if (launch.attempted && !launch.launched) {
4021
+ details.push(`Failed to start ${launch.browser || "the saved browser"}: ${launch.reason || "unknown error"}`);
4022
+ } else if (launch.attempted) {
4023
+ details.push(`${launch.browser} was started, but its extension did not connect in time.`);
4024
+ }
4025
+ return [
4026
+ "Extension not connected.",
4027
+ ...details,
4028
+ "Check that the Gecho extension is installed, enabled, and signed in for that browser profile."
4029
+ ].join(" ");
4030
+ }
4031
+ lastBrowserConnection = loadLastBrowserConnection();
3771
4032
  function sanitizeJobForDisk(job = {}) {
3772
4033
  const copy = { ...job };
3773
4034
  delete copy.data;
@@ -4197,12 +4458,13 @@ function hydrateJobWithDataFromFile(job) {
4197
4458
  return job;
4198
4459
  }
4199
4460
  }
4200
- function runAsyncAttempt({ jobId, action, params, payload, attempt }) {
4201
- if (!extensionSocket || extensionSocket.readyState !== 1) {
4461
+ async function runAsyncAttempt({ jobId, action, params, payload, attempt }) {
4462
+ const connection = await ensureExtensionConnection(action);
4463
+ if (!connection.connected) {
4202
4464
  updateJob(jobId, {
4203
4465
  status: "error",
4204
4466
  stage: "extension_disconnected",
4205
- error: "Extension not connected during async dispatch"
4467
+ error: extensionConnectionError(connection)
4206
4468
  });
4207
4469
  return;
4208
4470
  }
@@ -4291,8 +4553,10 @@ function gracefulShutdown(reason) {
4291
4553
  }
4292
4554
  }
4293
4555
  var wss = new WebSocketServer({ port: WS_PORT, host: "127.0.0.1" });
4294
- wss.on("connection", (ws) => {
4295
- console.log("\u2705 Chrome Extension connected to Service Layer");
4556
+ wss.on("connection", (ws, request) => {
4557
+ const browser = detectBrowserFromUserAgent(request?.headers?.["user-agent"]);
4558
+ if (browser) persistLastBrowserConnection(browser);
4559
+ console.log(`\u2705 Browser extension connected to Service Layer${browser ? ` (${browser})` : ""}`);
4296
4560
  extensionSocket = ws;
4297
4561
  ws.on("message", (message) => {
4298
4562
  try {
@@ -4349,8 +4613,11 @@ wss.on("connection", (ws) => {
4349
4613
  }
4350
4614
  });
4351
4615
  ws.on("close", () => {
4352
- console.log("\u274C Chrome Extension disconnected");
4353
- extensionSocket = null;
4616
+ console.log("\u274C Browser extension disconnected");
4617
+ if (extensionSocket === ws) extensionSocket = null;
4618
+ });
4619
+ ws.on("error", (error) => {
4620
+ console.warn(`Browser extension WebSocket error: ${error.message}`);
4354
4621
  });
4355
4622
  });
4356
4623
  loadAsyncJobsFromDisk();
@@ -4378,7 +4645,13 @@ var server = http.createServer(async (req, res) => {
4378
4645
  servicePath: __filename,
4379
4646
  startedAt: SERVICE_STARTED_AT,
4380
4647
  sourceMtimeMs: getServiceSourceMtimeMs(),
4381
- dataDir: JOBS_DIR
4648
+ dataDir: JOBS_DIR,
4649
+ extensionConnected: isExtensionConnected(),
4650
+ lastConnectedBrowser: lastBrowserConnection?.browser || null,
4651
+ autoBrowserLaunch: {
4652
+ enabled: AUTO_LAUNCH_BROWSER,
4653
+ browser: getBrowserToLaunch() || null
4654
+ }
4382
4655
  }));
4383
4656
  }
4384
4657
  if (req.method === "POST" && req.url === "/shutdown") {
@@ -4431,29 +4704,10 @@ var server = http.createServer(async (req, res) => {
4431
4704
  res.statusCode = 400;
4432
4705
  return res.end(JSON.stringify({ error: "Missing action" }));
4433
4706
  }
4434
- if (!extensionSocket || extensionSocket.readyState !== 1) {
4435
- console.log(`\u23F3 Extension not connected yet. Waiting for connection... (action: ${action})`);
4436
- const maxWaitMs = 15e3;
4437
- const checkIntervalMs = 500;
4438
- let waited = 0;
4439
- await new Promise((resolve) => {
4440
- const checkTimer = setInterval(() => {
4441
- waited += checkIntervalMs;
4442
- if (extensionSocket && extensionSocket.readyState === 1) {
4443
- clearInterval(checkTimer);
4444
- resolve();
4445
- } else if (waited >= maxWaitMs) {
4446
- clearInterval(checkTimer);
4447
- resolve();
4448
- }
4449
- }, checkIntervalMs);
4450
- });
4451
- if (!extensionSocket || extensionSocket.readyState !== 1) {
4452
- res.statusCode = 503;
4453
- return res.end(JSON.stringify({
4454
- error: "Extension not connected. Chrome \u63D2\u4EF6\u672A\u8FDE\u63A5\u3002\n\u8BF7\u68C0\u67E5\uFF1A\n1. Chrome \u662F\u5426\u4FDD\u6301\u6253\u5F00\u72B6\u6001\n2. Gecho TikTok Bridge \u63D2\u4EF6\u662F\u5426\u4ECD\u5728\u8FD0\u884C\uFF08\u6269\u5C55\u56FE\u6807\u662F\u5426\u4EAE\u7740\uFF09\n3. TikTok tab \u662F\u5426\u6D3B\u8DC3\uFF08\u6700\u597D\u5728 tiktok.com \u9875\u9762\u4E0A\uFF09\n\u5982\u679C\u63D2\u4EF6\u521A\u542F\u52A8\uFF0C\u8BF7\u7B49\u5F85\u51E0\u79D2\u540E\u518D\u8BD5\u3002"
4455
- }));
4456
- }
4707
+ const connection = await ensureExtensionConnection(action);
4708
+ if (!connection.connected) {
4709
+ res.statusCode = 503;
4710
+ return res.end(JSON.stringify({ error: extensionConnectionError(connection) }));
4457
4711
  }
4458
4712
  const jobId = `job-${Date.now()}-${requestIdCounter++}`;
4459
4713
  console.log(`\u{1F680} Dispatching ASYNC action: [${action}], jobId: ${jobId}`);
@@ -4509,29 +4763,10 @@ var server = http.createServer(async (req, res) => {
4509
4763
  res.statusCode = 400;
4510
4764
  return res.end(JSON.stringify({ error: "Missing action" }));
4511
4765
  }
4512
- if (!extensionSocket || extensionSocket.readyState !== 1) {
4513
- console.log(`\u23F3 Extension not connected yet. Waiting for connection... (action: ${action})`);
4514
- const maxWaitMs = 15e3;
4515
- const checkIntervalMs = 500;
4516
- let waited = 0;
4517
- await new Promise((resolve) => {
4518
- const checkTimer = setInterval(() => {
4519
- waited += checkIntervalMs;
4520
- if (extensionSocket && extensionSocket.readyState === 1) {
4521
- clearInterval(checkTimer);
4522
- resolve();
4523
- } else if (waited >= maxWaitMs) {
4524
- clearInterval(checkTimer);
4525
- resolve();
4526
- }
4527
- }, checkIntervalMs);
4528
- });
4529
- if (!extensionSocket || extensionSocket.readyState !== 1) {
4530
- res.statusCode = 503;
4531
- return res.end(JSON.stringify({
4532
- error: "Extension not connected. Chrome \u63D2\u4EF6\u672A\u8FDE\u63A5\u3002\n\u8BF7\u68C0\u67E5\uFF1A\n1. Chrome \u662F\u5426\u4FDD\u6301\u6253\u5F00\u72B6\u6001\n2. Gecho TikTok Bridge \u63D2\u4EF6\u662F\u5426\u4ECD\u5728\u8FD0\u884C\uFF08\u6269\u5C55\u56FE\u6807\u662F\u5426\u4EAE\u7740\uFF09\n3. TikTok tab \u662F\u5426\u6D3B\u8DC3\uFF08\u6700\u597D\u5728 tiktok.com \u9875\u9762\u4E0A\uFF09\n\u5982\u679C\u63D2\u4EF6\u521A\u542F\u52A8\uFF0C\u8BF7\u7B49\u5F85\u51E0\u79D2\u540E\u518D\u8BD5\u3002"
4533
- }));
4534
- }
4766
+ const connection = await ensureExtensionConnection(action);
4767
+ if (!connection.connected) {
4768
+ res.statusCode = 503;
4769
+ return res.end(JSON.stringify({ error: extensionConnectionError(connection) }));
4535
4770
  }
4536
4771
  console.log(`\u{1F680} Dispatching action: [${action}]`);
4537
4772
  const requestId = `svc-${Date.now()}-${requestIdCounter++}`;