nomadweb/redmini/release/xhs-bridge.js
eric bf7167c185 Rebuild redmini as full offline nomadro product mirror for XHS.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-27 13:04:35 -05:00

640 lines
18 KiB
JavaScript

/**
* Official XHS miniTool bridge helpers (Chrome 61 / ES2017).
* Prefer window.xhs.miniTool Storage (9.46+); fall back to localStorage.
*/
(function (global) {
"use strict";
var STORAGE_MIN = 9460;
var COMMENT_MIN = 9490;
var FILE_MIN = 9490;
function readBuildVersion(launchOptions) {
var env = launchOptions && launchOptions.miniToolEnv;
return Number(env && env.buildVersion) || 0;
}
function getClientVersion(buildVersion) {
return Math.floor((Number(buildVersion) || 0) / 1000);
}
function isClientVersionAtLeast(buildVersion, minimum) {
return getClientVersion(buildVersion) >= minimum;
}
function getMiniTool() {
return global.xhs && global.xhs.miniTool ? global.xhs.miniTool : null;
}
function getBuildVersion() {
var xhs = global.xhs;
var sync = readBuildVersion(xhs && xhs.launchOptions);
if (sync) {
return Promise.resolve(sync);
}
var mt = getMiniTool();
if (!mt || typeof mt.getLaunchOptions !== "function") {
return Promise.resolve(0);
}
return Promise.resolve(mt.getLaunchOptions())
.then(function (opts) {
return readBuildVersion(opts);
})
.catch(function () {
return 0;
});
}
function setLocalData(key, data, encrypt) {
var serialized;
try {
serialized = JSON.stringify(data);
} catch (e) {
return Promise.resolve(false);
}
if (typeof serialized !== "string") return Promise.resolve(false);
var useEncrypt = !!encrypt;
return getBuildVersion().then(function (buildVersion) {
var mt = getMiniTool();
if (
isClientVersionAtLeast(buildVersion, STORAGE_MIN) &&
mt &&
typeof mt.setStorage === "function"
) {
var payload = { key: key, data: serialized };
if (useEncrypt) payload.encrypt = true;
return Promise.resolve(mt.setStorage(payload))
.then(function () {
return true;
})
.catch(function () {
return writeBrowser(key, serialized);
});
}
return writeBrowser(key, serialized);
});
}
function writeBrowser(key, serialized) {
try {
global.localStorage.setItem(key, serialized);
return true;
} catch (e) {
return false;
}
}
function getLocalData(key, fallback, encrypt) {
var useEncrypt = !!encrypt;
return getBuildVersion().then(function (buildVersion) {
var mt = getMiniTool();
if (
isClientVersionAtLeast(buildVersion, STORAGE_MIN) &&
mt &&
typeof mt.getStorage === "function"
) {
var payload = { key: key };
if (useEncrypt) payload.encrypt = true;
return Promise.resolve(mt.getStorage(payload))
.then(function (res) {
var raw = res && res.data;
if (raw != null && raw !== "") {
try {
return JSON.parse(raw);
} catch (e) {
return fallback;
}
}
// Retry without encrypt (migration), then browser
if (useEncrypt) {
return Promise.resolve(mt.getStorage({ key: key }))
.then(function (res2) {
var raw2 = res2 && res2.data;
if (raw2 != null && raw2 !== "") {
try {
var parsed = JSON.parse(raw2);
return setLocalData(key, parsed, true).then(function () {
return parsed;
});
} catch (e2) {
return readBrowser(key, fallback);
}
}
return readBrowser(key, fallback);
})
.catch(function () {
return readBrowser(key, fallback);
});
}
var fromBrowser = readBrowser(key, null);
if (fromBrowser == null) return fallback;
return writeNative(mt, key, JSON.stringify(fromBrowser), false).then(
function () {
return fromBrowser;
},
function () {
return fromBrowser;
}
);
})
.catch(function () {
return readBrowser(key, fallback);
});
}
return readBrowser(key, fallback);
});
}
function writeNative(mt, key, serialized, encrypt) {
if (!mt || typeof mt.setStorage !== "function") {
return Promise.resolve(false);
}
var payload = { key: key, data: serialized };
if (encrypt) payload.encrypt = true;
return Promise.resolve(mt.setStorage(payload)).then(
function () {
return true;
},
function () {
return false;
}
);
}
function readBrowser(key, fallback) {
try {
var raw = global.localStorage.getItem(key);
if (raw == null || raw === "") return fallback;
return JSON.parse(raw);
} catch (e) {
return fallback;
}
}
function removeLocalData(key) {
return getBuildVersion().then(function (buildVersion) {
var mt = getMiniTool();
if (
isClientVersionAtLeast(buildVersion, STORAGE_MIN) &&
mt &&
typeof mt.removeStorage === "function"
) {
return Promise.resolve(mt.removeStorage({ key: key }))
.then(function () {
removeBrowser(key);
return true;
})
.catch(function () {
return removeBrowser(key);
});
}
return removeBrowser(key);
});
}
function clearLocalData() {
return getBuildVersion().then(function (buildVersion) {
var mt = getMiniTool();
if (
isClientVersionAtLeast(buildVersion, STORAGE_MIN) &&
mt &&
typeof mt.clearStorage === "function"
) {
return Promise.resolve(mt.clearStorage())
.then(function () {
clearBrowser();
return true;
})
.catch(function () {
return clearBrowser();
});
}
return clearBrowser();
});
}
function removeBrowser(key) {
try {
global.localStorage.removeItem(key);
return true;
} catch (e) {
return false;
}
}
function clearBrowser() {
try {
global.localStorage.clear();
return true;
} catch (e) {
return false;
}
}
function getLaunchOptions() {
var xhs = global.xhs;
if (xhs && xhs.launchOptions) {
return Promise.resolve(xhs.launchOptions);
}
var mt = getMiniTool();
if (!mt || typeof mt.getLaunchOptions !== "function") {
return Promise.resolve(null);
}
return Promise.resolve(mt.getLaunchOptions()).catch(function () {
return null;
});
}
/** Read miniToolSnapshotInfo from launch options (comment re-entry, §3.9). */
function readSnapshotInfo(launchOptions) {
if (!launchOptions) return null;
var raw =
launchOptions.miniToolSnapshotInfo ||
(launchOptions.query && launchOptions.query.miniToolSnapshotInfo) ||
(launchOptions.referrerInfo &&
launchOptions.referrerInfo.miniToolSnapshotInfo) ||
(launchOptions.extraData && launchOptions.extraData.miniToolSnapshotInfo);
if (!raw) return null;
if (typeof raw === "object") return raw;
try {
return JSON.parse(raw);
} catch (e) {
return null;
}
}
function callApi(name, options) {
var mt = getMiniTool();
if (!mt || typeof mt[name] !== "function") {
return Promise.reject({ errMsg: name + ":fail not available" });
}
return Promise.resolve(mt[name](options || {}));
}
function writeTempFile(dataUrl) {
return callApi("writeTempFile", { data: dataUrl }).then(function (res) {
return (res && res.filePath) || dataUrl;
});
}
function saveImageToPhotosAlbum(filePath) {
return callApi("saveImageToPhotosAlbum", { filePath: filePath });
}
function postNote(opts) {
return callApi("postNote", opts);
}
function canPostComment(buildVersion) {
var mt = getMiniTool();
return (
isClientVersionAtLeast(buildVersion, COMMENT_MIN) &&
mt &&
typeof mt.interactionOpenApi === "function"
);
}
function postComment(payload, saveToAlbum) {
return callApi("interactionOpenApi", {
payload: payload,
saveToAlbum: saveToAlbum !== false
});
}
function getStorageInfo() {
return getBuildVersion().then(function (buildVersion) {
var mt = getMiniTool();
if (
!isClientVersionAtLeast(buildVersion, STORAGE_MIN) ||
!mt ||
typeof mt.getStorageInfo !== "function"
) {
return null;
}
return Promise.resolve(mt.getStorageInfo()).catch(function () {
return null;
});
});
}
function canUseFileSystem(buildVersion) {
var mt = getMiniTool();
return (
isClientVersionAtLeast(buildVersion, FILE_MIN) &&
mt &&
typeof mt.writeFile === "function" &&
typeof mt.readFile === "function"
);
}
function getUserDataPath() {
return getLaunchOptions().then(function (opts) {
var env = opts && opts.miniToolEnv;
return (env && env.userDataPath) || "";
});
}
function ensureDir(dirPath) {
var mt = getMiniTool();
if (!mt || typeof mt.mkdir !== "function") {
return Promise.resolve(false);
}
return Promise.resolve(mt.mkdir({ dirPath: dirPath, recursive: true }))
.then(function () {
return true;
})
.catch(function () {
return false;
});
}
function writeUtf8File(relativePath, data) {
return getBuildVersion().then(function (bv) {
if (!canUseFileSystem(bv)) {
return Promise.reject({ errMsg: "writeFile:fail version" });
}
return getUserDataPath().then(function (root) {
if (!root) return Promise.reject({ errMsg: "writeFile:fail no path" });
var rel = String(relativePath || "").replace(/^\/+/, "");
var parts = rel.split("/");
var dir =
parts.length > 1 ? root + "/" + parts.slice(0, -1).join("/") : root;
var filePath = root + "/" + rel;
return ensureDir(dir).then(function () {
return callApi("writeFile", {
filePath: filePath,
data: typeof data === "string" ? data : JSON.stringify(data),
encoding: "utf8"
}).then(function () {
return filePath;
});
});
});
});
}
function appendUtf8File(relativePath, data) {
return getBuildVersion().then(function (bv) {
if (!canUseFileSystem(bv)) {
return Promise.reject({ errMsg: "appendFile:fail version" });
}
var mt = getMiniTool();
if (!mt || typeof mt.appendFile !== "function") {
// Fallback: read + write
return readUtf8File(relativePath).then(function (prev) {
return writeUtf8File(
relativePath,
(prev || "") + (typeof data === "string" ? data : String(data))
);
});
}
return getUserDataPath().then(function (root) {
if (!root) return Promise.reject({ errMsg: "appendFile:fail no path" });
var rel = String(relativePath || "").replace(/^\/+/, "");
var parts = rel.split("/");
var dir =
parts.length > 1 ? root + "/" + parts.slice(0, -1).join("/") : root;
var filePath = root + "/" + rel;
return ensureDir(dir).then(function () {
return callApi("appendFile", {
filePath: filePath,
data: typeof data === "string" ? data : String(data),
encoding: "utf8"
}).then(function () {
return filePath;
});
});
});
});
}
function postVideoNote(opts) {
var videoRes = { video_url: opts.videoPath };
if (opts.coverPath) videoRes.cover_url = opts.coverPath;
return callApi("postNote", {
title: opts.title,
content: opts.content,
pageType: "video_publish",
mediaInfo: {
video_resources: videoRes
}
});
}
function readUtf8File(relativePath) {
return getBuildVersion().then(function (bv) {
if (!canUseFileSystem(bv)) {
return Promise.resolve(null);
}
return getUserDataPath().then(function (root) {
if (!root) return null;
var filePath = root + "/" + String(relativePath || "").replace(/^\/+/, "");
return callApi("readFile", { filePath: filePath, encoding: "utf8" })
.then(function (res) {
return res && res.data != null ? res.data : null;
})
.catch(function () {
return null;
});
});
});
}
/** Write dataURL to temp, then optionally persist via saveFile (9.49+). */
function persistImage(dataUrl, relativePath) {
return writeTempFile(dataUrl).then(function (tempPath) {
return getBuildVersion().then(function (bv) {
var mt = getMiniTool();
if (
!relativePath ||
!isClientVersionAtLeast(bv, FILE_MIN) ||
!mt ||
typeof mt.saveFile !== "function"
) {
return tempPath;
}
return getUserDataPath().then(function (root) {
if (!root) return tempPath;
var dest = root + "/" + String(relativePath).replace(/^\/+/, "");
var dir = dest.replace(/\/[^/]+$/, "");
return ensureDir(dir).then(function () {
return callApi("saveFile", {
tempFilePath: tempPath,
filePath: dest
})
.then(function (res) {
return (res && res.savedFilePath) || dest || tempPath;
})
.catch(function () {
return tempPath;
});
});
});
});
});
}
function getFileStorageInfo() {
return getBuildVersion().then(function (bv) {
var mt = getMiniTool();
if (
!isClientVersionAtLeast(bv, FILE_MIN) ||
!mt ||
typeof mt.getFileStorageInfo !== "function"
) {
return null;
}
return Promise.resolve(mt.getFileStorageInfo()).catch(function () {
return null;
});
});
}
function unlinkFile(relativeOrAbsPath) {
return getBuildVersion().then(function (bv) {
var mt = getMiniTool();
if (
!isClientVersionAtLeast(bv, FILE_MIN) ||
!mt ||
typeof mt.unlink !== "function"
) {
return false;
}
var path = String(relativeOrAbsPath || "");
if (!path) return false;
var p = Promise.resolve(path);
if (path.indexOf("/") !== 0 && path.indexOf("usr") !== 0) {
p = getUserDataPath().then(function (root) {
return root ? root + "/" + path.replace(/^\/+/, "") : path;
});
}
return p.then(function (filePath) {
return callApi("unlink", { filePath: filePath })
.then(function () {
return true;
})
.catch(function () {
return false;
});
});
});
}
function readDirList(relativeDir) {
return getBuildVersion().then(function (bv) {
var mt = getMiniTool();
if (
!isClientVersionAtLeast(bv, FILE_MIN) ||
!mt ||
typeof mt.readDir !== "function"
) {
return [];
}
return getUserDataPath().then(function (root) {
if (!root) return [];
var dirPath =
root +
(relativeDir
? "/" + String(relativeDir).replace(/^\/+|\/+$/g, "")
: "");
return callApi("readDir", { dirPath: dirPath })
.then(function (res) {
var files = (res && res.files) || [];
return files.map(function (f) {
if (typeof f === "string") {
return { name: f, path: dirPath + "/" + f };
}
var name = f.name || f.fileName || String(f);
return {
name: name,
path: f.path || f.filePath || dirPath + "/" + name,
isDir: !!(f.isDir || f.isDirectory)
};
});
})
.catch(function () {
return [];
});
});
});
}
function cleanupShareDir() {
return readDirList("share").then(function (files) {
var chain = Promise.resolve(0);
for (var i = 0; i < files.length; i++) {
(function (file) {
if (file.isDir) return;
chain = chain.then(function (n) {
return callApi("unlink", { filePath: file.path })
.then(function () {
return n + 1;
})
.catch(function () {
return n;
});
});
})(files[i]);
}
return chain;
});
}
/** Convert <input type=file> File / Blob to data URL (offline, no network). */
function blobToDataUrl(blob) {
return new Promise(function (resolve, reject) {
if (!blob) {
reject({ errMsg: "blobToDataUrl:fail empty" });
return;
}
try {
var reader = new FileReader();
reader.onload = function () {
resolve(reader.result);
};
reader.onerror = function () {
reject({ errMsg: "blobToDataUrl:fail read" });
};
reader.readAsDataURL(blob);
} catch (e) {
reject({ errMsg: "blobToDataUrl:fail " + (e && e.message) });
}
});
}
global.XhsBridge = {
getBuildVersion: getBuildVersion,
getClientVersion: getClientVersion,
isClientVersionAtLeast: isClientVersionAtLeast,
setLocalData: setLocalData,
getLocalData: getLocalData,
removeLocalData: removeLocalData,
clearLocalData: clearLocalData,
getStorageInfo: getStorageInfo,
getFileStorageInfo: getFileStorageInfo,
getLaunchOptions: getLaunchOptions,
readSnapshotInfo: readSnapshotInfo,
writeTempFile: writeTempFile,
saveImageToPhotosAlbum: saveImageToPhotosAlbum,
postNote: postNote,
canPostComment: canPostComment,
postComment: postComment,
canUseFileSystem: canUseFileSystem,
getUserDataPath: getUserDataPath,
writeUtf8File: writeUtf8File,
appendUtf8File: appendUtf8File,
readUtf8File: readUtf8File,
persistImage: persistImage,
unlinkFile: unlinkFile,
readDirList: readDirList,
cleanupShareDir: cleanupShareDir,
blobToDataUrl: blobToDataUrl,
postVideoNote: postVideoNote,
getMiniTool: getMiniTool,
STORAGE_MIN: STORAGE_MIN,
COMMENT_MIN: COMMENT_MIN,
FILE_MIN: FILE_MIN
};
})(window);