Add product analysis extension release
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
const SANSHENG_AUTH_CONFIG={
|
||||
apiBase:'http://localhost:19174',
|
||||
pcBase:'http://localhost:5173',
|
||||
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};
|
||||
Reference in New Issue
Block a user