fix app invite gateway headers

This commit is contained in:
170-carry 2026-04-27 19:47:53 +08:00
parent f42551294f
commit c27a8acd8f

View File

@ -7,6 +7,14 @@ const ONLINE_INVITE_API_BASE = "https://jvapi.haiyihy.com/";
const ONLINE_INVITE_API_PREFIX = "/go";
const DEFAULT_AVATAR = "./assets/avatar-sample.png";
const DEFAULT_RESET_PERIOD_MS = 30 * 86400 * 1000;
const DEFAULT_REQUEST_HEADERS = {
"Req-Sys-Origin": "origin=LIKEI;originChild=LIKEI",
"Req-Client": "Android",
"Req-App-Intel": "version=1.0.0;build=1;channel=Web;model=H5;sysVersion=Web",
"Req-Lang": "en",
"Req-Version": "V2",
"Req-Zone": "Asia/Shanghai",
};
const DEFAULT_TASK_NOTES = {
transport: "(Only the highest-level bonuses may be awarded)",
quota: "(You'll receive a reward upon reaching each level)",
@ -37,6 +45,7 @@ const state = {
apiBase: resolveAPIBase(),
apiPrefix: resolveAPIPrefix(),
authorization: resolveAuthorization(),
requestHeaders: resolveRequestHeaders(),
landingCode: resolveLandingCode(),
inviteLink: query.get("inviteLink") || DEFAULTS.inviteLink,
inviteCode: query.get("inviteCode") || DEFAULTS.inviteCode,
@ -122,6 +131,8 @@ function hasAuthorizationInput() {
trimValue(query.get("authorization")) ||
trimValue(query.get("Authorization")) ||
trimValue(query.get("token")) ||
trimValue(query.get("token?")) ||
trimValue(query.get("accessToken")) ||
trimValue(query.get("auth")) ||
(typeof window.__APP_INVITE_AUTHORIZATION__ === "string" &&
trimValue(window.__APP_INVITE_AUTHORIZATION__)) ||
@ -214,6 +225,8 @@ function resolveAuthorization() {
query.get("authorization"),
query.get("Authorization"),
query.get("token"),
query.get("token?"),
query.get("accessToken"),
query.get("auth"),
typeof window.__APP_INVITE_AUTHORIZATION__ === "string"
? window.__APP_INVITE_AUTHORIZATION__
@ -234,6 +247,214 @@ function resolveAuthorization() {
return `Bearer ${normalized}`;
}
function resolveRequestHeaders() {
const headers = { ...DEFAULT_REQUEST_HEADERS };
const sysOrigin = trimValue(query.get("sysOrigin") || query.get("origin"));
const sysOriginChild = trimValue(
query.get("sysOriginChild") || query.get("originChild") || sysOrigin,
);
const reqClient = trimValue(query.get("reqClient") || query.get("client"));
const reqLang = trimValue(query.get("reqLang") || query.get("lang"));
const reqZone = trimValue(query.get("reqZone") || query.get("zone"));
const reqVersion = trimValue(query.get("reqVersion") || query.get("version"));
const reqImei = trimValue(query.get("reqImei") || query.get("imei"));
const appVersion = trimValue(query.get("appVersion")) || "1.0.0";
const buildVersion = trimValue(query.get("buildVersion") || query.get("build")) || "1";
const appChannel = trimValue(query.get("appChannel") || query.get("channel")) || "Web";
if (sysOrigin) {
headers["Req-Sys-Origin"] = `origin=${sysOrigin};originChild=${sysOriginChild || sysOrigin}`;
}
if (reqClient) {
headers["Req-Client"] = reqClient;
}
if (reqLang) {
headers["Req-Lang"] = reqLang;
}
if (reqZone) {
headers["Req-Zone"] = reqZone;
}
if (reqVersion) {
headers["Req-Version"] = reqVersion;
}
if (reqImei) {
headers["Req-Imei"] = reqImei;
}
headers["Req-App-Intel"] = buildAppIntelHeader({
version: appVersion,
build: buildVersion,
channel: appChannel,
model: trimValue(query.get("model")) || "H5",
sysVersion: trimValue(query.get("sysVersion")) || "Web",
imei: reqImei,
});
return headers;
}
function buildAppIntelHeader(parts) {
const items = [];
const version = trimValue(parts?.version) || "1.0.0";
const build = trimValue(parts?.build) || "1";
const channel = trimValue(parts?.channel) || "Web";
const model = trimValue(parts?.model);
const sysVersion = trimValue(parts?.sysVersion);
const imei = trimValue(parts?.imei);
items.push(`version=${version}`);
items.push(`build=${build}`);
items.push(`channel=${channel}`);
if (model) {
items.push(`model=${model}`);
}
if (sysVersion) {
items.push(`sysVersion=${sysVersion}`);
}
if (imei) {
items.push(`Req-Imei=${imei}`);
}
return items.join(";");
}
function splitHeaderPairs(value) {
const result = {};
trimValue(value)
.split(";")
.forEach((item) => {
const index = item.indexOf("=");
if (index > -1) {
result[item.slice(0, index)] = item.slice(index + 1);
}
});
return result;
}
function parseAccessOrigin(raw) {
const data = typeof raw === "string" ? JSON.parse(raw) : raw;
if (!data || typeof data !== "object") {
return {};
}
const appIntel = splitHeaderPairs(data["Req-App-Intel"] || data["req-app-intel"]);
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"] || data["req-sys-origin"]);
return {
authorization: data.Authorization || data.authorization,
reqLang: data["Req-Lang"] || data["req-lang"],
reqClient: data["Req-Client"] || data["req-client"],
reqImei: data["Req-Imei"] || data["req-imei"] || appIntel["Req-Imei"],
appVersion: appIntel.version,
buildVersion: appIntel.build,
appChannel: appIntel.channel,
model: appIntel.model,
sysVersion: appIntel.sysVersion,
sysOrigin: sysOrigin.origin,
sysOriginChild: sysOrigin.originChild || sysOrigin.child,
};
}
function setHeadersFromApp(info) {
if (!info || typeof info !== "object") {
return;
}
const next = {};
const authorization = normalizeAuthorizationValue(info.authorization);
if (authorization) {
state.authorization = authorization;
}
if (info.reqLang) {
next["Req-Lang"] = info.reqLang;
}
if (info.reqClient) {
next["Req-Client"] = info.reqClient;
}
if (info.reqImei) {
next["Req-Imei"] = info.reqImei;
}
if (info.sysOrigin) {
next["Req-Sys-Origin"] =
`origin=${info.sysOrigin};originChild=${info.sysOriginChild || info.sysOrigin}`;
}
if (info.appVersion || info.buildVersion || info.appChannel || info.model || info.sysVersion) {
next["Req-App-Intel"] = buildAppIntelHeader({
version: info.appVersion,
build: info.buildVersion,
channel: info.appChannel,
model: info.model,
sysVersion: info.sysVersion,
imei: info.reqImei,
});
}
state.requestHeaders = { ...state.requestHeaders, ...next };
}
function normalizeAuthorizationValue(value) {
const normalized = trimValue(value);
if (!normalized) {
return "";
}
if (/^bearer\s+/i.test(normalized)) {
return normalized;
}
return `Bearer ${normalized}`;
}
function connectToApp() {
return new Promise((resolve) => {
let finished = false;
const finish = (value) => {
if (finished) {
return;
}
finished = true;
resolve(value);
};
window.renderData = finish;
window.getIosAccessOriginParam = finish;
if (!(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp)) {
finish(null);
return;
}
let attempt = 0;
const poll = () => {
attempt += 1;
if (window.app && typeof window.app.getAccessOrigin === "function") {
try {
finish(window.app.getAccessOrigin());
} catch {
finish(null);
}
return;
}
if (attempt < 50) {
window.setTimeout(poll, 100);
return;
}
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
window.FlutterApp.postMessage("requestAccessOrigin");
}
window.setTimeout(() => finish(null), 300);
};
poll();
}).then((value) => {
if (value) {
try {
setHeadersFromApp(parseAccessOrigin(value));
} catch {
// App bridge data is best-effort; query token and default gateway headers still work.
}
}
});
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, (match) => {
const entityMap = {
@ -544,11 +765,13 @@ function extractErrorMessage(payload) {
async function requestJSON(path, options = {}) {
const { method = "GET", headers = {}, body, searchParams } = options;
const url = buildAPIURL(path, searchParams);
const requestHeaders = shouldAttachGatewayHeaders(path) ? state.requestHeaders : {};
const response = await fetch(url.toString(), {
method,
headers: {
Accept: "application/json",
...requestHeaders,
...headers,
},
body,
@ -565,6 +788,11 @@ async function requestJSON(path, options = {}) {
return payload?.body || payload?.data || payload || null;
}
function shouldAttachGatewayHeaders(path) {
const normalizedPath = joinURLPath(path);
return !normalizedPath.startsWith("/public/");
}
function joinURLPath(...parts) {
return `/${parts
.map((part) => trimValue(part).replace(/^\/+|\/+$/g, ""))
@ -945,6 +1173,7 @@ async function init() {
bindEvents();
setView(state.view);
setupCountdown();
await connectToApp();
await refreshPageData();
}