PageSourceSearch

https://xynergy.ai/_next/static/chunks/392-ae5e15677712a72f.js

js xynergy.ai collected 2026-10-05 16:01:35 UTC 10,943 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[392],{60392:(e,i,a)=>{a.d(i,{authService:()=>v});var t=a(24571),r=a(87777),l=a(71931),n=a(76458),s=a(18654);let o="loginTrackedAt";function c(e){try{let i=sessionStorage.getItem(`${o}:${e}`);if(!i)return!1;return Date.now()-Number(i)<9e4}catch{return!1}}class d{static instance;deviceFingerprint=null;inFlightByUser=new Map;constructor(){this.generateDeviceFingerprint()}static getInstance(){return d.instance||(d.instance=new d),d.instance}generateDeviceFingerprint(){let e=document.createElement("canvas"),i=e.getContext("2d");i&&(i.textBaseline="top",i.font="14px Arial",i.fillText("Device fingerprint",2,2));let a=[navigator.userAgent,navigator.language,screen.width+"x"+screen.height,new Date().getTimezoneOffset(),e.toDataURL()].join("|");this.deviceFingerprint=btoa(a).substring(0,32)}async getLocationInfo(){let e=Intl.DateTimeFormat().resolvedOptions().timeZone,i=navigator.language,a=Intl.Locale?new Intl.Locale(i).region:"未知";return`時區: ${e}, 語言: ${i}, 地區: ${a}`}async getIPAddress(){return"未知 IP"}async checkIfNewDevice(e){if(!this.deviceFingerprint||!n.db)return!0;try{let i=(0,t.rJ)(n.db,"userLoginLogs");return(await (0,t.GG)((0,t.P)(i,(0,t._M)("userId","==",e),(0,t._M)("deviceFingerprint","==",this.deviceFingerprint),(0,t.AB)(1)))).empty}catch(e){return!0}}async checkIfNewLocation(e,i){if(!n.db)return!0;try{let a=(0,t.rJ)(n.db,"userLoginLogs");for(let r of(await (0,t.GG)((0,t.P)(a,(0,t._M)("userId","==",e),(0,t.AB)(10)))).docs)if(r.data().location===i)return!1;return!0}catch(e){return!0}}async trackLogin(e,i){if(!n.db||!e)return;if(!i?.force&&c(e))return void(0,s.fH)("[loginTracker] skip duplicate trackLogin",{userId:e});let a=this.inFlightByUser.get(e);if(a)return(0,s.fH)("[loginTracker] join in-flight trackLogin",{userId:e}),a;let t=this.writeLoginLog(e,i);this.inFlightByUser.set(e,t);try{await t}finally{this.inFlightByUser.delete(e)}}async writeLoginLog(e,i){if(n.db&&!(!i?.force&&c(e))){try{sessionStorage.setItem(`${o}:${e}`,String(Date.now()))}catch{}try{let i=await this.getLocationInfo(),a=await this.getIPAddress(),[r,l]=await Promise.all([this.checkIfNewDevice(e),this.checkIfNewLocation(e,i)]),o={userId:e,timestamp:(0,t.serverTimestamp)(),deviceInfo:navigator.userAgent,location:i,ipAddress:a,isNewDevice:r,isNewLocation:l,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};this.deviceFingerprint&&(o.deviceFingerprint=this.deviceFingerprint),await (0,t.gS)((0,t.rJ)(n.db,"userLoginLogs"),o),(0,s.fH)("[loginTracker] userLoginLogs written",{userId:e,isNewDevice:r,isNewLocation:l})}catch(i){if(i?.code==="already-exists")return void(0,s.fH)("[loginTracker] userLoginLogs already written (retry collision)",{userId:e});throw(0,s.vV)("[loginTracker] trackLogin failed",i),i}}}}let u=d.getInstance();var m=a(47026),h=a(48716);function g(){if(n.functions)return n.functions;try{let e=(0,r.Sx)();return(0,l.getFunctions)(e,"asia-east1")}catch{return}}class f{defaultConfig={autoRedirect:!0,defaultRedirectPath:"/dashboard",trackLogin:!0,refreshExchangeRates:!0};async handleAuthSuccess(e,i,a){(0,s.MD)("[AuthService] handleAuthSuccess 被呼叫",{uid:e?.uid,hasCredential:!!i,credentialKeys:i?Object.keys(i).filter(e=>e.includes("line")||e.includes("Line")||"_lineEmail"===e||"_tokenResponse"===e):[]});let t={...this.defaultConfig,...a};try{if(await this.syncUserDataToFirestore(e,i),t.trackLogin)try{await u.trackLogin(e.uid)}catch(e){(0,s.vV)("記錄登入活動失敗:",e)}if(t.refreshExchangeRates)try{await (0,m.ME)()}catch(e){(0,s.vV)("更新匯率失敗:",e)}(0,h.setLoginState)(),(0,h.clearLogoutState)(),(0,h.clearOAuthRedirectState)()}catch(e){(0,s.vV)("處理登入成功邏輯時發生錯誤:",e)}}static isEmailAlreadyInUseError(e){let i=e?.code,a=e?.message??"";return"functions/already-exists"===i||"already-exists"===i||a.includes("此電子郵件已被使用")}async syncUserDataToFirestore(e,i){if((0,s.MD)("[AuthService] syncUserDataToFirestore 被呼叫",{uid:e?.uid,hasCredential:!!i}),!n.db){(0,s.vV)("Firestore 尚未初始化"),(0,s.MD)("[AuthService] 早期離開:db 為空");return}try{await e.getIdToken(!0);let a=(0,t.doc)(n.db,n.USERS,e.uid),r=await (0,t.getDoc)(a),o={uid:e.uid,email:e.email,displayName:e.displayName,photoURL:e.ph
1otoURL,emailVerified:e.emailVerified,providerData:e.providerData.map(e=>({providerId:e.providerId,uid:e.uid,displayName:e.displayName,email:e.email,photoURL:e.photoURL})),lastSignInTime:e.metadata.lastSignInTime,creationTime:e.metadata.creationTime,updatedAt:(0,t.serverTimestamp)()};if((0,s.MD)("[AuthService] syncUserDataToFirestore 開始",{uid:e.uid,hasCredential:!!i,userEmail:e.email,credentialLineEmail:i?._lineEmail??"(無)"}),(0,s.fH)("[AuthService] syncUserDataToFirestore 開始",{uid:e.uid,hasCredential:!!i,userEmail:e.email,credentialLineEmail:i?._lineEmail||"none"}),!o.email&&i&&e.providerData.some(e=>e.providerId===n.OIDC_LINE_PROVIDER_ID||e.providerId?.startsWith("oidc")))try{if((0,s.fH)("[AuthService] 處理 LINE 登入的 Email 取得",{uid:e.uid,hasProviderData:e.providerData.length>0}),(0,s.MD)("[AuthService] 開始處理 LINE 登入的 Email 取得",{uid:e.uid,hasCredential:!!i,hasLineEmail:!!i?._lineEmail,credentialLineEmail:i?._lineEmail||"none"}),i?._lineEmail)o.email=i._lineEmail,(0,s.MD)("[AuthService] ✅ 從 credential._lineEmail 取得 Email",{uid:e.uid,email:o.email}),(0,s.fH)("[AuthService] ✅ 從 credential._lineEmail 取得 Email",{uid:e.uid,email:o.email});else{(0,s.MD)("[AuthService] credential._lineEmail 不存在,嘗試從 ID Token 解析");try{let a=i?._tokenResponse,t=a?.oauthIdToken||a?.idToken;if((0,s.MD)("[AuthService] 檢查 credential 中的 ID Token",{hasTokenResponse:!!a,hasOauthIdToken:!!a?.oauthIdToken,hasIdToken:!!a?.idToken,lineIdTokenLength:t?.length||0}),t){let i=t.split(".");if(3===i.length){let a=i[1].replace(/-/g,"+").replace(/_/g,"/"),t=a+"=".repeat((4-a.length%4)%4),r=JSON.parse(atob(t));(0,s.MD)("[AuthService] ID Token payload 解析結果:",{payloadKeys:Object.keys(r||{}),hasEmail:"email"in(r||{}),email:r?.email}),r.email?(o.email=r.email,void 0!==r.email_verified&&(o.emailVerified=r.email_verified),(0,s.MD)("[AuthService] ✅ 從 credential ID Token 解析取得 Email",{uid:e.uid,email:o.email}),(0,s.fH)("[AuthService] ✅ 從 credential ID Token 解析取得 Email",{uid:e.uid,email:o.email})):((0,s.MD)("[AuthService] credential ID Token payload 中沒有 email",{payloadKeys:Object.keys(r||{})}),(0,s.vV)("[AuthService] credential ID Token payload 中沒有 email",{payloadKeys:Object.keys(r||{})}))}}else(0,s.MD)("[AuthService] credential 中沒有找到 ID Token",{hasTokenResponse:!!a,tokenResponseKeys:a?Object.keys(a):[]}),(0,s.vV)("[AuthService] credential 中沒有找到 ID Token",{hasTokenResponse:!!a,tokenResponseKeys:a?Object.keys(a):[]})}catch(e){(0,s.MD)("[AuthService] 解析 credential ID Token 失敗",e),(0,s.vV)("[AuthService] 解析 credential ID Token 失敗:",e)}}o.email?((0,s.MD)("[AuthService] ✅ LINE 登入 Email 已寫入 userData",{uid:e.uid,email:o.email}),(0,s.fH)("[AuthService] ✅ LINE 登入 Email 取得成功",{uid:e.uid,email:o.email,emailVerified:o.emailVerified})):(0,s.vV)("[AuthService] ⚠️ LINE 登入用戶缺少 Email",{uid:e.uid,displayName:e.displayName,providerData:e.providerData})}catch(e){(0,s.vV)("[AuthService] 處理 LINE 登入 Email 時發生錯誤:",e)}if((0,s.MD)("[AuthService] sync 分支前",{uid:e.uid,userDocExists:r.exists(),userDataEmail:o.email??"(null)"}),r.exists()){let i=o.email&&"string"==typeof o.email&&""!==o.email.trim();if(i){let i=g();if(i)try{(0,s.MD)("[AuthService] 現有用戶有 email,呼叫 initializeUserProfile callable",{uid:e.uid,email:o.email});let a=(0,l.httpsCallable)(i,"initializeUserProfile");await a({email:o.email,name:e.displayName||void 0}),(0,s.MD)("[AuthService] ✅ initializeUserProfile callable 成功(現有用戶)",{uid:e.uid,email:o.email}),(0,s.fH)("[AuthService] ✅ 已透過 initializeUserProfile callable 寫入/合併 email(現有用戶)",{uid:e.uid,email:o.email})}catch(i){(0,s.MD)("[AuthService] initializeUserProfile callable 失敗(現有用戶)",i),f.isEmailAlreadyInUseError(i)?(0,s.fH)("[AuthService] æ­¤ email 已被其他帳號使用,跳過寫入 email,登入流程照常繼續",{uid:e.uid,email:o.email}):(0,s.vV)("[AuthService] 現有用戶調用 initializeUserProfile 寫入 email 失敗:",i)}else(0,s.MD)("[AuthService] 無法取得 Functions 實例,無法呼叫 initializeUserProfile(現有用戶)")}let n=r.data(),c={...o};i?c.email=o.email:delete c.email,n?.emailVerified!==o.emailVerified&&(c.emailVerified=o.emailVerified),("string"==typeof n?.name?n.name.trim():"")?delete c.name:"string"==typeof e.displayName&&e.displayName.trim()&&(c.name=e.displayName.trim().slice(0,80)),await (0,t.setDoc)(a,c,{merge:!0})}else{if(o.email){let i=g();if(i)try{(0,s.MD)("[AuthService] 新用戶有 email,呼叫 initializeUserProfile callable",{uid:e.uid,email:o.email});let a=(0,l.httpsCallable)(i,"initializeUserProfile");await a({email:o.email,name:e.displayName||void 0}),(0,s.MD)("[AuthService] ✅ initializeUserProfile callable 成功(新用戶)",{uid:e.uid,email:o.email}),(0,s.fH)("[AuthService] ✅ 已透過 initializeUserProfile callable 建立 users 文件(含 email)",{uid:e.uid,email:o.email});return}catch(i){(0,s.MD)("[AuthService] initializeUserProfile callable 失敗(新用戶)",i),f.isEmailAlreadyInUseError(i)?(0,s.fH)("[AuthService] æ­¤ email 已被其他帳號使用,跳過寫入,改由 Gen2 觸發器建立 users 文件",{uid:e.uid}):(0,s.vV)("[AuthService] 調用 initializeUserProfile callable 失敗:",i)}else(0,s.MD)("[AuthService] 無法取得 Functions 實例,無法呼叫 initializeUserProfile(新用戶)"),(0,s.vV)("[AuthService] functions 未初始化,無法呼叫 initializeUserProfile");(0,s.fH)("[AuthService] 改為等待 Gen2 觸發器建立 users 文件",{uid:e.uid})}o.email||(0,s.fH)("[AuthService] 新用戶無 email,等待 Gen2 觸發器建立 users 文件",{uid:e.uid,providerIds:e.providerData.map(e=>e.providerId)});for(let i=0;i<10;i++){if((await (0,t.getDoc)(a)).exists())return void(0,s.fH)("[AuthService] Gen2 觸發器已建ç«
1‹ users 文件",{uid:e.uid});await new Promise(e=>setTimeout(e,500))}(0,s.fH)("[AuthService] ⚠️ Gen2 觸發器尚未建立 users 文件(逾時),useUserProfileLoader 將處理",{uid:e.uid});return}}catch(e){(0,s.vV)("同步使用者資料到 Firestore 失敗:",e.message)}}getRedirectPath(e){let i=localStorage.getItem("lineLoginReturnUrl");return i?(localStorage.removeItem("lineLoginReturnUrl"),i):e||this.defaultConfig.defaultRedirectPath}clearLoginFlags(){try{let{clearLoginState:e,clearOAuthRedirectState:i}=a(48716);e(),i(),sessionStorage.removeItem("lineLoginSuccess")}catch(e){}}}let v=new f}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.