From c27a8acd8f83f7a2b0c39cf46830ccb003d1975f Mon Sep 17 00:00:00 2001 From: 170-carry Date: Mon, 27 Apr 2026 19:47:53 +0800 Subject: [PATCH] fix app invite gateway headers --- h5/app-invite/app.js | 229 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/h5/app-invite/app.js b/h5/app-invite/app.js index 5805531..23a3557 100644 --- a/h5/app-invite/app.js +++ b/h5/app-invite/app.js @@ -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(); }