Files

120 lines
5.4 KiB
TypeScript

export interface VersionPolicy {
force: boolean;
minVersion: string | null;
apiBlacklist: string[];
}
// Empty means all players; otherwise only these verified WeChat openids are checked.
export const openidWhitelist: string[] = [
"o0tJD3asJusHgpSq35y7qOODjb8M", // 420418
"o0tJD3fvbo5eBKBxJ1ZJ9l1fM_X4", // 916541
"o0tJD3bAysG5Y3e93RikOtyLyN58", // 100057
"o0tJD3R-js9YQIz24WGNNWepW1j0", // 1327711
"o0tJD3dPjo9ghGoZ85q4QkAi4rKM", // 100022
];
export function shouldCheckPlayerVersion(openid: unknown): boolean {
return openidWhitelist.length === 0 || (typeof openid === "string" && openidWhitelist.includes(openid));
}
// Publish this internal module after editing. Select the SERVER release, never the request version.
export const currentVersion = "1.0.88";
export const versions: Record<string, VersionPolicy> = {
"1.0.82": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.83": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.84": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.85": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.86": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.87": { force: false, minVersion: null, apiBlacklist: [] },
"1.0.88": { force: false, minVersion: "1.0.88", apiBlacklist: ["login", "dayTaskInfo"] }
};
// Explicit version-check exemptions, including all currently called frontend APIs.
// Entries here take precedence over apiBlacklist, openidWhitelist and policy validation.
const nonPlayerFunctions = new Set([
// 微信支付相关
"wx/payCallBack", "wx/iosPayCall", "wx/KeFuInfo", "wx/getOrderReward", "wx/getPayInfo", "wx/iosgetPayInfo", "wx/iosorderPaySig", "wx/orderPaySig",
"suRecall",
// 黄金矿工
"goldMiner/merchantNotify", "goldMiner/index",
// 用户付费统计
"rechargeStats", "repairPayUser",
"clearPay", "clearSrank", "clearPHBDataById", "creatCdk", "creatCDkTwo",
"searchOrder", "searchPlayerDataByLevel",
// Frontend API names after URL routing (getAddressRankSelf -> getAddressRank).
"activityConfig/list", "address", "canRebornGift", "cloudRise/index", "comment",
// "dayTaskInfo", "getTaskReward",
"drawCat", "drawHead", "getAddressRank", "getFilm", "getHeadInfo",
"getRank", "getSRank", "getShareLv", "getUserInfoByID",
"jungleTreasure", "limitedTimeEvent",
// "login",
"miniProgramWelfare",
"monthGetReward", "monthlyCard", "passCheck", "passCheckLv", "passCheckSettlement",
"passCheckUpgrade", "pyramid/api", "rebornGift", "redeem", "setCatArr",
"setForcedUpdate", "setUserLevel", "setWinStreak", "shareLvSuccess", "shopDouble",
"signInActivityInfo", "userCoin", "userData", "userHealth", "userLevel", "userPower",
"userProp", "wucaiTransfer",
]);
export function isNonPlayerFunction(name: string): boolean {
return name.startsWith("__") || nonPlayerFunctions.has(name)
|| /(?:Admin|Jobs)$/.test(name) || /(?:^|\/)(?:admin|jobs|testPeriods)(?:\/|$)/.test(name);
}
function parseVersion(value: unknown): number[] | null {
if (typeof value !== "string" || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value)) return null;
const parts = value.split(".").map(Number);
return parts.every(Number.isSafeInteger) ? parts : null;
}
/** null means malformed; otherwise -1 / 0 / 1 (numeric components, not string ordering). */
export function compareVersions(left: unknown, right: unknown): number | null {
const a = parseVersion(left), b = parseVersion(right);
if (!a || !b) return null;
for (let index = 0; index < 3; index++) {
if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
}
return 0;
}
export function configUnavailable() {
return { code: 0, errorCode: "CLIENT_UPDATE_CONFIG_UNAVAILABLE", data: null, msg: "服务暂不可用,请稍后重试" };
}
export function validatePolicy(release: string, policy: VersionPolicy): boolean {
if (!parseVersion(release) || !policy || typeof policy.force !== "boolean"
|| !Array.isArray(policy.apiBlacklist)
|| !policy.apiBlacklist.every(name => typeof name === "string" && /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(name))) return false;
if (policy.minVersion === null) return !policy.force;
const compared = compareVersions(policy.minVersion, release);
return compared !== null && compared <= 0 && (!policy.force || compared === 0);
}
/** Returns null to continue normal authentication/business logic, otherwise a business error. */
export function checkClientVersion(
functionName: string,
clientVersion: unknown,
release = currentVersion,
policies: Record<string, VersionPolicy> = versions,
) {
if (isNonPlayerFunction(functionName)) return null;
if (!Array.isArray(openidWhitelist)
|| !openidWhitelist.every(openid => typeof openid === "string" && /^[A-Za-z0-9_-]+$/.test(openid))) return configUnavailable();
const policy = policies && Object.prototype.hasOwnProperty.call(policies, release) ? policies[release] : null;
if (!validatePolicy(release, policy)) return configUnavailable();
if (functionName !== "login" && !policy.apiBlacklist.includes(functionName)) return null;
if (policy.minVersion === null) return null;
const compared = compareVersions(clientVersion, policy.minVersion);
if (compared !== null && compared >= 0) return null;
return {
code: 0,
errorCode: "CLIENT_UPDATE_REQUIRED",
msg: "当前游戏版本过低,请更新后重新进入",
data: { version: release, minVersion: policy.minVersion },
};
}
export default async function (_ctx: FunctionContext) {
return { data: 'hi, laf' }
}