@gecho-ai/gecho-bridge 1.1.26 → 1.1.28
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/.claude-plugin/plugin.json +1 -1
- package/dist/mcp-client.cjs +13 -5
- package/dist/server.cjs +343 -58
- package/distribution-skills/tiktok-influencer/SKILL.md +360 -0
- package/distribution-skills/tiktok-influencer/_meta.json +5 -0
- package/distribution-skills/tiktok-insight/SKILL.md +391 -0
- package/distribution-skills/tiktok-insight/_meta.json +5 -0
- package/distribution-skills/tiktok-video-search/SKILL.md +358 -0
- package/distribution-skills/tiktok-video-search/_meta.json +5 -0
- package/mcp-client.js +14 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/server.js +376 -65
- package/skills/tiktok-search/_meta.json +1 -1
package/dist/mcp-client.cjs
CHANGED
|
@@ -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.
|
|
33857
|
+
version: "1.1.28",
|
|
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
|
|
34021
|
-
|
|
34022
|
-
|
|
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.
|
|
3643
|
+
version: "1.1.28",
|
|
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,14 +3711,32 @@ 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 EXTENSION_READY_GRACE_MS = Math.max(0, Number(process["env"].GECHO_EXTENSION_READY_GRACE_MS || 1500));
|
|
3721
|
+
var AUTO_LAUNCH_BROWSER = process["env"].GECHO_AUTO_LAUNCH_BROWSER !== "0";
|
|
3722
|
+
var AUTO_LAUNCH_BROWSER_DRY_RUN = process["env"].GECHO_AUTO_LAUNCH_BROWSER_DRY_RUN === "1";
|
|
3723
|
+
var AUTO_LAUNCH_BROWSER_COOLDOWN_MS = Math.max(0, Number(process["env"].GECHO_AUTO_LAUNCH_BROWSER_COOLDOWN_MS || 1e4));
|
|
3715
3724
|
var extensionSocket = null;
|
|
3725
|
+
var wss = null;
|
|
3726
|
+
var lastExtensionConnectedAt = 0;
|
|
3727
|
+
var lastBrowserConnection = null;
|
|
3728
|
+
var browserLaunchPromise = null;
|
|
3729
|
+
var lastBrowserLaunchAt = 0;
|
|
3716
3730
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
3717
3731
|
var requestIdCounter = 1;
|
|
3718
3732
|
var shuttingDown = false;
|
|
3719
3733
|
var lastDispatchedRequestId = "";
|
|
3734
|
+
var BRIDGE_TRACE_LIMIT = 100;
|
|
3735
|
+
var bridgeTrace = [];
|
|
3736
|
+
function traceBridgeEvent(event, details = {}) {
|
|
3737
|
+
bridgeTrace.push({ at: Date.now(), event, ...details });
|
|
3738
|
+
if (bridgeTrace.length > BRIDGE_TRACE_LIMIT) bridgeTrace.shift();
|
|
3739
|
+
}
|
|
3720
3740
|
var asyncJobs = /* @__PURE__ */ new Map();
|
|
3721
3741
|
var requestIndex = /* @__PURE__ */ new Map();
|
|
3722
3742
|
var ASYNC_ATTEMPT_TIMEOUT_MS = 36e4;
|
|
@@ -3738,6 +3758,27 @@ function ensureJobDetailsDirReady() {
|
|
|
3738
3758
|
function getJobDetailPath(jobId) {
|
|
3739
3759
|
return path.join(JOB_DETAILS_DIR, `${jobId}.json`);
|
|
3740
3760
|
}
|
|
3761
|
+
function writeFileAtomic(targetPath, content) {
|
|
3762
|
+
const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
|
|
3763
|
+
fs.writeFileSync(tmpPath, content, "utf8");
|
|
3764
|
+
try {
|
|
3765
|
+
fs.renameSync(tmpPath, targetPath);
|
|
3766
|
+
} catch (e) {
|
|
3767
|
+
if (e && (e.code === "EEXIST" || e.code === "EPERM")) {
|
|
3768
|
+
try {
|
|
3769
|
+
fs.unlinkSync(targetPath);
|
|
3770
|
+
} catch (_ignore) {
|
|
3771
|
+
}
|
|
3772
|
+
fs.renameSync(tmpPath, targetPath);
|
|
3773
|
+
} else {
|
|
3774
|
+
try {
|
|
3775
|
+
fs.unlinkSync(tmpPath);
|
|
3776
|
+
} catch (_ignore) {
|
|
3777
|
+
}
|
|
3778
|
+
throw e;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3741
3782
|
function enqueueWrite(task, label = "write_task") {
|
|
3742
3783
|
writeQueue = writeQueue.then(async () => {
|
|
3743
3784
|
await task();
|
|
@@ -3768,6 +3809,267 @@ async function writeFileAtomicAsync(targetPath, content) {
|
|
|
3768
3809
|
}
|
|
3769
3810
|
}
|
|
3770
3811
|
}
|
|
3812
|
+
function getOpenExtensionSocket() {
|
|
3813
|
+
if (extensionSocket && extensionSocket.readyState === 1) {
|
|
3814
|
+
return extensionSocket;
|
|
3815
|
+
}
|
|
3816
|
+
if (wss) {
|
|
3817
|
+
for (const socket of wss.clients) {
|
|
3818
|
+
if (socket.readyState === 1) {
|
|
3819
|
+
extensionSocket = socket;
|
|
3820
|
+
return socket;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3826
|
+
function isExtensionConnected() {
|
|
3827
|
+
return !!getOpenExtensionSocket();
|
|
3828
|
+
}
|
|
3829
|
+
function normalizeBrowserName(value) {
|
|
3830
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
3831
|
+
if (normalized === "edge" || normalized === "microsoft-edge" || normalized === "msedge") return "edge";
|
|
3832
|
+
if (normalized === "chrome" || normalized === "google-chrome") return "chrome";
|
|
3833
|
+
return "";
|
|
3834
|
+
}
|
|
3835
|
+
function detectBrowserFromUserAgent(userAgent) {
|
|
3836
|
+
const value = String(userAgent || "");
|
|
3837
|
+
if (/\bEdg\//i.test(value)) return "edge";
|
|
3838
|
+
if (/\bChrome\//i.test(value) && !/\b(?:OPR|Edg)\//i.test(value)) return "chrome";
|
|
3839
|
+
return "";
|
|
3840
|
+
}
|
|
3841
|
+
function loadLastBrowserConnection() {
|
|
3842
|
+
try {
|
|
3843
|
+
if (!fs.existsSync(BROWSER_CONNECTION_PATH)) return null;
|
|
3844
|
+
const parsed = JSON.parse(fs.readFileSync(BROWSER_CONNECTION_PATH, "utf8") || "{}");
|
|
3845
|
+
const browser = normalizeBrowserName(parsed?.browser);
|
|
3846
|
+
if (!browser) return null;
|
|
3847
|
+
return {
|
|
3848
|
+
browser,
|
|
3849
|
+
detectedAt: Number(parsed.detectedAt || 0) || 0
|
|
3850
|
+
};
|
|
3851
|
+
} catch (e) {
|
|
3852
|
+
console.warn(`Failed to load last browser connection: ${e.message}`);
|
|
3853
|
+
return null;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
function persistLastBrowserConnection(browser) {
|
|
3857
|
+
const normalized = normalizeBrowserName(browser);
|
|
3858
|
+
if (!normalized) return;
|
|
3859
|
+
lastBrowserConnection = { browser: normalized, detectedAt: Date.now() };
|
|
3860
|
+
try {
|
|
3861
|
+
ensureJobsDirReady();
|
|
3862
|
+
writeFileAtomic(BROWSER_CONNECTION_PATH, JSON.stringify(lastBrowserConnection));
|
|
3863
|
+
} catch (e) {
|
|
3864
|
+
console.warn(`Failed to persist last browser connection: ${e.message}`);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
function findFirstExistingPath(paths) {
|
|
3868
|
+
return paths.find((candidate) => {
|
|
3869
|
+
try {
|
|
3870
|
+
return !!candidate && fs.existsSync(candidate);
|
|
3871
|
+
} catch (_e) {
|
|
3872
|
+
return false;
|
|
3873
|
+
}
|
|
3874
|
+
}) || "";
|
|
3875
|
+
}
|
|
3876
|
+
function findFirstCommand(commands) {
|
|
3877
|
+
const pathEntries = String(process["env"].PATH || "").split(path.delimiter).filter(Boolean);
|
|
3878
|
+
const extensions = process.platform === "win32" ? String(process["env"].PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";") : [""];
|
|
3879
|
+
for (const command of commands) {
|
|
3880
|
+
for (const entry of pathEntries) {
|
|
3881
|
+
for (const extension of extensions) {
|
|
3882
|
+
const executable = path.join(entry, process.platform === "win32" ? `${command}${extension}` : command);
|
|
3883
|
+
if (fs.existsSync(executable)) return executable;
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
return "";
|
|
3888
|
+
}
|
|
3889
|
+
function getWindowsBrowserExecutable(browser) {
|
|
3890
|
+
const normalized = normalizeBrowserName(browser);
|
|
3891
|
+
const executable = normalized === "edge" ? "msedge.exe" : "chrome.exe";
|
|
3892
|
+
const vendorPath = normalized === "edge" ? ["Microsoft", "Edge", "Application"] : ["Google", "Chrome", "Application"];
|
|
3893
|
+
const roots = [
|
|
3894
|
+
process["env"].LOCALAPPDATA,
|
|
3895
|
+
process["env"].PROGRAMFILES,
|
|
3896
|
+
process["env"]["PROGRAMFILES(X86)"]
|
|
3897
|
+
].filter(Boolean);
|
|
3898
|
+
return findFirstExistingPath(roots.map((root) => path.join(root, ...vendorPath, executable)));
|
|
3899
|
+
}
|
|
3900
|
+
function isBrowserInstalled(browser) {
|
|
3901
|
+
const normalized = normalizeBrowserName(browser);
|
|
3902
|
+
if (!normalized) return false;
|
|
3903
|
+
if (process.platform === "darwin") {
|
|
3904
|
+
const appName = normalized === "edge" ? "Microsoft Edge.app" : "Google Chrome.app";
|
|
3905
|
+
return !!findFirstExistingPath([
|
|
3906
|
+
path.join("/Applications", appName),
|
|
3907
|
+
path.join("/System/Applications", appName)
|
|
3908
|
+
]);
|
|
3909
|
+
}
|
|
3910
|
+
if (process.platform === "win32") {
|
|
3911
|
+
return !!getWindowsBrowserExecutable(normalized);
|
|
3912
|
+
}
|
|
3913
|
+
return !!findFirstCommand(
|
|
3914
|
+
normalized === "edge" ? ["microsoft-edge", "microsoft-edge-stable"] : ["google-chrome", "google-chrome-stable"]
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3917
|
+
function getBrowserToLaunch() {
|
|
3918
|
+
const configured = normalizeBrowserName(process["env"].GECHO_BROWSER);
|
|
3919
|
+
if (configured) return configured;
|
|
3920
|
+
if (lastBrowserConnection?.browser) return lastBrowserConnection.browser;
|
|
3921
|
+
if (isBrowserInstalled("chrome")) return "chrome";
|
|
3922
|
+
if (isBrowserInstalled("edge")) return "edge";
|
|
3923
|
+
return "";
|
|
3924
|
+
}
|
|
3925
|
+
function getBrowserLaunchSpec(browser) {
|
|
3926
|
+
const normalized = normalizeBrowserName(browser);
|
|
3927
|
+
const appName = normalized === "edge" ? "Microsoft Edge" : "Google Chrome";
|
|
3928
|
+
if (!normalized) return null;
|
|
3929
|
+
if (process.platform === "darwin") {
|
|
3930
|
+
return {
|
|
3931
|
+
command: "open",
|
|
3932
|
+
args: ["-a", appName, "--args", "--new-window", "about:blank"]
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
if (process.platform === "win32") {
|
|
3936
|
+
const browserPath = getWindowsBrowserExecutable(normalized);
|
|
3937
|
+
if (browserPath) {
|
|
3938
|
+
return {
|
|
3939
|
+
command: browserPath,
|
|
3940
|
+
args: ["--new-window", "about:blank"]
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
const executable2 = normalized === "edge" ? "msedge" : "chrome";
|
|
3944
|
+
return {
|
|
3945
|
+
command: "cmd.exe",
|
|
3946
|
+
args: ["/d", "/s", "/c", "start", "", executable2, "--new-window", "about:blank"]
|
|
3947
|
+
};
|
|
3948
|
+
}
|
|
3949
|
+
const executable = findFirstCommand(
|
|
3950
|
+
normalized === "edge" ? ["microsoft-edge", "microsoft-edge-stable"] : ["google-chrome", "google-chrome-stable"]
|
|
3951
|
+
) || (normalized === "edge" ? "microsoft-edge" : "google-chrome");
|
|
3952
|
+
return {
|
|
3953
|
+
command: executable,
|
|
3954
|
+
args: ["--new-window", "about:blank"]
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
async function launchBrowserForExtension() {
|
|
3958
|
+
if (!AUTO_LAUNCH_BROWSER) {
|
|
3959
|
+
return { attempted: false, launched: false, reason: "disabled" };
|
|
3960
|
+
}
|
|
3961
|
+
const browser = getBrowserToLaunch();
|
|
3962
|
+
const spec = getBrowserLaunchSpec(browser);
|
|
3963
|
+
if (!spec) {
|
|
3964
|
+
return { attempted: false, launched: false, reason: "browser_unknown" };
|
|
3965
|
+
}
|
|
3966
|
+
if (browserLaunchPromise) return browserLaunchPromise;
|
|
3967
|
+
if (Date.now() - lastBrowserLaunchAt < AUTO_LAUNCH_BROWSER_COOLDOWN_MS) {
|
|
3968
|
+
return { attempted: false, launched: false, browser, reason: "cooldown" };
|
|
3969
|
+
}
|
|
3970
|
+
lastBrowserLaunchAt = Date.now();
|
|
3971
|
+
browserLaunchPromise = new Promise((resolve) => {
|
|
3972
|
+
if (AUTO_LAUNCH_BROWSER_DRY_RUN) {
|
|
3973
|
+
console.log(`\u{1F9EA} Browser launch dry run: ${browser}`);
|
|
3974
|
+
resolve({ attempted: true, launched: true, browser, dryRun: true });
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3977
|
+
let child;
|
|
3978
|
+
try {
|
|
3979
|
+
child = spawn(spec.command, spec.args, {
|
|
3980
|
+
// A detached Windows process starts in a separate process group. That
|
|
3981
|
+
// extra isolation is not needed for a GUI browser after unref(), and
|
|
3982
|
+
// can prevent an extension service worker from reconnecting when the
|
|
3983
|
+
// bridge itself was started by a desktop MCP host.
|
|
3984
|
+
detached: process.platform !== "win32",
|
|
3985
|
+
stdio: "ignore",
|
|
3986
|
+
windowsHide: true
|
|
3987
|
+
});
|
|
3988
|
+
} catch (e) {
|
|
3989
|
+
resolve({ attempted: true, launched: false, browser, reason: e.message });
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
child.once("error", (e) => {
|
|
3993
|
+
resolve({ attempted: true, launched: false, browser, reason: e.message });
|
|
3994
|
+
});
|
|
3995
|
+
child.once("spawn", () => {
|
|
3996
|
+
child.unref();
|
|
3997
|
+
console.log(`\u{1F310} Started ${browser} while waiting for the extension connection`);
|
|
3998
|
+
resolve({ attempted: true, launched: true, browser });
|
|
3999
|
+
});
|
|
4000
|
+
});
|
|
4001
|
+
try {
|
|
4002
|
+
return await browserLaunchPromise;
|
|
4003
|
+
} finally {
|
|
4004
|
+
browserLaunchPromise = null;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
function waitForExtensionConnection(timeoutMs = EXTENSION_CONNECT_TIMEOUT_MS) {
|
|
4008
|
+
return new Promise((resolve) => {
|
|
4009
|
+
if (isExtensionConnected()) return resolve(true);
|
|
4010
|
+
let waited = 0;
|
|
4011
|
+
const checkTimer = setInterval(() => {
|
|
4012
|
+
waited += EXTENSION_CONNECT_POLL_MS;
|
|
4013
|
+
if (isExtensionConnected()) {
|
|
4014
|
+
clearInterval(checkTimer);
|
|
4015
|
+
resolve(true);
|
|
4016
|
+
} else if (waited >= timeoutMs) {
|
|
4017
|
+
clearInterval(checkTimer);
|
|
4018
|
+
resolve(false);
|
|
4019
|
+
}
|
|
4020
|
+
}, EXTENSION_CONNECT_POLL_MS);
|
|
4021
|
+
});
|
|
4022
|
+
}
|
|
4023
|
+
async function waitForExtensionReadyGracePeriod() {
|
|
4024
|
+
const remainingMs = Math.max(0, EXTENSION_READY_GRACE_MS - (Date.now() - lastExtensionConnectedAt));
|
|
4025
|
+
if (remainingMs === 0) return isExtensionConnected();
|
|
4026
|
+
traceBridgeEvent("extension_ready_grace_wait_started", { remainingMs });
|
|
4027
|
+
await new Promise((resolve) => setTimeout(resolve, remainingMs));
|
|
4028
|
+
const connected = isExtensionConnected();
|
|
4029
|
+
traceBridgeEvent("extension_ready_grace_wait_finished", { connected });
|
|
4030
|
+
return connected;
|
|
4031
|
+
}
|
|
4032
|
+
async function ensureExtensionConnection(action) {
|
|
4033
|
+
if (isExtensionConnected()) {
|
|
4034
|
+
traceBridgeEvent("extension_already_connected", { action });
|
|
4035
|
+
return { connected: true, launch: { attempted: false, launched: false, reason: "already_connected" } };
|
|
4036
|
+
}
|
|
4037
|
+
traceBridgeEvent("extension_connection_wait_started", { action });
|
|
4038
|
+
const launch = await launchBrowserForExtension();
|
|
4039
|
+
console.log(
|
|
4040
|
+
`\u23F3 Extension not connected yet. Waiting up to ${EXTENSION_CONNECT_TIMEOUT_MS / 1e3}s (action: ${action}, browser: ${launch.browser || "unknown"})`
|
|
4041
|
+
);
|
|
4042
|
+
const connected = await waitForExtensionConnection();
|
|
4043
|
+
const ready = connected && await waitForExtensionReadyGracePeriod();
|
|
4044
|
+
traceBridgeEvent("extension_connection_wait_finished", {
|
|
4045
|
+
action,
|
|
4046
|
+
connected: ready,
|
|
4047
|
+
browser: launch.browser || "",
|
|
4048
|
+
launchReason: launch.reason || ""
|
|
4049
|
+
});
|
|
4050
|
+
return { connected: ready, launch };
|
|
4051
|
+
}
|
|
4052
|
+
function extensionConnectionError(connection) {
|
|
4053
|
+
const launch = connection?.launch || {};
|
|
4054
|
+
const details = [];
|
|
4055
|
+
if (launch.reason === "browser_unknown") {
|
|
4056
|
+
details.push("Bridge has not identified a browser yet. Open Edge or Chrome with the Gecho extension once, then retry.");
|
|
4057
|
+
} else if (launch.reason === "disabled") {
|
|
4058
|
+
details.push("Automatic browser launch is disabled (GECHO_AUTO_LAUNCH_BROWSER=0).");
|
|
4059
|
+
} else if (launch.reason === "cooldown") {
|
|
4060
|
+
details.push(`${launch.browser} was started recently; it did not reconnect in time.`);
|
|
4061
|
+
} else if (launch.attempted && !launch.launched) {
|
|
4062
|
+
details.push(`Failed to start ${launch.browser || "the saved browser"}: ${launch.reason || "unknown error"}`);
|
|
4063
|
+
} else if (launch.attempted) {
|
|
4064
|
+
details.push(`${launch.browser} was started, but its extension did not connect in time.`);
|
|
4065
|
+
}
|
|
4066
|
+
return [
|
|
4067
|
+
"Extension not connected.",
|
|
4068
|
+
...details,
|
|
4069
|
+
"Check that the Gecho extension is installed, enabled, and signed in for that browser profile."
|
|
4070
|
+
].join(" ");
|
|
4071
|
+
}
|
|
4072
|
+
lastBrowserConnection = loadLastBrowserConnection();
|
|
3771
4073
|
function sanitizeJobForDisk(job = {}) {
|
|
3772
4074
|
const copy = { ...job };
|
|
3773
4075
|
delete copy.data;
|
|
@@ -4197,17 +4499,19 @@ function hydrateJobWithDataFromFile(job) {
|
|
|
4197
4499
|
return job;
|
|
4198
4500
|
}
|
|
4199
4501
|
}
|
|
4200
|
-
function runAsyncAttempt({ jobId, action, params, payload, attempt }) {
|
|
4201
|
-
|
|
4502
|
+
async function runAsyncAttempt({ jobId, action, params, payload, attempt }) {
|
|
4503
|
+
const connection = await ensureExtensionConnection(action);
|
|
4504
|
+
if (!connection.connected) {
|
|
4202
4505
|
updateJob(jobId, {
|
|
4203
4506
|
status: "error",
|
|
4204
4507
|
stage: "extension_disconnected",
|
|
4205
|
-
error:
|
|
4508
|
+
error: extensionConnectionError(connection)
|
|
4206
4509
|
});
|
|
4207
4510
|
return;
|
|
4208
4511
|
}
|
|
4209
4512
|
const requestId = `${jobId}:a${attempt}`;
|
|
4210
4513
|
lastDispatchedRequestId = requestId;
|
|
4514
|
+
traceBridgeEvent("action_dispatched", { action, requestId, async: true });
|
|
4211
4515
|
requestIndex.set(requestId, { jobId, attempt, updatedAt: Date.now() });
|
|
4212
4516
|
schedulePersistRequestIndex();
|
|
4213
4517
|
appendJobEvent(jobId, "attempt_started", { attempt, requestId });
|
|
@@ -4290,14 +4594,19 @@ function gracefulShutdown(reason) {
|
|
|
4290
4594
|
}
|
|
4291
4595
|
}
|
|
4292
4596
|
}
|
|
4293
|
-
|
|
4294
|
-
wss.on("connection", (ws) => {
|
|
4295
|
-
|
|
4597
|
+
wss = new WebSocketServer({ port: WS_PORT, host: "127.0.0.1" });
|
|
4598
|
+
wss.on("connection", (ws, request) => {
|
|
4599
|
+
const browser = detectBrowserFromUserAgent(request?.headers?.["user-agent"]);
|
|
4600
|
+
if (browser) persistLastBrowserConnection(browser);
|
|
4601
|
+
console.log(`\u2705 Browser extension connected to Service Layer${browser ? ` (${browser})` : ""}`);
|
|
4296
4602
|
extensionSocket = ws;
|
|
4603
|
+
lastExtensionConnectedAt = Date.now();
|
|
4604
|
+
traceBridgeEvent("extension_connected", { browser, clientCount: wss.clients.size });
|
|
4297
4605
|
ws.on("message", (message) => {
|
|
4298
4606
|
try {
|
|
4299
4607
|
const parsed = JSON.parse(message);
|
|
4300
4608
|
if (parsed.method === "action_progress" && parsed.requestId) {
|
|
4609
|
+
traceBridgeEvent("extension_progress", { requestId: parsed.requestId });
|
|
4301
4610
|
const pending = pendingRequests.get(parsed.requestId);
|
|
4302
4611
|
if (!pending) {
|
|
4303
4612
|
console.warn(`WARN action_progress requestId not found: ${parsed.requestId}`);
|
|
@@ -4317,6 +4626,7 @@ wss.on("connection", (ws) => {
|
|
|
4317
4626
|
}
|
|
4318
4627
|
}
|
|
4319
4628
|
if (parsed.method === "action_result" && parsed.requestId) {
|
|
4629
|
+
traceBridgeEvent("extension_result", { requestId: parsed.requestId });
|
|
4320
4630
|
console.log(`\u{1F4E9} Received result from extension (ID: ${parsed.requestId})`);
|
|
4321
4631
|
const pending = pendingRequests.get(parsed.requestId);
|
|
4322
4632
|
if (pending) {
|
|
@@ -4349,8 +4659,13 @@ wss.on("connection", (ws) => {
|
|
|
4349
4659
|
}
|
|
4350
4660
|
});
|
|
4351
4661
|
ws.on("close", () => {
|
|
4352
|
-
console.log("\u274C
|
|
4353
|
-
|
|
4662
|
+
console.log("\u274C Browser extension disconnected");
|
|
4663
|
+
traceBridgeEvent("extension_disconnected", { clientCount: wss.clients.size });
|
|
4664
|
+
if (extensionSocket === ws) extensionSocket = null;
|
|
4665
|
+
});
|
|
4666
|
+
ws.on("error", (error) => {
|
|
4667
|
+
traceBridgeEvent("extension_socket_error", { message: error.message });
|
|
4668
|
+
console.warn(`Browser extension WebSocket error: ${error.message}`);
|
|
4354
4669
|
});
|
|
4355
4670
|
});
|
|
4356
4671
|
loadAsyncJobsFromDisk();
|
|
@@ -4378,7 +4693,14 @@ var server = http.createServer(async (req, res) => {
|
|
|
4378
4693
|
servicePath: __filename,
|
|
4379
4694
|
startedAt: SERVICE_STARTED_AT,
|
|
4380
4695
|
sourceMtimeMs: getServiceSourceMtimeMs(),
|
|
4381
|
-
dataDir: JOBS_DIR
|
|
4696
|
+
dataDir: JOBS_DIR,
|
|
4697
|
+
extensionConnected: isExtensionConnected(),
|
|
4698
|
+
lastConnectedBrowser: lastBrowserConnection?.browser || null,
|
|
4699
|
+
autoBrowserLaunch: {
|
|
4700
|
+
enabled: AUTO_LAUNCH_BROWSER,
|
|
4701
|
+
browser: getBrowserToLaunch() || null
|
|
4702
|
+
},
|
|
4703
|
+
bridgeTrace: bridgeTrace.slice(-30)
|
|
4382
4704
|
}));
|
|
4383
4705
|
}
|
|
4384
4706
|
if (req.method === "POST" && req.url === "/shutdown") {
|
|
@@ -4431,29 +4753,10 @@ var server = http.createServer(async (req, res) => {
|
|
|
4431
4753
|
res.statusCode = 400;
|
|
4432
4754
|
return res.end(JSON.stringify({ error: "Missing action" }));
|
|
4433
4755
|
}
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
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
|
-
}
|
|
4756
|
+
const connection = await ensureExtensionConnection(action);
|
|
4757
|
+
if (!connection.connected) {
|
|
4758
|
+
res.statusCode = 503;
|
|
4759
|
+
return res.end(JSON.stringify({ error: extensionConnectionError(connection) }));
|
|
4457
4760
|
}
|
|
4458
4761
|
const jobId = `job-${Date.now()}-${requestIdCounter++}`;
|
|
4459
4762
|
console.log(`\u{1F680} Dispatching ASYNC action: [${action}], jobId: ${jobId}`);
|
|
@@ -4509,33 +4812,15 @@ var server = http.createServer(async (req, res) => {
|
|
|
4509
4812
|
res.statusCode = 400;
|
|
4510
4813
|
return res.end(JSON.stringify({ error: "Missing action" }));
|
|
4511
4814
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
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
|
-
}
|
|
4815
|
+
const connection = await ensureExtensionConnection(action);
|
|
4816
|
+
if (!connection.connected) {
|
|
4817
|
+
res.statusCode = 503;
|
|
4818
|
+
return res.end(JSON.stringify({ error: extensionConnectionError(connection) }));
|
|
4535
4819
|
}
|
|
4536
4820
|
console.log(`\u{1F680} Dispatching action: [${action}]`);
|
|
4537
4821
|
const requestId = `svc-${Date.now()}-${requestIdCounter++}`;
|
|
4538
4822
|
lastDispatchedRequestId = requestId;
|
|
4823
|
+
traceBridgeEvent("action_dispatched", { action, requestId, async: false });
|
|
4539
4824
|
const { action: _a, ...params } = payload;
|
|
4540
4825
|
const result = await new Promise((resolve) => {
|
|
4541
4826
|
const timeoutId = setTimeout(() => {
|