weightxreps-oauth
Version:
Module to let your javascript app get user's credentials of [weightxreps.net](https://weightxreps.net/). It only focus on obtaining a valid access token, you are then responsible of adding it to your request's headers when connecting to the GraphQL endpoi
12 lines (11 loc) • 8.2 kB
JavaScript
var m=Object.defineProperty;var T=(n,t,e)=>t in n?m(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var i=(n,t,e)=>T(n,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react");class u{constructor(t){i(this,"listeners",new Set);this._value=t}get value(){return this._value}set value(t){this._value=t,Array.from(this.listeners).forEach(e=>e(t))}listen(t,e=!1){return this.listeners.add(t),e&&t(this._value),()=>{this.listeners.delete(t)}}get asReadOnly(){return this}}class v{constructor(t,e){i(this,"k");this.store=e,this.k=t+"--"}getItem(t){return this.store.getItem(this.k+t)}setItem(t,e){if(e==null){this.removeItem(t);return}this.store.setItem(this.k+t,e)}getObject(t){let e=this.getItem(t);if(e)try{return JSON.parse(e)}catch(r){console.error("Failed to recover stored object <"+(this.k+t)+"> from the localstore, got: ",r.message)}}setObject(t,e){this.setItem(t,JSON.stringify(e))}removeItem(t){this.store.removeItem(this.k+t)}}const S={fetch:window.fetch.bind(window),endpoint:"https://weightxreps.net/api/auth",asPopup:!1,redirectUri:void 0,store:localStorage,scope:"not-set"},h=class h extends EventTarget{constructor(e,r){super();i(this,"options");i(this,"instanceID","_"+Math.random().toString(36).substr(2,9));i(this,"_pkceCodeVerifier");i(this,"_token");i(this,"store");i(this,"_user",new u(void 0));i(this,"_onTokenUpdated",new u(void 0));i(this,"_isLoading",new u(!1));i(this,"_error",new u(void 0));this.clientId=e,this.options=Object.assign({...S},r),this.store=new v(e,this.options.store),this.setup()}get onLogged(){return this._user.asReadOnly}get onLoading(){return this._isLoading.asReadOnly}get onError(){return this._error.asReadOnly}get pkceCodeVerifier(){return this._pkceCodeVerifier??this.store.getItem("wxr-pkce-code-verifier")}set pkceCodeVerifier(e){this._pkceCodeVerifier=e,this.store.setItem("wxr-pkce-code-verifier",e)}set token(e){this._token=e,e||(this.pkceCodeVerifier=null),this.store.setObject("wxr-accessToken",e),this._onTokenUpdated.value=e}get token(){if(this._token)return this._token;let e=this.store.getObject("wxr-accessToken");return this._token=e,e}async getRequestHeadersAsync(e=!1){return await this.getFreshToken(e),this.getRequestHeadersSync()}getRequestHeadersSync(){return{Authorization:`${this.token.token_type} ${this.token.access_token}`}}setup(){this._isLoading.listen(e=>{e&&(this._error.value=void 0)}),requestAnimationFrame(async()=>{try{await this.continueLoginFlow()}catch(e){e.message==h.USER_MUST_LOGIN||(this._error.value=e.message)}})}async continueLoginFlow(){try{if(await this.getFreshToken(!1))return!0}catch(a){if(a.message!=h.USER_MUST_LOGIN)throw a}const e=window.location.href,r=new URL(e),o=new URLSearchParams(r.search),s=o.get("code"),d=o.get("error");if(s){await this.onAuthorizationCode(Object.fromEntries(o.entries()));const a=window.location.origin+window.location.pathname;return window.history.replaceState({},document.title,a),!0}else if(d)return this._error.value=d,!0;return!1}async onAuthorizationCode(e){if("error"in e)throw new Error(e.error);return await this.getAccessToken(e.code)}onWindowMessage(){return new Promise((e,r)=>{const o=s=>{let d=new URL(this.options.endpoint);if(s.origin===`${d.protocol}//${d.host}`){const a=s.data.weightxrepsOnOAuthResult;a&&(window.removeEventListener("message",o),e(a))}};window.addEventListener("message",o)})}onGetTokenResponse(e){if(e&&this.token==e)return this.token;if(e.error)throw new Error(e.error_description||e.error);return e.expirationTime=Date.now()+e.expires_in*1e3,this.token=e,this.token}async fetchToken(e="",r=!1){if(!r&&!this.pkceCodeVerifier)throw new Error("Code verifier was not found (you probably cleared the localStorage?)");let o=`${e?e+"&":""}client_id=${this.clientId}&`;o+=r?`refresh_token=${this.token.refresh_token}&grant_type=refresh_token`:`grant_type=authorization_code&code_verifier=${this.pkceCodeVerifier}`,r||(this.pkceCodeVerifier=null),this._isLoading.value=!0;try{return await this.options.fetch(this.options.endpoint+"/token",{method:"POST",body:o,headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then(s=>s.json()).then(s=>this.onGetTokenResponse(s)).then(s=>r?s:this.getUser().then(()=>s))}catch(s){throw s}finally{this._isLoading.value=!1}}async getAccessToken(e){return await this.fetchToken(`code=${e}`,!1)}async refreshToken(e=!0){if(!this.token)throw new Error("There's no token to refresh! (weird...)");try{var r=await this.fetchToken("",!0)}catch(o){if(o.message.startsWith("Refresh token not found")){if(e)return await this.redirectUserToLogin();throw new Error(h.USER_MUST_LOGIN)}else throw o}}mustBeHTTPS(){if(window.location.protocol!=="https:")throw new Error("Login requires HTTPS. Please access this site over a secure connection.")}async redirectUserToLogin(){const e=`${window.location.protocol}//${window.location.host}${window.location.pathname}`,r=await U(),o=await E(r),s="S256",d=this.options.asPopup?e:this.options.redirectUri??e;this.pkceCodeVerifier=r;const a=this.options.endpoint+"?grant_type=authorization_code&response_type=code&client_id="+encodeURIComponent(this.clientId)+"&redirect_uri="+encodeURIComponent(d)+"&state="+this.instanceID+"&code_challenge="+o+"&code_challenge_method="+s+"&scope="+this.options.scope;if(this.options.asPopup){const w=(screen.width-600)/2,_=(screen.height-600)/2;this._isLoading.value=!0,window.open(a,"weightxreps_oauth",`width=600,height=600,top=${_},left=${w},resizable=yes,scrollbars=yes`);try{var c=await this.onWindowMessage()}catch(y){throw y}finally{this._isLoading.value=!1}return await this.onAuthorizationCode(c)}else window.open(a,"_self"),await new Promise(()=>{})}async getFreshToken(e){if(this.token)return Date.now()>=this.token.expirationTime-5*60*1e3?(await this.refreshToken(e)).access_token:(this._user.value||await this.getUser(),this.token.access_token);if(e)return(await this.redirectUserToLogin()).access_token;throw new Error(h.USER_MUST_LOGIN)}async getUser(){var d,a;const e=await this.getRequestHeadersSync();this._isLoading.value=!0;try{var r=await fetch(this.options.endpoint.replace("auth","graphql"),{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify({operationName:"GetSession",query:`query GetSession {
getSession {
user {
id
uname
email
}
}
}
`})})}catch(c){throw c}finally{this._isLoading.value=!1}r.status==401&&this.logout();const o=await r.json();if(o.errors)throw new Error(o.errors.flatMap(c=>c.message).join(`
`));if(o.error)throw new Error(o.error);const s=(a=(d=o.data)==null?void 0:d.getSession)==null?void 0:a.user;if(!s)throw new Error("Unexpected server rersponse...");return this._user.value=s,s}async login(){this._isLoading.value=!0;try{await this.getFreshToken(!0)}catch(e){this._error.value=e.message}finally{this._isLoading.value=!1}}logout(){this.token=void 0,this._user.value=void 0}};i(h,"USER_CANCELED_AUTH_ERROR","user_canceled"),i(h,"USER_DECLINED_AUTH_ERROR","user_declined"),i(h,"USER_MUST_LOGIN","must_login"),i(h,"dicc",new Map),i(h,"get",(e,r)=>{if(h.dicc.has(e))return h.dicc.get(e);const o=new h(e,r);return h.dicc.set(e,o),o});let g=h;const k=n=>btoa(String.fromCharCode(...new Uint8Array(n))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),U=async()=>{const n=new Uint8Array(32);return window.crypto.getRandomValues(n),k(n)},E=async n=>{const e=new TextEncoder().encode(n),r=await window.crypto.subtle.digest("SHA-256",e);return k(r)},L=(n,t)=>{const[e,r]=l.useState(void 0),[o,s]=l.useState(),[d,a]=l.useState(),c=g.get(n,t);return l.useEffect(()=>{let p=c.onLogged.listen(r,!0),f=c.onError.listen(s,!0),w=c.onLoading.listen(a,!0);return()=>{p(),f(),w()}},[]),{login:c.login.bind(c),getAuthHeaders:c.getRequestHeadersAsync.bind(c),user:e,error:o,logout:c.logout.bind(c),loading:d}};exports.OAuthClient=g;exports.useWeightxrepsOAuth=L;
;