chatapp-h5/h5/app-invite/landing.js
2026-04-28 11:02:31 +08:00

300 lines
7.8 KiB
JavaScript

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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};
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)}]<br />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();