@ng-supabase/core
Version:
ng-supabase is a component library and helper utilities for integrating Supabase in your angular application.
1 lines • 91.7 kB
Source Map (JSON)
{"version":3,"file":"ng-supabase-core.mjs","sources":["../../../../libs/core/src/lib/array/remove-item.ts","../../../../libs/core/src/lib/array/remove-where.ts","../../../../libs/core/src/lib/sign-in/social-sign-in.ts","../../../../libs/core/src/lib/format/trim-end.function.ts","../../../../libs/core/src/lib/format/trim-start.function.ts","../../../../libs/core/src/lib/supabase-config.ts","../../../../libs/core/src/lib/type-check/is-string.ts","../../../../libs/core/src/lib/route.service.ts","../../../../libs/core/src/lib/logging/log-level.ts","../../../../libs/core/src/lib/logging/log.service.ts","../../../../libs/core/src/lib/supabase.service.ts","../../../../libs/core/src/lib/storage/persistent-storage.service.ts","../../../../libs/core/src/lib/sign-in/sign-in.component.ts","../../../../libs/core/src/lib/sign-in/sign-in.component.html","../../../../libs/core/src/lib/crypto/uuid.ts","../../../../libs/core/src/lib/notify/notify.service.ts","../../../../libs/core/src/lib/set-password/set-password.component.ts","../../../../libs/core/src/lib/set-password/set-password.component.html","../../../../libs/core/src/lib/reset-password/reset-password.component.ts","../../../../libs/core/src/lib/reset-password/reset-password.component.html","../../../../libs/core/src/lib/format/trim.function.ts","../../../../libs/core/src/lib/auth-guard/is-signed-in.guard.ts","../../../../libs/core/src/lib/register/register.component.ts","../../../../libs/core/src/lib/register/register.component.html","../../../../libs/core/src/lib/user-avatar/user-avatar.component.ts","../../../../libs/core/src/lib/user-avatar/user-avatar.component.html","../../../../libs/core/src/lib/user-avatar-button/user-avatar-button.component.ts","../../../../libs/core/src/lib/user-avatar-button/user-avatar-button.component.html","../../../../libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.ts","../../../../libs/core/src/lib/active-user-avatar-button/active-user-avatar-button.component.html","../../../../libs/core/src/lib/register-or-sign-in/register-or-sign-in.component.ts","../../../../libs/core/src/lib/register-or-sign-in/register-or-sign-in.component.html","../../../../libs/core/src/lib/initials.pipe.ts","../../../../libs/core/src/ng-supabase-core.ts"],"sourcesContent":["/**\n * Removes an item from an array.\n * @param list The list to remove the item from.\n * @param item The item to remove out of the list.\n */\nexport function removeItem(list: unknown[], item: unknown): void {\n const index = list.findIndex((i) => i === item);\n list.splice(index, 1);\n}\n","// Local.\nimport { removeItem } from './remove-item';\n\n/**\n * Removes items from a list based on a predicate function.\n * @param list The list to remove items from.\n * @param predicate A function that will be called for each item\n * to determine whether to remove that item from the list or not.\n */\nexport function removeWhere<T>(list: T[], predicate: (value: T) => boolean) {\n for (const item of list) {\n const remove = predicate(item);\n if (remove) {\n removeItem(list, item);\n }\n }\n}\n","export enum SocialSignIn {\n Apple = 'apple',\n Azure = 'microsoft',\n Bitbucket = 'bitbucket',\n Discord = 'discord',\n Facebook = 'facebook',\n Figma = 'figma',\n GitHub = 'github',\n GitLab = 'gitlab',\n Google = 'google',\n Kakao = 'kakao',\n Keycloak = 'keycloak',\n LinkedIn = 'linkedin',\n Notion = 'notion',\n Twitch = 'twitch',\n Twitter = 'twitter',\n Slack = 'slack',\n Spotify = 'spotify',\n WorkOS = 'workos',\n Zoom = 'zoom',\n}\n\nexport interface SocialSignInItem {\n title: string;\n value: SocialSignIn;\n icon?: string;\n}\n\nexport const ALL_SOCIAL_SIGN_INS = Object.values(SocialSignIn);\nexport const ALL_SOCIAL_SIGN_IN_ITEMS = Object.entries(SocialSignIn).map(\n ([title, value]) => {\n return { title, value };\n }\n);\n\nexport function toSocialItem(social: string[]): SocialSignInItem[];\nexport function toSocialItem(social: string): SocialSignInItem;\nexport function toSocialItem(\n social: string | string[]\n): SocialSignInItem | SocialSignInItem[] {\n const asArray = social as string[];\n const asSingle = social as string;\n return Array.isArray(social)\n ? asArray.map(toSingleSocialItem)\n : toSingleSocialItem(asSingle);\n}\n\nfunction toSingleSocialItem(socialValue: string): SocialSignInItem {\n const item = ALL_SOCIAL_SIGN_IN_ITEMS.find((s) => s.value === socialValue);\n if (!item) {\n throw new Error(`No social sign in item with value '${socialValue}' found`);\n }\n\n return item;\n}\n","export function trimEnd(value: string, trimValue = ' ') {\n const trimLength = trimValue.length;\n return value.endsWith(trimValue)\n ? value.substring(0, value.length - trimLength)\n : value;\n}\n","export function trimStart(value: string, trimValue = ' ') {\n const trimLength = trimValue.length;\n return value.startsWith(trimValue) ? value.substring(trimLength) : value;\n}\n","// Angular.\nimport { UrlTree } from '@angular/router';\n\n// 3rd party.\nimport { BehaviorSubject } from 'rxjs';\n\n// Local.\nimport { LogConfig } from './logging/log-config';\nimport { trimEnd } from './format/trim-end.function';\nimport {\n SocialSignIn,\n toSocialItem,\n SocialSignInItem,\n} from './sign-in/social-sign-in';\n\nexport const DEFAULT_ROUTES: ComponentRoutes = {\n main: '/',\n signIn: '/sign-in',\n register: '/register',\n registerOrSignIn: '/auth',\n setPassword: '/set-password',\n resetPassword: '/reset-password',\n postSignOut: '/sign-in',\n};\n\ninterface BaseSupabaseConfigProperties {\n apiKey: string;\n mainRoute?: string;\n signIn?: SignInConfigProperties;\n logging?: LogConfig;\n register?: RegisterProperties;\n setPassword?: SetPasswordProperties;\n routes?: Partial<ComponentRoutes>;\n profile?: ProfileProperties;\n}\n\ninterface SupabaseConfigPropertiesByUrl extends BaseSupabaseConfigProperties {\n apiUrl: string;\n}\n\ninterface SupabaseConfigPropertiesByProject\n extends BaseSupabaseConfigProperties {\n project: string;\n}\n\nexport type SupabaseConfigProperties =\n | SupabaseConfigPropertiesByUrl\n | SupabaseConfigPropertiesByProject;\n\ninterface ComponentRoutes {\n main: string;\n signIn: string;\n register: string;\n registerOrSignIn: string;\n setPassword: string;\n resetPassword: string;\n userProfile?: string;\n postSignOut?: string;\n}\n\ninterface UserRegistrationMetadata {\n label: string;\n field: string;\n type?: 'text' | 'number';\n required?: boolean;\n defaultValue?: string | number;\n}\n\ninterface RegisterProperties {\n title?: string;\n metadata?: UserRegistrationMetadata[];\n}\n\ninterface ProfileProperties {\n table?: string;\n avatarField?: string;\n firstNameField?: string;\n lastNameField?: string;\n}\n\ntype SocialSignInFn = (social: SocialSignIn) => boolean | void;\n\ninterface SignInConfigProperties {\n title?: string;\n magicLinks?: boolean;\n rememberMe?: boolean;\n socials?: SocialSignIn[];\n socialIconsRoot?: string;\n rememberMeStorageKey?: string;\n otpEnabled?: boolean;\n otpLength?: number;\n redirectTo?: string | string[] | UrlTree | null | undefined;\n onSocialSignIn?: SocialSignInFn;\n}\n\ninterface ApiInfo {\n url: string;\n key: string;\n}\n\ninterface SetPasswordProperties {\n title?: string;\n requireConfirm?: boolean;\n showMessageOnSave?: boolean;\n redirectTo?: string | string[] | UrlTree | null | undefined;\n}\n\nclass SetPasswordConfig implements SetPasswordProperties {\n title = '';\n requireConfirm = true;\n showMessageOnSave = true;\n\n constructor(init?: Partial<SetPasswordProperties>) {\n Object.assign(this, init);\n }\n}\n\nclass ProfileConfig implements ProfileProperties {\n table = '';\n userIdField = 'user_id';\n firstNameField = 'first_name';\n lastNameField = 'last_name';\n avatarField = 'avatar';\n\n constructor(init?: Partial<ProfileProperties>) {\n Object.assign(this, init);\n }\n}\n\nclass RegisterConfig implements RegisterConfig {\n title = '';\n metadata: UserRegistrationMetadata[] = [];\n\n constructor(init?: Partial<RegisterProperties>) {\n Object.assign(this, init);\n this.metadata = this.metadata || [];\n }\n}\n\nexport class SignInConfig implements SignInConfigProperties {\n title = '';\n magicLinks = true;\n socials: SocialSignIn[] = [];\n rememberMe = true;\n socialIconsRoot = 'https://supabase.com/dashboard/img/icons/';\n socialSignInItems: SocialSignInItem[] = [];\n redirectTo?: string | string[] | UrlTree | null | undefined;\n rememberMeStorageKey = 'supabase.auth.info';\n otpEnabled = true;\n otpLength = 6;\n onSocialSignIn?: SocialSignInFn;\n\n constructor(init?: Partial<SignInConfig>) {\n Object.assign(this, init);\n this.setSocialSignInItems();\n }\n\n private setSocialSignInItems(): void {\n for (const social of this.socials) {\n const item = toSocialItem(social);\n if (this.socialIconsRoot) {\n const root = trimEnd(this.socialIconsRoot, '/');\n item.icon = `${root}/${item.value}-icon.svg`;\n }\n\n this.socialSignInItems.push(item);\n }\n }\n}\n\nexport class SupabaseConfig {\n signIn: SignInConfig;\n api: BehaviorSubject<{ url: string; key: string }>;\n logging?: LogConfig;\n mainRoute = '/';\n setPassword: SetPasswordConfig;\n register: RegisterConfig;\n routes: ComponentRoutes = DEFAULT_ROUTES;\n redirectParamName: string | null | undefined = 'redirect';\n profile: ProfileConfig;\n\n constructor(init: SupabaseConfigProperties) {\n Object.assign(this.routes, init.routes);\n const options = init as SupabaseConfigPropertiesByUrl &\n SupabaseConfigPropertiesByProject;\n\n const url = SupabaseConfig.toApiUrl(options.apiUrl || options.project);\n this.logging = init.logging;\n this.setPassword = new SetPasswordConfig(init.setPassword);\n this.signIn = new SignInConfig(init.signIn);\n this.register = new RegisterConfig(init.register);\n this.profile = new ProfileConfig(init.profile);\n this.api = new BehaviorSubject<ApiInfo>({\n url: url,\n key: init.apiKey,\n });\n }\n\n static toApiUrl(urlOrProjectId: string): string {\n return urlOrProjectId.startsWith('http')\n ? urlOrProjectId\n : `https://${urlOrProjectId}.supabase.co`;\n }\n}\n","export function isString(value: unknown): boolean {\n return typeof value === 'string';\n}\n","// Angular.\nimport { Injectable } from '@angular/core';\nimport {\n Router,\n UrlTree,\n ActivatedRoute,\n NavigationExtras,\n NavigationBehaviorOptions,\n} from '@angular/router';\n\n// Local.\nimport { trimEnd } from './format/trim-end.function';\nimport { trimStart } from './format/trim-start.function';\nimport { LocationStrategy } from '@angular/common';\nimport { SupabaseConfig } from './supabase-config';\nimport { isString } from './type-check/is-string';\n\n@Injectable({ providedIn: 'root' })\nexport class RouteService {\n constructor(\n private readonly router: Router,\n private readonly config: SupabaseConfig,\n private readonly route: ActivatedRoute,\n private readonly locationStrategy: LocationStrategy\n ) {}\n\n getRootUrl(): string {\n const [root] = window.location.href.split(this.router.url);\n return root;\n }\n\n appendRoute(route: string): string {\n const root = trimEnd(this.getRootUrl(), '/');\n return this.join(root, route);\n }\n\n join(...parts: string[]): string {\n let result = '';\n let i = 0;\n\n for (const part of parts) {\n const isLast = i === parts.length - 1;\n\n if (isLast) {\n break;\n }\n\n const nextPart = parts[i + 1];\n const trimmed1 = trimEnd(part, '/');\n const trimmed2 = trimStart(nextPart, '/');\n result += trimmed2 ? `${trimmed1}/${trimmed2}` : trimmed1;\n i++;\n }\n\n return result;\n }\n\n getRedirectParamValue(): string | null {\n const { redirectParamName } = this.config;\n return redirectParamName\n ? this.route.snapshot.queryParams[redirectParamName]\n : null;\n }\n\n constructAbsoluteUrl(...appendRoutePaths: string[]): string {\n const baseHref = this.locationStrategy.getBaseHref();\n return this.join(location.origin, baseHref, ...appendRoutePaths);\n }\n\n goTo(route: string[], options?: NavigationExtras): Promise<boolean>;\n goTo(\n route: string | UrlTree,\n options?: NavigationBehaviorOptions\n ): Promise<boolean>;\n goTo(\n route: string | string[] | UrlTree,\n options?: NavigationExtras | NavigationBehaviorOptions\n ): Promise<boolean> {\n const asString = route as string;\n const isAbsoluteUrl = isString(route) && asString.startsWith('http');\n\n if (isAbsoluteUrl) {\n const root = this.getRootUrl();\n route = trimStart(asString, root);\n }\n\n return Array.isArray(route)\n ? this.router.navigate(route as string[], options as NavigationExtras)\n : this.router.navigateByUrl(route as string | UrlTree, options);\n }\n}\n","export enum LogLevel {\n Trace,\n Debug,\n Info,\n Warn,\n Error,\n Fatal,\n}\n","// Angular.\nimport { Injectable } from '@angular/core';\n\n// Local.\nimport { LogLevel } from './log-level';\nimport { KeyValue } from '../key-value';\nimport { LogConfig } from './log-config';\nimport { SupabaseConfig } from '../supabase-config';\n\ninterface LogEntry {\n level: LogLevel;\n timestamp: Date;\n message: string;\n error?: Error;\n}\n\nconst DEFAULT_LOG_CONFIG: Required<LogConfig> = {\n logLevel: LogLevel.Warn,\n enabled: true,\n persistLogs: false,\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class LogService {\n private readonly logs: LogEntry[] = [];\n private readonly config: Required<LogConfig>;\n private readonly consoleMap: KeyValue<(...data: unknown[]) => void> = {};\n\n constructor(config: SupabaseConfig) {\n this.config = { ...DEFAULT_LOG_CONFIG, ...config.logging };\n this.consoleMap[LogLevel.Trace] = console.trace;\n this.consoleMap[LogLevel.Debug] = console.debug;\n this.consoleMap[LogLevel.Info] = console.info;\n this.consoleMap[LogLevel.Warn] = console.warn;\n this.consoleMap[LogLevel.Error] = console.error;\n this.consoleMap[LogLevel.Fatal] = console.error;\n }\n\n log(level: LogLevel, message: string, error?: Error): void {\n const skip = this.config.enabled === false || level > this.config.logLevel;\n\n if (skip) {\n return;\n }\n\n const args: unknown[] = [message];\n if (error) {\n args.push(error);\n }\n\n this.consoleMap[level].apply(console, args);\n if (this.config.persistLogs) {\n this.logs.push({ timestamp: new Date(), level, message, error });\n }\n }\n\n trace(message: string): void {\n this.log(LogLevel.Trace, message);\n }\n\n debug(message: string): void {\n this.log(LogLevel.Debug, message);\n }\n\n info(message: string): void {\n this.log(LogLevel.Info, message);\n }\n\n warn(message: string, error?: Error): void {\n this.log(LogLevel.Warn, message, error);\n }\n\n error(message: string, error?: Error): void {\n this.log(LogLevel.Error, message, error);\n }\n\n fatal(message: string, error?: Error): void {\n this.log(LogLevel.Fatal, message, error);\n }\n}\n","// Angular.\nimport { Injectable, NgZone } from '@angular/core';\n\n// 3rd party.\nimport { BehaviorSubject, Subject, filter, firstValueFrom, map } from 'rxjs';\nimport {\n User,\n Session,\n createClient,\n SupabaseClient,\n AuthChangeEvent,\n} from '@supabase/supabase-js';\n\n// Local.\nimport { SupabaseConfig } from './supabase-config';\nimport { LogService } from './logging/log.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SupabaseService {\n client!: SupabaseClient;\n readonly authChange = new Subject<AuthChangeEvent>();\n readonly initialized = new BehaviorSubject<boolean>(false);\n readonly session = new BehaviorSubject<Session | null>(null);\n readonly user = new BehaviorSubject<User | null>(null);\n readonly userDisplayName = new BehaviorSubject<string>('');\n readonly userSubheading = new BehaviorSubject<string>('');\n readonly userProfile = new BehaviorSubject<unknown>(null);\n readonly userAvatar = new BehaviorSubject<string | null>(null);\n readonly signedIn = new BehaviorSubject<boolean>(false);\n readonly loading = new BehaviorSubject<boolean>(true);\n readonly clientReady: Promise<SupabaseClient>;\n\n get isSignedIn(): boolean {\n return this.signedIn.value;\n }\n\n get isNotSignedIn(): boolean {\n return !this.signedIn.value;\n }\n\n constructor(\n private readonly zone: NgZone,\n private readonly log: LogService,\n private readonly config: SupabaseConfig\n ) {\n this.user.subscribe((user: User | null) => this.setUserInformation(user));\n\n this.clientReady = firstValueFrom(\n this.initialized.pipe(\n filter(Boolean),\n map(() => this.client)\n )\n );\n\n this.config.api.subscribe(() => this.setup());\n }\n\n waitForSignedIn(): Promise<Session> {\n return firstValueFrom(\n this.signedIn.pipe(\n filter(Boolean),\n map(() => this.session.value as Session)\n )\n );\n }\n\n refreshUserDisplayInfo(): Promise<void> {\n return this.setUserInformation(this.user.value);\n }\n\n private async setUserInformation(user: User | null): Promise<void> {\n const profileTable = this.config.profile.table;\n let displayName = '';\n\n if (user && profileTable) {\n this.log.debug(`Retrieving user profile for user ID '${user.id}'`);\n const { error, data: profile } = await this.client\n .from(profileTable)\n .select()\n .eq(this.config.profile.userIdField, user.id)\n .limit(1)\n .single();\n\n if (error) {\n this.log.error(\n `Failed to retrieve user profile. ${error.details}`,\n error as unknown as Error\n );\n }\n\n if (profile) {\n const firstName = profile[this.config.profile.firstNameField];\n const lastName = profile[this.config.profile.lastNameField];\n const avatar = profile[this.config.profile.avatarField];\n displayName = `${firstName || ''} ${lastName || ''}`.trim();\n this.userProfile.next(profile);\n\n if (avatar) {\n this.userAvatar.next(avatar);\n }\n\n this.log.debug(\n `Retrieving display name of '${displayName}' from profile`\n );\n } else {\n this.log.warn(`No profile found for user ID '${user.id}'`);\n }\n }\n\n displayName = displayName || this.extractDisplay(user);\n const subheading =\n displayName === user?.email\n ? ''\n : user?.user_metadata?.['title'] || user?.email || '';\n\n this.userDisplayName.next(displayName);\n this.userSubheading.next(subheading);\n }\n\n private extractDisplay(user: User | null): string {\n const { first_name, last_name } = user?.user_metadata || {};\n const display = `${first_name || ''} ${last_name || ''}`.trim();\n return user ? display || user.email || user.id : '';\n }\n\n private setup(): void {\n if (this.isSignedIn) {\n this.setStateForSignedOut();\n }\n\n this.createClient();\n }\n\n private createClient(): void {\n const { url, key } = this.config.api.value;\n this.client = createClient(url, key);\n this.client.auth.onAuthStateChange((event) => {\n this.zone.run(() => {\n this.setAuthState(event);\n });\n });\n }\n\n private setAuthState(event: AuthChangeEvent): void {\n this.log.info(`Auth state change: '${event}'`);\n this.authChange.next(event);\n if (event === 'INITIAL_SESSION') {\n this.initialized.next(true);\n this.loading.next(false);\n } else if (event === 'SIGNED_IN') {\n this.signedIn.next(true);\n this.tryGetSession();\n } else if (event === 'SIGNED_OUT') {\n this.setStateForSignedOut();\n }\n }\n\n private async tryGetSession(): Promise<void> {\n const { data, error } = await this.client.auth.getSession();\n const noSession = !error && !data.session;\n\n if (noSession) {\n this.log.error('No session information retrieved');\n return;\n }\n\n if (error) {\n this.log.error(`Failed to get user session`, error);\n return;\n }\n\n this.session.next(data.session);\n\n if (data.session?.user) {\n this.user.next(data.session.user);\n }\n }\n\n private setStateForSignedOut(): void {\n this.session.next(null);\n this.signedIn.next(false);\n this.user.next(null);\n this.userProfile.next(null);\n this.userDisplayName.next('');\n this.userSubheading.next('');\n }\n}\n","// Angular.\nimport { Injectable } from '@angular/core';\n\n// Local.\nimport { KeyValue } from '../key-value';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class PersistentStorageService {\n getJson<T = KeyValue>(key: string): T | null {\n const item = this.getItem(key);\n return item ? (JSON.parse(item) as T) : null;\n }\n\n setJson(key: string, value: object): void {\n const json = JSON.stringify(value);\n this.setItem(key, json);\n }\n\n getItem(key: string): string | null {\n return localStorage.getItem(key);\n }\n\n setItem(key: string, value: string): void {\n localStorage.setItem(key, value);\n }\n\n clear(): void {\n localStorage.clear();\n }\n\n removeItem(key: string): void {\n localStorage.removeItem(key);\n }\n}\n","// Angular.\nimport { UrlTree } from '@angular/router';\nimport {\n Input,\n signal,\n OnInit,\n inject,\n Component,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport {\n FormGroup,\n Validators,\n FormControl,\n ReactiveFormsModule,\n} from '@angular/forms';\n\n// 3rd party.\nimport { Subject } from 'rxjs';\n\n// Local.\nimport { WaitMessage } from '../wait-message';\nimport { RouteService } from '../route.service';\nimport { LogService } from '../logging/log.service';\nimport { SupabaseConfig } from '../supabase-config';\nimport { SupabaseService } from '../supabase.service';\nimport { PersistentStorageService } from '../storage/persistent-storage.service';\n\n@Component({\n selector: 'supabase-sign-in',\n standalone: true,\n imports: [ReactiveFormsModule],\n templateUrl: './sign-in.component.html',\n styleUrl: './sign-in.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SignInComponent implements OnInit {\n @Input() title = '';\n @Input() email = '';\n @Input() password = '';\n @Input() usePassword = false;\n @Input() redirectTo = '';\n @Input() rememberMe: boolean | undefined;\n\n /**\n * The absolute route to redirect to from the email link. This should not\n * be used in conjunction with \"redirectToPath\" (use one or the other).\n */\n @Input() redirectToUrl = '';\n\n /**\n * A route path to redirect to from the email link (as apposed to an absolute path).\n * This path will be appended to the app's root URL and will be the URL that is\n * targeted from the email link. This should not be used in conjunction with\n * \"redirectTo\" (use one or the other).\n */\n @Input() redirectToPath = '';\n\n forgotPassword = false;\n signingIn = new Subject<boolean>();\n form = new FormGroup({\n email: new FormControl('', [Validators.required]),\n password: new FormControl(''),\n usePassword: new FormControl(false),\n rememberMe: new FormControl(true),\n });\n\n readonly errorMessage = signal<string | null>(null);\n readonly wait = signal<WaitMessage | null>(null);\n readonly verifyingOtp = signal(false);\n\n protected readonly log = inject(LogService);\n protected readonly config = inject(SupabaseConfig);\n protected readonly supabase = inject(SupabaseService);\n protected readonly routeService = inject(RouteService);\n protected readonly storage = inject(PersistentStorageService);\n\n ngOnInit(): void {\n this.title = this.title ?? this.config.signIn.title;\n const rememberMe = this.rememberMe ?? this.config.signIn.rememberMe;\n this.form.controls.email.setValue(this.email);\n this.form.controls.usePassword.setValue(this.usePassword);\n this.form.controls.password.setValue(this.password);\n this.form.controls.rememberMe.setValue(rememberMe);\n this.tryLoadRememberMe();\n }\n\n showSignInWithPassword(event?: MouseEvent): void {\n event?.preventDefault();\n this.form.controls.usePassword.setValue(true);\n this.form.controls.password.setValidators([Validators.required]);\n this.revalidateAllControls();\n }\n\n showSignInWithEmail(event?: MouseEvent): void {\n event?.preventDefault();\n this.form.controls.usePassword.setValue(false);\n this.form.controls.password.setValidators([]);\n this.revalidateAllControls();\n }\n\n showForgotPassword(event?: MouseEvent): void {\n event?.preventDefault();\n this.forgotPassword = true;\n }\n\n signIn(): void {\n if (this.form.disabled || this.form.invalid) {\n return;\n }\n\n this.form.value.usePassword\n ? this.signInWithPassword()\n : this.signInWithMagicLink();\n }\n\n async verifyOtp(token: string): Promise<void> {\n this.verifyingOtp.set(true);\n const email = this.form.value.email as string;\n const { error } = await this.supabase.client.auth.verifyOtp({\n email,\n token,\n type: 'email',\n });\n\n if (error) {\n this.errorMessage.set(error.message);\n return;\n }\n\n const redirectUrl = this.getRedirectTo();\n this.routeService.goTo(redirectUrl);\n }\n\n protected revalidateAllControls(): void {\n Object.values(this.form.controls).forEach((control) =>\n control.updateValueAndValidity()\n );\n }\n\n protected async signInWithPassword(): Promise<void> {\n try {\n this.log.debug('Logging in with password');\n this.signingIn.next(true);\n const email = this.form.value.email as string;\n const password = this.form.value.password as string;\n const { error } = await this.supabase.client.auth.signInWithPassword({\n email,\n password,\n });\n\n if (error) {\n this.log.debug(`Sign in failed. ${error.message}`);\n this.errorMessage.set(error.message);\n return;\n }\n\n const redirect = this.getRedirectUrl();\n this.log.debug(`Signed in successfully. Redirecting to ${redirect}`);\n this.trySaveRememberMe();\n\n await this.supabase.waitForSignedIn();\n this.routeService.goTo(redirect as string);\n } catch (error) {\n this.log.error(`Failed to sign in`);\n // TODO: Handle - @rusty.green.\n } finally {\n this.signingIn.next(false);\n }\n }\n\n protected getRedirectUrl(): string | string[] | UrlTree {\n return (\n this.redirectTo ||\n this.routeService.getRedirectParamValue() ||\n this.config.signIn.redirectTo ||\n this.config.mainRoute\n );\n }\n\n protected async signInWithMagicLink(): Promise<void> {\n try {\n this.signingIn.next(true);\n const email = this.form.value.email as string;\n const emailRedirectTo = this.getRedirectUrl().toString();\n const { error } = await this.supabase.client.auth.signInWithOtp({\n email,\n options: {\n emailRedirectTo,\n // TODO: Pass/configure other options (like \"shouldCreateUser\") - @rusty.green\n },\n });\n\n if (error) {\n this.errorMessage.set(error.message);\n return;\n }\n\n this.wait.set({\n icon: 'pi pi-envelope',\n title: 'Check your email',\n enableOtp: this.config.signIn.otpEnabled,\n 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.`,\n });\n\n this.trySaveRememberMe();\n } catch (error) {\n // TODO: Handle - @russell.green\n } finally {\n this.signingIn.next(false);\n }\n }\n\n protected tryLoadRememberMe(): void {\n const info = this.storage.getJson(this.config.signIn.rememberMeStorageKey);\n if (info) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.form.patchValue(info);\n this.form.value.usePassword\n ? this.showSignInWithPassword()\n : this.showSignInWithEmail();\n }\n }\n\n protected clearRememberMe(): void {\n this.storage.removeItem(this.config.signIn.rememberMeStorageKey);\n }\n\n protected trySaveRememberMe(): void {\n if (this.form.value.rememberMe) {\n const { email, usePassword } = this.form.value;\n const value = { email, usePassword };\n this.storage.setJson(this.config.signIn.rememberMeStorageKey, value);\n } else {\n this.clearRememberMe();\n }\n }\n\n protected getRedirectTo(): string {\n const fallback = this.config.routes.userProfile || this.config.routes.main;\n return this.redirectToPath\n ? this.routeService.appendRoute(this.redirectToPath)\n : this.redirectToUrl || this.routeService.appendRoute(fallback);\n }\n}\n","","export function uuid(): string {\n return crypto.randomUUID();\n}\n","// Angular.\nimport { Injectable } from '@angular/core';\n\n// Local.\nimport { Message } from './message';\nimport { uuid } from '../crypto/uuid';\nimport { isString } from '../type-check/is-string';\n\nexport interface MessageRequest extends Partial<Message> {\n message: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [x: string]: any;\n}\n\n@Injectable({ providedIn: 'root' })\nexport abstract class NotifyService {\n show(message: string, title?: string): Message;\n show(target: MessageRequest): Message;\n show(targetOrMessage: MessageRequest | string, title?: string): Message {\n const asString = targetOrMessage as string;\n const asMessage = targetOrMessage as MessageRequest;\n const target = isString(targetOrMessage)\n ? {\n message: asString,\n title: title,\n }\n : asMessage;\n\n const message: Message = {\n id: target.id || uuid(),\n message: target.message || '',\n position: target.position || 'bottom-right',\n title: target.title || '',\n severity: target.severity || 'info',\n };\n\n this.showNotify(message);\n return message;\n }\n\n showInfo(target: MessageRequest): Message {\n return this.show({ ...target, severity: 'info' });\n }\n\n showSuccess(target: MessageRequest): Message {\n return this.show({ ...target, severity: 'success' });\n }\n\n showWarn(target: MessageRequest): Message {\n return this.show({ ...target, severity: 'warn' });\n }\n\n showError(target: MessageRequest): Message {\n return this.show({ ...target, severity: 'error' });\n }\n\n showFatal(target: MessageRequest): Message {\n return this.show({ ...target, severity: 'fatal' });\n }\n\n protected abstract showNotify(message: Message): void;\n}\n","// Angular.\nimport { CommonModule } from '@angular/common';\nimport {\n Input,\n OnInit,\n inject,\n signal,\n Output,\n Component,\n OnDestroy,\n EventEmitter,\n ChangeDetectorRef,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { UrlTree } from '@angular/router';\nimport {\n FormGroup,\n Validators,\n FormControl,\n AbstractControl,\n ReactiveFormsModule,\n} from '@angular/forms';\n\n// 3rd party.\nimport { Subscription, debounceTime } from 'rxjs';\nimport { AuthError, User } from '@supabase/supabase-js';\n\n// Local.\nimport { RouteService } from '../route.service';\nimport { LogService } from '../logging/log.service';\nimport { SupabaseConfig } from '../supabase-config';\nimport { SupabaseService } from '../supabase.service';\nimport { NotifyService } from '../notify/notify.service';\n\n@Component({\n selector: 'supabase-set-password',\n standalone: true,\n imports: [CommonModule, ReactiveFormsModule],\n templateUrl: './set-password.component.html',\n styleUrl: './set-password.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SetPasswordComponent implements OnInit, OnDestroy {\n @Input() title = '';\n @Input() saveLabel = 'Save Password';\n @Input() savingLabel = 'Saving password...';\n @Input() confirmPassword!: boolean;\n @Input() redirectTo: string | string[] | UrlTree | null | undefined;\n @Output() saved = new EventEmitter<User | null>();\n\n readonly saving = signal(false);\n readonly errorMessage = signal('');\n readonly confirmMisMatch = signal(false);\n readonly form: FormGroup = new FormGroup({\n password: new FormControl('', [Validators.required]),\n });\n\n protected readonly log = inject(LogService);\n protected readonly notify = inject(NotifyService);\n protected readonly config = inject(SupabaseConfig);\n protected readonly supabase = inject(SupabaseService);\n protected readonly routeService = inject(RouteService);\n protected readonly changeDetector = inject(ChangeDetectorRef);\n protected readonly subscriptions: Subscription[] = [];\n\n ngOnInit(): void {\n this.title = this.title ?? this.config.setPassword.title;\n this.confirmPassword =\n this.confirmPassword ?? this.config.setPassword.requireConfirm;\n\n if (this.confirmPassword) {\n const confirm = new FormControl('', [\n Validators.required,\n validatePasswordsMatch,\n ]);\n\n this.form.addControl('confirm', confirm);\n this.subscriptions.push(\n this.form.valueChanges.pipe(debounceTime(250)).subscribe(() => {\n const isMisMatch =\n !this.form.disabled &&\n confirm.dirty &&\n confirm.value !== this.form.value.password;\n\n this.confirmMisMatch.set(isMisMatch);\n })\n );\n }\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((s) => s.unsubscribe());\n }\n\n async submit(): Promise<void> {\n if (this.form.invalid) {\n return;\n }\n\n this.form.disable();\n this.saving.set(true);\n\n try {\n const { data, error } = await this.supabase.client.auth.updateUser({\n password: this.form.value.password,\n });\n\n if (error) {\n this.errorMessage.set(error.message);\n this.log.error(`Failed to save password. ${error.message}`);\n this.onError(error);\n return;\n }\n\n this.log.info(`Set password for '${data?.user?.email || ''}'`);\n this.saved.emit(data.user);\n\n if (this.config.setPassword.showMessageOnSave) {\n this.notify.showSuccess({\n title: 'Password Changed',\n message: 'Your password was successfully reset',\n });\n }\n\n if (this.redirectTo) {\n await this.routeService.goTo(this.redirectTo as string);\n }\n\n this.form.reset();\n } finally {\n this.form.enable();\n this.saving.set(false);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onError(error: AuthError): void {\n // Do nothing in here because this is just a hook for child\n // components to easily subscribe to when an error occurs.\n }\n}\n\nfunction validatePasswordsMatch(\n c: AbstractControl\n): null | { passwordsMatch: false } {\n const root = c.root as FormGroup;\n return root.value['password'] === c.value ? null : { passwordsMatch: false };\n}\n","","// Angular.\nimport { CommonModule } from '@angular/common';\nimport {\n Input,\n signal,\n Component,\n ChangeDetectionStrategy,\n OnInit,\n} from '@angular/core';\nimport {\n FormGroup,\n Validators,\n FormControl,\n ReactiveFormsModule,\n} from '@angular/forms';\n\n// Local.\nimport { WaitMessage } from '../wait-message';\nimport { AuthError } from '@supabase/supabase-js';\nimport { LogService } from '../logging/log.service';\nimport { SupabaseService } from '../supabase.service';\nimport { RouteService } from '../route.service';\nimport { SupabaseConfig } from '../supabase-config';\n\n@Component({\n selector: 'supabase-reset-password',\n standalone: true,\n imports: [CommonModule, ReactiveFormsModule],\n templateUrl: './reset-password.component.html',\n styleUrl: './reset-password.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ResetPasswordComponent implements OnInit {\n @Input() title = 'Reset Password';\n @Input() email = '';\n /**\n * The absolute route to redirect to from the email link. This should not\n * be used in conjunction with \"redirectToPath\" (use one or the other).\n */\n @Input() redirectToUrl = '';\n\n /**\n * A route path to redirect to from the email link (as apposed to an absolute path).\n * This path will be appended to the app's root URL and will be the URL that is\n * targeted from the email link. This should not be used in conjunction with\n * \"redirectTo\" (use one or the other).\n */\n @Input() redirectToPath = '';\n\n readonly errorMessage = signal('');\n readonly sendingReset = signal(false);\n readonly wait = signal<WaitMessage | null>(null);\n readonly form = new FormGroup({\n email: new FormControl('', [Validators.required]),\n });\n\n constructor(\n private readonly log: LogService,\n private readonly config: SupabaseConfig,\n private readonly supabase: SupabaseService,\n private readonly routeService: RouteService\n ) {}\n\n ngOnInit(): void {\n this.title = this.title ?? this.config.signIn.title;\n this.form.controls.email.setValue(this.email);\n }\n\n async resetPassword(): Promise<void> {\n if (this.form.invalid) {\n return;\n }\n\n this.form.disable();\n this.sendingReset.set(true);\n\n try {\n const email = this.form.value.email as string;\n const redirectTo = this.getRedirectTo();\n\n const { error } = await this.supabase.client.auth.resetPasswordForEmail(\n email,\n { redirectTo }\n );\n\n if (error) {\n this.errorMessage.set(error.message);\n this.log.error(`Failed to save password. ${error.message}`);\n this.onError(error);\n }\n\n this.log.info(`Sent reset password email to '${email}'`);\n this.wait.set({\n icon: 'pi pi-envelope',\n title: 'Check your email',\n 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.`,\n });\n } finally {\n this.form.enable();\n this.sendingReset.set(false);\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onError(error: AuthError): void {\n // Do nothing in here because this is just a hook for child\n // components to easily subscribe to when an error occurs.\n }\n\n protected getRedirectTo(): string {\n return this.redirectToPath\n ? this.routeService.appendRoute(this.redirectToPath)\n : this.redirectToUrl ||\n this.routeService.appendRoute(this.config.routes.setPassword);\n }\n}\n","","import { trimEnd } from './trim-end.function';\nimport { trimStart } from './trim-start.function';\n\nexport function trim(value: string, trimValue = ' ') {\n const trimmedStart = trimStart(value, trimValue);\n return trimEnd(trimmedStart, trimValue);\n}\n","// Angular.\nimport { inject } from '@angular/core';\nimport {\n Router,\n CanActivateFn,\n RouterStateSnapshot,\n ActivatedRouteSnapshot,\n} from '@angular/router';\n\n// Local.\nimport { KeyValue } from '../key-value';\nimport { RouteService } from '../route.service';\nimport { SupabaseConfig } from '../supabase-config';\nimport { LogService } from '../logging/log.service';\nimport { SupabaseService } from '../supabase.service';\n\nexport const IsSignedIn: CanActivateFn = async (\n route: ActivatedRouteSnapshot,\n state: RouterStateSnapshot\n) => {\n const log = inject(LogService);\n const supabase = inject(SupabaseService);\n const router = inject(Router);\n const config = inject(SupabaseConfig);\n const routeService = inject(RouteService);\n\n await supabase.clientReady;\n const signedIn = supabase.isSignedIn;\n\n if (!signedIn) {\n const queryParams: KeyValue = {};\n\n if (config.redirectParamName) {\n const redirect = routeService.constructAbsoluteUrl(state.url);\n queryParams[config.redirectParamName] = redirect;\n }\n\n log.info(\n `User cannot access route '${state.url}', redirecting to sign in page`\n );\n\n return router.createUrlTree([config.routes.signIn], { queryParams });\n }\n\n log.debug(`Activating route '${state.url}' for 'IsSignedIn' guard`);\n return supabase.isSignedIn;\n};\n","// Angular.\nimport { CommonModule } from '@angular/common';\nimport { FormControl, FormGroup, Validators } from '@angular/forms';\nimport {\n Input,\n signal,\n OnInit,\n Component,\n ChangeDetectionStrategy,\n} from '@angular/core';\n\n// 3rd party.\nimport { AuthError } from '@supabase/supabase-js';\n\n// Local.\nimport { WaitMessage } from '../wait-message';\nimport { RouteService } from '../route.service';\nimport { LogService } from '../logging/log.service';\nimport { SupabaseConfig } from '../supabase-config';\nimport { SupabaseService } from '../supabase.service';\n\n@Component({\n selector: 'supabase-register',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './register.component.html',\n styleUrl: './register.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class RegisterComponent implements OnInit {\n @Input() title = '';\n @Input() email = '';\n\n /**\n * The absolute route to redirect to from the email link. This should not\n * be used in conjunction with \"redirectToPath\" (use one or the other).\n */\n @Input() redirectToUrl = '';\n\n /**\n * A route path to redirect to from the email link (as apposed to an absolute path).\n * This path will be appended to the app's root URL and will be the URL that is\n * targeted from the email link. This should not be used in conjunction with\n * \"redirectTo\" (use one or the other).\n */\n @Input() redirectToPath = '';\n\n readonly errorMessage = signal('');\n readonly working = signal(false);\n readonly verifyingOtp = signal(false);\n readonly wait = signal<WaitMessage | null>(null);\n readonly form: FormGroup = new FormGroup({\n email: new FormControl('', [Validators.required]),\n });\n\n constructor(\n readonly config: SupabaseConfig,\n private readonly log: LogService,\n private readonly supabase: SupabaseService,\n private readonly routeService: RouteService\n ) {}\n\n ngOnInit(): void {\n this.title = this.title ?? this.config.register.title;\n\n if (this.config.register.metadata.length) {\n this.setupForMetadata();\n }\n }\n\n async register(): Promise<void> {\n if (this.form.invalid) {\n return;\n }\n\n try {\n const email = this.form.value.email as string;\n const data = this.form.value.metadata;\n const emailRedirectTo = this.getRedirectTo();\n\n this.form.disable();\n this.working.set(true);\n\n const { error } = await this.supabase.client.auth.signInWithOtp({\n email,\n options: {\n shouldCreateUser: true,\n emailRedirectTo,\n data,\n },\n });\n\n if (error) {\n this.errorMessage.set(error.message);\n this.log.error(`Failed to save password. ${error.message}`);\n this.onError(error);\n }\n\n this.log.info(`Sent OTP email to '${email}'`);\n this.wait.set({\n icon: 'pi pi-envelope',\n title: 'Check your email',\n enableOtp: this.config.signIn.otpEnabled,\n 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.`,\n });\n } finally {\n this.form.enable();\n this.working.set(false);\n }\n }\n\n async verifyOtp(token: string): Promise<void> {\n this.verifyingOtp.set(true);\n const email = this.form.value.email as string;\n const { error } = await this.supabase.client.auth.verifyOtp({\n email,\n token,\n type: 'email',\n });\n\n if (error) {\n this.onError(error);\n return;\n }\n\n const redirectUrl = this.getRedirectTo();\n this.routeService.goTo(redirectUrl);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onError(error: AuthError | string): void {\n // Do nothing in here because this is just a hook for child\n // components to easily subscribe to when an error occurs.\n }\n\n protected setupForMetadata(): void {\n const group = new FormGroup({});\n\n for (const meta of this.config.register.metadata) {\n const validators = meta.required ? [Validators.required] : [];\n const value = meta.defaultValue || '';\n const control = new FormControl(value, validators);\n group.addControl(meta.field, control);\n }\n\n this.form.addControl('metadata', group);\n }\n\n protected getRedirectTo(): string {\n const fallback = this.config.routes.userProfile || this.config.routes.main;\n return this.redirectToPath\n ? this.routeService.appendRoute(this.redirectToPath)\n : this.redirectToUrl || this.routeService.appendRoute(fallback);\n }\n}\n","","import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n selector: 'supabase-user-avatar',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './user-avatar.component.html',\n styleUrl: './user-avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UserAvatarComponent {}\n","","// Angular.\nimport { CommonModule } from '@angular/common';\nimport { ChangeDetectionStrategy, Component } from '@angular/core';\n\n@Component({\n selector: 'supabase-user-avatar-button',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './user-avatar-button.component.html',\n styleUrl: './user-avatar-button.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UserAvatarButtonComponent {}\n","","// Angular.\nimport { Router } from '@angular/router';\nimport { CommonModule } from '@angular/common';\nimport {\n OnInit,\n inject,\n signal,\n Component,\n ChangeDetectionStrategy,\n} from '@angular/core';\n\n// Local.\nimport { SupabaseConfig } from '../supabase-config';\nimport { SupabaseService } from '../supabase.service';\n\n@Component({\n selector: 'supabase-active-user-avatar-button',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './active-user-avatar-button.component.html',\n styleUrl: './active-user-avatar-button.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ActiveUserAvatarButtonComponent implements OnInit {\n loading = signal<boolean>(true);\n\n protected router = inject(Router);\n protected config = inject(SupabaseConfig);\n protected supabase = inject(SupabaseService);\n\n async ngOnInit(): Promise<void> {\n await this.supabase.clientReady;\n this.loading.set(false);\n }\n\n signOut(): void {\n this.supabase.client.auth.signOut();\n if (this.config.routes.postSignOut) {\n this.router.navigate([this.config.routes.postSignOut]);\n }\n }\n}\n","","import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n selector: 'supabase-register-or-sign-in',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './register-or-sign-in.component.html',\n styleUrl: './register-or-sign-in.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class RegisterOrSignInComponent {}\n","","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'initials',\n standalone: true,\n})\nexport class InitialsPipe implements PipeTransform {\n transform(fullName: string | null | undefined, numChars = 2): string {\n if (!fullName) {\n return '';\n }\n\n return fullName\n .split(' ')\n .slice(0, numChars)\n .map((n) => n[0].toUpperCase())\n .join('');\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i2.SupabaseConfig","i1.SupabaseConfig","i1.LogService","i3.SupabaseService","i4.RouteService","i2.LogService"],"mappings":";;;;;;;;;;AAAA;;;;AAIG;AACa,SAAA,UAAU,CAAC,IAAe,EAAE,IAAa,EAAA;AACvD,IAAA,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;AAChD,IAAA,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACxB;;ACRA;AAGA;;;;;AAKG;AACa,SAAA,WAAW,CAAI,IAAS,EAAE,SAAgC,EAAA;AACxE,IAAA,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;AACvB,QAAA,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,MAAM,EAAE;AACV,YAAA,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SACxB;KACF;AACH;;IChBY,aAoBX;AApBD,CAAA,UAAY,YAAY,EAAA;AACtB,IAAA,YAAA,CAAA,OAAA,CAAA,GAAA,OAAe,CAAA;AACf,IAAA,YAAA,CAAA,OAAA,CAAA,GAAA,WAAmB,CAAA;AACnB,IAAA,YAAA,CAAA,WAAA,CAAA,GAAA,WAAuB,CAAA;AACvB,IAAA,YAAA,CAAA,SAAA,CAAA,GAAA,SAAmB,CAAA;AACnB,IAAA,YAAA,CAAA,UAAA,CAAA,GAAA,UAAqB,CAAA;AACrB,IAAA,YAAA,CAAA,OAAA,CAAA,GAAA,OAAe,CAAA;AACf,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,OAAA,CAAA,GAAA,OAAe,CAAA;AACf,IAAA,YAAA,CAAA,UAAA,CAAA,GAAA,UAAqB,CAAA;AACrB,IAAA,YAAA,CAAA,UAAA,CAAA,GAAA,UAAqB,CAAA;AACrB,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,SAAA,CAAA,GAAA,SAAmB,CAAA;AACnB,IAAA,YAAA,CAAA,OAAA,CAAA,GAAA,OAAe,CAAA;AACf,IAAA,YAAA,CAAA,SAAA,CAAA,GAAA,SAAmB,CAAA;AACnB,IAAA,YAAA,CAAA,QAAA,CAAA,GAAA,QAAiB,CAAA;AACjB,IAAA,YAAA,CAAA,MAAA,CAAA,GAAA,MAAa,CAAA;AACf,CAAC,EApBW,YAAY,KAAZ,YAAY,GAoBvB,EAA