const SANSHENG_AUTH_CONFIG={ apiBase:'https://api.sanshengai.net', pcBase:'https://www.sanshengai.net', clientId:'sansheng-product-analysis-extension', scope:'product.analysis', callbackPath:'/extension/authorize' }; const AUTH_ACCESS_KEY='sansheng_extension_access'; const AUTH_REFRESH_KEY='sansheng_extension_refresh'; const AUTH_PENDING_KEY='sansheng_extension_auth_pending'; const AUTH_LAST_CONTEXT_KEY='sansheng_extension_auth_last_context'; function authBase64Url(bytes){return btoa(String.fromCharCode(...bytes)).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')} function authRandom(size=32){const bytes=new Uint8Array(size);crypto.getRandomValues(bytes);return authBase64Url(bytes)} async function authChallenge(verifier){const digest=await crypto.subtle.digest('SHA-256',new TextEncoder().encode(verifier));return authBase64Url(new Uint8Array(digest))} function authCallbackUrl(){return SANSHENG_AUTH_CONFIG.pcBase+SANSHENG_AUTH_CONFIG.callbackPath} async function getExtensionAuth(){ const session=await chrome.storage.session.get(AUTH_ACCESS_KEY); const local=await chrome.storage.local.get(AUTH_REFRESH_KEY); return {access:session[AUTH_ACCESS_KEY]||null,refresh:local[AUTH_REFRESH_KEY]||null}; } async function startExtensionLogin(){ const verifier=authRandom(48),state=authRandom(24),challenge=await authChallenge(verifier); const tabs=await chrome.tabs.query({active:true,currentWindow:true}); await chrome.storage.local.set({[AUTH_PENDING_KEY]:{verifier,state,createdAt:Date.now(),tabId:tabs?.[0]?.id||0,windowId:tabs?.[0]?.windowId||0}}); const query=new URLSearchParams({client_id:SANSHENG_AUTH_CONFIG.clientId,redirect_uri:authCallbackUrl(),scope:SANSHENG_AUTH_CONFIG.scope,state,code_challenge:challenge,code_challenge_method:'S256'}); return chrome.tabs.create({url:`${SANSHENG_AUTH_CONFIG.pcBase}${SANSHENG_AUTH_CONFIG.callbackPath}?${query}`,active:true}); } async function exchangeExtensionCode(callbackUrl){ const url=new URL(callbackUrl),code=url.searchParams.get('code'),state=url.searchParams.get('state'); if(!code||!state)return false; const pending=(await chrome.storage.local.get(AUTH_PENDING_KEY))[AUTH_PENDING_KEY]; if(!pending||pending.state!==state||Date.now()-pending.createdAt>10*60*1000)throw new Error('扩展授权请求已过期,请重新登录'); const body=new URLSearchParams({grant_type:'authorization_code',client_id:SANSHENG_AUTH_CONFIG.clientId,code,redirect_uri:authCallbackUrl(),code_verifier:pending.verifier}); const response=await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Token`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body}); const token=await response.json(); if(!response.ok||!token.access_token)throw new Error(token.error_description||'扩展登录失败'); await chrome.storage.session.set({[AUTH_ACCESS_KEY]:{token:token.access_token,type:token.token_type||'Sansheng',expiresAt:Date.now()+(token.expires_in||7200)*1000}}); await chrome.storage.local.set({[AUTH_REFRESH_KEY]:{token:token.refresh_token,expiresAt:Date.now()+(token.refresh_expires_in||2592000)*1000}}); await chrome.storage.local.set({[AUTH_LAST_CONTEXT_KEY]:{tabId:pending.tabId||0,windowId:pending.windowId||0,updatedAt:Date.now()}}); await chrome.storage.local.remove(AUTH_PENDING_KEY); return true; } async function refreshExtensionToken(){ const auth=await getExtensionAuth(); if(!auth.refresh?.token)throw new Error('扩展登录已过期'); const body=new URLSearchParams({grant_type:'refresh_token',client_id:SANSHENG_AUTH_CONFIG.clientId,refresh_token:auth.refresh.token}); const response=await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Token`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body}); const token=await response.json(); if(!response.ok||!token.access_token)throw new Error(token.error_description||'扩展登录已过期'); await chrome.storage.session.set({[AUTH_ACCESS_KEY]:{token:token.access_token,type:token.token_type||'Sansheng',expiresAt:Date.now()+(token.expires_in||7200)*1000}}); await chrome.storage.local.set({[AUTH_REFRESH_KEY]:{token:token.refresh_token,expiresAt:Date.now()+(token.refresh_expires_in||2592000)*1000}}); return token.access_token; } async function extensionApiFetch(path,options={}){ let auth=await getExtensionAuth(); if(!auth.access?.token||auth.access.expiresAt-Date.now()<60_000){await refreshExtensionToken();auth=await getExtensionAuth()} const send=()=>fetch(SANSHENG_AUTH_CONFIG.apiBase+path,{...options,headers:{...(options.headers||{}),Authorization:`Sansheng ${auth.access.token}`,'Sansheng-Source':'chrome-extension'}}); let response=await send(); if(response.status===401){await refreshExtensionToken();auth=await getExtensionAuth();response=await send()} return response; } async function logoutExtension(){ const auth=await getExtensionAuth(); if(auth.refresh?.token){ const body=new URLSearchParams({client_id:SANSHENG_AUTH_CONFIG.clientId,refresh_token:auth.refresh.token,access_token:auth.access?.token||''}); await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Revoke`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body}).catch(()=>{}); } await chrome.storage.session.remove(AUTH_ACCESS_KEY); await chrome.storage.local.remove([AUTH_REFRESH_KEY,AUTH_PENDING_KEY,AUTH_LAST_CONTEXT_KEY]); } self.sanshengExtensionAuth={config:SANSHENG_AUTH_CONFIG,get:getExtensionAuth,start:startExtensionLogin,exchange:exchangeExtensionCode,fetch:extensionApiFetch,logout:logoutExtension};