// NOTE: staging credentials. Calling an OAuth2 client_credentials flow from // the browser exposes CLIENT_SECRET to anyone who views the page source. const AUTH_CONFIG = { CLIENT_ID: "4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u", CLIENT_SECRET: "KPc4dMSztNwAIB3vii3geXrzC1mKUIsAztz3t2ylC3HlrgJudJWhdrtoY6XeRJIuadTAREYYsXk9XtFHUDfPVcJvyfHMpNkz2VvghwrijhVprok0VYV7XrOirJ5nFUxD", TOKEN_URL: "https://accounts-staging.gooyal.ir/oauth2/token/", SCOPE: "accounts.invite:submit", }; const INVITE_URL = "https://accounts-staging.gooyal.ir/users/api/invite_requests/"; const phoneInput = document.getElementById("phoneInput"); const submitBtn = document.getElementById("submitBtn"); const formMessage = document.getElementById("formMessage"); let accessToken = null; let tokenRequest = null; function setMessage(text, type) { formMessage.textContent = text || ""; formMessage.classList.remove("error", "success"); if (type) formMessage.classList.add(type); } function setButtonLoading(isLoading, label) { submitBtn.disabled = isLoading; submitBtn.textContent = label || "ثبت شماره و دانلود برنامه"; } async function fetchAccessToken() { const basicAuth = btoa(`${AUTH_CONFIG.CLIENT_ID}:${AUTH_CONFIG.CLIENT_SECRET}`); const response = await fetch(AUTH_CONFIG.TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization: `Basic ${basicAuth}`, }, body: new URLSearchParams({ grant_type: "client_credentials", scope: AUTH_CONFIG.SCOPE, }), }); if (!response.ok) { throw new Error(`Token request failed (${response.status})`); } const data = await response.json(); accessToken = data.access_token; return accessToken; } // Reuses an in-flight request so concurrent callers don't fire duplicate token requests. function ensureAccessToken() { if (accessToken) return Promise.resolve(accessToken); if (!tokenRequest) { tokenRequest = fetchAccessToken().finally(() => { tokenRequest = null; }); } return tokenRequest; } function getInviteCode() { return new URLSearchParams(window.location.search).get("inviteCode"); } // Picks where to send the user after a successful submission, based on platform. function getDownloadRedirectUrl() { const ua = navigator.userAgent || ""; if (/android/i.test(ua)) { return "https://myket.ir/app/com.adv.winsoo"; } if (/iphone|ipad|ipod/i.test(ua)) { return "https://web.winsoo.ir"; } return "https://winsoo.ir/#Download"; } const PERSIAN_DIGITS = "۰۱۲۳۴۵۶۷۸۹"; const ARABIC_INDIC_DIGITS = "٠١٢٣٤٥٦٧٨٩"; // Converts Persian (۰-۹) and Arabic-Indic (٠-٩) numerals to Latin digits. function toLatinDigits(str) { return str.replace(/[۰-۹٠-٩]/g, (char) => { const persianIndex = PERSIAN_DIGITS.indexOf(char); if (persianIndex !== -1) return String(persianIndex); return String(ARABIC_INDIC_DIGITS.indexOf(char)); }); } // Normalizes Iranian mobile numbers (09xxxxxxxxx, 9xxxxxxxxx, 0098/98/+98 prefixed) to E.164. function toE164(raw) { let cleaned = toLatinDigits(raw.trim()).replace(/[\s-]/g, ""); if (cleaned.startsWith("+98")) cleaned = cleaned.slice(3); else if (cleaned.startsWith("0098")) cleaned = cleaned.slice(4); else if (cleaned.startsWith("98")) cleaned = cleaned.slice(2); else if (cleaned.startsWith("0")) cleaned = cleaned.slice(1); if (!/^9\d{9}$/.test(cleaned)) return null; return `+98${cleaned}`; } async function submitInviteRequest(inviteCode, mobileNumber, token) { const response = await fetch(INVITE_URL, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ invite_code: inviteCode, mobile_number: mobileNumber, }), }); if (!response.ok) { let body = null; try { body = await response.json(); } catch (_) { // no JSON body } const error = new Error(body?.status_message || `Invite request failed (${response.status})`); error.status = response.status; error.body = body; throw error; } return response.json(); } async function handleSubmit() { phoneInput.classList.remove("invalid"); setMessage("", null); const inviteCode = getInviteCode(); if (!inviteCode) { setMessage("لینک دعوت نامعتبر است.", "error"); return; } const mobileNumber = toE164(phoneInput.value); if (!mobileNumber) { phoneInput.classList.add("invalid"); setMessage("شماره موبایل را به‌درستی وارد کنید.", "error"); return; } setButtonLoading(true, "در حال ارسال..."); try { const token = await ensureAccessToken(); try { await submitInviteRequest(inviteCode, mobileNumber, token); } catch (err) { // Token may have expired between page load and submit — retry once with a fresh one. if (err.status === 401) { accessToken = null; const freshToken = await ensureAccessToken(); await submitInviteRequest(inviteCode, mobileNumber, freshToken); } else { throw err; } } setMessage("ثبت شماره با موفقیت انجام شد.", "success"); setButtonLoading(true, "در حال انتقال..."); setTimeout(() => { window.location.href = getDownloadRedirectUrl(); }, 1200); return; } catch (err) { console.error(err); setMessage(err.message || "خطا در ارسال اطلاعات. لطفاً دوباره تلاش کنید.", "error"); } setButtonLoading(false); } submitBtn.addEventListener("click", handleSubmit); ensureAccessToken().catch((err) => { console.error("Failed to fetch access token on load:", err); });