import { IdpRequests } from './classes.idprequests.js'; import * as plugins from './plugins.js'; export class IdpClient { private helpers = { async extractDataFromJwtString(jwtString: string): Promise { return plugins.webjwt.getDataFromJwtString(jwtString); }, }; public appData: plugins.idpInterfaces.data.IAppLegacy; public rolesReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1); public organizationsReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1); public parsedReceptionUrl: plugins.smarturl.Smarturl; constructor(receptionBaseUrlArg: string, appDataArg?: plugins.idpInterfaces.data.IAppLegacy) { if (receptionBaseUrlArg.endsWith('/')) { receptionBaseUrlArg = receptionBaseUrlArg.slice(0, -1); } if (!receptionBaseUrlArg.endsWith('/typedrequest')) { receptionBaseUrlArg = `${receptionBaseUrlArg}/typedrequest`; } this.parsedReceptionUrl = plugins.smarturl.Smarturl.createFromUrl(receptionBaseUrlArg); if (!appDataArg) { appDataArg = { id: '', appUrl: typeof window !== 'undefined' ? `https://${window.location.host}/` : '', description: '', logoUrl: '', name: '', }; } this.appData = appDataArg; } public requests = new IdpRequests(this); public checkWetherOnReceptionDomain() { return plugins.smarturl.Smarturl.createFromUrl(window.location.href).hostname === this.parsedReceptionUrl.hostname; } public async getAppDataOnSsoDomain() { if (!window.location.href.startsWith('https://sso.workspace.global/')) { console.error('You are trying to access SSO appData on a non sso domain.'); return null; } const appDataString = plugins.smarturl.Smarturl.createFromUrl(window.location.href).searchParams .appdata; if (!appDataString) { console.error('no appdata query arg detected'); return null; } return plugins.smartjson.parseBase64(appDataString); } public async setJwt(jwtStringArg: string) { await this.storeJwt(jwtStringArg); } public async setRefreshToken(refreshTokenArg: string) { await this.storeRefreshToken(refreshTokenArg); } public typedsocket!: plugins.typedsocket.TypedSocket; public typedrouter: any = new plugins.typedrequest.TypedRouter(); public statusObservable = new plugins.smartrx.rxjs.Subject(); public ssoStore = new plugins.webstore.WebStore({ storeName: 'idpglobalStore', dbName: 'main', }); public async storeJwt(jwtString: string) { await this.ssoStore.set('idpJwt', jwtString); } public async storeRefreshToken(refreshToken: string) { await this.ssoStore.set('idpRefreshToken', refreshToken); } public async getJwt(): Promise { return await this.ssoStore.get('idpJwt'); } public async getRefreshToken(): Promise { return await this.ssoStore.get('idpRefreshToken'); } public async getJwtData(): Promise { return this.helpers.extractDataFromJwtString(await this.getJwt()); } public async deleteJwt() { await this.ssoStore.delete('idpJwt'); } public async deleteRefreshToken() { await this.ssoStore.delete('idpRefreshToken'); } public async clearAuthState() { await Promise.all([this.deleteJwt(), this.deleteRefreshToken()]); } public async performJwtHousekeeping() { let jwt = await this.getJwt(); if (!jwt) { return null; } const extractedJwt = await this.helpers.extractDataFromJwtString(jwt); if (extractedJwt.data.refreshFrom < Date.now() && Date.now() < extractedJwt.data.validUntil) { jwt = await this.refreshJwt(); } else if (Date.now() > extractedJwt.data.validUntil) { await this.deleteJwt(); jwt = await this.refreshJwt(); } return jwt; } public async refreshJwt(refreshTokenArg?: string): Promise { const refreshToken = refreshTokenArg || (await this.getRefreshToken()); if (!refreshToken) { return null; } await this.typedsocketDeferred.promise; const refreshJwtReq = this.typedsocket.createTypedRequest('refreshJwt'); const response = await refreshJwtReq .fire({ refreshToken }) .catch(async () => { await this.clearAuthState(); return null; }); if (!response?.jwt) { await this.clearAuthState(); this.statusObservable.next(response?.status || 'loggedOut'); return null; } if (response.refreshToken) { await this.storeRefreshToken(response.refreshToken); } await this.storeJwt(response.jwt); this.statusObservable.next(response.status); return response.jwt; } public async getTransferToken(appDataArg?: plugins.idpInterfaces.data.IAppLegacy): Promise { await this.performJwtHousekeeping(); const refreshToken = await this.getRefreshToken(); if (!refreshToken) { return null; } await this.typedsocketDeferred.promise; const getTransferToken = this.typedsocket.createTypedRequest('exchangeRefreshTokenAndTransferToken'); const response = await getTransferToken.fire({ refreshToken, appData: appDataArg || this.appData, }); return response.transferToken; } public async getTransferTokenAndSwitchToLocation(newLocationArg: string): Promise { const transferToken = await this.getTransferToken(); if (!transferToken) { alert('failed to get transfer token!'); } const urlInstance = plugins.smarturl.Smarturl.createFromUrl(newLocationArg, { searchParams: { transfertoken: transferToken }, }); window.location.href = urlInstance.toString(); } public async processTransferToken(): Promise { const href = window.location.href; const url = plugins.smarturl.Smarturl.createFromUrl(href); const transferToken = url.searchParams['transfertoken']; if (transferToken) { await this.typedsocketDeferred.promise; const getTransferToken = this.typedsocket.createTypedRequest('exchangeRefreshTokenAndTransferToken'); const response = await getTransferToken.fire({ transferToken, appData: this.appData, }); if (response.refreshToken) { await this.refreshJwt(response.refreshToken); } else { globalThis.alert?.('transfer token invalid'); return false; } return true; } return false; } public async checkJwtPresent() { const jwt = await this.performJwtHousekeeping(); return !!jwt; } public async determineLoginStatus(requireLoginArg: boolean = false): Promise { const jwtPresent = await this.checkJwtPresent(); if (jwtPresent) { const jwt = await this.performJwtHousekeeping(); return !!jwt; } const refreshToken = await this.getRefreshToken(); if (refreshToken) { const jwt = await this.refreshJwt(refreshToken); if (jwt) { return true; } } const transferTokenResult = await this.processTransferToken(); if (transferTokenResult) { return true; } if (requireLoginArg) { const urlInstance = plugins.smarturl.Smarturl.createFromUrl( this.parsedReceptionUrl.clone().set('path', '/login').toString(), { searchParams: { appdata: plugins.smartjson.stringifyBase64(this.appData), }, }, ); if (!globalThis.location.href.startsWith(this.parsedReceptionUrl.toString())) { globalThis.location.href = urlInstance.toString(); } } return false; } public async logout() { const idpLogoutUrl = this.parsedReceptionUrl.clone().set('path', '/logout'); const refreshToken = await this.getRefreshToken(); if (!globalThis.location.href.startsWith(idpLogoutUrl.origin)) { await this.clearAuthState(); globalThis.location.href = idpLogoutUrl.toString(); return; } if (!refreshToken) { await this.clearAuthState(); window.location.href = this.parsedReceptionUrl.origin; return; } await this.enableTypedSocket(); const logoutTr = this.typedsocket.createTypedRequest('logout'); await logoutTr.fire({ refreshToken }); await this.clearAuthState(); const appData = await this.getAppDataOnSsoDomain(); if (appData) { window.location.href = appData.appUrl; } else if (window.location.href.startsWith(idpLogoutUrl.origin)) { window.location.href = this.parsedReceptionUrl.origin; } } public typedsocketDeferred = plugins.smartpromise.defer(); public async enableTypedSocket() { if (this.typedsocketDeferred.claimed) { return this.typedsocketDeferred.promise; } this.typedsocketDeferred.claim(); this.typedsocket = await plugins.typedsocket.TypedSocket.createClient( this.typedrouter, this.parsedReceptionUrl.toString(), ); this.typedsocketDeferred.resolve(this.typedsocket); return this.typedsocketDeferred.promise; } public async stop() { await this.typedsocket?.stop(); } public async createOrganization(orgNameArg: string, orgSlugArg: string, modeArg: 'checkAvailability' | 'manifest') { await this.typedsocketDeferred.promise; const validateOrg = this.typedsocket.createTypedRequest('createOrganization'); return validateOrg.fire({ jwt: await this.getJwt(), action: modeArg, organizationName: orgNameArg, organizationSlug: orgSlugArg, userId: (await this.getJwtData()).id, }); } public async getRolesAndOrganizations() { await this.typedsocketDeferred.promise; const rolesAndOrganizationsForUserId = this.typedsocket.createTypedRequest('getRolesAndOrganizationsForUserId'); return rolesAndOrganizationsForUserId.fire({ jwt: await this.getJwt(), userId: (await this.getJwtData()).id, }); } public async updatePaddleCheckoutId(orgIdArg: string, checkoutIdArg: string) { await this.typedsocketDeferred.promise; const updateBillingPlan = this.typedsocket.createTypedRequest('updatePaymentMethod'); return updateBillingPlan.fire({ jwtString: await this.getJwt(), orgId: orgIdArg, paddle: { checkoutId: checkoutIdArg }, }); } public async whoIs() { await this.typedsocketDeferred.promise; const whoIs = this.typedsocket.createTypedRequest('whoIs'); return whoIs.fire({ jwt: await this.getJwt() }); } }