UNPKG

kinde-angular

Version:

Angular wrapper for the Kinde TypeScript SDK

349 lines (335 loc) 15.3 kB
import * as i0 from '@angular/core'; import { VERSION, InjectionToken, Injectable, Inject, PLATFORM_ID, NgModule, inject, TemplateRef, ViewContainerRef, Directive, Input, makeEnvironmentProviders } from '@angular/core'; import { BehaviorSubject, ReplaySubject, scan, filter, distinctUntilChanged, switchMap, merge, defer, mergeMap, shareReplay, of, combineLatestWith, concatMap, Subject, iif, tap, takeUntil, from, map, take } from 'rxjs'; import { createKindeBrowserClient } from '@kinde-oss/kinde-typescript-sdk'; export { GrantType } from '@kinde-oss/kinde-typescript-sdk'; import * as i1 from '@angular/common'; import { isPlatformBrowser, DOCUMENT } from '@angular/common'; import * as Cookies from 'es-cookie'; import { Router } from '@angular/router'; class CookieManager { static getCookie(name) { return Cookies.get(name); } static setCookie(name, value, options = {}) { Cookies.set(name, value, { path: '', secure: true, sameSite: 'lax', ...options }); } static deleteCookie(name, options) { Cookies.remove(name, options); } } const keysInCookie = ['refresh_token', 'access_token', 'acwpf-state-key', 'ac-state-key', 'id_token', 'user', 'post_login_redirect_url']; const memCache = {}; const sessionManager = { async getSessionItemBrowser(key) { return CookieManager.getCookie(key) || memCache[key]; }, async getSessionItem(key) { return this.getSessionItemBrowser(key); }, async setSessionItemBrowser(key, value) { const inCookieList = keysInCookie.find(k => key.includes(k)); if (inCookieList) { CookieManager.setCookie(key, value); } else { memCache[key] = value; } }, async setSessionItem(key, value) { await this.setSessionItemBrowser(key, value); }, async removeSessionItemBrowser(key) { for (const key in memCache) { delete memCache[key]; } CookieManager.deleteCookie(key, { path: '' }); }, async removeSessionItem(key) { await this.removeSessionItemBrowser(key); }, async destroySession() { for (const key in memCache) { delete memCache[key]; } for (const key of keysInCookie) { CookieManager.deleteCookie(key, { path: '' }); } } }; class KindeClientFactory { static createClient(config, platform) { if (isPlatformBrowser(platform)) { return createKindeBrowserClient({ framework: 'Angular', frameworkVersion: VERSION.full, ...config, sessionManager: sessionManager }); } return null; } } const KINDE_FACTORY_TOKEN = new InjectionToken('KINDE_FACTORY_TOKEN'); class AuthStateService { constructor(kindeClient) { this.kindeClient = kindeClient; /** * A variant of Subject that requires an initial value and emits its current value whenever it is subscribed to. * We use BehaviorSubject because we want the initial value to be true */ this.isLoadingSubject$ = new BehaviorSubject(true); /** * A variant of Subject that "replays" old values to new subscribers by emitting them when they first subscribe. * We buffer the last emitted value and emit it to new subscribers if subscribed. */ this._accessToken$ = new ReplaySubject(1); this._user$ = new ReplaySubject(1); this.accessTokenStream$ = this._accessToken$.pipe(scan((acc, token) => ({ prev: acc.current, current: token, }), { prev: null, current: null }), filter(state => state.current !== state.prev)); this.isLoading$ = this.isLoadingSubject$.asObservable(); this.isAuthenticatedStream$ = this.isLoading$.pipe(filter(isLoading => !isLoading && this.kindeClient !== null), distinctUntilChanged(), switchMap(() => merge(defer(() => this.kindeClient.isAuthenticated()), this.accessTokenStream$.pipe(mergeMap(() => this.kindeClient.isAuthenticated()))))); this.isAuthenticated$ = this.isAuthenticatedStream$.pipe(distinctUntilChanged(), shareReplay(1)); this._userCache$ = this.isAuthenticatedStream$.pipe(filter(_ => this.kindeClient !== null), switchMap(isAuthenticated => isAuthenticated ? this.kindeClient.getUser() : of(null)), shareReplay(1)); this.user$ = this.isAuthenticatedStream$.pipe(filter(_ => this.kindeClient !== null), combineLatestWith(this._userCache$), concatMap(([isAuthenticated, cachedUser]) => { if (cachedUser) { return of(cachedUser); } return isAuthenticated ? this.kindeClient.getUserProfile() : of(null); })); this.accessToken$ = this.isAuthenticatedStream$.pipe(filter(_ => this.kindeClient !== null), concatMap(isAuthenticated => isAuthenticated ? this.kindeClient.getToken() : of(null))); } setIsLoading(isLoading) { this.isLoadingSubject$.next(isLoading); } setAccessToken(accessToken) { this._accessToken$.next(accessToken); } setUser(user) { this._user$.next(user); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: AuthStateService, deps: [{ token: KINDE_FACTORY_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: AuthStateService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: AuthStateService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [KINDE_FACTORY_TOKEN] }] }] }); class KindeAngularService { constructor(kindeClient, document, location, authState) { this.kindeClient = kindeClient; this.document = document; this.location = location; this.authState = authState; this.unsubscribe$ = new Subject(); this.user$ = this.authState.user$; this.isAuthenticated$ = this.authState.isAuthenticated$; this.isLoading$ = this.authState.isLoading$; this.accessToken$ = this.authState.accessToken$; this.shouldHandleCallback() .pipe(filter(_ => this.kindeClient !== null), switchMap(shouldHandleCallback => iif(() => shouldHandleCallback, defer(() => this.handleCallback()), of(false))), tap(() => authState.setIsLoading(false)), takeUntil(this.unsubscribe$)).subscribe(); } ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); } getClaim(claim, type) { // todo: make reusable function to handle isAuthenticated return this.isAuthenticated$.pipe(filter(_ => this.kindeClient !== null), switchMap(isAuthenticated => iif(() => isAuthenticated, from(this.kindeClient.getClaim(claim, type)), of(null)))); } ; getUserOrganizations() { // todo: check L59 return this.isAuthenticated$.pipe(filter(_ => this.kindeClient !== null), switchMap(isAuthenticated => iif(() => isAuthenticated, from(this.kindeClient.getUserOrganizations()), of({ orgCodes: [] })))); } getAccessToken() { if (!this.kindeClient) throw new Error("kindeClient is null"); return this.kindeClient.getToken(); } getFeatureFlag(code, defaultValue, flagType) { if (!this.kindeClient) throw new Error("kindeClient is null"); return this.kindeClient.getFlag(code, defaultValue, flagType); } async getFeatureFlagEnabled(code, defaultValue) { const BOOLEAN_FLAG_TYPE = 'b'; return (await this.getFeatureFlag(code, defaultValue, BOOLEAN_FLAG_TYPE)).value; } async login(options) { if (!this.kindeClient) throw new Error("kindeClient is null"); const loginUrl = await this.kindeClient.login(options); if (options?.post_login_redirect_url) { await sessionManager.setSessionItemBrowser('post_login_redirect_url', options.post_login_redirect_url); } this.document.location.href = loginUrl.href; } async logout() { if (!this.kindeClient) throw new Error("kindeClient is null"); const logoutUrl = await this.kindeClient.logout(); this.document.location.href = logoutUrl.href; } async register(options) { if (!this.kindeClient) throw new Error("kindeClient is null"); const registerUrl = await this.kindeClient.register(options); this.document.location.href = registerUrl.href; } shouldHandleCallback() { return of(this.document.location.search) .pipe(map(search => new URLSearchParams(search)), map(params => params.has('code') || params.has('state'))); } async handleCallback() { try { await this.kindeClient.handleRedirectToApp(new URL(this.document.location.href)); const token = await this.kindeClient.getToken(); this.authState.setAccessToken(token); let url = new URL(this.document.location.href); url.search = ''; const loginRedirectUrl = await sessionManager.getSessionItemBrowser('post_login_redirect_url'); if (loginRedirectUrl) { url = new URL(loginRedirectUrl); await sessionManager.removeSessionItemBrowser('post_login_redirect_url'); } this.document.location.href = url.href; this.location.go(url.toString()); } catch (e) { console.log(e); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularService, deps: [{ token: KINDE_FACTORY_TOKEN }, { token: DOCUMENT }, { token: i1.Location }, { token: AuthStateService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [KINDE_FACTORY_TOKEN] }] }, { type: Document, decorators: [{ type: Inject, args: [DOCUMENT] }] }, { type: i1.Location }, { type: AuthStateService }] }); const kindeConfigToken = new InjectionToken('[kinde angular] configToken'); class KindeAngularModule { static forRoot(config) { return { ngModule: KindeAngularModule, providers: [ KindeAngularService, { provide: kindeConfigToken, useValue: config }, { provide: KINDE_FACTORY_TOKEN, useFactory: KindeClientFactory.createClient, deps: [kindeConfigToken, PLATFORM_ID] } ] }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: KindeAngularModule, decorators: [{ type: NgModule }] }); const canMatchAuthGuard = () => { const authService = inject(KindeAngularService); return authService.isAuthenticated$.pipe(take(1)); }; const canActivateAuthGuard = () => { const authService = inject(KindeAngularService); return authService.isAuthenticated$.pipe(take(1), tap(async (isAuthenticated) => { if (!isAuthenticated) { await authService.login(); } })); }; const featureFlagGuard = (flagName, redirect) => { return async () => { const authService = inject(KindeAngularService); const router = inject(Router); const isEnabled = await authService.getFeatureFlagEnabled(flagName); return isEnabled || router.createUrlTree([redirect || '/']); }; }; class FeatureFlagDirective { constructor() { this.templateRef = inject((TemplateRef)); this.viewContainerRef = inject(ViewContainerRef); this.kindeAngularService = inject(KindeAngularService); } async ngOnInit() { try { const featureFlag = await this.kindeAngularService.getFeatureFlag(this.featureFlag); featureFlag.value ? this.onIf() : this.onElse(); } catch (error) { this.onElse(); } } onIf() { this.createView(this.templateRef); } onElse() { if (!this.featureFlagElse) { return; } this.createView(this.featureFlagElse); } createView(templateRef) { this.viewContainerRef.createEmbeddedView(templateRef); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: FeatureFlagDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.1", type: FeatureFlagDirective, isStandalone: true, selector: "[featureFlag]", inputs: { featureFlag: "featureFlag", featureFlagElse: "featureFlagElse" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: FeatureFlagDirective, decorators: [{ type: Directive, args: [{ selector: '[featureFlag]', standalone: true }] }], propDecorators: { featureFlag: [{ type: Input }], featureFlagElse: [{ type: Input }] } }); function provideKinde(config) { return makeEnvironmentProviders([ KindeAngularService, { provide: kindeConfigToken, useValue: config }, { provide: KINDE_FACTORY_TOKEN, useFactory: KindeClientFactory.createClient, deps: [kindeConfigToken, PLATFORM_ID] } ]); } /* * Public API Surface of kinde-angular */ /** * Generated bundle index. Do not edit. */ export { FeatureFlagDirective, KindeAngularModule, KindeAngularService, canActivateAuthGuard, canMatchAuthGuard, featureFlagGuard, kindeConfigToken, provideKinde }; //# sourceMappingURL=kinde-angular.mjs.map