/** * 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 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);