640 lines
18 KiB
JavaScript
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);
|