chatapp-h5/h5/bd-static/shared.js
2026-05-01 14:34:21 +08:00

663 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export const API_BASE_URL = "https://jvapi.haiyihy.com";
const text = {
en: {
language: "Language",
bd_leader_center: "BD Leader Center",
available_salaries: "Available salaries",
bd: "BD",
bd_leader: "BD Leader",
my_bd_salary: "My BD salary",
my_bd_leader_salary: "My BD leader salary",
exchange: "Exchange",
transfer: "Transfer",
cash_out: "Cash out",
details: "Details",
please_select_amount: "Please select amount",
please_select_coin_amount: "Please select coin amount",
please_select_payee: "Please select payee",
transfer_dollar_success: "Transfer succeeded",
transfer_dollar_failed: "Transfer failed",
transfer_error: "Transfer error",
exchange_success: "Exchange succeeded",
exchange_error_occurred: "Exchange failed",
amount: "Amount",
coins: "Coins",
dollar: "Dollar",
minimum_cash_out: "Minimum cash out is $100",
bd_policy_link: "BD policy",
bd_leader_policy: "BD leader policy",
bd_policy: "BD policy",
agency_list_link: "Agency list",
bd_list_link: "BD List",
invite_become_agent: "Invite to become agent",
invite_become_BD: "Invite to become BD",
bank_cards_link: "Bank cards",
bank_card: "Bank Card",
kyc: "KYC",
category: "Category",
card_number_label: "Card number",
card_number: "Card number",
payee: "Payee",
more: "More",
add_payment_method: "Add payment method",
complete_information: "Complete information",
passport_id_card_title: "Passport/ID card",
contact_number: "Contact number",
other_description: "Other description",
review_passed: "Review passed",
review_failed: "Review failed",
awaiting_review: "Awaiting review",
currently_in_use: "Currently in use",
all_bank_cards: "All bank cards",
no_bank_cards_in_use: "No bank cards in use",
no_bank_cards_added: "No bank cards added",
add_new_payment_method: "Add new payment method",
withdrawal_method: "Withdrawal method",
choose_withdrawal_method: "Choose withdrawal method",
card_name: "Card name",
enter_card_name: "Enter card name",
enter_card_number: "Enter card number",
enter_payee: "Enter payee",
delete: "Delete",
use: "Use",
using: "Using",
confirm: "Confirm",
submit: "Submit",
change_kyc: "Change KYC",
enter_contact_number: "Enter contact number",
enter_other_description: "Enter other description",
current_status: "Current status",
tips: "Tips",
confirm_modify_kyc: "Confirm modify KYC?",
cancel: "Cancel",
application_submitted: "Application submitted",
agency_number: "Agency number",
number_of_bd: "BD number",
bd_number: "BD number",
team_salary: "Team salary",
team_recharge: "Team recharge",
salary: "Salary",
recharge: "Recharge",
host: "Host",
agencies: "Agencies",
user_id_prefix: "ID: ",
revenue_details: "Revenue details",
bd_leader_history: "BD Leader history",
total_income: "Total income",
previous_period_income: "Previous period income",
income_details_label: "Income details",
team_top_up: "Team top up",
bd_income_salary: "BD income salary",
bd_income_top_up: "BD income top up",
total_team_salary: "Total team salary",
num_teams: "Num teams",
in_progress: "In Progress",
pending: "Pending",
completed: "Completed",
out_of_account: "Out of account",
information: "Information",
enter_user_id: "Enter User ID",
invite: "Invite",
no_users_found: "No users found",
success: "Success",
invitation_submitted: "Invitation submitted",
application_cancelled: "Application cancelled",
something_went_wrong: "Something went wrong",
cancellation_failed_try_again: "Cancellation failed, try again"
,
item_distribution: "Item distribution",
frames: "Frames",
vehicles: "Vehicles",
vip: "VIP",
send: "Send",
validity: "Validity",
day: " day",
enter_user_id_placeholder: "Enter user ID",
gift_delivery_successful: "Gift delivery successful",
available_salaries: "Available salaries",
details: "Details",
bd: "BD",
bd_leader: "BD Leader",
my_bd_salary: "My BD salary",
my_bd_leader_salary: "My BD leader salary",
exchange: "Exchange",
transfer: "Transfer",
cash_out: "Cash out",
please_select_amount: "Please select amount",
please_select_payee: "Please select payee",
transfer_dollar_success: "Transfer successful",
transfer_dollar_failed: "Transfer failed",
transfer_error: "Transfer error",
please_select_coin_amount: "Please select coin amount",
exchange_success: "Exchange successful",
exchange_error_occurred: "Exchange failed",
confirm_exchange: "Confirm exchange?",
fill_info_tips: "Please complete bank card and KYC information",
card_issue_tips: "Bank card information is required before cash out",
to_improve: "To improve",
cash_withdrawal_tips: "Cash withdrawal submitted",
enter_cash_out_amount: "Enter cash out amount",
cash_out_minimum: "Cash out minimum: $100",
change_withdrawal_method: "Change withdrawal method",
please_select_bank_card: "Please select bank card",
prompt: "Prompt",
failed_to_load_balance: "Failed to load balance"
},
ar: {},
tr: {},
bn: {}
};
["ar", "tr", "bn"].forEach((lang) => {
text[lang] = { ...text.en };
});
let headers = {
authorization: "",
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
"req-client": "Android",
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
"req-lang": "id-ID",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
"req-version": "V2",
"req-zone": "Asia/Shanghai",
"content-type": "application/json"
};
export const lang = resolveLanguage();
let appConnectionPromise = null;
export function t(key) {
return text[lang]?.[key] || text.en[key] || key;
}
export function applyLang() {
document.documentElement.lang = lang;
document.documentElement.dir = lang === "ar" ? "rtl" : "ltr";
document.querySelectorAll("[data-i18n]").forEach((node) => {
node.textContent = t(node.dataset.i18n);
});
const language = document.querySelector("[data-action='language']");
if (language) language.textContent = lang.toUpperCase();
}
function resolveLanguage() {
const params = new URLSearchParams(window.location.search);
const hash = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
const hashParams = new URLSearchParams(hash);
const value = params.get("lang") || hashParams.get("lang") || localStorage.getItem("preferred-language") || navigator.language || "en";
if (value.startsWith("ar")) return "ar";
if (value.startsWith("tr")) return "tr";
if (value.startsWith("bn")) return "bn";
return "en";
}
function splitHeaderPairs(value) {
const result = {};
if (!value) return result;
value.split(";").forEach((item) => {
const index = item.indexOf("=");
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
});
return result;
}
function mapHeaderValue(target, key, value) {
if (value === null || value === undefined || value === "" || target[key]) return;
target[key] = String(value);
}
function normalizeHeaderCandidate(value, depth = 0) {
if (depth > 4 || value === null || value === undefined) return {};
if (typeof value === "string") {
const trimmed = value.trim();
if (!trimmed) return {};
if (trimmed[0] === "{" || trimmed[0] === "[") {
try {
return normalizeHeaderCandidate(JSON.parse(trimmed), depth + 1);
} catch {
return {};
}
}
return /^bearer\s+/i.test(trimmed) ? { authorization: trimmed } : {};
}
if (Array.isArray(value)) {
return value.reduce((result, item) => ({ ...result, ...normalizeHeaderCandidate(item, depth + 1) }), {});
}
if (typeof value !== "object") return {};
const appIntel = splitHeaderPairs(value["Req-App-Intel"] || value["req-app-intel"]);
const sysOrigin = splitHeaderPairs(value["Req-Sys-Origin"] || value["req-sys-origin"]);
const normalized = {};
mapHeaderValue(normalized, "authorization", value.Authorization || value.authorization || value.token || value.accessToken);
mapHeaderValue(normalized, "reqLang", value["Req-Lang"] || value["req-lang"] || value.reqLang || value.lang || value.language);
mapHeaderValue(normalized, "appVersion", appIntel.version || value["App-Version"] || value.appVersion);
mapHeaderValue(normalized, "buildVersion", appIntel.build || value["Build-Version"] || value.buildVersion);
mapHeaderValue(normalized, "channel", appIntel.channel || value["App-Channel"] || value.appChannel || value.channel);
mapHeaderValue(normalized, "reqImei", value["Req-Imei"] || value["req-imei"] || value.reqImei || appIntel["Req-Imei"] || appIntel.reqImei);
mapHeaderValue(normalized, "origin", sysOrigin.origin || value["Sys-Origin"] || value.sysOrigin || value.origin);
mapHeaderValue(normalized, "child", sysOrigin.originChild || sysOrigin.child || value["Sys-Origin-Child"] || value.sysOriginChild || value.child);
["headerInfo", "headers", "auth", "data", "state"].forEach((key) => {
Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1));
});
Object.keys(value).forEach((key) => {
if (["headerInfo", "headers", "auth", "data", "state"].includes(key)) return;
if (value[key] && typeof value[key] === "object") {
Object.assign(normalized, normalizeHeaderCandidate(value[key], depth + 1));
}
});
return normalized;
}
function collectHeaderCandidate(collected, value) {
const normalized = normalizeHeaderCandidate(value);
if (Object.keys(normalized).length) collected.push(normalized);
}
function collectHeadersFromStorage(collected, storage) {
if (!storage) return;
["authorization", "Authorization", "token", "accessToken", "headerInfo", "headersFromApp", "appHeaders", "user", "userInfo"].forEach((key) => {
try {
collectHeaderCandidate(collected, storage.getItem(key));
} catch {}
});
try {
for (let index = 0; index < storage.length; index += 1) {
collectHeaderCandidate(collected, storage.getItem(storage.key(index)));
}
} catch {}
}
function queryHeaderCandidate() {
const params = new URLSearchParams(window.location.search);
return {
authorization: params.get("authorization") || params.get("Authorization") || params.get("token"),
reqLang: params.get("lang"),
appVersion: params.get("appVersion"),
buildVersion: params.get("buildVersion"),
channel: params.get("appChannel") || params.get("channel"),
reqImei: params.get("reqImei"),
origin: params.get("sysOrigin"),
child: params.get("sysOriginChild")
};
}
function discoverAppHeaders() {
const collected = [{ reqLang: lang }, queryHeaderCandidate()];
collectHeaderCandidate(collected, window.__APP_HEADERS__);
collectHeaderCandidate(collected, window.__HEADER_INFO__);
collectHeaderCandidate(collected, window.headerInfo);
collectHeadersFromStorage(collected, window.localStorage);
collectHeadersFromStorage(collected, window.sessionStorage);
return collected.reduce((result, item) => ({ ...result, ...item }), {});
}
function applyDiscoveredHeaders() {
const discovered = discoverAppHeaders();
if (headers.authorization) delete discovered.authorization;
if (Object.keys(discovered).length) setHeadersFromApp(discovered);
return discovered;
}
export function setHeadersFromApp(rawOrObject) {
const data = normalizeHeaderCandidate(rawOrObject || {});
const next = {};
if (data.authorization) next.authorization = data.authorization;
if (data.reqLang) next["req-lang"] = data.reqLang;
if (data.appVersion || data.buildVersion || data.channel) {
next["req-app-intel"] = [
data.appVersion ? `version=${data.appVersion}` : "",
data.buildVersion ? `build=${data.buildVersion}` : "",
data.channel ? `channel=${data.channel}` : ""
].filter(Boolean).join(";");
}
if (data.reqImei) next["req-imei"] = data.reqImei;
if (data.origin) next["req-sys-origin"] = [`origin=${data.origin}`, data.child ? `child=${data.child}` : ""].filter(Boolean).join(";");
headers = { ...headers, ...next };
}
function isAppEnvironment() {
return Boolean(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
}
function requestAccessOrigin() {
return new Promise((resolve) => {
let settled = false;
let timeoutId = null;
const finish = (raw) => {
if (settled) {
if (raw) setHeadersFromApp(raw);
return;
}
settled = true;
if (timeoutId) window.clearTimeout(timeoutId);
resolve(raw || null);
};
window.renderData = finish;
window.getIosAccessOriginParam = finish;
if (!isAppEnvironment()) {
setHeadersFromApp({
reqLang: lang,
appVersion: "1.0.0",
buildVersion: "1",
channel: "Web",
reqImei: "H5-STATIC",
origin: "LIKEI",
child: "LIKEI"
});
finish(null);
return;
}
const requestFromFlutter = () => {
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
window.FlutterApp.postMessage("requestAccessOrigin");
}
};
if (window.app && typeof window.app.getAccessOrigin === "function") {
finish(window.app.getAccessOrigin());
return;
}
requestFromFlutter();
timeoutId = window.setTimeout(() => finish(null), 12000);
let attempt = 0;
const poll = () => {
attempt += 1;
if (window.app && typeof window.app.getAccessOrigin === "function") {
finish(window.app.getAccessOrigin());
return;
}
if (attempt < 50) {
window.setTimeout(poll, 100);
return;
}
requestFromFlutter();
};
poll();
});
}
export async function connectToApp() {
if (!appConnectionPromise) {
applyDiscoveredHeaders();
appConnectionPromise = requestAccessOrigin()
.then((raw) => {
if (raw) setHeadersFromApp(raw);
else applyDiscoveredHeaders();
return { success: true, environment: isAppEnvironment() ? "app" : "browser", authenticated: Boolean(headers.authorization) };
})
.finally(() => {
appConnectionPromise = null;
});
}
return appConnectionPromise;
}
export async function request(method, path, body) {
applyDiscoveredHeaders();
const options = { method, headers: { ...headers } };
if (body !== undefined) options.body = JSON.stringify(body);
const response = await fetch(`${API_BASE_URL}${path}`, options);
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`);
error.response = data;
throw error;
}
return data;
}
export const get = (path) => request("GET", path);
export const post = (path, body) => request("POST", path, body);
export function navigateTo(target) {
const url = new URL(target, window.location.origin);
const currentLang = new URLSearchParams(window.location.search).get("lang");
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
window.location.href = `${url.pathname}${url.search}${url.hash}`;
}
export function closePage() {
const params = new URLSearchParams(window.location.search);
const from = params.get("from");
if (from) {
const target = new URL(from, window.location.origin);
const activeAction = params.get("activeAction");
if (activeAction) target.searchParams.set("activeAction", activeAction);
const currentLang = params.get("lang");
if (currentLang && !target.searchParams.has("lang")) target.searchParams.set("lang", currentLang);
window.location.href = `${target.pathname}${target.search}${target.hash}`;
return;
}
if (window.history.length > 1) {
window.history.back();
return;
}
if (window.app && typeof window.app.closePage === "function") {
window.app.closePage();
return;
}
if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") {
window.FlutterPageControl.postMessage("close_page");
}
}
export function bindHeader(titleKey) {
document.body.insertAdjacentHTML("afterbegin", `
<header class="header">
<button class="icon-btn" type="button" data-action="back" aria-label="Back"><img src="../bd-static/icons/back.svg" alt=""></button>
<h1>${t(titleKey)}</h1>
<button class="lang-btn" type="button" data-action="language">EN</button>
</header>
`);
document.querySelector("[data-action='back']").addEventListener("click", closePage);
document.querySelector("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
applyLang();
}
export function money(value) {
if (value === null || value === undefined || value === "") return "0";
const number = Number(value);
if (Number.isNaN(number)) return String(value);
return Number.isInteger(number) ? String(number) : number.toFixed(2);
}
const digitMaps = [
["٠", "0"], ["١", "1"], ["٢", "2"], ["٣", "3"], ["٤", "4"], ["٥", "5"], ["٦", "6"], ["٧", "7"], ["٨", "8"], ["٩", "9"],
["۰", "0"], ["۱", "1"], ["۲", "2"], ["۳", "3"], ["۴", "4"], ["۵", "5"], ["۶", "6"], ["۷", "7"], ["۸", "8"], ["۹", "9"],
["", "0"], ["১", "1"], ["২", "2"], ["৩", "3"], ["", "4"], ["৫", "5"], ["৬", "6"], ["", "7"], ["৮", "8"], ["৯", "9"]
];
const monthNames = new Map(Object.entries({
january: 1, jan: 1, "يناير": 1, "كانون الثاني": 1, ocak: 1, "জানুয়ারি": 1,
february: 2, feb: 2, "فبراير": 2, "شباط": 2, subat: 2, "şubat": 2, "ফেব্রুয়ারি": 2,
march: 3, mar: 3, "مارس": 3, "آذار": 3, mart: 3, "মার্চ": 3,
april: 4, apr: 4, "أبريل": 4, "ابريل": 4, "نيسان": 4, nisan: 4, "এপ্রিল": 4,
may: 5, "مايو": 5, "أيار": 5, mayis: 5, "mayıs": 5, "মে": 5,
june: 6, jun: 6, "يونيو": 6, "حزيران": 6, haziran: 6, "জুন": 6,
july: 7, jul: 7, "يوليو": 7, "تموز": 7, temmuz: 7, "জুলাই": 7,
august: 8, aug: 8, "أغسطس": 8, "اغسطس": 8, "آب": 8, agustos: 8, "ağustos": 8, "আগস্ট": 8,
september: 9, sep: 9, "سبتمبر": 9, "أيلول": 9, eylul: 9, "eylül": 9, "সেপ্টেম্বর": 9,
october: 10, oct: 10, "أكتوبر": 10, "اكتوبر": 10, "تشرين الأول": 10, ekim: 10, "অক্টোবর": 10,
november: 11, nov: 11, "نوفمبر": 11, "تشرين الثاني": 11, kasim: 11, "kasım": 11, "নভেম্বর": 11,
december: 12, dec: 12, "ديسمبر": 12, "كانون الأول": 12, aralik: 12, "aralık": 12, "ডিসেম্বর": 12
}));
function pad2(value) {
return String(value).padStart(2, "0");
}
function formatBillBelong(year, month, day, time) {
const monthNumber = Number(month);
const dayNumber = Number(day);
if (monthNumber < 1 || monthNumber > 12 || dayNumber < 1 || dayNumber > 31) return "";
return `${year}-${pad2(monthNumber)}-${pad2(dayNumber)}T${time}`;
}
function normalizeDateText(value) {
return digitMaps.reduce((textValue, [from, to]) => textValue.split(from).join(to), String(value || ""))
.replace(/&(?:rlm|lrm|nbsp);/gi, " ")
.replace(/[\u061c\u200e\u200f]/g, "")
.replace(/[،]/g, ",")
.replace(/\s+/g, " ")
.trim();
}
function normalizeTime(value, markerText = "") {
const match = String(value || "").match(/(\d{1,2}):(\d{2})(?::(\d{2}))?/);
if (!match) return "00:00:00";
let hour = Number(match[1]);
const marker = markerText.toLowerCase();
const isPm = /\bpm\b|مساء|(?:^|\s)م(?:\s|$)/.test(marker);
const isAm = /\bam\b|صباح|(?:^|\s)ص(?:\s|$)/.test(marker);
if (isPm && hour < 12) hour += 12;
if (isAm && hour === 12) hour = 0;
return `${pad2(hour)}:${match[2]}:${match[3] || "00"}`;
}
export function normalizeBillBelong(value) {
const textValue = normalizeDateText(value);
if (!textValue) return "";
const isoMatch = textValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
if (isoMatch) {
return formatBillBelong(isoMatch[1], isoMatch[2], isoMatch[3], `${isoMatch[4] || "00"}:${isoMatch[5] || "00"}:${isoMatch[6] || "00"}`);
}
const compactDateTime = textValue.match(/^(\d{4})(\d{2})(\d{2})(?:\D?(\d{2})(\d{2})(\d{2})?)?$/);
if (compactDateTime) {
return formatBillBelong(compactDateTime[1], compactDateTime[2], compactDateTime[3], `${compactDateTime[4] || "00"}:${compactDateTime[5] || "00"}:${compactDateTime[6] || "00"}`);
}
const numericDate = textValue.match(/\b(?:(\d{4})[./-](\d{1,2})[./-](\d{1,2})|(\d{1,2})[./-](\d{1,2})[./-](\d{4}))\b/);
if (numericDate) {
const time = normalizeTime(textValue, textValue);
if (numericDate[1]) return formatBillBelong(numericDate[1], numericDate[2], numericDate[3], time);
const first = Number(numericDate[4]);
const second = Number(numericDate[5]);
const month = first > 12 && second <= 12 ? second : first;
const day = second > 12 && first <= 12 ? second : first > 12 ? first : second;
return formatBillBelong(numericDate[6], month, day, time);
}
const lowerText = textValue.toLowerCase();
let month = 0;
for (const [name, valueForMonth] of monthNames) {
if (lowerText.includes(name)) {
month = valueForMonth;
break;
}
}
const yearMatch = textValue.match(/\b(20\d{2})\b/);
if (month && yearMatch) {
const withoutYear = textValue.replace(yearMatch[1], "");
const dayMatch = withoutYear.match(/\b([0-3]?\d)\b/);
if (dayMatch) {
return formatBillBelong(yearMatch[1], month, dayMatch[1], normalizeTime(textValue, textValue));
}
}
return "";
}
export function historyMoreUrl(basePath, billBelong) {
const normalized = normalizeBillBelong(billBelong);
return normalized ? `${basePath}?billBelong=${encodeURIComponent(normalized)}` : "";
}
export function safeJsonArray(value) {
try {
const result = JSON.parse(value || "[]");
return Array.isArray(result) ? result : [];
} catch {
return [];
}
}
export function getUserId() {
const keys = ["userInfo", "user", "USER_INFO"];
for (const key of keys) {
try {
const raw = localStorage.getItem(key) || sessionStorage.getItem(key);
if (!raw) continue;
const data = JSON.parse(raw);
if (data.id || data.userId) return data.id || data.userId;
} catch {}
}
return "";
}
export function toast(message) {
let node = document.getElementById("toast");
if (!node) {
node = document.createElement("div");
node.id = "toast";
node.className = "toast";
document.body.appendChild(node);
}
node.textContent = message;
node.hidden = false;
window.clearTimeout(toast.timer);
toast.timer = window.setTimeout(() => {
node.hidden = true;
}, 2500);
}
export function createMask(content) {
const old = document.getElementById("modalMask");
if (old) old.remove();
const mask = document.createElement("div");
mask.id = "modalMask";
mask.className = "mask";
mask.addEventListener("click", () => mask.remove());
content.addEventListener("click", (event) => event.stopPropagation());
mask.appendChild(content);
document.body.appendChild(mask);
return mask;
}
export function statusClass(status) {
const normalized = statusText(status);
if (normalized === t("completed")) return "completed";
if (normalized === t("pending")) return "pending";
if (normalized === t("out_of_account")) return "out";
return "";
}
export function statusText(status) {
const map = {
SETTLED: t("completed"),
PAY_OUT: t("completed"),
UNPAID: t("in_progress"),
HANG_UP: t("out_of_account"),
"In Progress": t("in_progress"),
Pending: t("pending"),
Completed: t("completed"),
"Out of account": t("out_of_account"),
"Out Of Account": t("out_of_account")
};
return map[status] || status || "";
}
export function isCompletedStatus(status) {
return status === "SETTLED" || status === "PAY_OUT" || statusText(status) === t("completed");
}
export function avatarError(img) {
img.addEventListener("error", () => {
img.removeAttribute("src");
});
}