@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
JavaScript
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