import{STORAGE_TOKENS_NAME as e}from"./constants.js";import{TypedEventTarget as t}from"./TypedEventTarget.js";const r=new Error;r.stack="";const o=new t;export class Tokens{origin;refresh;access;expires;_revoked=new AbortController;constructor(e,t,r,o){this.origin=e,this.refresh=t,this.access=r,this.expires=o}async activate(e,t){if(this._revoked.signal.aborted)throw new Error("Token revoked");return!(e&&this.expires<Date.now()&&!await this._fetchNewAccessToken())&&(this._sync(),e&&this._startRefreshTokensLoop(t),!0)}async revoke(e,t){this._revoked.abort();try{if(!(await fetch(`${this.origin}/oauth/revoke`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:this.refresh,token_type_hint:"refresh_token",client_id:e}),mode:"cors"})).ok)throw new Error("Failed to logout")}catch(e){if(!t)throw e}clearTokensInStorage()}static fromStorage(){const t=function readTokensFromStorage(){const t=localStorage.getItem(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}}();return t?new Tokens(t.origin,t.refresh,t.access,t.expires):null}static async fromAuthCode({editorOrigin:e,clientId:t,codeVerifier:r,authCode:o,redirectUri:n}){const s=await fetch(`${e}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:t,code:o,code_verifier:r,grant_type:"authorization_code",redirect_uri:n}),mode:"cors"});if(!s.ok)throw new Error(`Failed to fetch token: ${s.status}`);const i=await s.json();assertTokenResponse(i);const{access_token:a,refresh_token:c}=i,h=getExpiresFromTokenResponse(i);return new Tokens(e,c,a,h)}async _fetchNewAccessToken(){try{const e=await fetch(`${this.origin}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:this.refresh}),mode:"cors",signal:this._revoked.signal});if(!e.ok)throw r;const t=await e.json();assertTokenResponse(t);const{access_token:o,refresh_token:n}=t,s=getExpiresFromTokenResponse(t);return this.access=o,this.expires=s,this.refresh=n,!0}catch{return clearTokensInStorage(),!1}}_sync(){!function persistTokensInStorage(t){localStorage.setItem(e,JSON.stringify(t))}(this),function fireAccessTokenChanged(e){o.fireEvent(e)}(this.access)}async _startRefreshTokensLoop(e){for(;;){const t=this.expires-Date.now()-1e3;if(await wait(Math.max(t,1e3)),this._revoked.signal.aborted)return;if(!this._fetchNewAccessToken())return void e();this._sync()}}}export function clearTokensInStorage(){localStorage.removeItem(e)}export function addAccessTokenChangedListener(e){return o.listen(e)}function getExpiresFromTokenResponse({created_at:e,expires_in:t}){return 1e3*(e+t)}function assertTokenResponse(e){if("object"!=typeof e||!e)throw new Error("Invalid Token Response");if("string"!=typeof e.access_token||"string"!=typeof e.refresh_token||"number"!=typeof e.created_at||"number"!=typeof e.expires_in)throw new Error("Invalid Token Response")}function wait(e){return new Promise((t=>setTimeout(t,e)))}