const query = new URLSearchParams(window.location.search); const LOCAL_PROXY_API_BASE = "/api/"; const LOCAL_FILE_PROXY_API_BASE = "http://127.0.0.1:8800/api/"; const LOCAL_INVITE_API_BASE = "http://127.0.0.1:2900/"; const ONLINE_INVITE_API_BASE = "https://jvapi.haiyihy.com/"; const ONLINE_INVITE_API_PREFIX = "/go"; const DEFAULT_AVATAR = "./assets/avatar-sample.png"; const INVITEE_DISPLAY_GIFT_REWARD = 1000; const state = { apiBase: resolveAPIBase(), apiPrefix: resolveAPIPrefix(), inviteCode: resolveInviteCode(), friendName: trimValue(query.get("friendName")) || "Name", inviterAvatar: trimValue(query.get("inviterAvatar")) || DEFAULT_AVATAR, giftReward: INVITEE_DISPLAY_GIFT_REWARD, downloadUrl: trimValue(query.get("downloadUrl")), }; const els = { title: document.getElementById("landingTitle"), avatar: document.getElementById("inviterAvatar"), giftRewardAmount: document.getElementById("giftRewardAmount"), downloadButton: document.getElementById("downloadButton"), toast: document.getElementById("pageToast"), }; function trimValue(value) { return value == null ? "" : String(value).trim(); } function resolveInviteCode() { return trimValue( query.get("code") || query.get("landingCode") || query.get("invitationCode") || query.get("inviteCode"), ); } function isLocalHost(hostname = window.location.hostname) { return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"; } function isAbsoluteURL(value) { return /^https?:\/\//i.test(trimValue(value)); } function isOnlineAPIBase(base) { try { return new URL(base, window.location.href).origin === new URL(ONLINE_INVITE_API_BASE).origin; } catch { return false; } } function normalizeBaseURL(base) { const value = trimValue(base); const resolved = value || resolveDefaultAPIBase(); return resolved.endsWith("/") ? resolved : `${resolved}/`; } function resolveDefaultAPIBase() { if (window.location.protocol === "file:") { return LOCAL_FILE_PROXY_API_BASE; } if (window.location.protocol === "http:" && isLocalHost()) { return LOCAL_PROXY_API_BASE; } if (window.location.protocol === "https:" && !isLocalHost()) { return ONLINE_INVITE_API_BASE; } return LOCAL_INVITE_API_BASE; } function resolveAPIBase() { const queryBase = trimValue(query.get("apiBase")); if (queryBase) { return normalizeBaseURL(queryBase); } if (typeof window.__APP_INVITE_API_BASE__ === "string") { return normalizeBaseURL(window.__APP_INVITE_API_BASE__); } return normalizeBaseURL(resolveDefaultAPIBase()); } function resolveAPIPrefix() { const queryPrefix = trimValue(query.get("apiPrefix")); if (queryPrefix) { return queryPrefix; } if (typeof window.__APP_INVITE_API_PREFIX__ === "string") { return trimValue(window.__APP_INVITE_API_PREFIX__); } return isOnlineAPIBase(resolveAPIBase()) ? ONLINE_INVITE_API_PREFIX : ""; } function joinURLPath(...parts) { return `/${parts .map((part) => trimValue(part).replace(/^\/+|\/+$/g, "")) .filter(Boolean) .join("/")}`; } function buildAPIURL(path, searchParams = {}) { const apiPath = joinURLPath(state.apiPrefix, path); let url; if (isAbsoluteURL(state.apiBase)) { url = new URL(apiPath.replace(/^\/+/, ""), state.apiBase); } else { url = new URL(joinURLPath(state.apiBase, apiPath), window.location.href); } Object.entries(searchParams || {}).forEach(([key, value]) => { const raw = trimValue(value); if (raw) { url.searchParams.set(key, raw); } }); return url; } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, (match) => { const entityMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; return entityMap[match] || match; }); } function showToast(text) { if (!text) { return; } els.toast.textContent = text; els.toast.classList.add("show"); window.clearTimeout(showToast.timer); showToast.timer = window.setTimeout(() => { els.toast.classList.remove("show"); }, 1800); } function extractErrorMessage(payload) { return ( payload?.errorMsg || payload?.message || payload?.errorCodeName || payload?.code || "" ); } async function requestJSON(path, options = {}) { const { searchParams } = options; const response = await fetch(buildAPIURL(path, searchParams).toString(), { method: "GET", headers: { Accept: "application/json", }, }); const contentType = response.headers.get("content-type") || ""; const payload = contentType.includes("application/json") ? await response.json() : null; if (!response.ok || payload?.status === false) { throw new Error(extractErrorMessage(payload) || `Request failed (${response.status})`); } return payload?.body || payload?.data || payload || null; } function formatSignedReward(value) { const number = Number(value || 0); return `+${new Intl.NumberFormat("en-US").format(number)}`; } function render() { els.title.innerHTML = `Your friend [${escapeHtml(state.friendName)}]
has invited you to join`; els.avatar.src = trimValue(state.inviterAvatar) || DEFAULT_AVATAR; // els.giftRewardAmount.textContent = formatSignedReward(state.giftReward); } function applyLandingData(body) { if (!body || typeof body !== "object") { return; } state.friendName = trimValue(body.inviterNickname) || state.friendName; state.inviterAvatar = trimValue(body.inviterAvatar) || DEFAULT_AVATAR; state.downloadUrl = trimValue(body.downloadUrl) || state.downloadUrl; const inviteCode = trimValue(body.inviteCode); if (inviteCode) { state.inviteCode = inviteCode; } const shareTitle = trimValue(body.shareTitle); if (shareTitle) { document.title = shareTitle; } } function buildDownloadURL(downloadUrl, inviteCode) { const rawURL = trimValue(downloadUrl); const code = trimValue(inviteCode); if (!rawURL || !code) { return rawURL; } try { const url = new URL(rawURL, window.location.href); if (url.hostname.includes("play.google.com")) { const referrer = new URLSearchParams(url.searchParams.get("referrer") || ""); setPlayInstallReferrer(referrer, code); removeTopLevelInviteCodeParams(url.searchParams); url.searchParams.set("referrer", referrer.toString()); return url.toString(); } setInviteCodeParams(url.searchParams, code); return url.toString(); } catch { const separator = rawURL.includes("?") ? "&" : "?"; const params = new URLSearchParams(); setInviteCodeParams(params, code); return `${rawURL}${separator}${params.toString()}`; } } function setInviteCodeParams(searchParams, code) { searchParams.set("code", code); searchParams.set("invitationCode", code); searchParams.set("inviteCode", code); } function setPlayInstallReferrer(searchParams, code) { searchParams.set("invite_code", code); searchParams.set("source", "share"); } function removeTopLevelInviteCodeParams(searchParams) { ["code", "invitationCode", "inviteCode", "invite_code"].forEach((name) => { searchParams.delete(name); }); } async function loadLandingData() { if (!state.inviteCode) { showToast("Invitation code is missing"); return; } try { const body = await requestJSON("/public/h5/invite-campaign/landing", { searchParams: { code: state.inviteCode, }, }); applyLandingData(body); } catch (error) { showToast(trimValue(error?.message) || "Invite info load failed"); } finally { render(); } } function bindEvents() { els.downloadButton.addEventListener("click", () => { if (!state.downloadUrl) { showToast("Download link is not configured"); return; } window.location.href = buildDownloadURL(state.downloadUrl, state.inviteCode); }); } async function init() { render(); bindEvents(); await loadLandingData(); } init();