Files
sdk/ts_browser/classes.idpclient.ts

319 lines
11 KiB
TypeScript

import { IdpRequests } from './classes.idprequests.js';
import * as plugins from './plugins.js';
export class IdpClient {
private helpers = {
async extractDataFromJwtString(jwtString: string): Promise<plugins.idpInterfaces.data.IJwt> {
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<plugins.idpInterfaces.data.TLoginStatus>();
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<string> {
return await this.ssoStore.get('idpJwt');
}
public async getRefreshToken(): Promise<string> {
return await this.ssoStore.get('idpRefreshToken');
}
public async getJwtData(): Promise<plugins.idpInterfaces.data.IJwt> {
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<string | null> {
const refreshToken = refreshTokenArg || (await this.getRefreshToken());
if (!refreshToken) {
return null;
}
await this.typedsocketDeferred.promise;
const refreshJwtReq = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>('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<string | null> {
await this.performJwtHousekeeping();
const refreshToken = await this.getRefreshToken();
if (!refreshToken) {
return null;
}
await this.typedsocketDeferred.promise;
const getTransferToken = this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>('exchangeRefreshTokenAndTransferToken');
const response = await getTransferToken.fire({
refreshToken,
appData: appDataArg || this.appData,
});
return response.transferToken;
}
public async getTransferTokenAndSwitchToLocation(newLocationArg: string): Promise<void> {
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<boolean> {
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<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>('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<boolean> {
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<plugins.idpInterfaces.request.ILogoutRequest>('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<plugins.typedsocket.TypedSocket>();
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<plugins.idpInterfaces.request.IReq_CreateOrganization>('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<plugins.idpInterfaces.request.IReq_GetRolesAndOrganizationsForUserId>('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<plugins.idpInterfaces.request.IReq_UpdatePaymentMethod>('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<plugins.idpInterfaces.request.IReq_WhoIs>('whoIs');
return whoIs.fire({ jwt: await this.getJwt() });
}
}