UNPKG

@ng-supabase/core

Version:

ng-supabase is a component library and helper utilities for integrating Supabase in your angular application.

1,157 lines (1,129 loc) 49 kB
import * as i0 from '@angular/core'; import { Injectable, signal, inject, Component, ChangeDetectionStrategy, Input, EventEmitter, ChangeDetectorRef, Output, Pipe } from '@angular/core'; import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; import { BehaviorSubject, Subject, firstValueFrom, filter, map, debounceTime } from 'rxjs'; import * as i1 from '@angular/router'; import { Router } from '@angular/router'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import { createClient } from '@supabase/supabase-js'; /** * Removes an item from an array. * @param list The list to remove the item from. * @param item The item to remove out of the list. */ function removeItem(list, item) { const index = list.findIndex((i) => i === item); list.splice(index, 1); } // Local. /** * Removes items from a list based on a predicate function. * @param list The list to remove items from. * @param predicate A function that will be called for each item * to determine whether to remove that item from the list or not. */ function removeWhere(list, predicate) { for (const item of list) { const remove = predicate(item); if (remove) { removeItem(list, item); } } } var SocialSignIn; (function (SocialSignIn) { SocialSignIn["Apple"] = "apple"; SocialSignIn["Azure"] = "microsoft"; SocialSignIn["Bitbucket"] = "bitbucket"; SocialSignIn["Discord"] = "discord"; SocialSignIn["Facebook"] = "facebook"; SocialSignIn["Figma"] = "figma"; SocialSignIn["GitHub"] = "github"; SocialSignIn["GitLab"] = "gitlab"; SocialSignIn["Google"] = "google"; SocialSignIn["Kakao"] = "kakao"; SocialSignIn["Keycloak"] = "keycloak"; SocialSignIn["LinkedIn"] = "linkedin"; SocialSignIn["Notion"] = "notion"; SocialSignIn["Twitch"] = "twitch"; SocialSignIn["Twitter"] = "twitter"; SocialSignIn["Slack"] = "slack"; SocialSignIn["Spotify"] = "spotify"; SocialSignIn["WorkOS"] = "workos"; SocialSignIn["Zoom"] = "zoom"; })(SocialSignIn || (SocialSignIn = {})); const ALL_SOCIAL_SIGN_INS = Object.values(SocialSignIn); const ALL_SOCIAL_SIGN_IN_ITEMS = Object.entries(SocialSignIn).map(([title, value]) => { return { title, value }; }); function toSocialItem(social) { const asArray = social; const asSingle = social; return Array.isArray(social) ? asArray.map(toSingleSocialItem) : toSingleSocialItem(asSingle); } function toSingleSocialItem(socialValue) { const item = ALL_SOCIAL_SIGN_IN_ITEMS.find((s) => s.value === socialValue); if (!item) { throw new Error(`No social sign in item with value '${socialValue}' found`); } return item; } function trimEnd(value, trimValue = ' ') { const trimLength = trimValue.length; return value.endsWith(trimValue) ? value.substring(0, value.length - trimLength) : value; } function trimStart(value, trimValue = ' ') { const trimLength = trimValue.length; return value.startsWith(trimValue) ? value.substring(trimLength) : value; } // 3rd party. const DEFAULT_ROUTES = { main: '/', signIn: '/sign-in', register: '/register', registerOrSignIn: '/auth', setPassword: '/set-password', resetPassword: '/reset-password', postSignOut: '/sign-in', }; class SetPasswordConfig { constructor(init) { this.title = ''; this.requireConfirm = true; this.showMessageOnSave = true; Object.assign(this, init); } } class ProfileConfig { constructor(init) { this.table = ''; this.userIdField = 'user_id'; this.firstNameField = 'first_name'; this.lastNameField = 'last_name'; this.avatarField = 'avatar'; Object.assign(this, init); } } class RegisterConfig { constructor(init) { this.title = ''; this.metadata = []; Object.assign(this, init); this.metadata = this.metadata || []; } } class SignInConfig { constructor(init) { this.title = ''; this.magicLinks = true; this.socials = []; this.rememberMe = true; this.socialIconsRoot = 'https://supabase.com/dashboard/img/icons/'; this.socialSignInItems = []; this.rememberMeStorageKey = 'supabase.auth.info'; this.otpEnabled = true; this.otpLength = 6; Object.assign(this, init); this.setSocialSignInItems(); } setSocialSignInItems() { for (const social of this.socials) { const item = toSocialItem(social); if (this.socialIconsRoot) { const root = trimEnd(this.socialIconsRoot, '/'); item.icon = `${root}/${item.value}-icon.svg`; } this.socialSignInItems.push(item); } } } class SupabaseConfig { constructor(init) { this.mainRoute = '/'; this.routes = DEFAULT_ROUTES; this.redirectParamName = 'redirect'; Object.assign(this.routes, init.routes); const options = init; const url = SupabaseConfig.toApiUrl(options.apiUrl || options.project); this.logging = init.logging; this.setPassword = new SetPasswordConfig(init.setPassword); this.signIn = new SignInConfig(init.signIn); this.register = new RegisterConfig(init.register); this.profile = new ProfileConfig(init.profile); this.api = new BehaviorSubject({ url: url, key: init.apiKey, }); } static toApiUrl(urlOrProjectId) { return urlOrProjectId.startsWith('http') ? urlOrProjectId : `https://${urlOrProjectId}.supabase.co`; } } function isString(value) { return typeof value === 'string'; } // Angular. class RouteService { constructor(router, config, route, locationStrategy) { this.router = router; this.config = config; this.route = route; this.locationStrategy = locationStrategy; } getRootUrl() { const [root] = window.location.href.split(this.router.url); return root; } appendRoute(route) { const root = trimEnd(this.getRootUrl(), '/'); return this.join(root, route); } join(...parts) { let result = ''; let i = 0; for (const part of parts) { const isLast = i === parts.length - 1; if (isLast) { break; } const nextPart = parts[i + 1]; const trimmed1 = trimEnd(part, '/'); const trimmed2 = trimStart(nextPart, '/'); result += trimmed2 ? `${trimmed1}/${trimmed2}` : trimmed1; i++; } return result; } getRedirectParamValue() { const { redirectParamName } = this.config; return redirectParamName ? this.route.snapshot.queryParams[redirectParamName] : null; } constructAbsoluteUrl(...appendRoutePaths) { const baseHref = this.locationStrategy.getBaseHref(); return this.join(location.origin, baseHref, ...appendRoutePaths); } goTo(route, options) { const asString = route; const isAbsoluteUrl = isString(route) && asString.startsWith('http'); if (isAbsoluteUrl) { const root = this.getRootUrl(); route = trimStart(asString, root); } return Array.isArray(route) ? this.router.navigate(route, options) : this.router.navigateByUrl(route, options); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RouteService, deps: [{ token: i1.Router }, { token: SupabaseConfig }, { token: i1.ActivatedRoute }, { token: i3.LocationStrategy }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RouteService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RouteService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.Router }, { type: SupabaseConfig }, { type: i1.ActivatedRoute }, { type: i3.LocationStrategy }] }); var LogLevel; (function (LogLevel) { LogLevel[LogLevel["Trace"] = 0] = "Trace"; LogLevel[LogLevel["Debug"] = 1] = "Debug"; LogLevel[LogLevel["Info"] = 2] = "Info"; LogLevel[LogLevel["Warn"] = 3] = "Warn"; LogLevel[LogLevel["Error"] = 4] = "Error"; LogLevel[LogLevel["Fatal"] = 5] = "Fatal"; })(LogLevel || (LogLevel = {})); // Angular. const DEFAULT_LOG_CONFIG = { logLevel: LogLevel.Warn, enabled: true, persistLogs: false, }; class LogService { constructor(config) { this.logs = []; this.consoleMap = {}; this.config = { ...DEFAULT_LOG_CONFIG, ...config.logging }; this.consoleMap[LogLevel.Trace] = console.trace; this.consoleMap[LogLevel.Debug] = console.debug; this.consoleMap[LogLevel.Info] = console.info; this.consoleMap[LogLevel.Warn] = console.warn; this.consoleMap[LogLevel.Error] = console.error; this.consoleMap[LogLevel.Fatal] = console.error; } log(level, message, error) { const skip = this.config.enabled === false || level > this.config.logLevel; if (skip) { return; } const args = [message]; if (error) { args.push(error); } this.consoleMap[level].apply(console, args); if (this.config.persistLogs) { this.logs.push({ timestamp: new Date(), level, message, error }); } } trace(message) { this.log(LogLevel.Trace, message); } debug(message) { this.log(LogLevel.Debug, message); } info(message) { this.log(LogLevel.Info, message); } warn(message, error) { this.log(LogLevel.Warn, message, error); } error(message, error) { this.log(LogLevel.Error, message, error); } fatal(message, error) { this.log(LogLevel.Fatal, message, error); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: LogService, deps: [{ token: SupabaseConfig }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: LogService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: LogService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: SupabaseConfig }] }); // Angular. class SupabaseService { get isSignedIn() { return this.signedIn.value; } get isNotSignedIn() { return !this.signedIn.value; } constructor(zone, log, config) { this.zone = zone; this.log = log; this.config = config; this.authChange = new Subject(); this.initialized = new BehaviorSubject(false); this.session = new BehaviorSubject(null); this.user = new BehaviorSubject(null); this.userDisplayName = new BehaviorSubject(''); this.userSubheading = new BehaviorSubject(''); this.userProfile = new BehaviorSubject(null); this.userAvatar = new BehaviorSubject(null); this.signedIn = new BehaviorSubject(false); this.loading = new BehaviorSubject(true); this.user.subscribe((user) => this.setUserInformation(user)); this.clientReady = firstValueFrom(this.initialized.pipe(filter(Boolean), map(() => this.client))); this.config.api.subscribe(() => this.setup()); } waitForSignedIn() { return firstValueFrom(this.signedIn.pipe(filter(Boolean), map(() => this.session.value))); } refreshUserDisplayInfo() { return this.setUserInformation(this.user.value); } async setUserInformation(user) { const profileTable = this.config.profile.table; let displayName = ''; if (user && profileTable) { this.log.debug(`Retrieving user profile for user ID '${user.id}'`); const { error, data: profile } = await this.client .from(profileTable) .select() .eq(this.config.profile.userIdField, user.id) .limit(1) .single(); if (error) { this.log.error(`Failed to retrieve user profile. ${error.details}`, error); } if (profile) { const firstName = profile[this.config.profile.firstNameField]; const lastName = profile[this.config.profile.lastNameField]; const avatar = profile[this.config.profile.avatarField]; displayName = `${firstName || ''} ${lastName || ''}`.trim(); this.userProfile.next(profile); if (avatar) { this.userAvatar.next(avatar); } this.log.debug(`Retrieving display name of '${displayName}' from profile`); } else { this.log.warn(`No profile found for user ID '${user.id}'`); } } displayName = displayName || this.extractDisplay(user); const subheading = displayName === user?.email ? '' : user?.user_metadata?.['title'] || user?.email || ''; this.userDisplayName.next(displayName); this.userSubheading.next(subheading); } extractDisplay(user) { const { first_name, last_name } = user?.user_metadata || {}; const display = `${first_name || ''} ${last_name || ''}`.trim(); return user ? display || user.email || user.id : ''; } setup() { if (this.isSignedIn) { this.setStateForSignedOut(); } this.createClient(); } createClient() { const { url, key } = this.config.api.value; this.client = createClient(url, key); this.client.auth.onAuthStateChange((event) => { this.zone.run(() => { this.setAuthState(event); }); }); } setAuthState(event) { this.log.info(`Auth state change: '${event}'`); this.authChange.next(event); if (event === 'INITIAL_SESSION') { this.initialized.next(true); this.loading.next(false); } else if (event === 'SIGNED_IN') { this.signedIn.next(true); this.tryGetSession(); } else if (event === 'SIGNED_OUT') { this.setStateForSignedOut(); } } async tryGetSession() { const { data, error } = await this.client.auth.getSession(); const noSession = !error && !data.session; if (noSession) { this.log.error('No session information retrieved'); return; } if (error) { this.log.error(`Failed to get user session`, error); return; } this.session.next(data.session); if (data.session?.user) { this.user.next(data.session.user); } } setStateForSignedOut() { this.session.next(null); this.signedIn.next(false); this.user.next(null); this.userProfile.next(null); this.userDisplayName.next(''); this.userSubheading.next(''); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SupabaseService, deps: [{ token: i0.NgZone }, { token: LogService }, { token: SupabaseConfig }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SupabaseService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SupabaseService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i0.NgZone }, { type: LogService }, { type: SupabaseConfig }] }); // Angular. class PersistentStorageService { getJson(key) { const item = this.getItem(key); return item ? JSON.parse(item) : null; } setJson(key, value) { const json = JSON.stringify(value); this.setItem(key, json); } getItem(key) { return localStorage.getItem(key); } setItem(key, value) { localStorage.setItem(key, value); } clear() { localStorage.clear(); } removeItem(key) { localStorage.removeItem(key); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: PersistentStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: PersistentStorageService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: PersistentStorageService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class SignInComponent { constructor() { this.title = ''; this.email = ''; this.password = ''; this.usePassword = false; this.redirectTo = ''; /** * The absolute route to redirect to from the email link. This should not * be used in conjunction with "redirectToPath" (use one or the other). */ this.redirectToUrl = ''; /** * A route path to redirect to from the email link (as apposed to an absolute path). * This path will be appended to the app's root URL and will be the URL that is * targeted from the email link. This should not be used in conjunction with * "redirectTo" (use one or the other). */ this.redirectToPath = ''; this.forgotPassword = false; this.signingIn = new Subject(); this.form = new FormGroup({ email: new FormControl('', [Validators.required]), password: new FormControl(''), usePassword: new FormControl(false), rememberMe: new FormControl(true), }); this.errorMessage = signal(null); this.wait = signal(null); this.verifyingOtp = signal(false); this.log = inject(LogService); this.config = inject(SupabaseConfig); this.supabase = inject(SupabaseService); this.routeService = inject(RouteService); this.storage = inject(PersistentStorageService); } ngOnInit() { this.title = this.title ?? this.config.signIn.title; const rememberMe = this.rememberMe ?? this.config.signIn.rememberMe; this.form.controls.email.setValue(this.email); this.form.controls.usePassword.setValue(this.usePassword); this.form.controls.password.setValue(this.password); this.form.controls.rememberMe.setValue(rememberMe); this.tryLoadRememberMe(); } showSignInWithPassword(event) { event?.preventDefault(); this.form.controls.usePassword.setValue(true); this.form.controls.password.setValidators([Validators.required]); this.revalidateAllControls(); } showSignInWithEmail(event) { event?.preventDefault(); this.form.controls.usePassword.setValue(false); this.form.controls.password.setValidators([]); this.revalidateAllControls(); } showForgotPassword(event) { event?.preventDefault(); this.forgotPassword = true; } signIn() { if (this.form.disabled || this.form.invalid) { return; } this.form.value.usePassword ? this.signInWithPassword() : this.signInWithMagicLink(); } async verifyOtp(token) { this.verifyingOtp.set(true); const email = this.form.value.email; const { error } = await this.supabase.client.auth.verifyOtp({ email, token, type: 'email', }); if (error) { this.errorMessage.set(error.message); return; } const redirectUrl = this.getRedirectTo(); this.routeService.goTo(redirectUrl); } revalidateAllControls() { Object.values(this.form.controls).forEach((control) => control.updateValueAndValidity()); } async signInWithPassword() { try { this.log.debug('Logging in with password'); this.signingIn.next(true); const email = this.form.value.email; const password = this.form.value.password; const { error } = await this.supabase.client.auth.signInWithPassword({ email, password, }); if (error) { this.log.debug(`Sign in failed. ${error.message}`); this.errorMessage.set(error.message); return; } const redirect = this.getRedirectUrl(); this.log.debug(`Signed in successfully. Redirecting to ${redirect}`); this.trySaveRememberMe(); await this.supabase.waitForSignedIn(); this.routeService.goTo(redirect); } catch (error) { this.log.error(`Failed to sign in`); // TODO: Handle - @rusty.green. } finally { this.signingIn.next(false); } } getRedirectUrl() { return (this.redirectTo || this.routeService.getRedirectParamValue() || this.config.signIn.redirectTo || this.config.mainRoute); } async signInWithMagicLink() { try { this.signingIn.next(true); const email = this.form.value.email; const emailRedirectTo = this.getRedirectUrl().toString(); const { error } = await this.supabase.client.auth.signInWithOtp({ email, options: { emailRedirectTo, // TODO: Pass/configure other options (like "shouldCreateUser") - @rusty.green }, }); if (error) { this.errorMessage.set(error.message); return; } this.wait.set({ icon: 'pi pi-envelope', title: 'Check your email', enableOtp: this.config.signIn.otpEnabled, message: `An email has been sent to <strong>${email}</strong> with a magic link to sign in. Simply click the link from your email and you will automatically be signed into this app.`, }); this.trySaveRememberMe(); } catch (error) { // TODO: Handle - @russell.green } finally { this.signingIn.next(false); } } tryLoadRememberMe() { const info = this.storage.getJson(this.config.signIn.rememberMeStorageKey); if (info) { // eslint-disable-next-line @typescript-eslint/no-explicit-any this.form.patchValue(info); this.form.value.usePassword ? this.showSignInWithPassword() : this.showSignInWithEmail(); } } clearRememberMe() { this.storage.removeItem(this.config.signIn.rememberMeStorageKey); } trySaveRememberMe() { if (this.form.value.rememberMe) { const { email, usePassword } = this.form.value; const value = { email, usePassword }; this.storage.setJson(this.config.signIn.rememberMeStorageKey, value); } else { this.clearRememberMe(); } } getRedirectTo() { const fallback = this.config.routes.userProfile || this.config.routes.main; return this.redirectToPath ? this.routeService.appendRoute(this.redirectToPath) : this.redirectToUrl || this.routeService.appendRoute(fallback); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SignInComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: SignInComponent, isStandalone: true, selector: "supabase-sign-in", inputs: { title: "title", email: "email", password: "password", usePassword: "usePassword", redirectTo: "redirectTo", rememberMe: "rememberMe", redirectToUrl: "redirectToUrl", redirectToPath: "redirectToPath" }, ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SignInComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-sign-in', standalone: true, imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }], propDecorators: { title: [{ type: Input }], email: [{ type: Input }], password: [{ type: Input }], usePassword: [{ type: Input }], redirectTo: [{ type: Input }], rememberMe: [{ type: Input }], redirectToUrl: [{ type: Input }], redirectToPath: [{ type: Input }] } }); function uuid() { return crypto.randomUUID(); } // Angular. class NotifyService { show(targetOrMessage, title) { const asString = targetOrMessage; const asMessage = targetOrMessage; const target = isString(targetOrMessage) ? { message: asString, title: title, } : asMessage; const message = { id: target.id || uuid(), message: target.message || '', position: target.position || 'bottom-right', title: target.title || '', severity: target.severity || 'info', }; this.showNotify(message); return message; } showInfo(target) { return this.show({ ...target, severity: 'info' }); } showSuccess(target) { return this.show({ ...target, severity: 'success' }); } showWarn(target) { return this.show({ ...target, severity: 'warn' }); } showError(target) { return this.show({ ...target, severity: 'error' }); } showFatal(target) { return this.show({ ...target, severity: 'fatal' }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: NotifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: NotifyService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: NotifyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); // Angular. class SetPasswordComponent { constructor() { this.title = ''; this.saveLabel = 'Save Password'; this.savingLabel = 'Saving password...'; this.saved = new EventEmitter(); this.saving = signal(false); this.errorMessage = signal(''); this.confirmMisMatch = signal(false); this.form = new FormGroup({ password: new FormControl('', [Validators.required]), }); this.log = inject(LogService); this.notify = inject(NotifyService); this.config = inject(SupabaseConfig); this.supabase = inject(SupabaseService); this.routeService = inject(RouteService); this.changeDetector = inject(ChangeDetectorRef); this.subscriptions = []; } ngOnInit() { this.title = this.title ?? this.config.setPassword.title; this.confirmPassword = this.confirmPassword ?? this.config.setPassword.requireConfirm; if (this.confirmPassword) { const confirm = new FormControl('', [ Validators.required, validatePasswordsMatch, ]); this.form.addControl('confirm', confirm); this.subscriptions.push(this.form.valueChanges.pipe(debounceTime(250)).subscribe(() => { const isMisMatch = !this.form.disabled && confirm.dirty && confirm.value !== this.form.value.password; this.confirmMisMatch.set(isMisMatch); })); } } ngOnDestroy() { this.subscriptions.forEach((s) => s.unsubscribe()); } async submit() { if (this.form.invalid) { return; } this.form.disable(); this.saving.set(true); try { const { data, error } = await this.supabase.client.auth.updateUser({ password: this.form.value.password, }); if (error) { this.errorMessage.set(error.message); this.log.error(`Failed to save password. ${error.message}`); this.onError(error); return; } this.log.info(`Set password for '${data?.user?.email || ''}'`); this.saved.emit(data.user); if (this.config.setPassword.showMessageOnSave) { this.notify.showSuccess({ title: 'Password Changed', message: 'Your password was successfully reset', }); } if (this.redirectTo) { await this.routeService.goTo(this.redirectTo); } this.form.reset(); } finally { this.form.enable(); this.saving.set(false); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars onError(error) { // Do nothing in here because this is just a hook for child // components to easily subscribe to when an error occurs. } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SetPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: SetPasswordComponent, isStandalone: true, selector: "supabase-set-password", inputs: { title: "title", saveLabel: "saveLabel", savingLabel: "savingLabel", confirmPassword: "confirmPassword", redirectTo: "redirectTo" }, outputs: { saved: "saved" }, ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: SetPasswordComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-set-password', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }], propDecorators: { title: [{ type: Input }], saveLabel: [{ type: Input }], savingLabel: [{ type: Input }], confirmPassword: [{ type: Input }], redirectTo: [{ type: Input }], saved: [{ type: Output }] } }); function validatePasswordsMatch(c) { const root = c.root; return root.value['password'] === c.value ? null : { passwordsMatch: false }; } // Angular. class ResetPasswordComponent { constructor(log, config, supabase, routeService) { this.log = log; this.config = config; this.supabase = supabase; this.routeService = routeService; this.title = 'Reset Password'; this.email = ''; /** * The absolute route to redirect to from the email link. This should not * be used in conjunction with "redirectToPath" (use one or the other). */ this.redirectToUrl = ''; /** * A route path to redirect to from the email link (as apposed to an absolute path). * This path will be appended to the app's root URL and will be the URL that is * targeted from the email link. This should not be used in conjunction with * "redirectTo" (use one or the other). */ this.redirectToPath = ''; this.errorMessage = signal(''); this.sendingReset = signal(false); this.wait = signal(null); this.form = new FormGroup({ email: new FormControl('', [Validators.required]), }); } ngOnInit() { this.title = this.title ?? this.config.signIn.title; this.form.controls.email.setValue(this.email); } async resetPassword() { if (this.form.invalid) { return; } this.form.disable(); this.sendingReset.set(true); try { const email = this.form.value.email; const redirectTo = this.getRedirectTo(); const { error } = await this.supabase.client.auth.resetPasswordForEmail(email, { redirectTo }); if (error) { this.errorMessage.set(error.message); this.log.error(`Failed to save password. ${error.message}`); this.onError(error); } this.log.info(`Sent reset password email to '${email}'`); this.wait.set({ icon: 'pi pi-envelope', title: 'Check your email', message: `An email has been sent to <strong>${email}</strong> with a link to reset your password. Simply click the link from your email and follow the instructions.`, }); } finally { this.form.enable(); this.sendingReset.set(false); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars onError(error) { // Do nothing in here because this is just a hook for child // components to easily subscribe to when an error occurs. } getRedirectTo() { return this.redirectToPath ? this.routeService.appendRoute(this.redirectToPath) : this.redirectToUrl || this.routeService.appendRoute(this.config.routes.setPassword); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: ResetPasswordComponent, deps: [{ token: LogService }, { token: SupabaseConfig }, { token: SupabaseService }, { token: RouteService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: ResetPasswordComponent, isStandalone: true, selector: "supabase-reset-password", inputs: { title: "title", email: "email", redirectToUrl: "redirectToUrl", redirectToPath: "redirectToPath" }, ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: ResetPasswordComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-reset-password', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }], ctorParameters: () => [{ type: LogService }, { type: SupabaseConfig }, { type: SupabaseService }, { type: RouteService }], propDecorators: { title: [{ type: Input }], email: [{ type: Input }], redirectToUrl: [{ type: Input }], redirectToPath: [{ type: Input }] } }); function trim(value, trimValue = ' ') { const trimmedStart = trimStart(value, trimValue); return trimEnd(trimmedStart, trimValue); } // Angular. const IsSignedIn = async (route, state) => { const log = inject(LogService); const supabase = inject(SupabaseService); const router = inject(Router); const config = inject(SupabaseConfig); const routeService = inject(RouteService); await supabase.clientReady; const signedIn = supabase.isSignedIn; if (!signedIn) { const queryParams = {}; if (config.redirectParamName) { const redirect = routeService.constructAbsoluteUrl(state.url); queryParams[config.redirectParamName] = redirect; } log.info(`User cannot access route '${state.url}', redirecting to sign in page`); return router.createUrlTree([config.routes.signIn], { queryParams }); } log.debug(`Activating route '${state.url}' for 'IsSignedIn' guard`); return supabase.isSignedIn; }; // Angular. class RegisterComponent { constructor(config, log, supabase, routeService) { this.config = config; this.log = log; this.supabase = supabase; this.routeService = routeService; this.title = ''; this.email = ''; /** * The absolute route to redirect to from the email link. This should not * be used in conjunction with "redirectToPath" (use one or the other). */ this.redirectToUrl = ''; /** * A route path to redirect to from the email link (as apposed to an absolute path). * This path will be appended to the app's root URL and will be the URL that is * targeted from the email link. This should not be used in conjunction with * "redirectTo" (use one or the other). */ this.redirectToPath = ''; this.errorMessage = signal(''); this.working = signal(false); this.verifyingOtp = signal(false); this.wait = signal(null); this.form = new FormGroup({ email: new FormControl('', [Validators.required]), }); } ngOnInit() { this.title = this.title ?? this.config.register.title; if (this.config.register.metadata.length) { this.setupForMetadata(); } } async register() { if (this.form.invalid) { return; } try { const email = this.form.value.email; const data = this.form.value.metadata; const emailRedirectTo = this.getRedirectTo(); this.form.disable(); this.working.set(true); const { error } = await this.supabase.client.auth.signInWithOtp({ email, options: { shouldCreateUser: true, emailRedirectTo, data, }, }); if (error) { this.errorMessage.set(error.message); this.log.error(`Failed to save password. ${error.message}`); this.onError(error); } this.log.info(`Sent OTP email to '${email}'`); this.wait.set({ icon: 'pi pi-envelope', title: 'Check your email', enableOtp: this.config.signIn.otpEnabled, message: `An email has been sent to <strong>${email}</strong> with a link to verify your email address. Simply click the link from your email and follow the instructions to continue.`, }); } finally { this.form.enable(); this.working.set(false); } } async verifyOtp(token) { this.verifyingOtp.set(true); const email = this.form.value.email; const { error } = await this.supabase.client.auth.verifyOtp({ email, token, type: 'email', }); if (error) { this.onError(error); return; } const redirectUrl = this.getRedirectTo(); this.routeService.goTo(redirectUrl); } // eslint-disable-next-line @typescript-eslint/no-unused-vars onError(error) { // Do nothing in here because this is just a hook for child // components to easily subscribe to when an error occurs. } setupForMetadata() { const group = new FormGroup({}); for (const meta of this.config.register.metadata) { const validators = meta.required ? [Validators.required] : []; const value = meta.defaultValue || ''; const control = new FormControl(value, validators); group.addControl(meta.field, control); } this.form.addControl('metadata', group); } getRedirectTo() { const fallback = this.config.routes.userProfile || this.config.routes.main; return this.redirectToPath ? this.routeService.appendRoute(this.redirectToPath) : this.redirectToUrl || this.routeService.appendRoute(fallback); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RegisterComponent, deps: [{ token: SupabaseConfig }, { token: LogService }, { token: SupabaseService }, { token: RouteService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: RegisterComponent, isStandalone: true, selector: "supabase-register", inputs: { title: "title", email: "email", redirectToUrl: "redirectToUrl", redirectToPath: "redirectToPath" }, ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RegisterComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-register', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }], ctorParameters: () => [{ type: SupabaseConfig }, { type: LogService }, { type: SupabaseService }, { type: RouteService }], propDecorators: { title: [{ type: Input }], email: [{ type: Input }], redirectToUrl: [{ type: Input }], redirectToPath: [{ type: Input }] } }); class UserAvatarComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: UserAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: UserAvatarComponent, isStandalone: true, selector: "supabase-user-avatar", ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: UserAvatarComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-user-avatar', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }] }); // Angular. class UserAvatarButtonComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: UserAvatarButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: UserAvatarButtonComponent, isStandalone: true, selector: "supabase-user-avatar-button", ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: UserAvatarButtonComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-user-avatar-button', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }] }); // Angular. class ActiveUserAvatarButtonComponent { constructor() { this.loading = signal(true); this.router = inject(Router); this.config = inject(SupabaseConfig); this.supabase = inject(SupabaseService); } async ngOnInit() { await this.supabase.clientReady; this.loading.set(false); } signOut() { this.supabase.client.auth.signOut(); if (this.config.routes.postSignOut) { this.router.navigate([this.config.routes.postSignOut]); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: ActiveUserAvatarButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: ActiveUserAvatarButtonComponent, isStandalone: true, selector: "supabase-active-user-avatar-button", ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: ActiveUserAvatarButtonComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-active-user-avatar-button', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }] }); class RegisterOrSignInComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RegisterOrSignInComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.6", type: RegisterOrSignInComponent, isStandalone: true, selector: "supabase-register-or-sign-in", ngImport: i0, template: "", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: RegisterOrSignInComponent, decorators: [{ type: Component, args: [{ selector: 'supabase-register-or-sign-in', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "" }] }] }); class InitialsPipe { transform(fullName, numChars = 2) { if (!fullName) { return ''; } return fullName .split(' ') .slice(0, numChars) .map((n) => n[0].toUpperCase()) .join(''); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: InitialsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.6", ngImport: i0, type: InitialsPipe, isStandalone: true, name: "initials" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.6", ngImport: i0, type: InitialsPipe, decorators: [{ type: Pipe, args: [{ name: 'initials', standalone: true, }] }] }); /** * Generated bundle index. Do not edit. */ export { ALL_SOCIAL_SIGN_INS, ALL_SOCIAL_SIGN_IN_ITEMS, ActiveUserAvatarButtonComponent, DEFAULT_ROUTES, InitialsPipe, IsSignedIn, LogLevel, LogService, NotifyService, PersistentStorageService, RegisterComponent, RegisterOrSignInComponent, ResetPasswordComponent, RouteService, SetPasswordComponent, SignInComponent, SignInConfig, SocialSignIn, SupabaseConfig, SupabaseService, UserAvatarButtonComponent, UserAvatarComponent, isString, removeItem, removeWhere, toSocialItem, trim, trimEnd, trimStart, uuid }; //# sourceMappingURL=ng-supabase-core.mjs.map