kinde-angular
Version:
Angular wrapper for the Kinde TypeScript SDK
349 lines (335 loc) • 15.3 kB
JavaScript
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