@dereekb/dbx-firebase 13.31.0 → 13.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,25 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, provideAppInitializer, makeEnvironmentProviders, Component, model, computed, ChangeDetectionStrategy, signal, Directive, EventEmitter, input, output, Injector, viewChild, HostListener, NgModule, ElementRef, forwardRef, effect, DestroyRef, Inject, Optional, Pipe } from '@angular/core';
3
3
  import { DbxAnalyticsService } from '@dereekb/dbx-analytics';
4
- import { asObservable, timeoutStartWith, filterMaybe, isNot, SubscriptionObject, lazyFrom, switchMapWhileTrue, loadingStateFromObs, distinctUntilKeysChange, cleanupDestroyable, iterationHasNextAndCanLoadMore, pageItemAccumulatorCurrentPage, accumulatorFlattenPageListLoadingState, useFirst, itemAccumulatorNextPageUntilResultsCount, iteratorNextPageUntilPage, iteratorNextPageUntilMaxPageLoadLimit, pageLoadingStateFromObs, useAsObservable, filterMaybeArray, mapEachAsync, invertObservableDecision, filterItemsWithObservableDecision, switchMapMaybe, beginLoading, mapLoadingStateValueWithOperator, valueFromFinishedLoadingState, skipInitialMaybe, distinctUntilModelKeyChange, successResult, errorResult, isLoadingStateLoading, cleanup, mapLoadingState, throwErrorFromLoadingStateError, maybeValueFromObservableOrValue, distinctUntilHasDifferentValues, MultiSubscriptionObject, startWithBeginLoading, skipAllInitialMaybe } from '@dereekb/rxjs';
5
- import { Observable, switchMap, of, shareReplay, map, Subject, merge, distinctUntilChanged, EMPTY, catchError, firstValueFrom, tap, BehaviorSubject, combineLatest, first, from, interval, exhaustMap, filter, take, startWith, throttleTime, NEVER, defaultIfEmpty, combineLatestWith, mergeMap } from 'rxjs';
4
+ import { asObservable, timeoutStartWith, filterMaybe, isNot, SubscriptionObject, lazyFrom, switchMapWhileTrue, loadingStateFromObs, distinctUntilKeysChange, cleanupDestroyable, iterationHasNextAndCanLoadMore, pageItemAccumulatorCurrentPage, accumulatorFlattenPageListLoadingState, useFirst, itemAccumulatorNextPageUntilResultsCount, iteratorNextPageUntilPage, iteratorNextPageUntilMaxPageLoadLimit, pageLoadingStateFromObs, useAsObservable, filterMaybeArray, mapEachAsync, invertObservableDecision, filterItemsWithObservableDecision, switchMapMaybe, beginLoading, mapLoadingStateValueWithOperator, valueFromFinishedLoadingState, skipInitialMaybe, distinctUntilModelKeyChange, successResult, errorResult, isLoadingStateLoading, cleanup, mapLoadingState, startWithBeginLoading, throwErrorFromLoadingStateError, maybeValueFromObservableOrValue, distinctUntilHasDifferentValues, MultiSubscriptionObject, skipAllInitialMaybe } from '@dereekb/rxjs';
5
+ import { Observable, switchMap, of, shareReplay, map, Subject, merge, distinctUntilChanged, EMPTY, catchError, firstValueFrom, tap, BehaviorSubject, combineLatest, first, from, interval, exhaustMap, filter, take, startWith, throttleTime, NEVER, defaultIfEmpty, combineLatestWith, timer, takeWhile, withLatestFrom, mergeMap } from 'rxjs';
6
6
  import * as i2 from '@dereekb/dbx-core';
7
- import { loggedInObsFromIsLoggedIn, loggedOutObsFromIsLoggedIn, authUserIdentifier, DbxInjectionContext, DBX_INJECTION_COMPONENT_DATA, DbxInjectionComponent, AbstractForwardDbxInjectionContextDirective, DbxInjectionContextDirective, DbxAuthImpersonationDelegate, DbxAuthService, provideDbxAuthImpersonation, DbxActionButtonDirective, completeOnDestroy, cleanSubscription, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionAutoTriggerDirective, clean, cleanLoadingContext, provideDbxRouteModelIdDirectiveDelegate, provideDbxRouteModelKeyDirectiveDelegate, AbstractIfDirective, LockSetComponentStore, newWithInjector, CutTextPipe, DbxActionContextStoreSourceInstance, DbxActionHandlerInstance, cleanSubscriptionWithLockSet, DbxButton, SimpleStorageAccessorFactory, dbxRouteParamReaderInstance, DbxRouteParamDefaultRedirectInstance } from '@dereekb/dbx-core';
7
+ import { loggedInObsFromIsLoggedIn, loggedOutObsFromIsLoggedIn, authUserIdentifier, DbxInjectionContext, DBX_INJECTION_COMPONENT_DATA, DbxInjectionComponent, AbstractForwardDbxInjectionContextDirective, DbxInjectionContextDirective, DbxAuthImpersonationDelegate, DbxAuthService, provideDbxAuthImpersonation, DbxActionButtonDirective, completeOnDestroy, cleanSubscription, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionAutoTriggerDirective, clean, cleanLoadingContext, provideDbxRouteModelIdDirectiveDelegate, provideDbxRouteModelKeyDirectiveDelegate, AbstractIfDirective, LockSetComponentStore, newWithInjector, DbxActionDisabledDirective, CutTextPipe, DbxActionValueDirective, DbxActionContextStoreSourceInstance, DbxActionHandlerInstance, cleanSubscriptionWithLockSet, DbxButton, SimpleStorageAccessorFactory, dbxRouteParamReaderInstance, DbxRouteParamDefaultRedirectInstance } from '@dereekb/dbx-core';
8
8
  import { onAuthStateChanged, onIdTokenChanged, confirmPasswordReset, sendPasswordResetEmail, signInWithPopup, signInWithRedirect, linkWithPopup, linkWithRedirect, linkWithCredential, unlink, createUserWithEmailAndPassword, signInWithEmailAndPassword, signInAnonymously, reauthenticateWithPopup, reauthenticateWithRedirect, getRedirectResult, OAuthProvider, FacebookAuthProvider, GithubAuthProvider, GoogleAuthProvider, TwitterAuthProvider, getAuth, connectAuthEmulator } from 'firebase/auth';
9
- import { AUTH_ADMIN_ROLE, cachedGetter, urlWithoutParameters, addToSet, removeFromSet, filterMaybeArrayValues, mapIterable, asArray, excludeValuesFromArray, containsStringAnyCase, addToSetCopy, iterableToArray, runAsyncTasksForValues, forEachKeyValue, countAllInNestedArray, invertDecision, useIterableOrValue, filterUniqueValues, reverseCompareFn, sortByNumberFunction, separateValues, readableError, isMaybeSo, firstValue, splitJoinRemainder, MS_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_DAY, unixDateTimeSecondsNumberForNow, MS_IN_MINUTE, addMilliseconds, unixDateTimeSecondsNumberFromDate, dateFromDateOrTimeSecondsNumber, MS_IN_SECOND, isPast } from '@dereekb/util';
9
+ import { AUTH_ADMIN_ROLE, cachedGetter, urlWithoutParameters, addToSet, removeFromSet, filterMaybeArrayValues, mapIterable, asArray, excludeValuesFromArray, containsStringAnyCase, addToSetCopy, iterableToArray, runAsyncTasksForValues, forEachKeyValue, countAllInNestedArray, invertDecision, useIterableOrValue, filterUniqueValues, reverseCompareFn, sortByNumberFunction, separateValues, readableError, isMaybeSo, firstValue, fixExtraQueryParameters, areEqualPOJOValues, MS_IN_SECOND, splitJoinRemainder, MS_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_DAY, unixDateTimeSecondsNumberForNow, MS_IN_MINUTE, addMilliseconds, unixDateTimeSecondsNumberFromDate, dateFromDateOrTimeSecondsNumber, isPast } from '@dereekb/util';
10
10
  import { safeFormatToISO8601DateString, msToSeconds, isSameDate } from '@dereekb/date';
11
11
  import { getToken, initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
12
12
  import * as i1$1 from '@dereekb/dbx-web';
13
- import { DbxLinkComponent, DbxActionModule, DbxButtonModule, DbxErrorComponent, DbxActionErrorDirective, DbxButtonComponent, DbxButtonSpacerDirective, DbxRouterAnchorModule, DbxSectionComponent, DbxWidgetService, AbstractDbxSelectionListWrapperDirective, DbxListWrapperComponentImportsModule, provideDbxListViewWrapper, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxSelectionListViewDirective, DbxSelectionValueListViewComponentImportsModule, provideDbxListView, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxValueListViewItemComponent, TwoColumnsContextStore, DbxAnchorComponent, DbxTwoColumnFullLeftDirective, DbxWidgetViewComponent, DbxTwoColumnComponent, DbxTwoBlockComponent, DbxTwoColumnRightComponent, AbstractPopupDirective, DbxPopupService, DbxPopupContentComponent, DbxPopupControlsComponent, DbxModelTypesService, DbxModelTrackerService, allDbxModelViewTrackerEventModelKeys, DbxListItemAnchorModifierDirective, DbxValueListItemModifierDirective, AbstractPopoverDirective, DbxPopoverContentComponent, DbxPopoverHeaderComponent, DbxPopoverScrollContentDirective, DbxListEmptyContentComponent, AbstractPopoverRefDirective, DbxPopoverService, DbxLoadingComponent, DbxDetailBlockComponent, DbxClickToCopyTextComponent, DbxDownloadTextViewComponent, DbxModelObjectStateService, DbxListViewWrapper, AbstractDbxWidgetComponent, DbxPopoverInteractionModule, DbxListModifierModule, DbxWebFilePreviewService, DbxActionSnackbarErrorDirective, DbxActionDialogDirective, compressImageFile, DbxFileUploadComponent, DbxPdfMergeEditorStore, PDF_MERGE_RESULT_MIME_TYPE, DbxActionLoadingContextDirective, DbxFileUploadActionSyncDirective } from '@dereekb/dbx-web';
13
+ import { DbxLinkComponent, DbxActionModule, DbxButtonModule, DbxErrorComponent, DbxActionErrorDirective, DbxButtonComponent, DbxButtonSpacerDirective, DbxRouterAnchorModule, DbxSectionComponent, DbxWidgetService, AbstractDbxSelectionListWrapperDirective, DbxListWrapperComponentImportsModule, provideDbxListViewWrapper, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxSelectionListViewDirective, DbxSelectionValueListViewComponentImportsModule, provideDbxListView, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxValueListViewItemComponent, TwoColumnsContextStore, DbxAnchorComponent, DbxTwoColumnFullLeftDirective, DbxWidgetViewComponent, DbxTwoColumnComponent, DbxTwoBlockComponent, DbxTwoColumnRightComponent, AbstractPopupDirective, DbxPopupService, DbxPopupContentComponent, DbxPopupControlsComponent, DbxModelTypesService, DbxModelTrackerService, allDbxModelViewTrackerEventModelKeys, DbxListItemAnchorModifierDirective, DbxValueListItemModifierDirective, AbstractPopoverDirective, DbxPopoverContentComponent, DbxPopoverHeaderComponent, DbxPopoverScrollContentDirective, DbxListEmptyContentComponent, AbstractPopoverRefDirective, DbxPopoverService, DbxLoadingComponent, DbxDetailBlockComponent, DbxClickToCopyTextComponent, DbxDownloadTextViewComponent, DbxModelObjectStateService, DbxListViewWrapper, AbstractDbxListWrapperDirective, AbstractDbxListViewDirective, DbxValueListViewComponentImportsModule, DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DbxActionConfirmDirective, DbxAvatarComponent, DbxChipDirective, DbxColorDirective, DbxIconTileComponent, DbxContentPitDirective, AbstractDbxWidgetComponent, AbstractDialogDirective, DbxDialogContentDirective, DbxPopoverInteractionModule, DbxListModifierModule, DbxWebFilePreviewService, DbxActionSnackbarErrorDirective, DbxActionDialogDirective, compressImageFile, DbxFileUploadComponent, DbxPdfMergeEditorStore, PDF_MERGE_RESULT_MIME_TYPE, DbxActionLoadingContextDirective, DbxFileUploadActionSyncDirective } from '@dereekb/dbx-web';
14
14
  import * as i1 from '@angular/material/icon';
15
15
  import { MatIconModule, MatIcon } from '@angular/material/icon';
16
- import { firebaseAuthErrorToReadableError, FIREBASE_AUTH_PASSWORD_MIN_LENGTH, FirebaseDevelopmentFunctions, ScheduledFunctionDevelopmentFunctionTypeEnum, clientFirebaseFirestoreContextFactory, SystemStateFirestoreCollections, NotificationFirestoreCollections, StorageFileFirestoreCollections, FIREBASE_DEVELOPMENT_FUNCTIONS_MAP_KEY, iterationQueryDocChangeWatcher, firebaseQuerySnapshotAccumulator, firebaseQueryItemAccumulator, firestoreModelKeysFromDocuments, firestoreModelIdsFromDocuments, documentReferencesFromDocuments, getDocumentSnapshots, getDataFromDocumentSnapshots, latestSnapshotsFromDocuments, dataFromDocumentSnapshots, loadDocumentsForKeys, loadDocumentsForDocumentReferences, loadDocumentsForIds, firestoreModelKeyCollectionTypePair, buildFirebaseCollectionTypeModelTypeMap, firestoreModelId, flatFirestoreModelKey, twoWayFlatFirestoreModelKey, FirestoreAccessorStreamMode, firestoreModelIdsFromKey, firestoreModelKeyPartPairs, firestoreModelKeyPairObject, documentDataWithIdAndKey, isClientFirebaseError, FIRESTORE_PERMISSION_DENIED_ERROR_CODE, inferKeyFromTwoWayFlatFirestoreModelKey, firestoreModelKeyParentKey, AppNotificationTemplateTypeInfoRecordService, NotificationFunctions, unreadNotificationItems, StorageFileFunctions, firestoreModelKey, storageFileIdentity, clientFirebaseStorageContextFactory } from '@dereekb/firebase';
16
+ import { firebaseAuthErrorToReadableError, FIREBASE_AUTH_PASSWORD_MIN_LENGTH, FirebaseDevelopmentFunctions, ScheduledFunctionDevelopmentFunctionTypeEnum, clientFirebaseFirestoreContextFactory, SystemStateFirestoreCollections, NotificationFirestoreCollections, StorageFileFirestoreCollections, FIREBASE_DEVELOPMENT_FUNCTIONS_MAP_KEY, iterationQueryDocChangeWatcher, firebaseQuerySnapshotAccumulator, firebaseQueryItemAccumulator, firestoreModelKeysFromDocuments, firestoreModelIdsFromDocuments, documentReferencesFromDocuments, getDocumentSnapshots, getDataFromDocumentSnapshots, latestSnapshotsFromDocuments, dataFromDocumentSnapshots, loadDocumentsForKeys, loadDocumentsForDocumentReferences, loadDocumentsForIds, firestoreModelKeyCollectionTypePair, buildFirebaseCollectionTypeModelTypeMap, firestoreModelId, flatFirestoreModelKey, twoWayFlatFirestoreModelKey, FirestoreAccessorStreamMode, firestoreModelIdsFromKey, firestoreModelKeyPartPairs, firestoreModelKeyPairObject, documentDataWithIdAndKey, isClientFirebaseError, FIRESTORE_PERMISSION_DENIED_ERROR_CODE, inferKeyFromTwoWayFlatFirestoreModelKey, firestoreModelKeyParentKey, userExternalConnectionEntryIsConnected, CALCOM_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE, ZOOM_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE, DISCORD_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE, ZOHO_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE, UserExternalConnectionFunctions, NotificationHealthCheckStatus, NotificationDeliveryMethod, MailgunNotificationHealthCheckIssueCode, KnownNotificationHealthCheckIssueCode, AppNotificationTemplateTypeInfoRecordService, NotificationFunctions, notificationUserHealthCheckNextRunAt, notificationUserHealthCheckNextProbeAtByMethod, notificationHealthCheckPendingProbeMethods, notificationUserHealthCheckNextVerifyAt, DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLE_SECONDS, ALL_NOTIFICATION_DELIVERY_METHODS, unreadNotificationItems, StorageFileFunctions, firestoreModelKey, storageFileIdentity, clientFirebaseStorageContextFactory } from '@dereekb/firebase';
17
17
  import * as i1$2 from '@dereekb/dbx-form';
18
18
  import { AbstractSyncForgeFormDirective, dbxForgeEmailField, DbxForgeFormComponentImportsModule, dbxForgeFormComponentProviders, DBX_FORGE_FORM_COMPONENT_TEMPLATE, AbstractConfigAsyncForgeFormDirective, dbxForgeUsernameLoginField, dbxForgeTextPasswordField, dbxForgeTextVerifyPasswordField, DbxActionFormDirective, DbxFormSourceDirective, dbxForgeTextPasswordWithVerifyField, dbxForgeTextField, dbxForgePickableChipField, filterPickableItemFieldValuesByLabel } from '@dereekb/dbx-form';
19
19
  import { NgTemplateOutlet, DatePipe } from '@angular/common';
20
20
  import * as i1$3 from '@angular/material/button';
21
21
  import { MatButtonModule } from '@angular/material/button';
22
- import { toSignal, toObservable } from '@angular/core/rxjs-interop';
22
+ import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
23
  import { MatSnackBar } from '@angular/material/snack-bar';
24
24
  import { initializeApp } from 'firebase/app';
25
25
  import { persistentSingleTabManager, persistentMultipleTabManager, persistentLocalCache, initializeFirestore, connectFirestoreEmulator } from 'firebase/firestore';
@@ -30,6 +30,7 @@ import { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle, Mat
30
30
  import { ComponentStore } from '@ngrx/component-store';
31
31
  import { __decorate, __param } from 'tslib';
32
32
  import { MatDialog } from '@angular/material/dialog';
33
+ import { isAfter, addMinutes } from 'date-fns';
33
34
  import { BaseError } from 'make-error';
34
35
 
35
36
  /**
@@ -288,6 +289,34 @@ const DEFAULT_DBX_FIREBASE_AUTH_SERVICE_DELEGATE = {
288
289
  return of(undefined);
289
290
  }
290
291
  };
292
+ /**
293
+ * Creates a function that returns a shared observable scoped to the input's uid, discarding and rebuilding it
294
+ * whenever the uid changes.
295
+ *
296
+ * Exists because a `shareReplay()` buffer cannot be cleared: a shared stream that outlives the signed-in user
297
+ * hands the NEXT user's subscribers a value derived from the PREVIOUS user's token, synchronously. Any consumer
298
+ * that reads the first emission — the UIRouter auth transition hooks, for instance — then acts on data belonging
299
+ * to a different user. Rebuilding per-uid gives a same-page re-login (log out, log back in as someone else
300
+ * without reloading) the same cold-start behavior as a page reload.
301
+ *
302
+ * The per-uid observable should use `refCount: true` so the discarded uid's source is torn down rather than
303
+ * leaking one live subscription per user switch. That is safe here because the returned observable is only
304
+ * consumed through a `switchMap`, so its reference count tracks the outer subscribers exactly.
305
+ *
306
+ * @param config - The uid reader and the per-uid observable factory.
307
+ * @returns Function that returns the shared observable for the input.
308
+ */
309
+ function sharedObservableForUidFunction(config) {
310
+ const { uidForInput, factoryForInput } = config;
311
+ let current;
312
+ return (input) => {
313
+ const uid = uidForInput(input);
314
+ if (current == null || current.uid !== uid) {
315
+ current = { uid, obs: factoryForInput(input) };
316
+ }
317
+ return current.obs;
318
+ };
319
+ }
291
320
  // MARK: Service
292
321
  class DbxFirebaseAuthService {
293
322
  firebaseAuth = inject(FIREBASE_AUTH_TOKEN);
@@ -328,26 +357,71 @@ class DbxFirebaseAuthService {
328
357
  * Alias for uid$
329
358
  */
330
359
  userIdentifier$ = this.uid$;
331
- currentIdTokenString$ = firebaseIdToken(this.firebaseAuth).pipe(distinctUntilChanged(), shareReplay(1));
360
+ /**
361
+ * The current ID token string, or null while logged out.
362
+ *
363
+ * Scoped per-uid via {@link sharedObservableForUidFunction} so the replay buffer can never hand a subscriber
364
+ * the PREVIOUS user's token after a same-page re-login.
365
+ */
366
+ currentIdTokenString$ = (() => {
367
+ const sharedForUid = sharedObservableForUidFunction({
368
+ uidForInput: (uid) => uid,
369
+ factoryForInput: (uid) => (uid == null ? of(null) : firebaseIdToken(this.firebaseAuth).pipe(distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true })))
370
+ });
371
+ return this.currentUid$.pipe(switchMap((uid) => sharedForUid(uid)));
372
+ })();
332
373
  idTokenString$ = this.currentUid$.pipe(switchMap((x) => (x ? this.currentIdTokenString$.pipe(filterMaybe()) : EMPTY)));
333
- currentIdTokenResult$ = this.currentAuthUser$.pipe(switchMap((x) => (x ? this.currentIdTokenString$.pipe(switchMap((y) => (y ? x.getIdTokenResult() : of(null)))) : of(null))), distinctUntilChanged(), shareReplay(1));
374
+ /**
375
+ * The current user's decoded ID token, or null while logged out.
376
+ *
377
+ * Scoped per-uid via {@link sharedObservableForUidFunction}. Token refreshes still flow through, as they arrive
378
+ * on {@link currentIdTokenString$}; only a uid change rebuilds the stream.
379
+ */
380
+ currentIdTokenResult$ = (() => {
381
+ const sharedForUser = sharedObservableForUidFunction({
382
+ uidForInput: (user) => user?.uid,
383
+ factoryForInput: (user) => user == null
384
+ ? of(null)
385
+ : this.currentIdTokenString$.pipe(switchMap((token) => (token ? user.getIdTokenResult() : of(null))), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }))
386
+ });
387
+ return this.currentAuthUser$.pipe(distinctUntilChanged((a, b) => a?.uid === b?.uid), switchMap((user) => sharedForUser(user)));
388
+ })();
334
389
  idTokenResult$ = this.currentIdTokenResult$.pipe(filterMaybe());
335
- currentClaims$ = this.currentIdTokenResult$.pipe(map((x) => (x ? x.claims : null)), distinctUntilChanged(), shareReplay(1));
390
+ /**
391
+ * The current user's token claims, or null while logged out.
392
+ *
393
+ * Intentionally not replayed: {@link currentIdTokenResult$} is already shared per-uid, and a buffer here would
394
+ * reintroduce the cross-user staleness that scoping removes.
395
+ */
396
+ currentClaims$ = this.currentIdTokenResult$.pipe(map((x) => (x ? x.claims : null)), distinctUntilChanged());
336
397
  claims$ = this.currentClaims$.pipe(filterMaybe());
337
398
  currentAuthContextInfo$ = this.currentAuthUser$.pipe(switchMap((x) => this.loadAuthContextInfoForUser(x)), shareReplay(1));
338
399
  authContextInfo$ = this.currentAuthContextInfo$.pipe(filterMaybe());
400
+ /**
401
+ * The current {@link AuthUserState}.
402
+ *
403
+ * The delegate's state stream is scoped per-uid via {@link sharedObservableForUidFunction}, so it is rebuilt
404
+ * whenever the signed-in uid changes. Without that, a subscriber attaching right after a same-page re-login is
405
+ * served the prior session's state synchronously from the replay buffer — and every consumer that reads the
406
+ * first emission, including the UIRouter auth transition hooks, then routes on it. The delegate factory is
407
+ * re-invoked per uid so the delegate's own internal replay is discarded along with it.
408
+ */
339
409
  authUserState$ = (() => {
340
- const delegateAuthUserStateObs = this.delegate.authUserStateObs(this).pipe(catchError(() => of('error')), distinctUntilChanged(), shareReplay(1));
341
- let obs;
342
- if (this.delegate.fullControlOfAuthUserState) {
343
- obs = delegateAuthUserStateObs;
344
- }
345
- else {
346
- obs = this._authState$.pipe(distinctUntilChanged(), switchMap((x) => {
347
- return x == null ? of('none') : delegateAuthUserStateObs;
348
- }));
349
- }
350
- return obs;
410
+ const fullControlOfAuthUserState = Boolean(this.delegate.fullControlOfAuthUserState);
411
+ const sharedDelegateStateForUid = sharedObservableForUidFunction({
412
+ uidForInput: (user) => user?.uid,
413
+ factoryForInput: (user) => {
414
+ let obs;
415
+ if (user == null && !fullControlOfAuthUserState) {
416
+ obs = of('none');
417
+ }
418
+ else {
419
+ obs = this.delegate.authUserStateObs(this).pipe(catchError(() => of('error')), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
420
+ }
421
+ return obs;
422
+ }
423
+ });
424
+ return this._authState$.pipe(distinctUntilChanged((a, b) => a?.uid === b?.uid), switchMap((user) => sharedDelegateStateForUid(user)));
351
425
  })();
352
426
  authRoles$ = this.delegate.authRolesObs(this);
353
427
  isOnboarded$ = this.delegate.isOnboarded(this);
@@ -2279,7 +2353,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
2279
2353
  function authRolesObsWithClaimsService(config) {
2280
2354
  const { addAuthUserStateToRoles: addAuthUserState, claimsService } = config;
2281
2355
  return (dbxFirebaseAuthService) => {
2282
- let obs = dbxFirebaseAuthService.idTokenResult$.pipe(map((x) => claimsService.toRoles(x.claims)));
2356
+ /**
2357
+ * Sourced from currentIdTokenResult$ instead of idTokenResult$ so the logged-out null is not swallowed by that
2358
+ * stream's filterMaybe(): the claim-derived roles have to CLEAR when there is no token. Otherwise they persist
2359
+ * through the entire logged-out window and on into the next user's session, and a role guard evaluated in that
2360
+ * window passes on the PREVIOUS user's roles.
2361
+ */
2362
+ let obs = dbxFirebaseAuthService.currentIdTokenResult$.pipe(map((x) => (x == null ? new Set() : claimsService.toRoles(x.claims))));
2283
2363
  if (addAuthUserState) {
2284
2364
  obs = obs.pipe(switchMap((authRoleSet) => dbxFirebaseAuthService.authUserState$.pipe(map((userState) => addToSetCopy(authRoleSet, [userState])))));
2285
2365
  }
@@ -6183,175 +6263,1842 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6183
6263
  }] });
6184
6264
 
6185
6265
  /**
6186
- * Client-side service used for retrieving notification templates.
6266
+ * Configuration for the external connection registry.
6267
+ *
6268
+ * Declared as an abstract class so it is its own injection token (the newer style, as
6269
+ * `DbxFirebaseOidcConfig`). Unlike login there is no library-supplied default catalog for an
6270
+ * `InjectionToken`-with-`useFactory` shape to override, and the registry needs more than a list.
6187
6271
  */
6188
- class DbxFirebaseNotificationTemplateService {
6189
- appNotificationTemplateTypeInfoRecordService = inject(AppNotificationTemplateTypeInfoRecordService);
6190
- notificationItemSubjectMessagePairForNotificationSummaryItem(item) {
6191
- const templateType = item.t;
6192
- const templateDetails = this.appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[templateType];
6193
- const subject = item.s ?? templateDetails.name;
6194
- const message = item.g ?? '';
6195
- const date = item.cat;
6272
+ class DbxFirebaseExternalConnectionsConfig {
6273
+ /**
6274
+ * Which providers are enabled. Pass `true` (the default) to enable every registered provider.
6275
+ */
6276
+ enabledProviders;
6277
+ /**
6278
+ * Whether the connect flow mints a signed `state` and carries it on the authorize request.
6279
+ * Defaults to true.
6280
+ *
6281
+ * This is what identifies the connecting user to the server, since a top-level navigation carries
6282
+ * no Firebase ID token: the app's own authorize endpoint bounces a stateless request straight to
6283
+ * the failure url. Turn it off only for an endpoint that mints its own state, and note that doing
6284
+ * so means no authenticated call is made before the redirect.
6285
+ */
6286
+ mintAuthorizeState;
6287
+ /**
6288
+ * Origin the authorize paths are resolved against (e.g. the hosting origin that fronts the API).
6289
+ *
6290
+ * When undefined the authorize path is used as-is, which is the correct behavior when the app and
6291
+ * the OAuth controller share an origin.
6292
+ */
6293
+ authorizeOrigin;
6294
+ /**
6295
+ * Builds the default authorize path for a provider that does not declare its own.
6296
+ *
6297
+ * Defaults to {@link DEFAULT_EXTERNAL_CONNECTION_AUTHORIZE_PATH_FACTORY}.
6298
+ */
6299
+ authorizePathFactory;
6300
+ /**
6301
+ * Navigation seam used by the default connect behavior.
6302
+ *
6303
+ * Defaults to {@link DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_FUNCTION}. An override owns the same
6304
+ * contract: settle only once the new page is actually opening.
6305
+ */
6306
+ navigate;
6307
+ }
6308
+ /**
6309
+ * Default authorize path for a provider: `/oauth/<providerType>/authorize`.
6310
+ *
6311
+ * @param providerType - The provider to build a path for.
6312
+ * @returns The authorize path.
6313
+ */
6314
+ const DEFAULT_EXTERNAL_CONNECTION_AUTHORIZE_PATH_FACTORY = (providerType) => `/oauth/${providerType}/authorize`;
6315
+ /**
6316
+ * Derives a row's presentation status from its entry.
6317
+ *
6318
+ * @param entry - The user's entry for the provider, if any.
6319
+ * @returns The row status.
6320
+ */
6321
+ function dbxFirebaseExternalConnectionRowStatusForEntry(entry) {
6322
+ let result;
6323
+ if (userExternalConnectionEntryIsConnected(entry)) {
6324
+ result = 'connected';
6325
+ }
6326
+ else if (entry?.st === 'error') {
6327
+ result = 'error';
6328
+ }
6329
+ else {
6330
+ result = 'notConnected';
6331
+ }
6332
+ return result;
6333
+ }
6334
+ /**
6335
+ * Builds the rows to render.
6336
+ *
6337
+ * The row set is the union of the enabled providers and any registered provider the user still has
6338
+ * an entry for, so a provider that has since been disabled can still be disconnected.
6339
+ *
6340
+ * @param input - The registry state plus the user's entries.
6341
+ * @returns The rows, in registration order.
6342
+ */
6343
+ function dbxFirebaseExternalConnectionRows(input) {
6344
+ const { providers, enabledProviderTypes, entries, loading } = input;
6345
+ const enabled = new Set(enabledProviderTypes);
6346
+ return providers
6347
+ .filter((x) => enabled.has(x.providerType) || entries?.[x.providerType] != null)
6348
+ .map((provider) => {
6349
+ const entry = entries?.[provider.providerType];
6196
6350
  return {
6197
- item,
6198
- subject,
6199
- message,
6200
- date
6351
+ providerType: provider.providerType,
6352
+ assets: provider.assets,
6353
+ entry,
6354
+ status: loading ? 'loading' : dbxFirebaseExternalConnectionRowStatusForEntry(entry),
6355
+ enabled: enabled.has(provider.providerType)
6201
6356
  };
6202
- }
6203
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6204
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService });
6357
+ });
6205
6358
  }
6206
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService, decorators: [{
6207
- type: Injectable
6208
- }] });
6209
6359
 
6210
6360
  /**
6211
- * Selection list wrapper for notification items with view-only default selection mode.
6361
+ * Default presentation for Cal.com.
6362
+ *
6363
+ * `icon` is a Material Symbols name throughout these defaults: the brand logo set the login registry
6364
+ * pulls from covers Firebase's own auth providers only, so a real mark for any of these services
6365
+ * would need a new asset. An app that has one passes `assets.logoUrl` instead.
6212
6366
  */
6213
- class DbxFirebaseNotificationItemListComponent extends AbstractDbxSelectionListWrapperDirective {
6214
- constructor() {
6215
- super({
6216
- componentClass: DbxFirebaseNotificationItemListViewComponent,
6217
- defaultSelectionMode: 'view'
6218
- });
6367
+ const DBX_FIREBASE_EXTERNAL_CONNECTION_CALCOM_PROVIDER = {
6368
+ providerType: CALCOM_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE,
6369
+ assets: {
6370
+ providerName: 'Cal.com',
6371
+ icon: 'event',
6372
+ description: 'Schedule and manage bookings from your Cal.com account.'
6219
6373
  }
6220
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6221
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-list", providers: provideDbxListViewWrapper(DbxFirebaseNotificationItemListComponent), usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabledSignal()\" [selectionMode]=\"selectionModeSignal()\">\n <ng-content top select=\"[top]\"></ng-content>\n <ng-content bottom select=\"[bottom]\"></ng-content>\n <ng-content empty select=\"[empty]\"></ng-content>\n <ng-content emptyLoading select=\"[emptyLoading]\"></ng-content>\n <ng-content end select=\"[end]\"></ng-content>\n </dbx-list>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxListWrapperComponentImportsModule }, { kind: "component", type: i1$1.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6222
- }
6223
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListComponent, decorators: [{
6224
- type: Component,
6225
- args: [{
6226
- selector: 'dbx-firebase-notificationitem-list',
6227
- template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
6228
- imports: [DbxListWrapperComponentImportsModule],
6229
- changeDetection: ChangeDetectionStrategy.OnPush,
6230
- providers: provideDbxListViewWrapper(DbxFirebaseNotificationItemListComponent),
6231
- standalone: true
6232
- }]
6233
- }], ctorParameters: () => [] });
6374
+ };
6234
6375
  /**
6235
- * List view component that renders notification items using the selection list pattern.
6376
+ * Default presentation for Zoom.
6236
6377
  */
6237
- class DbxFirebaseNotificationItemListViewComponent extends AbstractDbxSelectionListViewDirective {
6238
- config = {
6239
- componentClass: DbxFirebaseNotificationItemListViewItemComponent,
6240
- mapValuesToItemValues: (x) => of(x.map((y) => ({ ...y, key: y.id, icon: y.icon, itemValue: y })))
6241
- };
6242
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
6243
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListViewComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-list-view", providers: provideDbxListView(DbxFirebaseNotificationItemListViewComponent), usesInheritance: true, ngImport: i0, template: "<dbx-selection-list-view [config]=\"config\"></dbx-selection-list-view>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxSelectionValueListViewComponentImportsModule }, { kind: "component", type: i1$1.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6244
- }
6245
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewComponent, decorators: [{
6246
- type: Component,
6247
- args: [{
6248
- selector: 'dbx-firebase-notificationitem-list-view',
6249
- template: DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
6250
- imports: [DbxSelectionValueListViewComponentImportsModule],
6251
- changeDetection: ChangeDetectionStrategy.OnPush,
6252
- providers: provideDbxListView(DbxFirebaseNotificationItemListViewComponent),
6253
- standalone: true
6254
- }]
6255
- }] });
6378
+ const DBX_FIREBASE_EXTERNAL_CONNECTION_ZOOM_PROVIDER = {
6379
+ providerType: ZOOM_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE,
6380
+ assets: {
6381
+ providerName: 'Zoom',
6382
+ icon: 'videocam',
6383
+ description: 'Create and manage Zoom meetings.'
6384
+ }
6385
+ };
6256
6386
  /**
6257
- * Individual list view item component rendering a notification's subject, message, and date.
6387
+ * Default presentation for Discord.
6258
6388
  */
6259
- class DbxFirebaseNotificationItemListViewItemComponent extends AbstractDbxValueListViewItemComponent {
6260
- dbxFirebaseNotificationTemplateService = inject(DbxFirebaseNotificationTemplateService);
6261
- pairGetter = cachedGetter(() => this.dbxFirebaseNotificationTemplateService.notificationItemSubjectMessagePairForNotificationSummaryItem(this.itemValue));
6262
- get subject() {
6263
- return this.pairGetter().subject;
6389
+ const DBX_FIREBASE_EXTERNAL_CONNECTION_DISCORD_PROVIDER = {
6390
+ providerType: DISCORD_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE,
6391
+ assets: {
6392
+ providerName: 'Discord',
6393
+ icon: 'forum',
6394
+ description: 'Link your Discord account.'
6264
6395
  }
6265
- get message() {
6266
- return this.pairGetter().message;
6396
+ };
6397
+ /**
6398
+ * Default presentation for Zoho.
6399
+ */
6400
+ const DBX_FIREBASE_EXTERNAL_CONNECTION_ZOHO_PROVIDER = {
6401
+ providerType: ZOHO_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE,
6402
+ assets: {
6403
+ providerName: 'Zoho',
6404
+ icon: 'work',
6405
+ description: 'Connect your Zoho account.'
6267
6406
  }
6268
- get date() {
6269
- return this.pairGetter().date;
6407
+ };
6408
+ /**
6409
+ * The library's provider for each known third-party service, keyed by provider type.
6410
+ *
6411
+ * Every service the workspace ships a server-side OAuth adapter for appears here, so an app that
6412
+ * mounts one of those controllers has nothing to declare on the client beyond naming it.
6413
+ */
6414
+ const DBX_FIREBASE_KNOWN_EXTERNAL_CONNECTION_PROVIDERS = {
6415
+ calcom: DBX_FIREBASE_EXTERNAL_CONNECTION_CALCOM_PROVIDER,
6416
+ zoom: DBX_FIREBASE_EXTERNAL_CONNECTION_ZOOM_PROVIDER,
6417
+ discord: DBX_FIREBASE_EXTERNAL_CONNECTION_DISCORD_PROVIDER,
6418
+ zoho: DBX_FIREBASE_EXTERNAL_CONNECTION_ZOHO_PROVIDER
6419
+ };
6420
+ /**
6421
+ * Builds a provider from a known one, with the given overrides applied.
6422
+ *
6423
+ * @param config - The known provider to start from, plus the values to override.
6424
+ * @returns The provider to register.
6425
+ * @throws {Error} When there is no known provider for the given type.
6426
+ */
6427
+ function dbxFirebaseKnownExternalConnectionProvider(config) {
6428
+ const { providerType, assets, authorizePath, connect, disconnectConfirm } = config;
6429
+ const known = DBX_FIREBASE_KNOWN_EXTERNAL_CONNECTION_PROVIDERS[providerType];
6430
+ if (known == null) {
6431
+ throw new Error(`dbxFirebaseKnownExternalConnectionProvider(): there is no known provider for "${providerType}".`);
6270
6432
  }
6271
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
6272
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListViewItemComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
6273
- <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
6274
- <div class="item-left">
6275
- <span class="notificationitem-subject">{{ subject }}</span>
6276
- <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
6277
- <span class="notificationitem-date item-details-footnote">{{ date | date: 'medium' }}</span>
6278
- </div>
6279
- </div>
6280
- `, isInline: true, dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: CutTextPipe, name: "cutText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6433
+ return {
6434
+ ...known,
6435
+ assets: { ...known.assets, ...assets },
6436
+ authorizePath: authorizePath ?? known.authorizePath,
6437
+ connect: connect ?? known.connect,
6438
+ disconnectConfirm: disconnectConfirm ?? known.disconnectConfirm
6439
+ };
6281
6440
  }
6282
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewItemComponent, decorators: [{
6283
- type: Component,
6284
- args: [{
6285
- template: `
6286
- <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
6287
- <div class="item-left">
6288
- <span class="notificationitem-subject">{{ subject }}</span>
6289
- <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
6290
- <span class="notificationitem-date item-details-footnote">{{ date | date: 'medium' }}</span>
6291
- </div>
6292
- </div>
6293
- `,
6294
- imports: [DatePipe, CutTextPipe],
6295
- changeDetection: ChangeDetectionStrategy.OnPush,
6296
- standalone: true
6297
- }]
6298
- }] });
6299
-
6300
6441
  /**
6301
- * Presentational component for notification item content, displaying subject, message, and date.
6442
+ * Resolves a configured entry to the provider to register.
6443
+ *
6444
+ * @param entry - A known provider type, or a fully-declared provider.
6445
+ * @returns The provider to register.
6446
+ * @throws {Error} When the entry names a provider type the library has no defaults for.
6302
6447
  */
6303
- class DbxFirebaseNotificationItemContentComponent {
6304
- subject = input(...(ngDevMode ? [undefined, { debugName: "subject" }] : /* istanbul ignore next */ []));
6305
- message = input(...(ngDevMode ? [undefined, { debugName: "message" }] : /* istanbul ignore next */ []));
6306
- date = input(...(ngDevMode ? [undefined, { debugName: "date" }] : /* istanbul ignore next */ []));
6307
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6308
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemContentComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-content", inputs: { subject: { classPropertyName: "subject", publicName: "subject", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6448
+ function dbxFirebaseExternalConnectionProviderForEntry(entry) {
6449
+ let result;
6450
+ if (typeof entry === 'string') {
6451
+ const known = DBX_FIREBASE_KNOWN_EXTERNAL_CONNECTION_PROVIDERS[entry];
6452
+ if (known == null) {
6453
+ throw new Error(`dbxFirebaseExternalConnectionProviderForEntry(): there is no known provider for "${entry}". Declare the provider instead of naming it.`);
6454
+ }
6455
+ result = known;
6456
+ }
6457
+ else {
6458
+ result = entry;
6459
+ }
6460
+ return result;
6309
6461
  }
6310
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, decorators: [{
6311
- type: Component,
6312
- args: [{ selector: 'dbx-firebase-notificationitem-content', imports: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"] }]
6313
- }], propDecorators: { subject: [{ type: i0.Input, args: [{ isSignal: true, alias: "subject", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }] } });
6314
6462
 
6315
6463
  /**
6316
- * Prefix used by all widgets.
6464
+ * How long {@link DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_FUNCTION} waits for the browser to leave the
6465
+ * page before treating the redirect as having failed.
6317
6466
  *
6318
- * Does not include the "-" between the prefix and the template type returned by dbxWidgetTypeForNotificationTemplateType().
6467
+ * Generous, because the wait spans the request to the app's own authorize endpoint plus the redirect
6468
+ * chain out to the provider, and a cold serverless function alone can take several seconds.
6319
6469
  */
6320
- const FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX = 'dbxFirebaseNotificationItem';
6321
- const DEFAULT_FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE = `${FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX}Default`;
6470
+ const DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_TIMEOUT = 20 * 1000;
6322
6471
  /**
6323
- * Derives the widget type string for a given notification template type by prepending the standard prefix.
6472
+ * Starts a top-level navigation and resolves only once the browser has committed to leaving the
6473
+ * current page, rejecting when it has not done so within the timeout.
6324
6474
  *
6325
- * @param notificationTemplateType - The notification template type to generate a widget type for.
6326
- * @returns The prefixed widget type string.
6475
+ * `pagehide` is the signal: it fires as the browser is about to unload this document for the new one,
6476
+ * which is the earliest point the navigation is known to have actually taken. Anything earlier — an
6477
+ * assignment to `location.href` returning, say — only says the navigation was *requested*.
6478
+ *
6479
+ * The navigation is invoked here rather than by the caller so the listener is always in place before
6480
+ * the page can go, and so a refused navigation settles immediately instead of waiting out the timeout.
6481
+ *
6482
+ * @param navigate - Starts the navigation.
6483
+ * @param timeout - How long to wait before rejecting. Defaults to {@link DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_TIMEOUT}.
6484
+ * @returns Resolves when the page is being unloaded for the navigation.
6327
6485
  */
6328
- function dbxWidgetTypeForNotificationTemplateType(notificationTemplateType) {
6329
- return `${FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX}-${notificationTemplateType}`;
6486
+ function navigateAndWaitForPageToLeave(navigate, timeout = DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_TIMEOUT) {
6487
+ return new Promise((resolve, reject) => {
6488
+ // aborting drops the listener, so a page that stays put does not accumulate one per attempt
6489
+ const abortController = new AbortController();
6490
+ const timeoutRef = setTimeout(() => {
6491
+ abortController.abort();
6492
+ reject(new Error('The page did not open. The browser may have blocked the redirect.'));
6493
+ }, timeout);
6494
+ const cancel = () => {
6495
+ abortController.abort();
6496
+ clearTimeout(timeoutRef);
6497
+ };
6498
+ window.addEventListener('pagehide', () => {
6499
+ clearTimeout(timeoutRef);
6500
+ resolve();
6501
+ }, { once: true, signal: abortController.signal });
6502
+ try {
6503
+ navigate();
6504
+ }
6505
+ catch (e) {
6506
+ cancel();
6507
+ reject(e);
6508
+ }
6509
+ });
6330
6510
  }
6331
-
6332
6511
  /**
6333
- * Service used for registering widgets used for notification items.
6512
+ * Default navigation: a top-level browser redirect that settles only once the new page is opening.
6513
+ *
6514
+ * Assigning `location.href` returns immediately — before the request for the new document has even
6515
+ * been answered — so a caller that treated the assignment as the end of the work reported success
6516
+ * while the old page was still sitting there. Waiting for the unload instead means the connect action
6517
+ * stays in its working state for as long as the user is still looking at the settings page, and a
6518
+ * redirect the browser refuses outright surfaces as an error rather than a silent success.
6519
+ *
6520
+ * @param url - The url to navigate to.
6521
+ * @returns Resolves once the browser is leaving the page for the new url.
6334
6522
  */
6335
- class DbxFirebaseNotificationItemWidgetService {
6336
- dbxWidgetService = inject(DbxWidgetService);
6337
- dbxFirebaseNotificationTemplateService = inject(DbxFirebaseNotificationTemplateService);
6338
- _entries = new Map();
6523
+ const DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_FUNCTION = (url) => {
6524
+ return navigateAndWaitForPageToLeave(() => {
6525
+ window.location.href = url;
6526
+ });
6527
+ };
6528
+ /**
6529
+ * Registry of the third-party services a user can connect their account to.
6530
+ *
6531
+ * Modeled on `DbxFirebaseAuthLoginService`, but WITHOUT any per-user state: it is root-scoped, so a
6532
+ * Firestore listener held here would live for the app's lifetime and lock the UI to one user. The
6533
+ * connection state is resolved by the container component instead, from a single document read that
6534
+ * is fanned out to every row.
6535
+ */
6536
+ class DbxFirebaseExternalConnectionService {
6537
+ config = inject(DbxFirebaseExternalConnectionsConfig);
6339
6538
  /**
6340
- * Used to register a item widget. If widget for the given type is already registered, this will override it by default.
6539
+ * Optional so the registry is usable and testable in an app that has not wired the
6540
+ * userExternalConnection callables. Only state minting needs them, and it says so when they are
6541
+ * missing rather than failing every injection of this service.
6542
+ */
6543
+ _userExternalConnectionFunctions = inject(UserExternalConnectionFunctions, { optional: true });
6544
+ _providers = new Map();
6545
+ _assets = new Map();
6546
+ _enableAll = false;
6547
+ _enabled = new Set();
6548
+ constructor() {
6549
+ const { providers, enabledProviders } = this.config;
6550
+ providers.forEach((x) => this.register(dbxFirebaseExternalConnectionProviderForEntry(x), false));
6551
+ if (enabledProviders == null || enabledProviders === true) {
6552
+ this._enableAll = true;
6553
+ }
6554
+ else {
6555
+ this.enable(enabledProviders);
6556
+ }
6557
+ }
6558
+ /**
6559
+ * Registers a provider.
6341
6560
  *
6342
- * @param provider - The notification item widget entry to register.
6343
- * @param override - Whether to override an existing widget for the same notification template type. Defaults to true.
6344
- * @returns True if the widget was registered, false if it already existed and override was false or the template type was unknown.
6561
+ * @param provider - The provider to register.
6562
+ * @param override - Whether to override an existing provider of the same type. Defaults to true.
6563
+ * @returns True if the provider was registered.
6345
6564
  */
6346
6565
  register(provider, override = true) {
6347
- const { componentClass, notificationTemplateType } = provider;
6348
- const widgetType = dbxWidgetTypeForNotificationTemplateType(notificationTemplateType);
6349
- let result = false;
6350
- if (override || !this._entries.has(notificationTemplateType)) {
6351
- const notificationTemplateTypeInfo = this.dbxFirebaseNotificationTemplateService.appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[notificationTemplateType];
6352
- if (notificationTemplateTypeInfo) {
6353
- const entry = {
6354
- notificationTemplateType,
6566
+ let result;
6567
+ if (override || !this._providers.has(provider.providerType)) {
6568
+ this._providers.set(provider.providerType, provider);
6569
+ this.updateAssetsForProvider(provider.providerType, provider.assets);
6570
+ result = true;
6571
+ }
6572
+ else {
6573
+ result = false;
6574
+ }
6575
+ return result;
6576
+ }
6577
+ /**
6578
+ * Patches the assets for a provider type.
6579
+ *
6580
+ * @param providerType - The provider to update.
6581
+ * @param assets - The asset values to merge in.
6582
+ */
6583
+ updateAssetsForProvider(providerType, assets) {
6584
+ const current = this._assets.get(providerType);
6585
+ this._assets.set(providerType, { ...current, ...assets });
6586
+ }
6587
+ // MARK: Enable/Disable
6588
+ /**
6589
+ * Enables all providers, including any registered later.
6590
+ *
6591
+ * @param enableAll - Whether to enable all providers. Defaults to true.
6592
+ */
6593
+ setEnableAll(enableAll = true) {
6594
+ this._enableAll = enableAll;
6595
+ }
6596
+ clearEnabled() {
6597
+ this._enabled = new Set();
6598
+ }
6599
+ enable(types) {
6600
+ addToSet(this._enabled, types);
6601
+ }
6602
+ disable(types) {
6603
+ removeFromSet(this._enabled, types);
6604
+ }
6605
+ // MARK: Get
6606
+ getRegisteredTypes() {
6607
+ return [...this._providers.keys()];
6608
+ }
6609
+ getEnabledTypes() {
6610
+ return this._enableAll ? this.getRegisteredTypes() : [...this._enabled];
6611
+ }
6612
+ getProvider(providerType) {
6613
+ return this._providers.get(providerType);
6614
+ }
6615
+ getProviders(types) {
6616
+ return filterMaybeArrayValues(mapIterable(types ?? this.getRegisteredTypes(), (x) => this._providers.get(x)));
6617
+ }
6618
+ getAssetsForProvider(providerType) {
6619
+ return this._assets.get(providerType);
6620
+ }
6621
+ // MARK: Connect
6622
+ /**
6623
+ * Whether the connect flow mints a signed `state` and carries it on the authorize request.
6624
+ *
6625
+ * @returns True when state minting is enabled, which is the default.
6626
+ */
6627
+ get mintsAuthorizeState() {
6628
+ return this.config.mintAuthorizeState ?? true;
6629
+ }
6630
+ /**
6631
+ * Mints the short-lived signed `state` that begins a provider's OAuth handoff.
6632
+ *
6633
+ * An AUTHENTICATED call, which is the entire point of it: the top-level navigation that follows
6634
+ * carries no Firebase ID token, so this is the only place the server learns who is connecting.
6635
+ *
6636
+ * Requires the user to already have a connection document — the server asserts a role against it,
6637
+ * and a role map is only consulted for a document that exists. `DbxFirebaseExternalConnectionsComponent`
6638
+ * creates it on load; a custom UI that reaches this call by another route has to create it too.
6639
+ *
6640
+ * @param providerType - The provider the state is for.
6641
+ * @returns The state to send on the authorize request.
6642
+ */
6643
+ async mintAuthorizeStateForProvider(providerType) {
6644
+ const userExternalConnectionFunctions = this._userExternalConnectionFunctions;
6645
+ if (!userExternalConnectionFunctions) {
6646
+ throw new Error(`DbxFirebaseExternalConnectionService: cannot mint an authorize state for "${providerType}" because UserExternalConnectionFunctions was not provided. Add the userExternalConnection functions to the app's functions config map, or configure mintAuthorizeState: false.`);
6647
+ }
6648
+ const { state } = await userExternalConnectionFunctions.userExternalConnection.readUserExternalConnection.authorizeState({ providerType });
6649
+ return state;
6650
+ }
6651
+ /**
6652
+ * Resolves the authorize url for a provider, carrying a freshly minted `state` when state minting
6653
+ * is enabled.
6654
+ *
6655
+ * @param providerType - The provider to resolve.
6656
+ * @returns The authorize url, or null when the provider is not registered.
6657
+ */
6658
+ async authorizeUrlWithStateForProvider(providerType) {
6659
+ const authorizeUrl = this.authorizeUrlForProvider(providerType);
6660
+ let result = authorizeUrl;
6661
+ if (authorizeUrl && this.mintsAuthorizeState) {
6662
+ const state = await this.mintAuthorizeStateForProvider(providerType);
6663
+ // appended as text rather than through URL, since an app that shares an origin with its API
6664
+ // configures no authorizeOrigin and the path stays relative
6665
+ result = fixExtraQueryParameters(`${authorizeUrl}?state=${encodeURIComponent(state)}`);
6666
+ }
6667
+ return result;
6668
+ }
6669
+ /**
6670
+ * Resolves the authorize url for a provider, WITHOUT a `state`.
6671
+ *
6672
+ * @param providerType - The provider to resolve.
6673
+ * @returns The authorize url, or null when the provider is not registered.
6674
+ */
6675
+ authorizeUrlForProvider(providerType) {
6676
+ const provider = this.getProvider(providerType);
6677
+ let result = null;
6678
+ if (provider) {
6679
+ const pathFactory = this.config.authorizePathFactory ?? DEFAULT_EXTERNAL_CONNECTION_AUTHORIZE_PATH_FACTORY;
6680
+ const path = provider.authorizePath ?? pathFactory(providerType);
6681
+ result = this.config.authorizeOrigin ? `${this.config.authorizeOrigin}${path}` : path;
6682
+ }
6683
+ return result;
6684
+ }
6685
+ /**
6686
+ * Starts the connect flow for a provider.
6687
+ *
6688
+ * Uses the provider's own `connect` handler when it declares one, otherwise mints the `state` and
6689
+ * navigates to the authorize url carrying it. The mint is deliberately part of the default rather
6690
+ * than something each app re-implements: the app's authorize endpoint bounces a stateless request
6691
+ * straight to its failure url, so a "connect" that skipped it would look like it worked and land
6692
+ * the user back on the settings page unconnected.
6693
+ *
6694
+ * @param providerType - The provider to connect.
6695
+ * @returns Resolves once the authorize page is actually opening, and rejects when it never opened.
6696
+ */
6697
+ async connectToProvider(providerType) {
6698
+ const provider = this.getProvider(providerType);
6699
+ if (!provider) {
6700
+ throw new Error(`DbxFirebaseExternalConnectionService: no provider registered for "${providerType}".`);
6701
+ }
6702
+ const navigate = this.config.navigate ?? DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_FUNCTION;
6703
+ if (provider.connect) {
6704
+ await provider.connect({
6705
+ providerType,
6706
+ provider,
6707
+ authorizeUrl: this.authorizeUrlForProvider(providerType),
6708
+ mintAuthorizeState: () => this.mintAuthorizeStateForProvider(providerType),
6709
+ navigate
6710
+ });
6711
+ }
6712
+ else {
6713
+ const authorizeUrl = await this.authorizeUrlWithStateForProvider(providerType);
6714
+ if (!authorizeUrl) {
6715
+ throw new Error(`DbxFirebaseExternalConnectionService: no authorize url could be resolved for "${providerType}".`);
6716
+ }
6717
+ await navigate(authorizeUrl);
6718
+ }
6719
+ }
6720
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6721
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionService });
6722
+ }
6723
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionService, decorators: [{
6724
+ type: Injectable
6725
+ }], ctorParameters: () => [] });
6726
+
6727
+ /**
6728
+ * Injection token for the app's UserExternalConnection collection.
6729
+ *
6730
+ * The model's own `UserExternalConnectionFirestoreCollections` is an interface (the canonical model
6731
+ * folder shape) and so cannot be an Angular injection token. This abstract class is that token, and
6732
+ * is bound to the app's collections class by `provideDbxFirebaseExternalConnections()`.
6733
+ */
6734
+ class DbxFirebaseUserExternalConnectionCollections {
6735
+ }
6736
+ /**
6737
+ * Document store for the signed-in user's single UserExternalConnection document.
6738
+ *
6739
+ * The document id IS the user's uid, so this store is keyed with `setId(currentUid$)`.
6740
+ */
6741
+ class UserExternalConnectionDocumentStore extends AbstractDbxFirebaseDocumentStore {
6742
+ userExternalConnectionFunctions = inject(UserExternalConnectionFunctions);
6743
+ constructor() {
6744
+ super({ firestoreCollection: inject(DbxFirebaseUserExternalConnectionCollections).userExternalConnectionCollection });
6745
+ }
6746
+ /**
6747
+ * Creates the user's connection document.
6748
+ *
6749
+ * Required before any provider can be connected: the server asserts a role against this document,
6750
+ * and a role map is only consulted for a document that exists. Throws when the user already has
6751
+ * one, so call it only for a user known not to.
6752
+ */
6753
+ createUserExternalConnection = firebaseDocumentStoreCreateFunction(this, this.userExternalConnectionFunctions.userExternalConnection.createUserExternalConnection);
6754
+ /**
6755
+ * Disconnects the user from a provider. The only write a client can make to the connection pair.
6756
+ */
6757
+ disconnectUserExternalConnection = firebaseDocumentStoreUpdateFunction(this, this.userExternalConnectionFunctions.userExternalConnection.updateUserExternalConnection.disconnect);
6758
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UserExternalConnectionDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6759
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UserExternalConnectionDocumentStore });
6760
+ }
6761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UserExternalConnectionDocumentStore, decorators: [{
6762
+ type: Injectable
6763
+ }], ctorParameters: () => [] });
6764
+
6765
+ /**
6766
+ * Directive providing a {@link UserExternalConnectionDocumentStore} for a single user's connection document.
6767
+ */
6768
+ class DbxFirebaseUserExternalConnectionDocumentStoreDirective extends DbxFirebaseDocumentStoreDirective {
6769
+ constructor() {
6770
+ super(inject(UserExternalConnectionDocumentStore));
6771
+ }
6772
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseUserExternalConnectionDocumentStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
6773
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseUserExternalConnectionDocumentStoreDirective, isStandalone: true, selector: "[dbxFirebaseUserExternalConnectionDocument]", providers: provideDbxFirebaseDocumentStoreDirective(DbxFirebaseUserExternalConnectionDocumentStoreDirective, UserExternalConnectionDocumentStore), usesInheritance: true, ngImport: i0 });
6774
+ }
6775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseUserExternalConnectionDocumentStoreDirective, decorators: [{
6776
+ type: Directive,
6777
+ args: [{
6778
+ selector: '[dbxFirebaseUserExternalConnectionDocument]',
6779
+ providers: provideDbxFirebaseDocumentStoreDirective(DbxFirebaseUserExternalConnectionDocumentStoreDirective, UserExternalConnectionDocumentStore),
6780
+ standalone: true
6781
+ }]
6782
+ }], ctorParameters: () => [] });
6783
+
6784
+ /**
6785
+ * Maps the document store's loading state into the per-provider entry map the UI renders from.
6786
+ *
6787
+ * This mapper exists because `dataLoadingState$` turns a MISSING document into
6788
+ * `errorResult(modelDoesNotExistError())` — and a user who has never connected anything has no
6789
+ * document, which is the common case, not an error. `exists$` catches `permission-denied` but does
6790
+ * not feed `dataLoadingState$`, so that case has to be handled here too.
6791
+ *
6792
+ * Both cases mean the same thing to this UI: an empty connection map.
6793
+ *
6794
+ * Pure and exported so it is unit-testable without a TestBed.
6795
+ *
6796
+ * @param state - The document store's data loading state.
6797
+ * @returns The loading state of the user's entry map.
6798
+ */
6799
+ function externalConnectionsLoadingStateFromDocumentLoadingState(state) {
6800
+ const errorCode = state.error?.code;
6801
+ let result;
6802
+ if (state.value) {
6803
+ result = successResult(state.value.e ?? {});
6804
+ }
6805
+ else if (errorCode === DBX_FIREBASE_MODEL_DOES_NOT_EXIST_ERROR || errorCode === FIRESTORE_PERMISSION_DENIED_ERROR_CODE) {
6806
+ result = successResult({});
6807
+ }
6808
+ else if (state.error) {
6809
+ result = { ...state, value: undefined };
6810
+ }
6811
+ else {
6812
+ result = { ...state, value: undefined };
6813
+ }
6814
+ return result;
6815
+ }
6816
+ /**
6817
+ * Decides whether a document load means the user has no connection document and needs one created.
6818
+ *
6819
+ * Only a definitive does-not-exist counts. A state that is still loading carries no value and would
6820
+ * otherwise read as missing — creating on it would fail for every user who does have a document. A
6821
+ * denied read is a rules problem rather than an absent document, so it does not create either.
6822
+ *
6823
+ * Pure and exported so it is unit-testable without a TestBed.
6824
+ *
6825
+ * @param state - The document store's data loading state.
6826
+ * @returns True when the document is known not to exist.
6827
+ */
6828
+ function shouldCreateUserExternalConnectionForDocumentLoadingState(state) {
6829
+ return !isLoadingStateLoading(state) && state.value == null && state.error?.code === DBX_FIREBASE_MODEL_DOES_NOT_EXIST_ERROR;
6830
+ }
6831
+ /**
6832
+ * Derived state for the signed-in user's external connections.
6833
+ *
6834
+ * Holds THE shared subscription: one document read fanned out to every provider row, instead of one
6835
+ * read per provider. That is the payoff of collapsing to a single document per user.
6836
+ */
6837
+ class DbxFirebaseUserExternalConnectionsStore {
6838
+ userExternalConnectionDocumentStore = inject(UserExternalConnectionDocumentStore);
6839
+ /**
6840
+ * The user's per-provider entries. A missing document (or a denied read) resolves to an empty map.
6841
+ */
6842
+ entriesLoadingState$ = this.userExternalConnectionDocumentStore.dataLoadingState$.pipe(map(externalConnectionsLoadingStateFromDocumentLoadingState), shareReplay(1));
6843
+ /**
6844
+ * Creates the user's connection document if they do not have one, and does nothing if they do.
6845
+ *
6846
+ * Waits for a settled load before deciding, because a loading state carries no value and would
6847
+ * otherwise read as "missing" for a user who does have a document. Only the definitive
6848
+ * does-not-exist error creates: a denied read is a rules problem, not an absent document.
6849
+ *
6850
+ * The create runs through the document store, so a rejection (including losing the race with
6851
+ * another tab, which the server reports as already-exists) arrives as `error` on the returned
6852
+ * loading state rather than as a thrown error.
6853
+ *
6854
+ * @returns The loading state of the create, or a state with no value when one already exists.
6855
+ */
6856
+ createIfMissing() {
6857
+ return this.userExternalConnectionDocumentStore.dataLoadingState$.pipe(filter((x) => !isLoadingStateLoading(x)), first(), switchMap((state) => (shouldCreateUserExternalConnectionForDocumentLoadingState(state) ? this.userExternalConnectionDocumentStore.createUserExternalConnection({}) : of(undefined))));
6858
+ }
6859
+ /**
6860
+ * Sets the uid whose connection document is loaded. The document id IS the uid.
6861
+ *
6862
+ * @param uid - The uid, or an observable of it. Null clears the loaded document.
6863
+ */
6864
+ setUid(uid) {
6865
+ this.userExternalConnectionDocumentStore.setId(uid);
6866
+ }
6867
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseUserExternalConnectionsStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6868
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseUserExternalConnectionsStore });
6869
+ }
6870
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseUserExternalConnectionsStore, decorators: [{
6871
+ type: Injectable
6872
+ }] });
6873
+
6874
+ const CONNECTION_STATUS_LABELS = {
6875
+ loading: 'Loading',
6876
+ connected: 'Connected',
6877
+ notConnected: 'Not connected',
6878
+ error: 'Needs attention'
6879
+ };
6880
+ const CONNECTION_STATUS_COLORS = {
6881
+ loading: 'grey',
6882
+ connected: 'success',
6883
+ notConnected: 'grey',
6884
+ error: 'warn'
6885
+ };
6886
+ /**
6887
+ * Fallback icon for a provider whose assets declare neither a logo nor an icon.
6888
+ */
6889
+ const DEFAULT_CONNECTION_ICON = 'link';
6890
+ /**
6891
+ * Used by actions that declare no confirm config, such as Connect, so they run without a dialog.
6892
+ *
6893
+ * The directive prompts with its defaults for a nullish config, which is not what an action with nothing to
6894
+ * confirm wants.
6895
+ */
6896
+ const AUTO_CONFIRM_CONFIG = { autoConfirm: true };
6897
+ /**
6898
+ * Renders every third-party connection as a card row.
6899
+ *
6900
+ * Composed list utilities (see `DocProgressItemListComponent` for the same recipe):
6901
+ * - `.dbx-list-card-items-list` paints each row as a tinted, rounded card;
6902
+ * - `.dbx-list-no-hover-effects` drops the hover state layer, since a row is not clickable — only its buttons are;
6903
+ * - `.dbx-list-auto-height` lets the list flow inline inside the settings section rather than filling it.
6904
+ *
6905
+ * @example
6906
+ * ```html
6907
+ * <dbx-firebase-external-connection-list [state]="listStateSignal()"></dbx-firebase-external-connection-list>
6908
+ * ```
6909
+ */
6910
+ class DbxFirebaseExternalConnectionListComponent extends AbstractDbxListWrapperDirective {
6911
+ constructor() {
6912
+ super({
6913
+ componentClass: DbxFirebaseExternalConnectionListViewComponent
6914
+ });
6915
+ }
6916
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6917
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseExternalConnectionListComponent, isStandalone: true, selector: "dbx-firebase-external-connection-list", host: { classAttribute: "dbx-firebase-external-connection-list dbx-list-auto-height dbx-list-card-items-list dbx-list-no-hover-effects" }, usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabledSignal()\" [selectionMode]=\"selectionModeSignal()\">\n <ng-content top select=\"[top]\"></ng-content>\n <ng-content bottom select=\"[bottom]\"></ng-content>\n <ng-content empty select=\"[empty]\"></ng-content>\n <ng-content emptyLoading select=\"[emptyLoading]\"></ng-content>\n <ng-content end select=\"[end]\"></ng-content>\n </dbx-list>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxListWrapperComponentImportsModule }, { kind: "component", type: i1$1.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6918
+ }
6919
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListComponent, decorators: [{
6920
+ type: Component,
6921
+ args: [{
6922
+ selector: 'dbx-firebase-external-connection-list',
6923
+ template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
6924
+ imports: [DbxListWrapperComponentImportsModule],
6925
+ host: {
6926
+ class: 'dbx-firebase-external-connection-list dbx-list-auto-height dbx-list-card-items-list dbx-list-no-hover-effects'
6927
+ },
6928
+ standalone: true,
6929
+ changeDetection: ChangeDetectionStrategy.OnPush
6930
+ }]
6931
+ }], ctorParameters: () => [] });
6932
+ class DbxFirebaseExternalConnectionListViewComponent extends AbstractDbxListViewDirective {
6933
+ config = {
6934
+ componentClass: DbxFirebaseExternalConnectionListViewItemComponent,
6935
+ mapValuesToItemValues: (values) => of(values.map((value) => ({
6936
+ ...value,
6937
+ itemValue: value,
6938
+ // the template paints its own leading avatar, so the built-in icon slot stays empty
6939
+ icon: undefined,
6940
+ // The key carries the row's rendered state, not just its identity. An item value reaches its
6941
+ // component through the injected DBX_VALUE_LIST_VIEW_ITEM provider, and
6942
+ // `dbxInjectionComponentConfigIsEqual` deliberately ignores the provider array — so under a
6943
+ // stable key the component is never recreated and would keep rendering the state it was built
6944
+ // with. Here that would pin every row to its initial 'loading' status forever.
6945
+ key: `${value.row.providerType}_${value.row.status}_${value.row.entry?.uat?.getTime() ?? 0}`
6946
+ })))
6947
+ };
6948
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
6949
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseExternalConnectionListViewComponent, isStandalone: true, selector: "dbx-firebase-external-connection-list-view", host: { classAttribute: "dbx-list-item-p0" }, providers: provideDbxListView(DbxFirebaseExternalConnectionListViewComponent), usesInheritance: true, ngImport: i0, template: "<dbx-list-view [config]=\"config\"></dbx-list-view>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxValueListViewComponentImportsModule }, { kind: "component", type: i1$1.DbxValueListViewComponent, selector: "dbx-list-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6950
+ }
6951
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListViewComponent, decorators: [{
6952
+ type: Component,
6953
+ args: [{
6954
+ selector: 'dbx-firebase-external-connection-list-view',
6955
+ template: DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
6956
+ imports: [DbxValueListViewComponentImportsModule],
6957
+ providers: provideDbxListView(DbxFirebaseExternalConnectionListViewComponent),
6958
+ host: {
6959
+ // the item template owns all of its padding, including the leading avatar's inset
6960
+ class: 'dbx-list-item-p0'
6961
+ },
6962
+ standalone: true,
6963
+ changeDetection: ChangeDetectionStrategy.OnPush
6964
+ }]
6965
+ }] });
6966
+ /**
6967
+ * One third-party connection: its brand, its current state for the signed-in user, and the actions available for it.
6968
+ *
6969
+ * Presentational — the row and its actions arrive as the injected list item, built by the container that reads every
6970
+ * provider's state from ONE document.
6971
+ */
6972
+ class DbxFirebaseExternalConnectionListViewItemComponent extends AbstractDbxValueListViewItemComponent {
6973
+ defaultIcon = DEFAULT_CONNECTION_ICON;
6974
+ autoConfirmConfig = AUTO_CONFIRM_CONFIG;
6975
+ get row() {
6976
+ return this.itemValue.row;
6977
+ }
6978
+ get assets() {
6979
+ return this.row.assets;
6980
+ }
6981
+ /**
6982
+ * Actions for this row. An ARRAY because the `error` status offers both Reconnect and Disconnect.
6983
+ *
6984
+ * @returns The actions to render, empty when there are none.
6985
+ */
6986
+ get actions() {
6987
+ return this.itemValue.actions ?? [];
6988
+ }
6989
+ get statusLabel() {
6990
+ return CONNECTION_STATUS_LABELS[this.row.status];
6991
+ }
6992
+ get statusColor() {
6993
+ return CONNECTION_STATUS_COLORS[this.row.status];
6994
+ }
6995
+ get connectedAt() {
6996
+ return this.row.status === 'connected' ? this.row.entry?.coa : undefined;
6997
+ }
6998
+ /**
6999
+ * The line under the provider name: which account is connected, or what the provider is for.
7000
+ *
7001
+ * @returns The detail line, or nullish when there is nothing to say.
7002
+ */
7003
+ get detail() {
7004
+ const { status, entry, assets } = this.row;
7005
+ let result;
7006
+ if (status === 'connected' || status === 'error') {
7007
+ result = entry?.l ?? entry?.ea ?? assets.description;
7008
+ }
7009
+ else {
7010
+ result = assets.description;
7011
+ }
7012
+ return result;
7013
+ }
7014
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListViewItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7015
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseExternalConnectionListViewItemComponent, isStandalone: true, selector: "dbx-firebase-external-connection-list-view-item", usesInheritance: true, ngImport: i0, template: `
7016
+ <div class="dbx-list-item-padded-thick dbx-firebase-external-connection-item">
7017
+ <div class="dbx-flex-bar dbx-pb1">
7018
+ <dbx-avatar class="dbx-icon-spacer dbx-firebase-external-connection-tile" [avatarTile]="true" avatarStyle="square" [avatarUrl]="assets.logoUrl" [avatarIcon]="assets.icon ?? defaultIcon" [avatarImageFilter]="assets.logoFilter" [avatarIgnoreDefaultUrl]="true" [avatarColor]="statusColor" [avatarColorTone]="18"></dbx-avatar>
7019
+ <div class="dbx-flex-fill">
7020
+ <div class="dbx-text-title-medium">{{ assets.providerName }}</div>
7021
+ @if (detail; as detailValue) {
7022
+ <div class="dbx-text-body-small dbx-hint">{{ detailValue }}</div>
7023
+ }
7024
+ </div>
7025
+ <dbx-chip [small]="true" [color]="statusColor">{{ statusLabel }}</dbx-chip>
7026
+ </div>
7027
+
7028
+ @if (connectedAt; as connectedAtValue) {
7029
+ <div class="dbx-text-body-small dbx-hint">Connected {{ connectedAtValue | date: 'medium' }}</div>
7030
+ }
7031
+
7032
+ @if (actions.length) {
7033
+ <div class="dbx-flex-bar dbx-pt2">
7034
+ @for (action of actions; track action.label) {
7035
+ <!-- dbxActionValue is not used: each action's handler takes no input and the confirm marks it ready, dialog or not -->
7036
+ <div dbxAction [dbxActionHandler]="action.handler" [dbxActionConfirm]="action.confirm ?? autoConfirmConfig">
7037
+ <dbx-button dbxActionButton [stroked]="true" [text]="action.label" [icon]="action.icon"></dbx-button>
7038
+ <dbx-error dbxActionError></dbx-error>
7039
+ </div>
7040
+ }
7041
+ </div>
7042
+ }
7043
+ </div>
7044
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionErrorDirective, selector: "[dbxActionError]" }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "component", type: DbxAvatarComponent, selector: "dbx-avatar", inputs: ["context", "avatarSelector", "avatarUid", "avatarUrl", "avatarName", "avatarKey", "avatarIcon", "avatarStyle", "avatarSize", "avatarHideOnError", "avatarTile", "avatarColor", "avatarColorTone", "avatarImageFit", "avatarImageFilter", "avatarIgnoreDefaultUrl"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7045
+ }
7046
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionListViewItemComponent, decorators: [{
7047
+ type: Component,
7048
+ args: [{
7049
+ selector: 'dbx-firebase-external-connection-list-view-item',
7050
+ template: `
7051
+ <div class="dbx-list-item-padded-thick dbx-firebase-external-connection-item">
7052
+ <div class="dbx-flex-bar dbx-pb1">
7053
+ <dbx-avatar class="dbx-icon-spacer dbx-firebase-external-connection-tile" [avatarTile]="true" avatarStyle="square" [avatarUrl]="assets.logoUrl" [avatarIcon]="assets.icon ?? defaultIcon" [avatarImageFilter]="assets.logoFilter" [avatarIgnoreDefaultUrl]="true" [avatarColor]="statusColor" [avatarColorTone]="18"></dbx-avatar>
7054
+ <div class="dbx-flex-fill">
7055
+ <div class="dbx-text-title-medium">{{ assets.providerName }}</div>
7056
+ @if (detail; as detailValue) {
7057
+ <div class="dbx-text-body-small dbx-hint">{{ detailValue }}</div>
7058
+ }
7059
+ </div>
7060
+ <dbx-chip [small]="true" [color]="statusColor">{{ statusLabel }}</dbx-chip>
7061
+ </div>
7062
+
7063
+ @if (connectedAt; as connectedAtValue) {
7064
+ <div class="dbx-text-body-small dbx-hint">Connected {{ connectedAtValue | date: 'medium' }}</div>
7065
+ }
7066
+
7067
+ @if (actions.length) {
7068
+ <div class="dbx-flex-bar dbx-pt2">
7069
+ @for (action of actions; track action.label) {
7070
+ <!-- dbxActionValue is not used: each action's handler takes no input and the confirm marks it ready, dialog or not -->
7071
+ <div dbxAction [dbxActionHandler]="action.handler" [dbxActionConfirm]="action.confirm ?? autoConfirmConfig">
7072
+ <dbx-button dbxActionButton [stroked]="true" [text]="action.label" [icon]="action.icon"></dbx-button>
7073
+ <dbx-error dbxActionError></dbx-error>
7074
+ </div>
7075
+ }
7076
+ </div>
7077
+ }
7078
+ </div>
7079
+ `,
7080
+ imports: [DatePipe, DbxActionButtonDirective, DbxActionConfirmDirective, DbxActionDirective, DbxActionErrorDirective, DbxActionHandlerDirective, DbxAvatarComponent, DbxButtonComponent, DbxChipDirective, DbxErrorComponent],
7081
+ standalone: true,
7082
+ changeDetection: ChangeDetectionStrategy.OnPush
7083
+ }]
7084
+ }] });
7085
+
7086
+ /**
7087
+ * Renders every registered third-party connection for the signed-in user.
7088
+ *
7089
+ * Owns the stores and makes exactly ONE `toSignal` subscription, which every row is computed from —
7090
+ * the payoff of collapsing to one connection document per user. The rows are presentational and take
7091
+ * their entry as a plain input rather than loading anything themselves.
7092
+ *
7093
+ * @example
7094
+ * ```html
7095
+ * <dbx-firebase-external-connections></dbx-firebase-external-connections>
7096
+ * ```
7097
+ */
7098
+ class DbxFirebaseExternalConnectionsComponent {
7099
+ dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
7100
+ dbxFirebaseExternalConnectionService = inject(DbxFirebaseExternalConnectionService);
7101
+ dbxFirebaseUserExternalConnectionsStore = inject(DbxFirebaseUserExternalConnectionsStore);
7102
+ userExternalConnectionDocumentStore = inject(UserExternalConnectionDocumentStore);
7103
+ constructor() {
7104
+ // currentUid$ rather than userIdentifier$: the latter substitutes NO_AUTH_USER_IDENTIFIER ('0')
7105
+ // when signed out, which would ask Firestore for uec/0.
7106
+ this.dbxFirebaseUserExternalConnectionsStore.setUid(this.dbxFirebaseAuthService.currentUid$);
7107
+ // The connection document has to exist before a provider can be connected: the server asserts a
7108
+ // role against it, and a role map is only consulted for a document that exists. This page is where
7109
+ // it gets created — the user is present and known to want the feature, and creating it is gated
7110
+ // server-side in one place instead of being a side effect of the OAuth handoff. Nothing is created
7111
+ // for a signed-out user: no uid means no document ref, so the load never settles.
7112
+ this.dbxFirebaseUserExternalConnectionsStore.createIfMissing().pipe(takeUntilDestroyed()).subscribe();
7113
+ }
7114
+ _currentUidSignal = toSignal(this.dbxFirebaseAuthService.currentUid$, { initialValue: undefined });
7115
+ /**
7116
+ * THE shared subscription. Every row is computed from this one document read.
7117
+ */
7118
+ _entriesStateSignal = toSignal(this.dbxFirebaseUserExternalConnectionsStore.entriesLoadingState$, { initialValue: beginLoading() });
7119
+ signedInSignal = computed(() => this._currentUidSignal() != null, ...(ngDevMode ? [{ debugName: "signedInSignal" }] : /* istanbul ignore next */ []));
7120
+ errorSignal = computed(() => this._entriesStateSignal().error, ...(ngDevMode ? [{ debugName: "errorSignal" }] : /* istanbul ignore next */ []));
7121
+ rowsSignal = computed(() => {
7122
+ const state = this._entriesStateSignal();
7123
+ return dbxFirebaseExternalConnectionRows({
7124
+ providers: this.dbxFirebaseExternalConnectionService.getProviders(),
7125
+ enabledProviderTypes: this.dbxFirebaseExternalConnectionService.getEnabledTypes(),
7126
+ entries: state.value,
7127
+ loading: isLoadingStateLoading(state)
7128
+ });
7129
+ }, ...(ngDevMode ? [{ debugName: "rowsSignal" }] : /* istanbul ignore next */ []));
7130
+ /**
7131
+ * The list's state.
7132
+ *
7133
+ * Always a success result, never a loading one: a row carries its own `loading` status and renders a "Loading"
7134
+ * chip, which reads better here than the list swapping itself out for a spinner and then reflowing.
7135
+ */
7136
+ listStateSignal = computed(() => successResult(this.rowsSignal().map((row) => ({ row, actions: this.actionsForRow(row) }))), ...(ngDevMode ? [{ debugName: "listStateSignal" }] : /* istanbul ignore next */ []));
7137
+ /**
7138
+ * Builds the actions for a row.
7139
+ *
7140
+ * An errored connection offers both Reconnect and Disconnect: reconnecting is the usual fix, but a
7141
+ * user who no longer wants the integration must not be forced to reauthorize just to remove it.
7142
+ *
7143
+ * @param row - The row to build actions for.
7144
+ * @returns The actions to render, or null while loading.
7145
+ */
7146
+ actionsForRow(row) {
7147
+ const { providerType, assets, status } = row;
7148
+ const provider = this.dbxFirebaseExternalConnectionService.getProvider(providerType);
7149
+ const result = [];
7150
+ if (status !== 'loading') {
7151
+ if (status !== 'connected' && row.enabled) {
7152
+ result.push({
7153
+ label: assets.connectText ?? (status === 'error' ? 'Reconnect' : 'Connect'),
7154
+ icon: 'link',
7155
+ handler: (_, context) => context.startWorkingWithPromise(this.connectToProvider(providerType))
7156
+ });
7157
+ }
7158
+ if (status !== 'notConnected') {
7159
+ result.push({
7160
+ label: assets.disconnectText ?? 'Disconnect',
7161
+ icon: 'link_off',
7162
+ confirm: provider?.disconnectConfirm ?? {
7163
+ title: `Disconnect ${assets.providerName}?`,
7164
+ prompt: `This account will no longer be connected to ${assets.providerName}.`,
7165
+ confirmText: 'Disconnect'
7166
+ },
7167
+ handler: (_, context) => context.startWorkingWithLoadingStateObservable(this.disconnectFromProvider(providerType))
7168
+ });
7169
+ }
7170
+ }
7171
+ return result.length ? result : null;
7172
+ }
7173
+ /**
7174
+ * Starts the connect flow for a provider.
7175
+ *
7176
+ * The action stays working until the authorize page is actually opening, rather than succeeding the
7177
+ * moment the redirect is requested — the page the user is looking at has not changed yet, so a
7178
+ * success there reads as "nothing happened".
7179
+ *
7180
+ * @param providerType - The provider to connect.
7181
+ * @returns Resolves once the authorize page is actually opening.
7182
+ */
7183
+ async connectToProvider(providerType) {
7184
+ await this.dbxFirebaseExternalConnectionService.connectToProvider(providerType);
7185
+ }
7186
+ /**
7187
+ * Disconnects the signed-in user from a provider.
7188
+ *
7189
+ * @param providerType - The provider to disconnect.
7190
+ * @returns The loading state observable of the callable.
7191
+ */
7192
+ disconnectFromProvider(providerType) {
7193
+ return this.userExternalConnectionDocumentStore.disconnectUserExternalConnection({ providerType });
7194
+ }
7195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7196
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseExternalConnectionsComponent, isStandalone: true, selector: "dbx-firebase-external-connections", host: { classAttribute: "d-block dbx-firebase-external-connections" }, providers: [UserExternalConnectionDocumentStore, DbxFirebaseUserExternalConnectionsStore], ngImport: i0, template: `
7197
+ @if (signedInSignal()) {
7198
+ @if (errorSignal(); as error) {
7199
+ <dbx-error [error]="error"></dbx-error>
7200
+ } @else {
7201
+ <dbx-firebase-external-connection-list [state]="listStateSignal()">
7202
+ <dbx-list-empty-content empty>
7203
+ <p class="dbx-hint no-margin">There are no apps available to connect.</p>
7204
+ </dbx-list-empty-content>
7205
+ </dbx-firebase-external-connection-list>
7206
+ }
7207
+ } @else {
7208
+ <p class="dbx-hint no-margin">Sign in to connect your apps.</p>
7209
+ }
7210
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxFirebaseExternalConnectionListComponent, selector: "dbx-firebase-external-connection-list" }, { kind: "component", type: DbxListEmptyContentComponent, selector: "dbx-list-empty-content" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7211
+ }
7212
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseExternalConnectionsComponent, decorators: [{
7213
+ type: Component,
7214
+ args: [{
7215
+ selector: 'dbx-firebase-external-connections',
7216
+ template: `
7217
+ @if (signedInSignal()) {
7218
+ @if (errorSignal(); as error) {
7219
+ <dbx-error [error]="error"></dbx-error>
7220
+ } @else {
7221
+ <dbx-firebase-external-connection-list [state]="listStateSignal()">
7222
+ <dbx-list-empty-content empty>
7223
+ <p class="dbx-hint no-margin">There are no apps available to connect.</p>
7224
+ </dbx-list-empty-content>
7225
+ </dbx-firebase-external-connection-list>
7226
+ }
7227
+ } @else {
7228
+ <p class="dbx-hint no-margin">Sign in to connect your apps.</p>
7229
+ }
7230
+ `,
7231
+ host: {
7232
+ class: 'd-block dbx-firebase-external-connections'
7233
+ },
7234
+ standalone: true,
7235
+ imports: [DbxErrorComponent, DbxFirebaseExternalConnectionListComponent, DbxListEmptyContentComponent],
7236
+ providers: [UserExternalConnectionDocumentStore, DbxFirebaseUserExternalConnectionsStore],
7237
+ changeDetection: ChangeDetectionStrategy.OnPush
7238
+ }]
7239
+ }], ctorParameters: () => [] });
7240
+
7241
+ /**
7242
+ * Registers the third-party connection providers an app offers, plus the registry service and the
7243
+ * collection token the connection stores read from.
7244
+ *
7245
+ * A SIBLING of `provideDbxFirebase()` (like `provideDbxFirebaseLogin()` / `provideDbxFirebaseOidc()`)
7246
+ * rather than part of it: it requires an app-supplied provider catalog and depends on
7247
+ * `provideDbxFirestoreCollection()` having run.
7248
+ *
7249
+ * Naming a known provider type is all an app needs to offer that service: the library carries the
7250
+ * presentation, and the connect flow — minting the signed `state`, then redirecting to the authorize
7251
+ * url carrying it — is the library's default. Nothing about it belongs in an app's config.
7252
+ *
7253
+ * @param config - The provider catalog and authorize configuration.
7254
+ * @returns EnvironmentProviders.
7255
+ *
7256
+ * @example
7257
+ * ```ts
7258
+ * provideDbxFirebaseExternalConnections({
7259
+ * appCollectionClass: DemoFirestoreCollections,
7260
+ * authorizeOrigin: environment.externalConnectionAuthorizeOrigin,
7261
+ * providers: [
7262
+ * CALCOM_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE,
7263
+ * // start from a known provider and reword one line
7264
+ * dbxFirebaseKnownExternalConnectionProvider({ providerType: DISCORD_USER_EXTERNAL_CONNECTION_PROVIDER_TYPE, assets: { description: 'Link Discord to get notified in your server.' } }),
7265
+ * // or declare a service the library has no defaults for
7266
+ * APP_INTERNAL_EXTERNAL_CONNECTION_PROVIDER
7267
+ * ]
7268
+ * })
7269
+ * ```
7270
+ */
7271
+ function provideDbxFirebaseExternalConnections(config) {
7272
+ const { appCollectionClass } = config;
7273
+ const providers = [
7274
+ {
7275
+ provide: DbxFirebaseExternalConnectionsConfig,
7276
+ useValue: config
7277
+ },
7278
+ {
7279
+ provide: DbxFirebaseUserExternalConnectionCollections,
7280
+ useExisting: appCollectionClass
7281
+ },
7282
+ {
7283
+ provide: DbxFirebaseExternalConnectionService,
7284
+ useClass: DbxFirebaseExternalConnectionService
7285
+ }
7286
+ ];
7287
+ return makeEnvironmentProviders(providers);
7288
+ }
7289
+
7290
+ /**
7291
+ * App-level tuning for the notification delivery health check UI.
7292
+ *
7293
+ * These windows are ENFORCED BY THE SERVER; the client only counts down to them so it can disable an
7294
+ * action instead of letting the user trigger a call that comes back as an error. So every value here
7295
+ * must match what the server was configured with — declare it once in a package both sides import
7296
+ * rather than writing the number down twice, or the UI will offer a test message the server rejects.
7297
+ */
7298
+ class DbxFirebaseNotificationHealthCheckConfig {
7299
+ }
7300
+ /**
7301
+ * How long the client keeps watching an in-flight test message before giving up on it.
7302
+ *
7303
+ * Comfortably longer than a provider takes to record a delivery outcome — the point is to stop polling
7304
+ * a provider that is never going to answer, not to impose a deadline of the client's own.
7305
+ */
7306
+ const DEFAULT_NOTIFICATION_HEALTH_CHECK_PROBE_WATCH_MINUTES = 20;
7307
+ /**
7308
+ * Configuration for the {@link DbxFirebaseNotificationHealthCheckPresentationService}.
7309
+ */
7310
+ class DbxFirebaseNotificationHealthCheckPresentationServiceConfig {
7311
+ }
7312
+ /**
7313
+ * Colour for each health check status.
7314
+ *
7315
+ * `WARNING` and `ERROR` deliberately share `warn` — the palette has no distinct error tone — so they
7316
+ * are told apart by their icons instead.
7317
+ */
7318
+ const NOTIFICATION_HEALTH_CHECK_STATUS_COLORS = {
7319
+ [NotificationHealthCheckStatus.OK]: 'success',
7320
+ [NotificationHealthCheckStatus.WARNING]: 'warn',
7321
+ [NotificationHealthCheckStatus.ERROR]: 'warn',
7322
+ [NotificationHealthCheckStatus.PENDING]: 'notice',
7323
+ [NotificationHealthCheckStatus.SKIPPED]: 'grey',
7324
+ [NotificationHealthCheckStatus.UNKNOWN]: 'grey'
7325
+ };
7326
+ /**
7327
+ * Icon for each health check status. Also what distinguishes a warning from an error.
7328
+ */
7329
+ const NOTIFICATION_HEALTH_CHECK_STATUS_ICONS = {
7330
+ [NotificationHealthCheckStatus.OK]: 'check_circle',
7331
+ [NotificationHealthCheckStatus.WARNING]: 'warning',
7332
+ [NotificationHealthCheckStatus.ERROR]: 'error',
7333
+ [NotificationHealthCheckStatus.PENDING]: 'hourglass_top',
7334
+ [NotificationHealthCheckStatus.SKIPPED]: 'remove_circle_outline',
7335
+ [NotificationHealthCheckStatus.UNKNOWN]: 'help'
7336
+ };
7337
+ /**
7338
+ * Human-readable label for each health check status.
7339
+ *
7340
+ * Used as the chip label for an issue code with no registered entry.
7341
+ */
7342
+ const NOTIFICATION_HEALTH_CHECK_STATUS_LABELS = {
7343
+ [NotificationHealthCheckStatus.OK]: 'OK',
7344
+ [NotificationHealthCheckStatus.WARNING]: 'Warning',
7345
+ [NotificationHealthCheckStatus.ERROR]: 'Problem',
7346
+ [NotificationHealthCheckStatus.PENDING]: 'Pending',
7347
+ [NotificationHealthCheckStatus.SKIPPED]: 'Not Checked',
7348
+ [NotificationHealthCheckStatus.UNKNOWN]: 'Unknown'
7349
+ };
7350
+ /**
7351
+ * Human-readable label for each delivery method.
7352
+ */
7353
+ const NOTIFICATION_DELIVERY_METHOD_LABELS = {
7354
+ [NotificationDeliveryMethod.EMAIL]: 'Email',
7355
+ [NotificationDeliveryMethod.TEXT]: 'Text Message',
7356
+ [NotificationDeliveryMethod.PUSH]: 'Push Notification',
7357
+ [NotificationDeliveryMethod.NOTIFICATION_SUMMARY]: 'In-App Notification'
7358
+ };
7359
+ /**
7360
+ * Icon for each delivery method.
7361
+ */
7362
+ const NOTIFICATION_DELIVERY_METHOD_ICONS = {
7363
+ [NotificationDeliveryMethod.EMAIL]: 'mail',
7364
+ [NotificationDeliveryMethod.TEXT]: 'sms',
7365
+ [NotificationDeliveryMethod.PUSH]: 'notifications',
7366
+ [NotificationDeliveryMethod.NOTIFICATION_SUMMARY]: 'inbox'
7367
+ };
7368
+ /**
7369
+ * Label for each delivery method's "send a test message" action.
7370
+ *
7371
+ * Always names what the method actually delivers — "Send Test Email", "Send Test Text Message" — rather
7372
+ * than a generic "Send Test Message", so the button says which of a user's contact details it is about
7373
+ * to send something real to.
7374
+ */
7375
+ const NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_LABELS = {
7376
+ [NotificationDeliveryMethod.EMAIL]: 'Send Test Email',
7377
+ [NotificationDeliveryMethod.TEXT]: 'Send Test Text Message',
7378
+ [NotificationDeliveryMethod.PUSH]: 'Send Test Push Notification',
7379
+ [NotificationDeliveryMethod.NOTIFICATION_SUMMARY]: 'Send Test In-App Notification'
7380
+ };
7381
+ /**
7382
+ * Noun for what each delivery method's test message actually is, for use mid-sentence.
7383
+ *
7384
+ * Lowercase because it reads inside prose — "A test text message was sent recently."
7385
+ */
7386
+ const NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_NOUNS = {
7387
+ [NotificationDeliveryMethod.EMAIL]: 'test email',
7388
+ [NotificationDeliveryMethod.TEXT]: 'test text message',
7389
+ [NotificationDeliveryMethod.PUSH]: 'test push notification',
7390
+ [NotificationDeliveryMethod.NOTIFICATION_SUMMARY]: 'test in-app notification'
7391
+ };
7392
+ /**
7393
+ * Short name for each delivery method, for a label that has to stay compact.
7394
+ *
7395
+ * Used to compose the probe chips — `Test Email Sent`, `Test Text Sent` — where the full
7396
+ * {@link NOTIFICATION_DELIVERY_METHOD_LABELS} wording would make the chip too long to scan.
7397
+ */
7398
+ const NOTIFICATION_DELIVERY_METHOD_SHORT_LABELS = {
7399
+ [NotificationDeliveryMethod.EMAIL]: 'Email',
7400
+ [NotificationDeliveryMethod.TEXT]: 'Text',
7401
+ [NotificationDeliveryMethod.PUSH]: 'Push',
7402
+ [NotificationDeliveryMethod.NOTIFICATION_SUMMARY]: 'In-App'
7403
+ };
7404
+ /**
7405
+ * What each probe lifecycle code says happened to the test message.
7406
+ *
7407
+ * The other half of the composed probe chip label — see {@link notificationHealthCheckProbeIssueLabel}.
7408
+ */
7409
+ const NOTIFICATION_HEALTH_CHECK_PROBE_ISSUE_OUTCOMES = {
7410
+ [KnownNotificationHealthCheckIssueCode.PROBE_PENDING]: 'Sent',
7411
+ [KnownNotificationHealthCheckIssueCode.PROBE_DELIVERED]: 'Delivered',
7412
+ [KnownNotificationHealthCheckIssueCode.PROBE_FAILED]: 'Failed',
7413
+ [KnownNotificationHealthCheckIssueCode.PROBE_DISPATCH_FAILED]: 'Not Sent',
7414
+ [MailgunNotificationHealthCheckIssueCode.PROBE_NOT_CONFIGURED]: 'Unavailable'
7415
+ };
7416
+ /**
7417
+ * The chip label for a probe lifecycle finding, naming the delivery method it belongs to.
7418
+ *
7419
+ * Every provider emits the same probe codes, so these labels cannot be fixed strings in the presentation
7420
+ * registry the way every other code's is: the same `probeDelivered` finding should read `Test Email
7421
+ * Delivered` in the email section and `Test Text Delivered` in the text section.
7422
+ *
7423
+ * @param code - The issue code to label.
7424
+ * @param method - The delivery method whose section the finding is rendered in.
7425
+ * @returns The composed label, or undefined when the code is not a probe lifecycle code.
7426
+ */
7427
+ function notificationHealthCheckProbeIssueLabel(code, method) {
7428
+ const outcome = NOTIFICATION_HEALTH_CHECK_PROBE_ISSUE_OUTCOMES[code];
7429
+ return outcome == null ? undefined : `Test ${NOTIFICATION_DELIVERY_METHOD_SHORT_LABELS[method] ?? 'Message'} ${outcome}`;
7430
+ }
7431
+ /**
7432
+ * Presentation entries for every issue code the library and the Mailgun email check emit.
7433
+ *
7434
+ * None of them set a colour — see {@link DbxFirebaseNotificationHealthCheckPresentationEntry.color}.
7435
+ */
7436
+ const DEFAULT_NOTIFICATION_HEALTH_CHECK_PRESENTATION_ENTRIES = [
7437
+ // configuration
7438
+ { code: KnownNotificationHealthCheckIssueCode.SEND_SERVICE_NOT_CONFIGURED, label: 'Not Available', icon: 'block' },
7439
+ { code: KnownNotificationHealthCheckIssueCode.SEND_SERVICE_HEALTH_CHECK_UNAVAILABLE, label: 'Not Verified', icon: 'help' },
7440
+ { code: KnownNotificationHealthCheckIssueCode.NO_DELIVERY_TARGET, label: 'No Destination', icon: 'person_off' },
7441
+ { code: KnownNotificationHealthCheckIssueCode.RECIPIENT_OPTED_OUT, label: 'Opted Out', icon: 'unsubscribe' },
7442
+ { code: KnownNotificationHealthCheckIssueCode.RECIPIENT_DISABLED, label: 'Turned Off', icon: 'notifications_off' },
7443
+ { code: KnownNotificationHealthCheckIssueCode.METHOD_DISABLED_GLOBALLY, label: 'Off Everywhere', icon: 'notifications_off' },
7444
+ { code: KnownNotificationHealthCheckIssueCode.METHOD_DISABLED_FOR_TEMPLATE, label: 'Off For This Type', icon: 'notifications_paused' },
7445
+ { code: KnownNotificationHealthCheckIssueCode.METHOD_DISABLED_FOR_BOX, label: 'Off For A Subscription', icon: 'notifications_paused' },
7446
+ // subscriptions
7447
+ { code: KnownNotificationHealthCheckIssueCode.NO_NOTIFICATION_BOXES, label: 'No Subscriptions', icon: 'inbox' },
7448
+ { code: KnownNotificationHealthCheckIssueCode.NOTIFICATION_BOX_EXCLUSIONS, label: 'Suppressed', icon: 'filter_alt_off' },
7449
+ { code: KnownNotificationHealthCheckIssueCode.NEEDS_CONFIG_SYNC, label: 'Still Saving', icon: 'sync_problem' },
7450
+ { code: KnownNotificationHealthCheckIssueCode.SUBSCRIPTION_BROKEN, label: 'Broken Subscription', icon: 'link_off' },
7451
+ { code: KnownNotificationHealthCheckIssueCode.SUBSCRIPTION_NOT_READY, label: 'Still Setting Up', icon: 'hourglass_top' },
7452
+ // probe. These labels are only the fallback for a finding rendered without a delivery method in hand:
7453
+ // given one, notificationHealthCheckProbeIssueLabel() names it — `Test Email Sent`, `Test Text Sent`.
7454
+ { code: KnownNotificationHealthCheckIssueCode.PROBE_PENDING, label: 'Test Sent', icon: 'hourglass_top' },
7455
+ { code: KnownNotificationHealthCheckIssueCode.PROBE_DELIVERED, label: 'Test Delivered', icon: 'mark_email_read' },
7456
+ { code: KnownNotificationHealthCheckIssueCode.PROBE_FAILED, label: 'Test Failed', icon: 'error' },
7457
+ { code: KnownNotificationHealthCheckIssueCode.PROBE_DISPATCH_FAILED, label: 'Test Not Sent', icon: 'error' },
7458
+ // mailgun
7459
+ { code: MailgunNotificationHealthCheckIssueCode.SUPPRESSED_BOUNCE, label: 'Blocked After Bounce', icon: 'block' },
7460
+ { code: MailgunNotificationHealthCheckIssueCode.SUPPRESSED_COMPLAINT, label: 'Blocked After Spam Report', icon: 'report' },
7461
+ { code: MailgunNotificationHealthCheckIssueCode.SUPPRESSED_UNSUBSCRIBE, label: 'Unsubscribed', icon: 'unsubscribe' },
7462
+ { code: MailgunNotificationHealthCheckIssueCode.RECENT_DELIVERY_FAILURE, label: 'Recent Delivery Failed', icon: 'error' },
7463
+ { code: MailgunNotificationHealthCheckIssueCode.RECENT_DELIVERY_SUCCESS, label: 'Recently Delivered', icon: 'mark_email_read' },
7464
+ { code: MailgunNotificationHealthCheckIssueCode.NO_RECENT_ACTIVITY, label: 'No Recent Activity', icon: 'history_toggle_off' },
7465
+ { code: MailgunNotificationHealthCheckIssueCode.DOMAIN_NOT_ACTIVE, label: 'Sending System Down', icon: 'dns' },
7466
+ { code: MailgunNotificationHealthCheckIssueCode.ADDRESS_UNDELIVERABLE, label: 'Address Undeliverable', icon: 'person_off' },
7467
+ { code: MailgunNotificationHealthCheckIssueCode.ADDRESS_DISPOSABLE, label: 'Disposable Address', icon: 'delete_forever' },
7468
+ { code: MailgunNotificationHealthCheckIssueCode.PROBE_NOT_CONFIGURED, label: 'Test Unavailable', icon: 'block' }
7469
+ ];
7470
+
7471
+ /**
7472
+ * Registry of presentation metadata for notification health check findings.
7473
+ *
7474
+ * Health check issue codes are open-ended, so this maps a code to a label and an icon. An
7475
+ * unregistered code is not an error — it falls back to a presentation derived from the issue's
7476
+ * status, and the issue's own `m`/`f` strings carry the meaning either way.
7477
+ *
7478
+ * Apps extend the table by supplying a {@link DbxFirebaseNotificationHealthCheckPresentationServiceConfig}
7479
+ * through `provideDbxFirebaseNotifications({ healthCheckPresentation })`.
7480
+ */
7481
+ class DbxFirebaseNotificationHealthCheckPresentationService {
7482
+ _config = inject(DbxFirebaseNotificationHealthCheckPresentationServiceConfig, { optional: true });
7483
+ _entries = new Map();
7484
+ constructor() {
7485
+ if (this._config?.registerDefaultEntries !== false) {
7486
+ this.register(DEFAULT_NOTIFICATION_HEALTH_CHECK_PRESENTATION_ENTRIES);
7487
+ }
7488
+ if (this._config?.entries) {
7489
+ this.register(this._config.entries);
7490
+ }
7491
+ }
7492
+ /**
7493
+ * Registers presentation entries, overriding any already registered for the same code by default.
7494
+ *
7495
+ * @param entries - The entries to register.
7496
+ * @param override - Whether to replace an existing entry for the same code. Defaults to true.
7497
+ */
7498
+ register(entries, override = true) {
7499
+ asArray(entries).forEach((entry) => {
7500
+ if (override || !this._entries.has(entry.code)) {
7501
+ this._entries.set(entry.code, entry);
7502
+ }
7503
+ });
7504
+ }
7505
+ /**
7506
+ * The entry registered for an issue code, if any.
7507
+ *
7508
+ * @param code - The issue code to look up.
7509
+ * @returns The registered entry, or undefined when the code has none.
7510
+ */
7511
+ entryForCode(code) {
7512
+ return this._entries.get(code);
7513
+ }
7514
+ /**
7515
+ * Resolves how to present a single finding.
7516
+ *
7517
+ * Pass the delivery method whose section the finding is rendered in to get a probe label that names
7518
+ * it — `Test Email Sent` rather than `Test Sent`. Every provider emits the same probe codes, so that
7519
+ * is the one label a registry entry cannot supply on its own.
7520
+ *
7521
+ * @param issue - The finding to present.
7522
+ * @param method - The delivery method the finding belongs to, when it belongs to one.
7523
+ * @returns The label, icon, and colour to render it with.
7524
+ */
7525
+ presentationForIssue(issue, method) {
7526
+ const entry = this._entries.get(issue.c);
7527
+ const probeLabel = method == null ? undefined : notificationHealthCheckProbeIssueLabel(issue.c, method);
7528
+ return {
7529
+ label: probeLabel ?? entry?.label ?? this.labelForStatus(issue.s),
7530
+ icon: entry?.icon ?? this.iconForStatus(issue.s),
7531
+ color: entry?.color ?? this.colorForStatus(issue.s)
7532
+ };
7533
+ }
7534
+ /**
7535
+ * @param status - The status to colour.
7536
+ * @returns The theme colour for the status.
7537
+ */
7538
+ colorForStatus(status) {
7539
+ return NOTIFICATION_HEALTH_CHECK_STATUS_COLORS[status] ?? NOTIFICATION_HEALTH_CHECK_STATUS_COLORS[NotificationHealthCheckStatus.UNKNOWN];
7540
+ }
7541
+ /**
7542
+ * @param status - The status to find an icon for.
7543
+ * @returns The icon for the status.
7544
+ */
7545
+ iconForStatus(status) {
7546
+ return NOTIFICATION_HEALTH_CHECK_STATUS_ICONS[status] ?? NOTIFICATION_HEALTH_CHECK_STATUS_ICONS[NotificationHealthCheckStatus.UNKNOWN];
7547
+ }
7548
+ /**
7549
+ * @param status - The status to label.
7550
+ * @returns The human-readable label for the status.
7551
+ */
7552
+ labelForStatus(status) {
7553
+ return NOTIFICATION_HEALTH_CHECK_STATUS_LABELS[status] ?? NOTIFICATION_HEALTH_CHECK_STATUS_LABELS[NotificationHealthCheckStatus.UNKNOWN];
7554
+ }
7555
+ /**
7556
+ * @param method - The delivery method to label.
7557
+ * @returns The human-readable label for the delivery method.
7558
+ */
7559
+ labelForDeliveryMethod(method) {
7560
+ return NOTIFICATION_DELIVERY_METHOD_LABELS[method] ?? 'Notification';
7561
+ }
7562
+ /**
7563
+ * @param method - The delivery method to find an icon for.
7564
+ * @returns The icon for the delivery method.
7565
+ */
7566
+ iconForDeliveryMethod(method) {
7567
+ return NOTIFICATION_DELIVERY_METHOD_ICONS[method] ?? 'notifications';
7568
+ }
7569
+ /**
7570
+ * @param method - The delivery method whose test message action is being labelled.
7571
+ * @returns The label for the method's "send a test message" action, naming what it sends.
7572
+ */
7573
+ testMessageLabelForDeliveryMethod(method) {
7574
+ return NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_LABELS[method] ?? 'Send Test Message';
7575
+ }
7576
+ /**
7577
+ * @param method - The delivery method whose test message is being described.
7578
+ * @returns The lowercase noun for the method's test message, for use mid-sentence.
7579
+ */
7580
+ testMessageNounForDeliveryMethod(method) {
7581
+ return NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_NOUNS[method] ?? 'test message';
7582
+ }
7583
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckPresentationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7584
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckPresentationService, providedIn: 'root' });
7585
+ }
7586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckPresentationService, decorators: [{
7587
+ type: Injectable,
7588
+ args: [{
7589
+ providedIn: 'root'
7590
+ }]
7591
+ }], ctorParameters: () => [] });
7592
+
7593
+ /**
7594
+ * Renders a single {@link NotificationHealthCheckIssue}: its status chip, what was found, and what to
7595
+ * do about it.
7596
+ *
7597
+ * The chip's label and icon come from the {@link DbxFirebaseNotificationHealthCheckPresentationService}
7598
+ * registry; the message and the suggested fix always come from the issue itself, so an issue code the
7599
+ * registry has never seen still renders correctly.
7600
+ *
7601
+ * The finding's structured detail (`d`) is deliberately not rendered — values like the sending domain
7602
+ * or the delivery method key are diagnostic rather than user-facing. They remain on the issue itself,
7603
+ * so an API/callModel consumer still receives them.
7604
+ */
7605
+ class DbxFirebaseNotificationHealthCheckIssueComponent {
7606
+ _presentationService = inject(DbxFirebaseNotificationHealthCheckPresentationService);
7607
+ issue = input(...(ngDevMode ? [undefined, { debugName: "issue" }] : /* istanbul ignore next */ []));
7608
+ /**
7609
+ * The delivery method this finding belongs to, when it belongs to one.
7610
+ *
7611
+ * Only used to label a probe finding with what was actually sent — `Test Email Sent` rather than the
7612
+ * method-agnostic `Test Sent` — since every provider emits the same probe codes. An account-wide
7613
+ * finding leaves it unset.
7614
+ */
7615
+ method = input(...(ngDevMode ? [undefined, { debugName: "method" }] : /* istanbul ignore next */ []));
7616
+ presentationSignal = computed(() => {
7617
+ const issue = this.issue();
7618
+ const method = this.method();
7619
+ return issue ? this._presentationService.presentationForIssue(issue, method) : { label: '', icon: '', color: 'grey' };
7620
+ }, ...(ngDevMode ? [{ debugName: "presentationSignal" }] : /* istanbul ignore next */ []));
7621
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckIssueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7622
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckIssueComponent, isStandalone: true, selector: "dbx-firebase-notification-healthcheck-issue", inputs: { issue: { classPropertyName: "issue", publicName: "issue", isSignal: true, isRequired: false, transformFunction: null }, method: { classPropertyName: "method", publicName: "method", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block dbx-firebase-notification-healthcheck-issue" }, ngImport: i0, template: `
7623
+ @if (issue(); as issueValue) {
7624
+ <div class="dbx-flex-bar dbx-pb1">
7625
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="presentationSignal().icon" [dbxColor]="presentationSignal().color" [dbxColorTone]="18"></dbx-icon-tile>
7626
+ <dbx-chip [small]="true" [color]="presentationSignal().color">{{ presentationSignal().label }}</dbx-chip>
7627
+ </div>
7628
+ <p class="no-margin">{{ issueValue.m }}</p>
7629
+ @if (issueValue.f) {
7630
+ <p class="dbx-hint no-margin dbx-pt1">{{ issueValue.f }}</p>
7631
+ }
7632
+ }
7633
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }, { kind: "component", type: DbxIconTileComponent, selector: "dbx-icon-tile", inputs: ["icon", "round"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7634
+ }
7635
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckIssueComponent, decorators: [{
7636
+ type: Component,
7637
+ args: [{
7638
+ selector: 'dbx-firebase-notification-healthcheck-issue',
7639
+ template: `
7640
+ @if (issue(); as issueValue) {
7641
+ <div class="dbx-flex-bar dbx-pb1">
7642
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="presentationSignal().icon" [dbxColor]="presentationSignal().color" [dbxColorTone]="18"></dbx-icon-tile>
7643
+ <dbx-chip [small]="true" [color]="presentationSignal().color">{{ presentationSignal().label }}</dbx-chip>
7644
+ </div>
7645
+ <p class="no-margin">{{ issueValue.m }}</p>
7646
+ @if (issueValue.f) {
7647
+ <p class="dbx-hint no-margin dbx-pt1">{{ issueValue.f }}</p>
7648
+ }
7649
+ }
7650
+ `,
7651
+ host: {
7652
+ class: 'd-block dbx-firebase-notification-healthcheck-issue'
7653
+ },
7654
+ standalone: true,
7655
+ imports: [DbxChipDirective, DbxColorDirective, DbxIconTileComponent],
7656
+ changeDetection: ChangeDetectionStrategy.OnPush
7657
+ }]
7658
+ }], propDecorators: { issue: [{ type: i0.Input, args: [{ isSignal: true, alias: "issue", required: false }] }], method: [{ type: i0.Input, args: [{ isSignal: true, alias: "method", required: false }] }] } });
7659
+
7660
+ /**
7661
+ * Renders one delivery method's health check result: where it would deliver to, everything the check
7662
+ * found for it, the state of any test message that was sent through it, and — when the method can be
7663
+ * probed and an action is supplied — the button for sending a new one.
7664
+ *
7665
+ * Presentational: it injects nothing beyond the presentation registry, so it can be dropped anywhere a
7666
+ * result is already in hand. The test message action is an input rather than something it resolves.
7667
+ */
7668
+ class DbxFirebaseNotificationHealthCheckMethodComponent {
7669
+ _presentationService = inject(DbxFirebaseNotificationHealthCheckPresentationService);
7670
+ result = input(...(ngDevMode ? [undefined, { debugName: "result" }] : /* istanbul ignore next */ []));
7671
+ /**
7672
+ * The test message action to offer for this method, when the owner of the check has one to offer.
7673
+ *
7674
+ * Ignored unless the result reports that the method can actually be probed.
7675
+ */
7676
+ probeAction = input(...(ngDevMode ? [undefined, { debugName: "probeAction" }] : /* istanbul ignore next */ []));
7677
+ methodLabelSignal = computed(() => {
7678
+ const method = this.result()?.me;
7679
+ return method ? this._presentationService.labelForDeliveryMethod(method) : '';
7680
+ }, ...(ngDevMode ? [{ debugName: "methodLabelSignal" }] : /* istanbul ignore next */ []));
7681
+ methodIconSignal = computed(() => {
7682
+ const method = this.result()?.me;
7683
+ return method ? this._presentationService.iconForDeliveryMethod(method) : 'notifications';
7684
+ }, ...(ngDevMode ? [{ debugName: "methodIconSignal" }] : /* istanbul ignore next */ []));
7685
+ statusColorSignal = computed(() => {
7686
+ const status = this.result()?.s;
7687
+ return status ? this._presentationService.colorForStatus(status) : 'grey';
7688
+ }, ...(ngDevMode ? [{ debugName: "statusColorSignal" }] : /* istanbul ignore next */ []));
7689
+ statusLabelSignal = computed(() => {
7690
+ const status = this.result()?.s;
7691
+ return status ? this._presentationService.labelForStatus(status) : '';
7692
+ }, ...(ngDevMode ? [{ debugName: "statusLabelSignal" }] : /* istanbul ignore next */ []));
7693
+ /**
7694
+ * Introduces the dispatched probe's timestamp, naming what was actually sent.
7695
+ *
7696
+ * Sentence-cased from the method's mid-sentence noun, since it opens the line.
7697
+ */
7698
+ testMessageSentLabelSignal = computed(() => {
7699
+ const method = this.result()?.me;
7700
+ const noun = method ? this._presentationService.testMessageNounForDeliveryMethod(method) : 'test message';
7701
+ return `${noun.charAt(0).toUpperCase()}${noun.slice(1)} sent`;
7702
+ }, ...(ngDevMode ? [{ debugName: "testMessageSentLabelSignal" }] : /* istanbul ignore next */ []));
7703
+ /**
7704
+ * The action to render, which requires the server to have reported this method as probe-capable.
7705
+ *
7706
+ * The result is the authority on that, so an action supplied for a method that cannot be probed is
7707
+ * dropped rather than offering the user a test message the server would refuse to send.
7708
+ */
7709
+ probeActionSignal = computed(() => {
7710
+ const probeSupported = this.result()?.pb === true;
7711
+ const probeAction = this.probeAction();
7712
+ return probeSupported ? probeAction : undefined;
7713
+ }, ...(ngDevMode ? [{ debugName: "probeActionSignal" }] : /* istanbul ignore next */ []));
7714
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckMethodComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7715
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckMethodComponent, isStandalone: true, selector: "dbx-firebase-notification-healthcheck-method", inputs: { result: { classPropertyName: "result", publicName: "result", isSignal: true, isRequired: false, transformFunction: null }, probeAction: { classPropertyName: "probeAction", publicName: "probeAction", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block dbx-firebase-notification-healthcheck-method" }, ngImport: i0, template: `
7716
+ @if (result(); as resultValue) {
7717
+ <div class="dbx-flex-bar dbx-pb1">
7718
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="methodIconSignal()" [dbxColor]="statusColorSignal()" [dbxColorTone]="18"></dbx-icon-tile>
7719
+ <div class="dbx-flex-fill">
7720
+ <div class="dbx-text-title-medium">{{ methodLabelSignal() }}</div>
7721
+ @if (resultValue.tg) {
7722
+ <div class="dbx-text-body-small dbx-hint">{{ resultValue.tg }}</div>
7723
+ } @else {
7724
+ <div class="dbx-text-body-small dbx-hint">No destination</div>
7725
+ }
7726
+ </div>
7727
+ <dbx-chip [small]="true" [color]="statusColorSignal()">{{ statusLabelSignal() }}</dbx-chip>
7728
+ </div>
7729
+
7730
+ @for (issue of resultValue.is; track $index) {
7731
+ <div class="dbx-pt2">
7732
+ <dbx-firebase-notification-healthcheck-issue [issue]="issue" [method]="resultValue.me"></dbx-firebase-notification-healthcheck-issue>
7733
+ </div>
7734
+ }
7735
+
7736
+ @if (resultValue.pr; as probe) {
7737
+ <div class="dbx-pt2 dbx-text-body-small dbx-hint">
7738
+ {{ testMessageSentLabelSignal() }} {{ probe.at | date: 'medium' }}
7739
+ @if (probe.d) {
7740
+ · {{ probe.d }}
7741
+ }
7742
+ </div>
7743
+ }
7744
+
7745
+ @if (probeActionSignal(); as probeAction) {
7746
+ <div class="dbx-pt2">
7747
+ <!-- dbxActionDisabled rather than the button's own disabled input, since dbxActionButton drives that from the action's state -->
7748
+ <div dbxAction [dbxActionDisabled]="probeAction.disabled === true" [dbxActionHandler]="probeAction.handler" [dbxActionConfirm]="probeAction.confirm">
7749
+ <dbx-button dbxActionButton [stroked]="true" [text]="probeAction.label" [icon]="probeAction.icon"></dbx-button>
7750
+ <dbx-error dbxActionError></dbx-error>
7751
+ </div>
7752
+ </div>
7753
+ @if (probeAction.notice) {
7754
+ <div class="dbx-small dbx-hint dbx-pt2">{{ probeAction.notice }}</div>
7755
+ }
7756
+ }
7757
+ }
7758
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionDisabledDirective, selector: "[dbxActionDisabled]", inputs: ["dbxActionDisabled"] }, { kind: "directive", type: DbxActionErrorDirective, selector: "[dbxActionError]" }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }, { kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxIconTileComponent, selector: "dbx-icon-tile", inputs: ["icon", "round"] }, { kind: "component", type: DbxFirebaseNotificationHealthCheckIssueComponent, selector: "dbx-firebase-notification-healthcheck-issue", inputs: ["issue", "method"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7759
+ }
7760
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckMethodComponent, decorators: [{
7761
+ type: Component,
7762
+ args: [{
7763
+ selector: 'dbx-firebase-notification-healthcheck-method',
7764
+ template: `
7765
+ @if (result(); as resultValue) {
7766
+ <div class="dbx-flex-bar dbx-pb1">
7767
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="methodIconSignal()" [dbxColor]="statusColorSignal()" [dbxColorTone]="18"></dbx-icon-tile>
7768
+ <div class="dbx-flex-fill">
7769
+ <div class="dbx-text-title-medium">{{ methodLabelSignal() }}</div>
7770
+ @if (resultValue.tg) {
7771
+ <div class="dbx-text-body-small dbx-hint">{{ resultValue.tg }}</div>
7772
+ } @else {
7773
+ <div class="dbx-text-body-small dbx-hint">No destination</div>
7774
+ }
7775
+ </div>
7776
+ <dbx-chip [small]="true" [color]="statusColorSignal()">{{ statusLabelSignal() }}</dbx-chip>
7777
+ </div>
7778
+
7779
+ @for (issue of resultValue.is; track $index) {
7780
+ <div class="dbx-pt2">
7781
+ <dbx-firebase-notification-healthcheck-issue [issue]="issue" [method]="resultValue.me"></dbx-firebase-notification-healthcheck-issue>
7782
+ </div>
7783
+ }
7784
+
7785
+ @if (resultValue.pr; as probe) {
7786
+ <div class="dbx-pt2 dbx-text-body-small dbx-hint">
7787
+ {{ testMessageSentLabelSignal() }} {{ probe.at | date: 'medium' }}
7788
+ @if (probe.d) {
7789
+ · {{ probe.d }}
7790
+ }
7791
+ </div>
7792
+ }
7793
+
7794
+ @if (probeActionSignal(); as probeAction) {
7795
+ <div class="dbx-pt2">
7796
+ <!-- dbxActionDisabled rather than the button's own disabled input, since dbxActionButton drives that from the action's state -->
7797
+ <div dbxAction [dbxActionDisabled]="probeAction.disabled === true" [dbxActionHandler]="probeAction.handler" [dbxActionConfirm]="probeAction.confirm">
7798
+ <dbx-button dbxActionButton [stroked]="true" [text]="probeAction.label" [icon]="probeAction.icon"></dbx-button>
7799
+ <dbx-error dbxActionError></dbx-error>
7800
+ </div>
7801
+ </div>
7802
+ @if (probeAction.notice) {
7803
+ <div class="dbx-small dbx-hint dbx-pt2">{{ probeAction.notice }}</div>
7804
+ }
7805
+ }
7806
+ }
7807
+ `,
7808
+ host: {
7809
+ class: 'd-block dbx-firebase-notification-healthcheck-method'
7810
+ },
7811
+ standalone: true,
7812
+ imports: [DatePipe, DbxActionButtonDirective, DbxActionConfirmDirective, DbxActionDirective, DbxActionDisabledDirective, DbxActionErrorDirective, DbxActionHandlerDirective, DbxButtonComponent, DbxChipDirective, DbxColorDirective, DbxErrorComponent, DbxIconTileComponent, DbxFirebaseNotificationHealthCheckIssueComponent],
7813
+ changeDetection: ChangeDetectionStrategy.OnPush
7814
+ }]
7815
+ }], propDecorators: { result: [{ type: i0.Input, args: [{ isSignal: true, alias: "result", required: false }] }], probeAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "probeAction", required: false }] }] } });
7816
+
7817
+ /**
7818
+ * Delivery methods this report never renders a section for, whatever the check says about them.
7819
+ *
7820
+ * The in-app summary channel is the app's own notification page, so the user is looking at the delivery
7821
+ * target while they read the report — there is nothing a section could tell them that the page does not
7822
+ * already show. The server has no health check service for it either, so it can currently only report
7823
+ * "Not Checked", which reads as a broken channel next to a working inbox.
7824
+ *
7825
+ * The server still checks and stores the method, so an API/callModel consumer keeps the result. Drop the
7826
+ * entry here to bring the section back if the server starts contributing findings worth acting on.
7827
+ */
7828
+ const HIDDEN_NOTIFICATION_DELIVERY_METHODS = new Set([NotificationDeliveryMethod.NOTIFICATION_SUMMARY]);
7829
+ /**
7830
+ * Renders a whole {@link NotificationHealthCheck}: the rolled-up status, then one section per delivery
7831
+ * method that this system can actually send through.
7832
+ *
7833
+ * This is the inlineable report — it takes the check as an input and injects nothing beyond the
7834
+ * presentation registry, so it can be dropped anywhere a check is already in hand (a settings page, a
7835
+ * dialog, an admin view). The per-method test message actions are an input for the same reason: the
7836
+ * report renders them, whoever owns the store supplies them.
7837
+ *
7838
+ * The view is client-facing, so it shows only what the user can act on. The check's account-wide
7839
+ * findings (`is`), any method the system has no send service for, and the methods in
7840
+ * {@link HIDDEN_NOTIFICATION_DELIVERY_METHODS} are left out here — all of them remain on the check
7841
+ * itself for an API/callModel consumer.
7842
+ */
7843
+ class DbxFirebaseNotificationHealthCheckComponent {
7844
+ _presentationService = inject(DbxFirebaseNotificationHealthCheckPresentationService);
7845
+ healthCheck = input(...(ngDevMode ? [undefined, { debugName: "healthCheck" }] : /* istanbul ignore next */ []));
7846
+ /**
7847
+ * The test message action to offer in each method's section, keyed by delivery method.
7848
+ *
7849
+ * Left unset the report is read-only, which is what an admin or historical view wants.
7850
+ */
7851
+ probeActions = input(...(ngDevMode ? [undefined, { debugName: "probeActions" }] : /* istanbul ignore next */ []));
7852
+ statusColorSignal = computed(() => {
7853
+ const status = this.healthCheck()?.s;
7854
+ return status ? this._presentationService.colorForStatus(status) : 'grey';
7855
+ }, ...(ngDevMode ? [{ debugName: "statusColorSignal" }] : /* istanbul ignore next */ []));
7856
+ statusIconSignal = computed(() => {
7857
+ const status = this.healthCheck()?.s;
7858
+ return status ? this._presentationService.iconForStatus(status) : 'help';
7859
+ }, ...(ngDevMode ? [{ debugName: "statusIconSignal" }] : /* istanbul ignore next */ []));
7860
+ statusLabelSignal = computed(() => {
7861
+ const status = this.healthCheck()?.s;
7862
+ return status ? this._presentationService.labelForStatus(status) : '';
7863
+ }, ...(ngDevMode ? [{ debugName: "statusLabelSignal" }] : /* istanbul ignore next */ []));
7864
+ /**
7865
+ * The method results worth showing the user.
7866
+ *
7867
+ * A method the server has no send service for is not something the user can do anything about, so it
7868
+ * is dropped rather than rendered as a "Not Checked" section, as is any method this report does not
7869
+ * render at all — see {@link HIDDEN_NOTIFICATION_DELIVERY_METHODS}.
7870
+ */
7871
+ methodResultsSignal = computed(() => (this.healthCheck()?.m ?? []).filter((result) => !HIDDEN_NOTIFICATION_DELIVERY_METHODS.has(result.me) && !result.is.some((issue) => issue.c === KnownNotificationHealthCheckIssueCode.SEND_SERVICE_NOT_CONFIGURED)), ...(ngDevMode ? [{ debugName: "methodResultsSignal" }] : /* istanbul ignore next */ []));
7872
+ /**
7873
+ * Each method section to render, paired with the test message action for its method.
7874
+ *
7875
+ * Paired up here rather than indexed in the template so the map lookup stays out of the view.
7876
+ */
7877
+ methodSectionsSignal = computed(() => {
7878
+ const probeActions = this.probeActions();
7879
+ return this.methodResultsSignal().map((result) => ({ result, probeAction: probeActions?.[result.me] }));
7880
+ }, ...(ngDevMode ? [{ debugName: "methodSectionsSignal" }] : /* istanbul ignore next */ []));
7881
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7882
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckComponent, isStandalone: true, selector: "dbx-firebase-notification-healthcheck", inputs: { healthCheck: { classPropertyName: "healthCheck", publicName: "healthCheck", isSignal: true, isRequired: false, transformFunction: null }, probeActions: { classPropertyName: "probeActions", publicName: "probeActions", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block dbx-firebase-notification-healthcheck" }, ngImport: i0, template: `
7883
+ @if (healthCheck(); as healthCheckValue) {
7884
+ <div class="dbx-flex-bar dbx-pb3">
7885
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="statusIconSignal()" [dbxColor]="statusColorSignal()" [dbxColorTone]="18"></dbx-icon-tile>
7886
+ <div class="dbx-flex-fill">
7887
+ <div class="dbx-text-label-medium dbx-uppercase dbx-tracked-wide dbx-hint">Notification Delivery</div>
7888
+ <div class="dbx-text-title-large">{{ statusLabelSignal() }}</div>
7889
+ <div class="dbx-text-body-small dbx-hint">Last checked {{ healthCheckValue.at | date: 'medium' }}</div>
7890
+ </div>
7891
+ </div>
7892
+
7893
+ @for (methodSection of methodSectionsSignal(); track methodSection.result.me) {
7894
+ <dbx-content-pit class="dbx-mb3">
7895
+ <dbx-firebase-notification-healthcheck-method [result]="methodSection.result" [probeAction]="methodSection.probeAction"></dbx-firebase-notification-healthcheck-method>
7896
+ </dbx-content-pit>
7897
+ }
7898
+ }
7899
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxColorDirective, selector: "[dbxColor]", inputs: ["dbxColor", "dbxColorTone"], outputs: ["dbxColorChange", "dbxColorToneChange"] }, { kind: "directive", type: DbxContentPitDirective, selector: "dbx-content-pit, [dbxContentPit]", inputs: ["scrollable", "square"] }, { kind: "component", type: DbxIconTileComponent, selector: "dbx-icon-tile", inputs: ["icon", "round"] }, { kind: "component", type: DbxFirebaseNotificationHealthCheckMethodComponent, selector: "dbx-firebase-notification-healthcheck-method", inputs: ["result", "probeAction"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7900
+ }
7901
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckComponent, decorators: [{
7902
+ type: Component,
7903
+ args: [{
7904
+ selector: 'dbx-firebase-notification-healthcheck',
7905
+ template: `
7906
+ @if (healthCheck(); as healthCheckValue) {
7907
+ <div class="dbx-flex-bar dbx-pb3">
7908
+ <dbx-icon-tile class="dbx-icon-spacer" [icon]="statusIconSignal()" [dbxColor]="statusColorSignal()" [dbxColorTone]="18"></dbx-icon-tile>
7909
+ <div class="dbx-flex-fill">
7910
+ <div class="dbx-text-label-medium dbx-uppercase dbx-tracked-wide dbx-hint">Notification Delivery</div>
7911
+ <div class="dbx-text-title-large">{{ statusLabelSignal() }}</div>
7912
+ <div class="dbx-text-body-small dbx-hint">Last checked {{ healthCheckValue.at | date: 'medium' }}</div>
7913
+ </div>
7914
+ </div>
7915
+
7916
+ @for (methodSection of methodSectionsSignal(); track methodSection.result.me) {
7917
+ <dbx-content-pit class="dbx-mb3">
7918
+ <dbx-firebase-notification-healthcheck-method [result]="methodSection.result" [probeAction]="methodSection.probeAction"></dbx-firebase-notification-healthcheck-method>
7919
+ </dbx-content-pit>
7920
+ }
7921
+ }
7922
+ `,
7923
+ host: {
7924
+ class: 'd-block dbx-firebase-notification-healthcheck'
7925
+ },
7926
+ standalone: true,
7927
+ imports: [DatePipe, DbxColorDirective, DbxContentPitDirective, DbxIconTileComponent, DbxFirebaseNotificationHealthCheckMethodComponent],
7928
+ changeDetection: ChangeDetectionStrategy.OnPush
7929
+ }]
7930
+ }], propDecorators: { healthCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "healthCheck", required: false }] }], probeActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "probeActions", required: false }] }] } });
7931
+
7932
+ /**
7933
+ * Client-side service used for retrieving notification templates.
7934
+ */
7935
+ class DbxFirebaseNotificationTemplateService {
7936
+ appNotificationTemplateTypeInfoRecordService = inject(AppNotificationTemplateTypeInfoRecordService);
7937
+ notificationItemSubjectMessagePairForNotificationSummaryItem(item) {
7938
+ const templateType = item.t;
7939
+ const templateDetails = this.appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[templateType];
7940
+ const subject = item.s ?? templateDetails.name;
7941
+ const message = item.g ?? '';
7942
+ const date = item.cat;
7943
+ return {
7944
+ item,
7945
+ subject,
7946
+ message,
7947
+ date
7948
+ };
7949
+ }
7950
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7951
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService });
7952
+ }
7953
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationTemplateService, decorators: [{
7954
+ type: Injectable
7955
+ }] });
7956
+
7957
+ /**
7958
+ * Selection list wrapper for notification items with view-only default selection mode.
7959
+ */
7960
+ class DbxFirebaseNotificationItemListComponent extends AbstractDbxSelectionListWrapperDirective {
7961
+ constructor() {
7962
+ super({
7963
+ componentClass: DbxFirebaseNotificationItemListViewComponent,
7964
+ defaultSelectionMode: 'view'
7965
+ });
7966
+ }
7967
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7968
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-list", providers: provideDbxListViewWrapper(DbxFirebaseNotificationItemListComponent), usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabledSignal()\" [selectionMode]=\"selectionModeSignal()\">\n <ng-content top select=\"[top]\"></ng-content>\n <ng-content bottom select=\"[bottom]\"></ng-content>\n <ng-content empty select=\"[empty]\"></ng-content>\n <ng-content emptyLoading select=\"[emptyLoading]\"></ng-content>\n <ng-content end select=\"[end]\"></ng-content>\n </dbx-list>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxListWrapperComponentImportsModule }, { kind: "component", type: i1$1.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7969
+ }
7970
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListComponent, decorators: [{
7971
+ type: Component,
7972
+ args: [{
7973
+ selector: 'dbx-firebase-notificationitem-list',
7974
+ template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
7975
+ imports: [DbxListWrapperComponentImportsModule],
7976
+ changeDetection: ChangeDetectionStrategy.OnPush,
7977
+ providers: provideDbxListViewWrapper(DbxFirebaseNotificationItemListComponent),
7978
+ standalone: true
7979
+ }]
7980
+ }], ctorParameters: () => [] });
7981
+ /**
7982
+ * List view component that renders notification items using the selection list pattern.
7983
+ */
7984
+ class DbxFirebaseNotificationItemListViewComponent extends AbstractDbxSelectionListViewDirective {
7985
+ config = {
7986
+ componentClass: DbxFirebaseNotificationItemListViewItemComponent,
7987
+ mapValuesToItemValues: (x) => of(x.map((y) => ({ ...y, key: y.id, icon: y.icon, itemValue: y })))
7988
+ };
7989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
7990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListViewComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-list-view", providers: provideDbxListView(DbxFirebaseNotificationItemListViewComponent), usesInheritance: true, ngImport: i0, template: "<dbx-selection-list-view [config]=\"config\"></dbx-selection-list-view>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxSelectionValueListViewComponentImportsModule }, { kind: "component", type: i1$1.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7991
+ }
7992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewComponent, decorators: [{
7993
+ type: Component,
7994
+ args: [{
7995
+ selector: 'dbx-firebase-notificationitem-list-view',
7996
+ template: DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
7997
+ imports: [DbxSelectionValueListViewComponentImportsModule],
7998
+ changeDetection: ChangeDetectionStrategy.OnPush,
7999
+ providers: provideDbxListView(DbxFirebaseNotificationItemListViewComponent),
8000
+ standalone: true
8001
+ }]
8002
+ }] });
8003
+ /**
8004
+ * Individual list view item component rendering a notification's subject, message, and date.
8005
+ */
8006
+ class DbxFirebaseNotificationItemListViewItemComponent extends AbstractDbxValueListViewItemComponent {
8007
+ dbxFirebaseNotificationTemplateService = inject(DbxFirebaseNotificationTemplateService);
8008
+ pairGetter = cachedGetter(() => this.dbxFirebaseNotificationTemplateService.notificationItemSubjectMessagePairForNotificationSummaryItem(this.itemValue));
8009
+ get subject() {
8010
+ return this.pairGetter().subject;
8011
+ }
8012
+ get message() {
8013
+ return this.pairGetter().message;
8014
+ }
8015
+ get date() {
8016
+ return this.pairGetter().date;
8017
+ }
8018
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8019
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemListViewItemComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
8020
+ <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
8021
+ <div class="item-left">
8022
+ <span class="notificationitem-subject">{{ subject }}</span>
8023
+ <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
8024
+ <span class="notificationitem-date item-details-footnote">{{ date | date: 'medium' }}</span>
8025
+ </div>
8026
+ </div>
8027
+ `, isInline: true, dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: CutTextPipe, name: "cutText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8028
+ }
8029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemListViewItemComponent, decorators: [{
8030
+ type: Component,
8031
+ args: [{
8032
+ template: `
8033
+ <div class="dbx-list-item-padded dbx-list-two-line-item dbx-firebase-notificationitem-list-view-item" role="article" [attr.aria-label]="subject">
8034
+ <div class="item-left">
8035
+ <span class="notificationitem-subject">{{ subject }}</span>
8036
+ <span class="notificationitem-message item-details">{{ message | cutText: 90 }}</span>
8037
+ <span class="notificationitem-date item-details-footnote">{{ date | date: 'medium' }}</span>
8038
+ </div>
8039
+ </div>
8040
+ `,
8041
+ imports: [DatePipe, CutTextPipe],
8042
+ changeDetection: ChangeDetectionStrategy.OnPush,
8043
+ standalone: true
8044
+ }]
8045
+ }] });
8046
+
8047
+ /**
8048
+ * Presentational component for notification item content, displaying subject, message, and date.
8049
+ */
8050
+ class DbxFirebaseNotificationItemContentComponent {
8051
+ subject = input(...(ngDevMode ? [undefined, { debugName: "subject" }] : /* istanbul ignore next */ []));
8052
+ message = input(...(ngDevMode ? [undefined, { debugName: "message" }] : /* istanbul ignore next */ []));
8053
+ date = input(...(ngDevMode ? [undefined, { debugName: "date" }] : /* istanbul ignore next */ []));
8054
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8055
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationItemContentComponent, isStandalone: true, selector: "dbx-firebase-notificationitem-content", inputs: { subject: { classPropertyName: "subject", publicName: "subject", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8056
+ }
8057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationItemContentComponent, decorators: [{
8058
+ type: Component,
8059
+ args: [{ selector: 'dbx-firebase-notificationitem-content', imports: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"dbx-firebase-notificationitem-content\" role=\"article\" [attr.aria-label]=\"subject()\">\n <div class=\"notificationitem-header dbx-pb2\">\n @if (subject()) {\n <div class=\"notificationitem-subject dbx-pb1\">{{ subject() }}</div>\n }\n <ng-content select=\"[header]\"></ng-content>\n @if (date()) {\n <div class=\"notificationitem-date dbx-label dbx-small\">{{ date() | date: 'medium' }}</div>\n }\n </div>\n <ng-content select=\"[premessage]\"></ng-content>\n <div class=\"notificationitem-message\">{{ message() }}</div>\n <ng-content></ng-content>\n</div>\n", styles: [".dbx-firebase-notificationitem-content .notificationitem-subject{font-size:1.25em}\n"] }]
8060
+ }], propDecorators: { subject: [{ type: i0.Input, args: [{ isSignal: true, alias: "subject", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }] } });
8061
+
8062
+ /**
8063
+ * Prefix used by all widgets.
8064
+ *
8065
+ * Does not include the "-" between the prefix and the template type returned by dbxWidgetTypeForNotificationTemplateType().
8066
+ */
8067
+ const FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX = 'dbxFirebaseNotificationItem';
8068
+ const DEFAULT_FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE = `${FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX}Default`;
8069
+ /**
8070
+ * Derives the widget type string for a given notification template type by prepending the standard prefix.
8071
+ *
8072
+ * @param notificationTemplateType - The notification template type to generate a widget type for.
8073
+ * @returns The prefixed widget type string.
8074
+ */
8075
+ function dbxWidgetTypeForNotificationTemplateType(notificationTemplateType) {
8076
+ return `${FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX}-${notificationTemplateType}`;
8077
+ }
8078
+
8079
+ /**
8080
+ * Service used for registering widgets used for notification items.
8081
+ */
8082
+ class DbxFirebaseNotificationItemWidgetService {
8083
+ dbxWidgetService = inject(DbxWidgetService);
8084
+ dbxFirebaseNotificationTemplateService = inject(DbxFirebaseNotificationTemplateService);
8085
+ _entries = new Map();
8086
+ /**
8087
+ * Used to register a item widget. If widget for the given type is already registered, this will override it by default.
8088
+ *
8089
+ * @param provider - The notification item widget entry to register.
8090
+ * @param override - Whether to override an existing widget for the same notification template type. Defaults to true.
8091
+ * @returns True if the widget was registered, false if it already existed and override was false or the template type was unknown.
8092
+ */
8093
+ register(provider, override = true) {
8094
+ const { componentClass, notificationTemplateType } = provider;
8095
+ const widgetType = dbxWidgetTypeForNotificationTemplateType(notificationTemplateType);
8096
+ let result = false;
8097
+ if (override || !this._entries.has(notificationTemplateType)) {
8098
+ const notificationTemplateTypeInfo = this.dbxFirebaseNotificationTemplateService.appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[notificationTemplateType];
8099
+ if (notificationTemplateTypeInfo) {
8100
+ const entry = {
8101
+ notificationTemplateType,
6355
8102
  notificationTemplateTypeInfo,
6356
8103
  widget: {
6357
8104
  type: widgetType,
@@ -6460,6 +8207,529 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6460
8207
  }]
6461
8208
  }] });
6462
8209
 
8210
+ /**
8211
+ * Document store for a single NotificationUser with update, resync, and health check functions.
8212
+ */
8213
+ class NotificationUserDocumentStore extends AbstractDbxFirebaseDocumentStore {
8214
+ notificationFunctions = inject(NotificationFunctions);
8215
+ constructor() {
8216
+ super({ firestoreCollection: inject(NotificationFirestoreCollections).notificationUserCollection });
8217
+ }
8218
+ updateNotificationUser = firebaseDocumentStoreUpdateFunction(this, this.notificationFunctions.notificationUser.updateNotificationUser.update);
8219
+ resyncNotificationUser = firebaseDocumentStoreUpdateFunction(this, this.notificationFunctions.notificationUser.updateNotificationUser.resync);
8220
+ /**
8221
+ * Runs a delivery health check for this NotificationUser.
8222
+ *
8223
+ * Dispatching a real test message is opt-in via the `sendProbe` param, since it delivers actual
8224
+ * mail/SMS to the user.
8225
+ *
8226
+ * The result is only returned by the call, so use the DbxFirebaseNotificationUserHealthCheckStore
8227
+ * to run a check whose outcome is retained as state.
8228
+ */
8229
+ healthCheck = firebaseDocumentStoreInvokeFunction(this, this.notificationFunctions.notificationUser.invokeNotificationUser.healthCheck);
8230
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8231
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore });
8232
+ }
8233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore, decorators: [{
8234
+ type: Injectable
8235
+ }], ctorParameters: () => [] });
8236
+
8237
+ /**
8238
+ * Counts down the seconds until the input time, once per second.
8239
+ *
8240
+ * Emits 0 and stops ticking as soon as the time passes, so nothing keeps counting while the action it
8241
+ * gates is available. A null input means there is nothing to wait for.
8242
+ *
8243
+ * @param date$ - The time being waited for.
8244
+ * @returns The seconds remaining, counting down to 0.
8245
+ */
8246
+ function secondsRemainingUntil(date$) {
8247
+ return date$.pipe(switchMap((date) => date == null
8248
+ ? of(0)
8249
+ : timer(0, 1000).pipe(map(() => Math.max(0, Math.ceil((date.getTime() - Date.now()) / 1000))), takeWhile((secondsRemaining) => secondsRemaining > 0, true))), distinctUntilChanged(), shareReplay(1));
8250
+ }
8251
+ /**
8252
+ * Counts down the seconds until each delivery method's time, once per second.
8253
+ *
8254
+ * The per-method form of {@link secondsRemainingUntil}. One timer drives every method, so several open
8255
+ * countdowns tick in step rather than drifting apart, and the ticking stops once every window has
8256
+ * passed. A method with nothing to wait for is left out.
8257
+ *
8258
+ * @param datesByMethod$ - The time being waited for on each method.
8259
+ * @returns The seconds remaining per method, counting down to 0.
8260
+ */
8261
+ function secondsRemainingUntilByMethod(datesByMethod$) {
8262
+ return datesByMethod$.pipe(switchMap((datesByMethod) => {
8263
+ const pendingEntries = Object.entries(datesByMethod).filter(([, date]) => date != null);
8264
+ return pendingEntries.length === 0
8265
+ ? of({})
8266
+ : timer(0, 1000).pipe(map(() => {
8267
+ const secondsRemainingByMethod = {};
8268
+ pendingEntries.forEach(([method, date]) => {
8269
+ secondsRemainingByMethod[method] = Math.max(0, Math.ceil((date.getTime() - Date.now()) / 1000));
8270
+ });
8271
+ return secondsRemainingByMethod;
8272
+ }),
8273
+ // emit the all-zero value, then stop: every window has passed
8274
+ takeWhile((secondsRemainingByMethod) => Object.values(secondsRemainingByMethod).some((x) => x > 0), true));
8275
+ }), distinctUntilChanged(areEqualPOJOValues), shareReplay(1));
8276
+ }
8277
+ /**
8278
+ * Store for running a notification delivery health check against the NotificationUser of the
8279
+ * injected {@link NotificationUserDocumentStore}, and for retaining the result of that run.
8280
+ *
8281
+ * Exists separately from the document store because a run returns more than what is persisted:
8282
+ * `probesDispatched`/`probesResolved` are only available on the invocation result, and are only
8283
+ * relevant to the session that ran the check.
8284
+ */
8285
+ class DbxFirebaseNotificationUserHealthCheckStore extends ComponentStore {
8286
+ notificationUserDocumentStore = inject(NotificationUserDocumentStore);
8287
+ /**
8288
+ * The app's health check tuning, when it configured any.
8289
+ *
8290
+ * Optional: without it both windows fall back to the library defaults, which is correct for an app
8291
+ * that did not override them on the server either.
8292
+ */
8293
+ _config = inject(DbxFirebaseNotificationHealthCheckConfig, { optional: true });
8294
+ constructor() {
8295
+ super({});
8296
+ }
8297
+ // MARK: Accessors
8298
+ /**
8299
+ * Whether the NotificationUser being checked exists.
8300
+ *
8301
+ * There is nothing to check until notifications have been set up for the account.
8302
+ */
8303
+ exists$ = this.notificationUserDocumentStore.currentExists$;
8304
+ /**
8305
+ * The health check stored on the document.
8306
+ *
8307
+ * The check is persisted on every run, so the most recent one renders without invoking anything.
8308
+ */
8309
+ healthCheck$ = this.notificationUserDocumentStore.data$.pipe(map((x) => x.hc), distinctUntilChanged(), shareReplay(1));
8310
+ /**
8311
+ * The loading state of the most recent {@link runHealthCheck} dispatch.
8312
+ *
8313
+ * A dispatch sets this to a loading state synchronously, so a caller that dispatches and then
8314
+ * watches this observable is watching its own run.
8315
+ */
8316
+ healthCheckResultState$ = this.select((state) => state.healthCheckResultState).pipe(distinctUntilChanged(), shareReplay(1));
8317
+ /**
8318
+ * The full result of the most recent {@link runHealthCheck} run from this store.
8319
+ *
8320
+ * `probesDispatched`/`probesResolved` are not part of the document, so they are only available here
8321
+ * and only until the store is destroyed.
8322
+ */
8323
+ latestHealthCheckResult$ = this.healthCheckResultState$.pipe(map((x) => x?.value), distinctUntilChanged(), shareReplay(1));
8324
+ // MARK: Throttle
8325
+ /**
8326
+ * The earliest time another run is allowed, derived from the check stored on the document.
8327
+ *
8328
+ * Undefined until a check has been run.
8329
+ */
8330
+ nextRunAt$ = this.healthCheck$.pipe(map((healthCheck) => notificationUserHealthCheckNextRunAt({ healthCheck, throttleMinutes: this._config?.runThrottleMinutes })), distinctUntilChanged((a, b) => a?.getTime() === b?.getTime()), shareReplay(1));
8331
+ /**
8332
+ * The earliest time another test message may be dispatched through each delivery method.
8333
+ *
8334
+ * Per method, because the server's probe window is per method: each method has its own test message
8335
+ * action, and a test email must not hold the test text message off. Tracked separately from
8336
+ * {@link nextRunAt$} as well — running the check does not consume the test message allowance, so a
8337
+ * plain run must not disable any of the probe actions.
8338
+ */
8339
+ nextProbeAtByMethod$ = this.healthCheck$.pipe(map((healthCheck) => notificationUserHealthCheckNextProbeAtByMethod({ healthCheck, throttleMinutes: this._config?.probeThrottleMinutes })), distinctUntilChanged(areEqualPOJOValues), shareReplay(1));
8340
+ /**
8341
+ * How long until another run is allowed, counting down once per second.
8342
+ */
8343
+ throttleSecondsRemaining$ = secondsRemainingUntil(this.nextRunAt$);
8344
+ /**
8345
+ * How long until another test message may be dispatched through each delivery method, counting down
8346
+ * once per second.
8347
+ *
8348
+ * A method whose window has never opened — no probe has been dispatched through it — is absent, which
8349
+ * a consumer reads the same as 0.
8350
+ */
8351
+ probeThrottleSecondsRemainingByMethod$ = secondsRemainingUntilByMethod(this.nextProbeAtByMethod$);
8352
+ /**
8353
+ * Whether the server would reject a run right now.
8354
+ *
8355
+ * The server enforces the throttle; this is what lets the view disable the action instead of letting
8356
+ * the user trigger a call that comes back as an error.
8357
+ */
8358
+ isThrottled$ = this.throttleSecondsRemaining$.pipe(map((secondsRemaining) => secondsRemaining > 0), distinctUntilChanged(), shareReplay(1));
8359
+ // MARK: Pending Probes
8360
+ /**
8361
+ * The delivery methods whose test message is still waiting on an outcome.
8362
+ *
8363
+ * The one part of a stored check that changes without anyone re-running it, so it is what
8364
+ * {@link watchPendingProbes} watches.
8365
+ */
8366
+ pendingProbeMethods$ = this.healthCheck$.pipe(map((healthCheck) => notificationHealthCheckPendingProbeMethods(healthCheck)), distinctUntilChanged(areEqualPOJOValues), shareReplay(1));
8367
+ /**
8368
+ * Whether a test message is in flight right now.
8369
+ */
8370
+ hasPendingProbe$ = this.pendingProbeMethods$.pipe(map((x) => x.length > 0), distinctUntilChanged(), shareReplay(1));
8371
+ /**
8372
+ * The next verification to run, or null when there is nothing to watch.
8373
+ *
8374
+ * Re-derived from the document on every change, which is what paces the loop: each verification
8375
+ * advances the stored check's `vat`, so the moment the server will accept another poll moves forward
8376
+ * and this emits the next one. The watch gives up once the oldest probe has been in flight longer than
8377
+ * the configured window — a provider that is never going to record an outcome should not be polled for
8378
+ * as long as the page stays open.
8379
+ */
8380
+ _nextProbeVerification$ = this.healthCheck$.pipe(map((healthCheck) => {
8381
+ const methods = notificationHealthCheckPendingProbeMethods(healthCheck);
8382
+ const dispatchedAt = filterMaybeArrayValues((healthCheck?.m ?? []).filter((x) => methods.includes(x.me)).map((x) => x.pr?.at));
8383
+ const watchMinutes = this._config?.probeWatchMinutes ?? DEFAULT_NOTIFICATION_HEALTH_CHECK_PROBE_WATCH_MINUTES;
8384
+ const oldestDispatchedAt = dispatchedAt.length ? new Date(Math.min(...dispatchedAt.map((x) => x.getTime()))) : undefined;
8385
+ const at = notificationUserHealthCheckNextVerifyAt({ healthCheck, throttleSeconds: this._config?.verifyThrottleSeconds }) ?? new Date();
8386
+ const stillWorthWatching = oldestDispatchedAt != null && isAfter(addMinutes(oldestDispatchedAt, watchMinutes), at);
8387
+ return stillWorthWatching ? { methods, at } : undefined;
8388
+ }), distinctUntilChanged(areEqualPOJOValues), shareReplay(1));
8389
+ // MARK: State Changes
8390
+ _setHealthCheckResultState = this.updater((state, healthCheckResultState) => ({ ...state, healthCheckResultState }));
8391
+ // MARK: Effects
8392
+ /**
8393
+ * Runs the document store's health check for its NotificationUser and puts the outcome on
8394
+ * {@link healthCheckResultState$}.
8395
+ *
8396
+ * Dispatching a real test message is opt-in via the `sendProbe` param, since it delivers actual
8397
+ * mail/SMS to the user.
8398
+ *
8399
+ * While a run is in flight further dispatches are ignored, so the state always reflects the run
8400
+ * that is actually happening.
8401
+ */
8402
+ runHealthCheck = this.effect((input) => input.pipe(exhaustMap((params) => this.notificationUserDocumentStore.healthCheck(params ?? {}).pipe(startWithBeginLoading(), // emit loading synchronously so a dispatcher can pick this run up off the state
8403
+ catchError((error) => of(errorResult(error))), // an error here would otherwise kill this effect's subscription
8404
+ tap((healthCheckResultState) => this._setHealthCheckResultState(healthCheckResultState))))));
8405
+ /**
8406
+ * Watches every in-flight test message until it has an outcome, without the user doing anything.
8407
+ *
8408
+ * A dispatched probe settles on the delivery provider's schedule rather than the caller's, so
8409
+ * something has to ask again once the outcome exists. That used to be the user, told to come back and
8410
+ * re-run the check; this is that same poll, run automatically. Each verification is scoped to the
8411
+ * methods actually awaiting an outcome and answers to its own server-side window, so it neither sends
8412
+ * anything nor consumes the user's allowance for running the check itself. The result lands on the
8413
+ * NotificationUser, which is already streamed into {@link healthCheck$}, so a settled probe simply
8414
+ * appears in the report.
8415
+ *
8416
+ * Idempotent to start and safe to leave running: it does nothing at all while no probe is in flight,
8417
+ * and stops on its own once every probe has settled or aged out.
8418
+ */
8419
+ watchPendingProbes = this.effect((input) => input.pipe(switchMap(() => this._nextProbeVerification$.pipe(
8420
+ // The interval is the recovery path: a verification that advances `vat` re-emits the value
8421
+ // above and restarts this timer, while one that fails to reach the server leaves the value
8422
+ // unchanged and is simply retried on the next tick.
8423
+ switchMap((verification) => (verification == null ? EMPTY : timer(Math.max(0, verification.at.getTime() - Date.now()), (this._config?.verifyThrottleSeconds ?? DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLE_SECONDS) * MS_IN_SECOND).pipe(map(() => verification.methods)))), withLatestFrom(this.healthCheckResultState$),
8424
+ // A verification carries the whole stored check forward, so one racing a run the user started
8425
+ // could write back a copy taken before that run's result landed. The user's run always wins.
8426
+ filter(([, healthCheckResultState]) => healthCheckResultState?.loading !== true), exhaustMap(([methods]) => this.notificationUserDocumentStore.healthCheck({ verifyPendingProbesOnly: true, methods }).pipe(catchError(() => EMPTY) // nothing to report: the poll is invisible, and the next tick retries
8427
+ ))))));
8428
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationUserHealthCheckStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8429
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationUserHealthCheckStore });
8430
+ }
8431
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationUserHealthCheckStore, decorators: [{
8432
+ type: Injectable
8433
+ }], ctorParameters: () => [] });
8434
+
8435
+ /**
8436
+ * Formats a throttle countdown for display.
8437
+ *
8438
+ * @param secondsRemaining - Seconds left in the window.
8439
+ * @returns A short duration, e.g. `9m 12s` or `43s`.
8440
+ */
8441
+ function formatSecondsRemaining(secondsRemaining) {
8442
+ const minutes = Math.floor(secondsRemaining / 60);
8443
+ const minutesPrefix = minutes > 0 ? `${minutes}m ` : '';
8444
+ return `${minutesPrefix}${secondsRemaining % 60}s`;
8445
+ }
8446
+ /**
8447
+ * Renders the notification delivery health check for the ancestor NotificationUserDocumentStore, and
8448
+ * the actions for re-running it.
8449
+ *
8450
+ * The check is persisted on the NotificationUser, so the last one renders immediately without invoking
8451
+ * anything. "Run Check" re-runs it and stays a single global action, since a run only reads
8452
+ * configuration and provider state. Sending a test message is per delivery method instead — it
8453
+ * dispatches a real message through that one method, and answers to that method's own throttle window —
8454
+ * so its action lives in that method's section rather than in a footer.
8455
+ *
8456
+ * A dispatched test message settles later, on the delivery provider's schedule. This view watches for
8457
+ * that outcome itself rather than telling the user to come back and re-run the check: the store polls a
8458
+ * cheap server-side verification while anything is in flight, and the report — read live from the
8459
+ * document — updates the moment the result lands.
8460
+ */
8461
+ class DbxFirebaseNotificationHealthCheckViewComponent {
8462
+ _presentationService = inject(DbxFirebaseNotificationHealthCheckPresentationService);
8463
+ healthCheckStore = inject(DbxFirebaseNotificationUserHealthCheckStore);
8464
+ existsSignal = toSignal(this.healthCheckStore.exists$, { initialValue: false });
8465
+ healthCheckSignal = toSignal(this.healthCheckStore.healthCheck$);
8466
+ latestHealthCheckResultSignal = toSignal(this.healthCheckStore.latestHealthCheckResult$);
8467
+ throttleSecondsRemainingSignal = toSignal(this.healthCheckStore.throttleSecondsRemaining$, { initialValue: 0 });
8468
+ isThrottledSignal = toSignal(this.healthCheckStore.isThrottled$, { initialValue: false });
8469
+ probeThrottleSecondsRemainingByMethodSignal = toSignal(this.healthCheckStore.probeThrottleSecondsRemainingByMethod$, { initialValue: {} });
8470
+ _lastRequestedProbeMethod = signal(undefined, ...(ngDevMode ? [{ debugName: "_lastRequestedProbeMethod" }] : /* istanbul ignore next */ []));
8471
+ constructor() {
8472
+ // The report is streamed from the document, so an outcome the server settles shows up on its own.
8473
+ // This is what makes the server settle it without the user asking.
8474
+ this.healthCheckStore.watchPendingProbes();
8475
+ }
8476
+ /**
8477
+ * The delivery method whose test message was most recently requested from this view.
8478
+ *
8479
+ * Recorded because the invocation result reports only how many probes it dispatched, so this is what
8480
+ * lets the notice name what was sent.
8481
+ */
8482
+ lastRequestedProbeMethodSignal = this._lastRequestedProbeMethod.asReadonly();
8483
+ /**
8484
+ * The test message handler for each delivery method, built once up front.
8485
+ *
8486
+ * A per-method countdown rebuilds that method's action config every second, and `dbxActionHandler`
8487
+ * installs its handler in an effect, so handing over a fresh closure on each tick would re-install it
8488
+ * once a second for no reason.
8489
+ */
8490
+ _sendTestMessageHandlers = new Map(ALL_NOTIFICATION_DELIVERY_METHODS.map((method) => [
8491
+ method,
8492
+ (_, context) => {
8493
+ this._lastRequestedProbeMethod.set(method);
8494
+ this.runHealthCheckAsWork(context, { sendProbe: true, methods: [method] });
8495
+ }
8496
+ ]));
8497
+ /**
8498
+ * Explains why "Run Check" is disabled while the server's run throttle is in effect.
8499
+ */
8500
+ throttleNoticeSignal = computed(() => {
8501
+ const secondsRemaining = this.throttleSecondsRemainingSignal();
8502
+ return secondsRemaining > 0 ? `This was checked a moment ago. Another check can be run in ${formatSecondsRemaining(secondsRemaining)}.` : undefined;
8503
+ }, ...(ngDevMode ? [{ debugName: "throttleNoticeSignal" }] : /* istanbul ignore next */ []));
8504
+ /**
8505
+ * The test message action for each delivery method the check reports as probe-capable.
8506
+ *
8507
+ * A method that cannot be probed gets no entry, so no test message is offered where the server has no
8508
+ * way to send one.
8509
+ *
8510
+ * Everything the user needs to read about a test message belongs in the section holding the button
8511
+ * that sent it, so the notice is composed here rather than in a footer: what was just sent, that its
8512
+ * outcome is being watched for, and when another may be sent.
8513
+ */
8514
+ probeActionsSignal = computed(() => {
8515
+ const secondsRemainingByMethod = this.probeThrottleSecondsRemainingByMethodSignal();
8516
+ const justSentMethod = this.latestHealthCheckResultSignal()?.probesDispatched ? this.lastRequestedProbeMethodSignal() : undefined;
8517
+ const probeActions = {};
8518
+ (this.healthCheckSignal()?.m ?? [])
8519
+ .filter((methodResult) => methodResult.pb === true)
8520
+ .forEach(({ me: method, tg: target, pr: probe }) => {
8521
+ const label = this._presentationService.testMessageLabelForDeliveryMethod(method);
8522
+ const noun = this._presentationService.testMessageNounForDeliveryMethod(method);
8523
+ const methodLabel = this._presentationService.labelForDeliveryMethod(method).toLowerCase();
8524
+ const secondsRemaining = secondsRemainingByMethod[method] ?? 0;
8525
+ probeActions[method] = {
8526
+ label,
8527
+ icon: 'send',
8528
+ disabled: secondsRemaining > 0,
8529
+ notice: this.probeNoticeForMethod({ noun, secondsRemaining, justSent: method === justSentMethod, pending: probe?.s === NotificationHealthCheckStatus.PENDING }),
8530
+ confirm: {
8531
+ title: label,
8532
+ prompt: `This sends a real ${methodLabel} to ${target ?? 'your contact details'} so we can confirm whether it arrives. Continue?`,
8533
+ confirmText: label
8534
+ },
8535
+ handler: this._sendTestMessageHandlers.get(method)
8536
+ };
8537
+ });
8538
+ return probeActions;
8539
+ }, ...(ngDevMode ? [{ debugName: "probeActionsSignal" }] : /* istanbul ignore next */ []));
8540
+ /**
8541
+ * Composes one delivery method's test message notice.
8542
+ *
8543
+ * Deliberately never tells the user to come back and check: a pending outcome is watched for
8544
+ * automatically, so the only thing left to say about the wait is that they need do nothing.
8545
+ *
8546
+ * @param input - What the method sends, how long its window has left, and the state of its probe.
8547
+ * @returns The notice, or undefined when there is nothing worth saying.
8548
+ */
8549
+ probeNoticeForMethod(input) {
8550
+ const { noun, secondsRemaining, justSent, pending } = input;
8551
+ const sent = justSent ? `A ${noun} was just sent.` : secondsRemaining > 0 ? `A ${noun} was sent recently.` : undefined;
8552
+ const waiting = pending ? ' The result will appear here on its own as soon as we hear whether it arrived.' : '';
8553
+ const nextAllowed = secondsRemaining > 0 ? ` Another can be sent in ${formatSecondsRemaining(secondsRemaining)}.` : '';
8554
+ return sent == null ? undefined : `${sent}${waiting}${nextAllowed}`;
8555
+ }
8556
+ handleRunHealthCheck = (_, context) => {
8557
+ this.runHealthCheckAsWork(context, {});
8558
+ };
8559
+ /**
8560
+ * Dispatches a run to the store, then tracks that run's loading state as the action's work.
8561
+ *
8562
+ * The store's effect sets the loading state synchronously as it is dispatched, so handing the state
8563
+ * observable over afterwards tracks this run instead of completing on the previous run's result.
8564
+ *
8565
+ * @param context - The work instance of the action being handled.
8566
+ * @param params - The params for this run, such as which methods to send a test message through.
8567
+ */
8568
+ runHealthCheckAsWork(context, params) {
8569
+ this.healthCheckStore.runHealthCheck(params);
8570
+ context.startWorkingWithLoadingStateObservable(this.healthCheckStore.healthCheckResultState$);
8571
+ }
8572
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8573
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckViewComponent, isStandalone: true, selector: "dbx-firebase-notification-healthcheck-view", host: { classAttribute: "d-block dbx-firebase-notification-healthcheck-view" }, providers: [DbxFirebaseNotificationUserHealthCheckStore], ngImport: i0, template: `
8574
+ @if (existsSignal()) {
8575
+ @if (healthCheckSignal(); as healthCheck) {
8576
+ <dbx-firebase-notification-healthcheck [healthCheck]="healthCheck" [probeActions]="probeActionsSignal()"></dbx-firebase-notification-healthcheck>
8577
+ } @else {
8578
+ <dbx-content-pit class="dbx-mb3">
8579
+ <p class="dbx-hint no-margin">Your notification delivery has not been checked yet. Run a check to find out why you may not be receiving notifications.</p>
8580
+ </dbx-content-pit>
8581
+ }
8582
+
8583
+ <div class="dbx-flex-bar">
8584
+ <!-- the action takes no input, so the bare dbxActionValue marks it value-ready on trigger -->
8585
+ <!-- dbxActionDisabled rather than the button's own disabled input, since dbxActionButton drives that from the action's state -->
8586
+ <div dbxAction dbxActionValue [dbxActionDisabled]="isThrottledSignal()" [dbxActionHandler]="handleRunHealthCheck">
8587
+ <dbx-button dbxActionButton [stroked]="true" text="Run Check" icon="refresh"></dbx-button>
8588
+ <dbx-error dbxActionError></dbx-error>
8589
+ </div>
8590
+ </div>
8591
+ <div>
8592
+ @if (throttleNoticeSignal(); as throttleNotice) {
8593
+ <div class="dbx-small dbx-hint dbx-pt2">{{ throttleNotice }}</div>
8594
+ }
8595
+ </div>
8596
+ } @else {
8597
+ <dbx-content-pit>
8598
+ <p class="dbx-hint no-margin">Notification settings have not been set up for this account yet, so there is nothing to check.</p>
8599
+ </dbx-content-pit>
8600
+ }
8601
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionDisabledDirective, selector: "[dbxActionDisabled]", inputs: ["dbxActionDisabled"] }, { kind: "directive", type: DbxActionErrorDirective, selector: "[dbxActionError]" }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxContentPitDirective, selector: "dbx-content-pit, [dbxContentPit]", inputs: ["scrollable", "square"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxFirebaseNotificationHealthCheckComponent, selector: "dbx-firebase-notification-healthcheck", inputs: ["healthCheck", "probeActions"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8602
+ }
8603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckViewComponent, decorators: [{
8604
+ type: Component,
8605
+ args: [{
8606
+ selector: 'dbx-firebase-notification-healthcheck-view',
8607
+ template: `
8608
+ @if (existsSignal()) {
8609
+ @if (healthCheckSignal(); as healthCheck) {
8610
+ <dbx-firebase-notification-healthcheck [healthCheck]="healthCheck" [probeActions]="probeActionsSignal()"></dbx-firebase-notification-healthcheck>
8611
+ } @else {
8612
+ <dbx-content-pit class="dbx-mb3">
8613
+ <p class="dbx-hint no-margin">Your notification delivery has not been checked yet. Run a check to find out why you may not be receiving notifications.</p>
8614
+ </dbx-content-pit>
8615
+ }
8616
+
8617
+ <div class="dbx-flex-bar">
8618
+ <!-- the action takes no input, so the bare dbxActionValue marks it value-ready on trigger -->
8619
+ <!-- dbxActionDisabled rather than the button's own disabled input, since dbxActionButton drives that from the action's state -->
8620
+ <div dbxAction dbxActionValue [dbxActionDisabled]="isThrottledSignal()" [dbxActionHandler]="handleRunHealthCheck">
8621
+ <dbx-button dbxActionButton [stroked]="true" text="Run Check" icon="refresh"></dbx-button>
8622
+ <dbx-error dbxActionError></dbx-error>
8623
+ </div>
8624
+ </div>
8625
+ <div>
8626
+ @if (throttleNoticeSignal(); as throttleNotice) {
8627
+ <div class="dbx-small dbx-hint dbx-pt2">{{ throttleNotice }}</div>
8628
+ }
8629
+ </div>
8630
+ } @else {
8631
+ <dbx-content-pit>
8632
+ <p class="dbx-hint no-margin">Notification settings have not been set up for this account yet, so there is nothing to check.</p>
8633
+ </dbx-content-pit>
8634
+ }
8635
+ `,
8636
+ host: {
8637
+ class: 'd-block dbx-firebase-notification-healthcheck-view'
8638
+ },
8639
+ standalone: true,
8640
+ imports: [DbxActionButtonDirective, DbxActionDirective, DbxActionDisabledDirective, DbxActionErrorDirective, DbxActionHandlerDirective, DbxActionValueDirective, DbxButtonComponent, DbxContentPitDirective, DbxErrorComponent, DbxFirebaseNotificationHealthCheckComponent],
8641
+ providers: [DbxFirebaseNotificationUserHealthCheckStore],
8642
+ changeDetection: ChangeDetectionStrategy.OnPush
8643
+ }]
8644
+ }], ctorParameters: () => [] });
8645
+
8646
+ /**
8647
+ * Dialog wrapper around {@link DbxFirebaseNotificationHealthCheckViewComponent}.
8648
+ */
8649
+ class DbxFirebaseNotificationHealthCheckDialogComponent extends AbstractDialogDirective {
8650
+ /**
8651
+ * Opens the notification delivery health check in a dialog.
8652
+ *
8653
+ * @param matDialog - The dialog service.
8654
+ * @param config - The injector to resolve the NotificationUserDocumentStore from.
8655
+ * @returns The dialog reference.
8656
+ */
8657
+ static openDialog(matDialog, config) {
8658
+ return matDialog.open(DbxFirebaseNotificationHealthCheckDialogComponent, {
8659
+ // a report with several delivery methods can exceed the viewport. Material inherits the pane's
8660
+ // max-height down to the dialog surface, which already scrolls, so capping it here keeps the
8661
+ // actions reachable. A percentage cap would not work, as the pane's height is not definite.
8662
+ maxHeight: 'calc(var(--vh100) * 0.9)',
8663
+ injector: config?.injector
8664
+ });
8665
+ }
8666
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckDialogComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8667
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckDialogComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
8668
+ <dbx-dialog-content>
8669
+ <dbx-firebase-notification-healthcheck-view></dbx-firebase-notification-healthcheck-view>
8670
+ </dbx-dialog-content>
8671
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxDialogContentDirective, selector: "dbx-dialog-content,[dbxDialogContent],.dbx-dialog-content", inputs: ["width"] }, { kind: "component", type: DbxFirebaseNotificationHealthCheckViewComponent, selector: "dbx-firebase-notification-healthcheck-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8672
+ }
8673
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckDialogComponent, decorators: [{
8674
+ type: Component,
8675
+ args: [{
8676
+ template: `
8677
+ <dbx-dialog-content>
8678
+ <dbx-firebase-notification-healthcheck-view></dbx-firebase-notification-healthcheck-view>
8679
+ </dbx-dialog-content>
8680
+ `,
8681
+ standalone: true,
8682
+ imports: [DbxDialogContentDirective, DbxFirebaseNotificationHealthCheckViewComponent],
8683
+ changeDetection: ChangeDetectionStrategy.OnPush
8684
+ }]
8685
+ }] });
8686
+
8687
+ /**
8688
+ * Button that opens the notification delivery health check in a dialog.
8689
+ *
8690
+ * Place it inside a `dbxFirebaseNotificationUserDocument` so the dialog's view component can resolve
8691
+ * that directive's NotificationUserDocumentStore.
8692
+ */
8693
+ class DbxFirebaseNotificationHealthCheckDialogButtonComponent {
8694
+ _matDialog = inject(MatDialog);
8695
+ _injector = inject(Injector);
8696
+ config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
8697
+ text = input(...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
8698
+ icon = input(...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
8699
+ textSignal = computed(() => {
8700
+ const config = this.config();
8701
+ return this.text() ?? config?.text ?? 'Check Notification Delivery';
8702
+ }, ...(ngDevMode ? [{ debugName: "textSignal" }] : /* istanbul ignore next */ []));
8703
+ iconSignal = computed(() => {
8704
+ const config = this.config();
8705
+ return this.icon() ?? config?.icon ?? 'monitor_heart';
8706
+ }, ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
8707
+ openHealthCheckDialog() {
8708
+ // the dialog renders outside this component's view, so its injector has to be handed over for the
8709
+ // NotificationUserDocumentStore to resolve inside it
8710
+ DbxFirebaseNotificationHealthCheckDialogComponent.openDialog(this._matDialog, { injector: this._injector });
8711
+ }
8712
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckDialogButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8713
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", type: DbxFirebaseNotificationHealthCheckDialogButtonComponent, isStandalone: true, selector: "dbx-firebase-notification-healthcheck-dialog-button", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "dbx-firebase-notification-healthcheck-dialog-button" }, ngImport: i0, template: `
8714
+ <dbx-button [stroked]="true" [text]="textSignal()" [icon]="iconSignal()" (buttonClick)="openHealthCheckDialog()"></dbx-button>
8715
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8716
+ }
8717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseNotificationHealthCheckDialogButtonComponent, decorators: [{
8718
+ type: Component,
8719
+ args: [{
8720
+ selector: 'dbx-firebase-notification-healthcheck-dialog-button',
8721
+ template: `
8722
+ <dbx-button [stroked]="true" [text]="textSignal()" [icon]="iconSignal()" (buttonClick)="openHealthCheckDialog()"></dbx-button>
8723
+ `,
8724
+ host: {
8725
+ class: 'dbx-firebase-notification-healthcheck-dialog-button'
8726
+ },
8727
+ standalone: true,
8728
+ imports: [DbxButtonComponent],
8729
+ changeDetection: ChangeDetectionStrategy.OnPush
8730
+ }]
8731
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
8732
+
6463
8733
  const DEFAULT_DBX_FIREBASE_NOTIFICATION_ITEM_STORE_POPOVER_KEY = 'notification-item-store-notifications';
6464
8734
  class DbxFirebaseNotificationItemStorePopoverComponent extends AbstractPopoverDirective {
6465
8735
  static openPopover(popupService, { origin, header, icon, emptyText, makeNotificationItemAnchor, notificationItemsLoadingState$ }, popoverKey) {
@@ -6825,23 +9095,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6825
9095
  }]
6826
9096
  }], ctorParameters: () => [] });
6827
9097
 
6828
- /**
6829
- * Document store for a single NotificationUser with update and resync functions.
6830
- */
6831
- class NotificationUserDocumentStore extends AbstractDbxFirebaseDocumentStore {
6832
- notificationFunctions = inject(NotificationFunctions);
6833
- constructor() {
6834
- super({ firestoreCollection: inject(NotificationFirestoreCollections).notificationUserCollection });
6835
- }
6836
- updateNotificationUser = firebaseDocumentStoreUpdateFunction(this, this.notificationFunctions.notificationUser.updateNotificationUser.update);
6837
- resyncNotificationUser = firebaseDocumentStoreUpdateFunction(this, this.notificationFunctions.notificationUser.updateNotificationUser.resync);
6838
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6839
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore });
6840
- }
6841
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: NotificationUserDocumentStore, decorators: [{
6842
- type: Injectable
6843
- }], ctorParameters: () => [] });
6844
-
6845
9098
  /**
6846
9099
  * Directive providing a {@link NotificationUserDocumentStore} for accessing a single notification user document.
6847
9100
  */
@@ -6868,7 +9121,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6868
9121
  * @returns EnvironmentProviders.
6869
9122
  */
6870
9123
  function provideDbxFirebaseNotifications(config) {
6871
- const { appNotificationTemplateTypeInfoRecordService } = config;
9124
+ const { appNotificationTemplateTypeInfoRecordService, healthCheckPresentation, healthCheck } = config;
6872
9125
  const providers = [
6873
9126
  {
6874
9127
  provide: AppNotificationTemplateTypeInfoRecordService,
@@ -6894,6 +9147,18 @@ function provideDbxFirebaseNotifications(config) {
6894
9147
  }
6895
9148
  })
6896
9149
  ];
9150
+ if (healthCheckPresentation) {
9151
+ providers.push({
9152
+ provide: DbxFirebaseNotificationHealthCheckPresentationServiceConfig,
9153
+ useValue: healthCheckPresentation
9154
+ });
9155
+ }
9156
+ if (healthCheck) {
9157
+ providers.push({
9158
+ provide: DbxFirebaseNotificationHealthCheckConfig,
9159
+ useValue: healthCheck
9160
+ });
9161
+ }
6897
9162
  return makeEnvironmentProviders(providers);
6898
9163
  }
6899
9164
 
@@ -7252,7 +9517,7 @@ class DbxFirebaseStorageFileDownloadButtonComponent {
7252
9517
  if (downloadUrl || downloadUrl === null) {
7253
9518
  this.downloadUrlSignal.set(downloadUrl);
7254
9519
  }
7255
- }, { ...(ngDevMode ? { debugName: "storageFileDownloadUrlEffect" } : /* istanbul ignore next */ {}), allowSignalWrites: true });
9520
+ }, ...(ngDevMode ? [{ debugName: "storageFileDownloadUrlEffect" }] : /* istanbul ignore next */ []));
7256
9521
  // Preview
7257
9522
  showPreviewButtonSignal = computed(() => {
7258
9523
  const config = this.configSignal();
@@ -7286,7 +9551,7 @@ class DbxFirebaseStorageFileDownloadButtonComponent {
7286
9551
  this.downloadMimeTypeSignal.set(cachedPair.mimeType);
7287
9552
  this.downloadUrlExpiresAtSignal.set(cachedPair.expiresAt);
7288
9553
  }
7289
- }, { ...(ngDevMode ? { debugName: "cachedUrlEffect" } : /* istanbul ignore next */ {}), allowSignalWrites: true });
9554
+ }, ...(ngDevMode ? [{ debugName: "cachedUrlEffect" }] : /* istanbul ignore next */ []));
7290
9555
  // Expiration Effect
7291
9556
  downloadUrlExpiresAtDate$ = toObservable(this.downloadUrlExpiresAtSignal).pipe(map(dateFromDateOrTimeSecondsNumber), distinctUntilChanged(isSameDate), shareReplay(1));
7292
9557
  downloadUrlExpiresAtDateSignal = toSignal(this.downloadUrlExpiresAtDate$);
@@ -7308,7 +9573,7 @@ class DbxFirebaseStorageFileDownloadButtonComponent {
7308
9573
  this.downloadMimeTypeSignal.set(undefined);
7309
9574
  this.downloadUrlExpiresAtSignal.set(undefined);
7310
9575
  }
7311
- }, { ...(ngDevMode ? { debugName: "downloadUrlHasExpiredEffect" } : /* istanbul ignore next */ {}), allowSignalWrites: true });
9576
+ }, ...(ngDevMode ? [{ debugName: "downloadUrlHasExpiredEffect" }] : /* istanbul ignore next */ []));
7312
9577
  // Output Effect
7313
9578
  downloadDetailsSignal = computed(() => {
7314
9579
  const downloadUrl = this.downloadUrlSignal();
@@ -7333,7 +9598,7 @@ class DbxFirebaseStorageFileDownloadButtonComponent {
7333
9598
  if (source?.downloadDetailsChangeCallback) {
7334
9599
  source.downloadDetailsChangeCallback(details);
7335
9600
  }
7336
- }, { ...(ngDevMode ? { debugName: "sourceDownloadDetailsChangeCallbackEffect" } : /* istanbul ignore next */ {}), allowSignalWrites: true });
9601
+ }, ...(ngDevMode ? [{ debugName: "sourceDownloadDetailsChangeCallbackEffect" }] : /* istanbul ignore next */ []));
7337
9602
  // Handlers
7338
9603
  handleGetDownloadUrl = (value, context) => {
7339
9604
  const source = this.source();
@@ -8495,5 +10760,5 @@ function provideDbxFirebase(config) {
8495
10760
  * Generated bundle index. Do not edit.
8496
10761
  */
8497
10762
 
8498
- export { AbstractConfiguredDbxFirebaseLoginButtonDirective, AbstractDbxFirebaseCollectionStore, AbstractDbxFirebaseCollectionWithParentStore, AbstractDbxFirebaseDocumentStore, AbstractDbxFirebaseDocumentWithParentStore, AbstractDbxFirebaseModelEntityWidgetDirective, AbstractDbxFirebaseNotificationItemWidgetComponent, AbstractRootSingleItemDbxFirebaseDocument, AbstractSingleItemDbxFirebaseDocument, AbstractSystemStateDocumentStoreAccessor, DBX_CONFIGURED_DBX_FIREBASE_LOGIN_BUTTON_COMPONENT_CONFIGURATION, DBX_FIREBASE_APP_OPTIONS_TOKEN, DBX_FIREBASE_AUTH_FLOW_TOKEN, DBX_FIREBASE_DOCUMENT_STORE_CONTEXT_STORE_TOKEN, DBX_FIREBASE_MODEL_DOES_NOT_EXIST_ERROR, DBX_FIREBASE_MODEL_ENTITY_WITH_STORE_TOKEN, DBX_FIREBASE_STORAGEFILE_DOWNLOAD_STORAGE_ACCESSOR_TOKEN, DBX_FIREBASE_STORAGE_CONTEXT_CONFIG_TOKEN, DBX_FIREBASE_STORAGE_CONTEXT_TOKEN, DBX_FIREBASE_STORAGE_PDF_MERGE_UPLOAD_FILE_NAME, DBX_FIRESTORE_CONTEXT_TOKEN, DEFAULT_CONFIGURED_DBX_FIREBASE_LOGIN_BUTTON_TEMPLATE, DEFAULT_DBX_FIREBASE_ANALYTICS_USER_PROPERTIES_FACTORY, DEFAULT_DBX_FIREBASE_AUTH_FLOW, DEFAULT_DBX_FIREBASE_AUTH_SERVICE_DELEGATE, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_ID_PARAM_KEY, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_KEY_PARAM_KEY, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_USE_PARAM_VALUE, DEFAULT_DBX_FIREBASE_MODEL_ENTITIES_COMPONENT_POPOVER_KEY, DEFAULT_DBX_FIREBASE_MODEL_HISTORY_COMPONENT_POPOVER_KEY, DEFAULT_DBX_FIREBASE_NOTIFICATION_ITEM_STORE_POPOVER_KEY, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN, DEFAULT_FIREBASE_COLLECTION_CHANGE_TRIGGER_FUNCTION, DEFAULT_FIREBASE_DEVELOPMENT_ENABLED_TOKEN, DEFAULT_FIREBASE_DEVELOPMENT_POPUP_KEY, DEFAULT_FIREBASE_DEVELOPMENT_SCHEDULER_ENABLED_TOKEN, DEFAULT_FIREBASE_DEVELOPMENT_WIDGET_PROVIDERS_TOKEN, DEFAULT_FIREBASE_LOGIN_METHOD_CATEGORY, DEFAULT_FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE, DEVELOPMENT_FIREBASE_SERVER_SCHEDULER_WIDGET_KEY, DbxFirebaseAnalyticsUserEventsListenerService, DbxFirebaseAnalyticsUserSource, DbxFirebaseAppCheckHttpInterceptor, DbxFirebaseAuthImpersonationDelegate, DbxFirebaseAuthLoginService, DbxFirebaseAuthService, DbxFirebaseAuthServiceDelegate, DbxFirebaseCollectionChangeDirective, DbxFirebaseCollectionChangeTriggerInstance, DbxFirebaseCollectionHasChangeDirective, DbxFirebaseCollectionListDirective, DbxFirebaseCollectionLoaderInstance, DbxFirebaseCollectionStoreDirective, DbxFirebaseCollectionWithParentStoreDirective, DbxFirebaseDevelopmentDirective, DbxFirebaseDevelopmentModule, DbxFirebaseDevelopmentPopupComponent, DbxFirebaseDevelopmentPopupContentComponent, DbxFirebaseDevelopmentPopupContentForgeFormComponent, DbxFirebaseDevelopmentSchedulerListComponent, DbxFirebaseDevelopmentSchedulerListViewComponent, DbxFirebaseDevelopmentSchedulerListViewItemComponent, DbxFirebaseDevelopmentSchedulerService, DbxFirebaseDevelopmentSchedulerWidgetComponent, DbxFirebaseDevelopmentService, DbxFirebaseDevelopmentWidgetService, DbxFirebaseDocumentLoaderInstance, DbxFirebaseDocumentStoreContextModelEntitiesSourceDirective, DbxFirebaseDocumentStoreContextStore, DbxFirebaseDocumentStoreContextStoreDirective, DbxFirebaseDocumentStoreDirective, DbxFirebaseDocumentStoreIdFromTwoWayModelKeyDirective, DbxFirebaseDocumentStoreTwoWayKeyProvider, DbxFirebaseDocumentStoreTwoWayModelKeySourceDirective, DbxFirebaseEmailForgeFormComponent, DbxFirebaseEmailRecoveryForgeFormComponent, DbxFirebaseEmulatorService, DbxFirebaseLoginAnonymousComponent, DbxFirebaseLoginAppleComponent, DbxFirebaseLoginButtonComponent, DbxFirebaseLoginButtonContainerComponent, DbxFirebaseLoginComponent, DbxFirebaseLoginContext, DbxFirebaseLoginContextBackButtonComponent, DbxFirebaseLoginContextDirective, DbxFirebaseLoginEmailComponent, DbxFirebaseLoginEmailContentComponent, DbxFirebaseLoginFacebookComponent, DbxFirebaseLoginGitHubComponent, DbxFirebaseLoginGoogleComponent, DbxFirebaseLoginListComponent, DbxFirebaseLoginMicrosoftComponent, DbxFirebaseLoginTermsComponent, DbxFirebaseLoginTermsSimpleComponent, DbxFirebaseLoginTwitterComponent, DbxFirebaseManageAuthProvidersComponent, DbxFirebaseModelContextService, DbxFirebaseModelEntitiesComponent, DbxFirebaseModelEntitiesDebugWidgetComponent, DbxFirebaseModelEntitiesPopoverButtonComponent, DbxFirebaseModelEntitiesPopoverComponent, DbxFirebaseModelEntitiesSource, DbxFirebaseModelEntitiesWidgetService, DbxFirebaseModelEntitiesWidgetServiceConfig, DbxFirebaseModelHistoryComponent, DbxFirebaseModelHistoryPopoverButtonComponent, DbxFirebaseModelHistoryPopoverComponent, DbxFirebaseModelKeyComponent, DbxFirebaseModelModule, DbxFirebaseModelStoreModule, DbxFirebaseModelTrackerService, DbxFirebaseModelTypeInstanceListComponent, DbxFirebaseModelTypeInstanceListViewComponent, DbxFirebaseModelTypeInstanceListViewItemComponent, DbxFirebaseModelTypesService, DbxFirebaseModelTypesServiceConfig, DbxFirebaseModelViewedEventDirective, DbxFirebaseModule, DbxFirebaseNotificationBoxCollectionStoreDirective, DbxFirebaseNotificationBoxDocumentStoreDirective, DbxFirebaseNotificationCollectionStoreDirective, DbxFirebaseNotificationDocumentStoreDirective, DbxFirebaseNotificationItemContentComponent, DbxFirebaseNotificationItemDefaultViewComponent, DbxFirebaseNotificationItemListComponent, DbxFirebaseNotificationItemListViewComponent, DbxFirebaseNotificationItemListViewItemComponent, DbxFirebaseNotificationItemStore, DbxFirebaseNotificationItemStorePopoverButtonComponent, DbxFirebaseNotificationItemStorePopoverComponent, DbxFirebaseNotificationItemViewComponent, DbxFirebaseNotificationItemWidgetService, DbxFirebaseNotificationSummaryCollectionStoreDirective, DbxFirebaseNotificationSummaryDocumentStoreDirective, DbxFirebaseNotificationTemplateService, DbxFirebaseNotificationUserCollectionStoreDirective, DbxFirebaseNotificationUserDocumentStoreDirective, DbxFirebaseParsedEmulatorsConfig, DbxFirebasePasswordResetComponent, DbxFirebasePasswordResetFormComponent, DbxFirebaseRegisterComponent, DbxFirebaseRegisterEmailComponent, DbxFirebaseStorageFileCollectionStoreDirective, DbxFirebaseStorageFileDocumentStoreDirective, DbxFirebaseStorageFileDownloadButtonComponent, DbxFirebaseStorageFileDownloadService, DbxFirebaseStorageFileDownloadStorage, DbxFirebaseStorageFileGroupDocumentStoreDirective, DbxFirebaseStorageFileUploadActionHandlerDirective, DbxFirebaseStorageFileUploadInitializeDocumentDirective, DbxFirebaseStorageFileUploadModule, DbxFirebaseStorageFileUploadStore, DbxFirebaseStorageFileUploadStoreDirective, DbxFirebaseStorageFileUploadSyncDirective, DbxFirebaseStoragePdfMergeUploadSyncDirective, DbxFirebaseStorageService, DbxFirebaseSystemStateCollectionStoreDirective, DbxFirebaseSystemStateDocumentStoreDirective, DbxFirestoreContextService, DbxLimitedFirebaseDocumentLoaderInstance, FIREBASE_APP_CHECK_TOKEN, FIREBASE_APP_TOKEN, FIREBASE_AUTH_TOKEN, FIREBASE_FIRESTORE_TOKEN, FIREBASE_FUNCTIONS_TOKEN, FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX, FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP, FIREBASE_STORAGE_TOKEN, FlatFirestoreModelKeyPipe, IMPORTS_AND_EXPORTS, LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP, NotificationBoxCollectionStore, NotificationBoxDocumentStore, NotificationCollectionStore, NotificationDocumentStore, NotificationSummaryCollectionStore, NotificationSummaryDocumentStore, NotificationUserCollectionStore, NotificationUserDocumentStore, OAUTH_FIREBASE_LOGIN_METHOD_CATEGORY, StorageFileCollectionStore, StorageFileDocumentStore, StorageFileGroupDocumentStore, StorageFileUploadFilesError, SystemStateCollectionStore, SystemStateDocumentStore, TwoWayFlatFirestoreModelKeyPipe, authRolesObsWithClaimsService, authUserInfoFromAuthUser, authUserStateFromFirebaseAuthServiceFunction, createDbxFirebaseAppCheck, createDbxFirebaseAuth, createDbxFirebaseFirestore, createDbxFirebaseFunctions, createDbxFirebaseStorage, dbxFirebaseAuthContextInfo, dbxFirebaseCollectionChangeTrigger, dbxFirebaseCollectionChangeTriggerForStore, dbxFirebaseCollectionChangeTriggerForWatcher, dbxFirebaseCollectionChangeWatcher, dbxFirebaseCollectionLoaderInstance, dbxFirebaseCollectionLoaderInstanceWithCollection, dbxFirebaseDocumentLoaderInstance, dbxFirebaseDocumentLoaderInstanceWithAccessor, dbxFirebaseDocumentStoreContextModelEntitiesSourceFactory, dbxFirebaseIdRouteParamRedirect, dbxFirebaseInContextFirebaseModelServiceInstance, dbxFirebaseInContextFirebaseModelServiceInstanceFactory, dbxFirebaseKeyRouteParamRedirect, dbxFirebaseModelContextServiceInfoInstanceFactory, dbxFirebaseModelEntityWidgetInjectionConfigFactory, dbxFirebaseModelTypesServiceInstance, dbxFirebaseModelTypesServiceInstancePairForKeysFactory, dbxFirebaseSourceSelectLoadSource, dbxFirebaseStorageFileDownloadServiceCustomSourceFromObs, dbxFirebaseStorageFileImageCompressionFileModifier, dbxFirebaseStorageProvidersContextConfigFactory, dbxLimitedFirebaseDocumentLoaderInstance, dbxLimitedFirebaseDocumentLoaderInstanceWithAccessor, dbxWidgetTypeForNotificationTemplateType, defaultDbxFirebaseAuthServiceDelegateWithClaimsService, defaultDbxFirebaseStorageFileDownloadStorageAccessorFactory, developmentFirebaseServerSchedulerWidgetEntry, enableAppCheckDebugTokenGeneration, firebaseAuthState, firebaseAuthTokenFromUser, firebaseCollectionStoreCreateFunction, firebaseCollectionStoreCrudFunction, firebaseContextServiceEntityMap, firebaseDocumentStoreCreateFunction, firebaseDocumentStoreCrudFunction, firebaseDocumentStoreDeleteFunction, firebaseDocumentStoreInvokeFunction, firebaseDocumentStoreReadFunction, firebaseDocumentStoreUpdateFunction, firebaseIdToken, firebaseProviderIdToLoginMethodType, isDbxFirebaseModelEntityWithStore, isStandaloneWebApp, linkDocumentStoreToParentContextStores, loginMethodTypeToFirebaseProviderId, modelDoesNotExistError, parseDbxFirebaseEmulatorsConfig, provideDbxFirebase, provideDbxFirebaseAnalyticsUserEventsListenerService, provideDbxFirebaseApp, provideDbxFirebaseAuth, provideDbxFirebaseAuthImpersonation, provideDbxFirebaseCollectionStoreDirective, provideDbxFirebaseCollectionWithParentStoreDirective, provideDbxFirebaseDevelopment, provideDbxFirebaseDocumentStoreContextStore, provideDbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreTwoWayKeyProvider, provideDbxFirebaseFunctions, provideDbxFirebaseLogin, provideDbxFirebaseModelContextService, provideDbxFirebaseModelEntitiesWidgetService, provideDbxFirebaseNotifications, provideDbxFirebaseStorageFileService, provideDbxFirestoreCollection, provideNotificationFirestoreCollections, provideStorageFileFirestoreCollections, provideSystemStateFirestoreCollections, providedDbxFirebaseStorage, readDbxAnalyticsUserPropertiesFromAuthUserInfo, readValueFromIdToken, resolveDbxFirebaseAuthFlow, setParentStoreEffect, stateFromTokenForLoggedInUserFunction, storageFileUploadFiles, storageFileUploadHandler };
10763
+ export { AbstractConfiguredDbxFirebaseLoginButtonDirective, AbstractDbxFirebaseCollectionStore, AbstractDbxFirebaseCollectionWithParentStore, AbstractDbxFirebaseDocumentStore, AbstractDbxFirebaseDocumentWithParentStore, AbstractDbxFirebaseModelEntityWidgetDirective, AbstractDbxFirebaseNotificationItemWidgetComponent, AbstractRootSingleItemDbxFirebaseDocument, AbstractSingleItemDbxFirebaseDocument, AbstractSystemStateDocumentStoreAccessor, DBX_CONFIGURED_DBX_FIREBASE_LOGIN_BUTTON_COMPONENT_CONFIGURATION, DBX_FIREBASE_APP_OPTIONS_TOKEN, DBX_FIREBASE_AUTH_FLOW_TOKEN, DBX_FIREBASE_DOCUMENT_STORE_CONTEXT_STORE_TOKEN, DBX_FIREBASE_EXTERNAL_CONNECTION_CALCOM_PROVIDER, DBX_FIREBASE_EXTERNAL_CONNECTION_DISCORD_PROVIDER, DBX_FIREBASE_EXTERNAL_CONNECTION_ZOHO_PROVIDER, DBX_FIREBASE_EXTERNAL_CONNECTION_ZOOM_PROVIDER, DBX_FIREBASE_KNOWN_EXTERNAL_CONNECTION_PROVIDERS, DBX_FIREBASE_MODEL_DOES_NOT_EXIST_ERROR, DBX_FIREBASE_MODEL_ENTITY_WITH_STORE_TOKEN, DBX_FIREBASE_STORAGEFILE_DOWNLOAD_STORAGE_ACCESSOR_TOKEN, DBX_FIREBASE_STORAGE_CONTEXT_CONFIG_TOKEN, DBX_FIREBASE_STORAGE_CONTEXT_TOKEN, DBX_FIREBASE_STORAGE_PDF_MERGE_UPLOAD_FILE_NAME, DBX_FIRESTORE_CONTEXT_TOKEN, DEFAULT_CONFIGURED_DBX_FIREBASE_LOGIN_BUTTON_TEMPLATE, DEFAULT_DBX_FIREBASE_ANALYTICS_USER_PROPERTIES_FACTORY, DEFAULT_DBX_FIREBASE_AUTH_FLOW, DEFAULT_DBX_FIREBASE_AUTH_SERVICE_DELEGATE, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_ID_PARAM_KEY, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_KEY_PARAM_KEY, DEFAULT_DBX_FIREBASE_ID_ROUTER_PARAM_USE_PARAM_VALUE, DEFAULT_DBX_FIREBASE_MODEL_ENTITIES_COMPONENT_POPOVER_KEY, DEFAULT_DBX_FIREBASE_MODEL_HISTORY_COMPONENT_POPOVER_KEY, DEFAULT_DBX_FIREBASE_NOTIFICATION_ITEM_STORE_POPOVER_KEY, DEFAULT_EXTERNAL_CONNECTION_AUTHORIZE_PATH_FACTORY, DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_FUNCTION, DEFAULT_EXTERNAL_CONNECTION_NAVIGATE_TIMEOUT, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG, DEFAULT_FIREBASE_AUTH_LOGIN_PASSWORD_CONFIG_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_PROVIDERS_TOKEN, DEFAULT_FIREBASE_AUTH_LOGIN_TERMS_COMPONENT_CLASS_TOKEN, DEFAULT_FIREBASE_COLLECTION_CHANGE_TRIGGER_FUNCTION, DEFAULT_FIREBASE_DEVELOPMENT_ENABLED_TOKEN, DEFAULT_FIREBASE_DEVELOPMENT_POPUP_KEY, DEFAULT_FIREBASE_DEVELOPMENT_SCHEDULER_ENABLED_TOKEN, DEFAULT_FIREBASE_DEVELOPMENT_WIDGET_PROVIDERS_TOKEN, DEFAULT_FIREBASE_LOGIN_METHOD_CATEGORY, DEFAULT_FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE, DEFAULT_NOTIFICATION_HEALTH_CHECK_PRESENTATION_ENTRIES, DEFAULT_NOTIFICATION_HEALTH_CHECK_PROBE_WATCH_MINUTES, DEVELOPMENT_FIREBASE_SERVER_SCHEDULER_WIDGET_KEY, DbxFirebaseAnalyticsUserEventsListenerService, DbxFirebaseAnalyticsUserSource, DbxFirebaseAppCheckHttpInterceptor, DbxFirebaseAuthImpersonationDelegate, DbxFirebaseAuthLoginService, DbxFirebaseAuthService, DbxFirebaseAuthServiceDelegate, DbxFirebaseCollectionChangeDirective, DbxFirebaseCollectionChangeTriggerInstance, DbxFirebaseCollectionHasChangeDirective, DbxFirebaseCollectionListDirective, DbxFirebaseCollectionLoaderInstance, DbxFirebaseCollectionStoreDirective, DbxFirebaseCollectionWithParentStoreDirective, DbxFirebaseDevelopmentDirective, DbxFirebaseDevelopmentModule, DbxFirebaseDevelopmentPopupComponent, DbxFirebaseDevelopmentPopupContentComponent, DbxFirebaseDevelopmentPopupContentForgeFormComponent, DbxFirebaseDevelopmentSchedulerListComponent, DbxFirebaseDevelopmentSchedulerListViewComponent, DbxFirebaseDevelopmentSchedulerListViewItemComponent, DbxFirebaseDevelopmentSchedulerService, DbxFirebaseDevelopmentSchedulerWidgetComponent, DbxFirebaseDevelopmentService, DbxFirebaseDevelopmentWidgetService, DbxFirebaseDocumentLoaderInstance, DbxFirebaseDocumentStoreContextModelEntitiesSourceDirective, DbxFirebaseDocumentStoreContextStore, DbxFirebaseDocumentStoreContextStoreDirective, DbxFirebaseDocumentStoreDirective, DbxFirebaseDocumentStoreIdFromTwoWayModelKeyDirective, DbxFirebaseDocumentStoreTwoWayKeyProvider, DbxFirebaseDocumentStoreTwoWayModelKeySourceDirective, DbxFirebaseEmailForgeFormComponent, DbxFirebaseEmailRecoveryForgeFormComponent, DbxFirebaseEmulatorService, DbxFirebaseExternalConnectionListComponent, DbxFirebaseExternalConnectionListViewComponent, DbxFirebaseExternalConnectionListViewItemComponent, DbxFirebaseExternalConnectionService, DbxFirebaseExternalConnectionsComponent, DbxFirebaseExternalConnectionsConfig, DbxFirebaseLoginAnonymousComponent, DbxFirebaseLoginAppleComponent, DbxFirebaseLoginButtonComponent, DbxFirebaseLoginButtonContainerComponent, DbxFirebaseLoginComponent, DbxFirebaseLoginContext, DbxFirebaseLoginContextBackButtonComponent, DbxFirebaseLoginContextDirective, DbxFirebaseLoginEmailComponent, DbxFirebaseLoginEmailContentComponent, DbxFirebaseLoginFacebookComponent, DbxFirebaseLoginGitHubComponent, DbxFirebaseLoginGoogleComponent, DbxFirebaseLoginListComponent, DbxFirebaseLoginMicrosoftComponent, DbxFirebaseLoginTermsComponent, DbxFirebaseLoginTermsSimpleComponent, DbxFirebaseLoginTwitterComponent, DbxFirebaseManageAuthProvidersComponent, DbxFirebaseModelContextService, DbxFirebaseModelEntitiesComponent, DbxFirebaseModelEntitiesDebugWidgetComponent, DbxFirebaseModelEntitiesPopoverButtonComponent, DbxFirebaseModelEntitiesPopoverComponent, DbxFirebaseModelEntitiesSource, DbxFirebaseModelEntitiesWidgetService, DbxFirebaseModelEntitiesWidgetServiceConfig, DbxFirebaseModelHistoryComponent, DbxFirebaseModelHistoryPopoverButtonComponent, DbxFirebaseModelHistoryPopoverComponent, DbxFirebaseModelKeyComponent, DbxFirebaseModelModule, DbxFirebaseModelStoreModule, DbxFirebaseModelTrackerService, DbxFirebaseModelTypeInstanceListComponent, DbxFirebaseModelTypeInstanceListViewComponent, DbxFirebaseModelTypeInstanceListViewItemComponent, DbxFirebaseModelTypesService, DbxFirebaseModelTypesServiceConfig, DbxFirebaseModelViewedEventDirective, DbxFirebaseModule, DbxFirebaseNotificationBoxCollectionStoreDirective, DbxFirebaseNotificationBoxDocumentStoreDirective, DbxFirebaseNotificationCollectionStoreDirective, DbxFirebaseNotificationDocumentStoreDirective, DbxFirebaseNotificationHealthCheckComponent, DbxFirebaseNotificationHealthCheckConfig, DbxFirebaseNotificationHealthCheckDialogButtonComponent, DbxFirebaseNotificationHealthCheckDialogComponent, DbxFirebaseNotificationHealthCheckIssueComponent, DbxFirebaseNotificationHealthCheckMethodComponent, DbxFirebaseNotificationHealthCheckPresentationService, DbxFirebaseNotificationHealthCheckPresentationServiceConfig, DbxFirebaseNotificationHealthCheckViewComponent, DbxFirebaseNotificationItemContentComponent, DbxFirebaseNotificationItemDefaultViewComponent, DbxFirebaseNotificationItemListComponent, DbxFirebaseNotificationItemListViewComponent, DbxFirebaseNotificationItemListViewItemComponent, DbxFirebaseNotificationItemStore, DbxFirebaseNotificationItemStorePopoverButtonComponent, DbxFirebaseNotificationItemStorePopoverComponent, DbxFirebaseNotificationItemViewComponent, DbxFirebaseNotificationItemWidgetService, DbxFirebaseNotificationSummaryCollectionStoreDirective, DbxFirebaseNotificationSummaryDocumentStoreDirective, DbxFirebaseNotificationTemplateService, DbxFirebaseNotificationUserCollectionStoreDirective, DbxFirebaseNotificationUserDocumentStoreDirective, DbxFirebaseNotificationUserHealthCheckStore, DbxFirebaseParsedEmulatorsConfig, DbxFirebasePasswordResetComponent, DbxFirebasePasswordResetFormComponent, DbxFirebaseRegisterComponent, DbxFirebaseRegisterEmailComponent, DbxFirebaseStorageFileCollectionStoreDirective, DbxFirebaseStorageFileDocumentStoreDirective, DbxFirebaseStorageFileDownloadButtonComponent, DbxFirebaseStorageFileDownloadService, DbxFirebaseStorageFileDownloadStorage, DbxFirebaseStorageFileGroupDocumentStoreDirective, DbxFirebaseStorageFileUploadActionHandlerDirective, DbxFirebaseStorageFileUploadInitializeDocumentDirective, DbxFirebaseStorageFileUploadModule, DbxFirebaseStorageFileUploadStore, DbxFirebaseStorageFileUploadStoreDirective, DbxFirebaseStorageFileUploadSyncDirective, DbxFirebaseStoragePdfMergeUploadSyncDirective, DbxFirebaseStorageService, DbxFirebaseSystemStateCollectionStoreDirective, DbxFirebaseSystemStateDocumentStoreDirective, DbxFirebaseUserExternalConnectionCollections, DbxFirebaseUserExternalConnectionDocumentStoreDirective, DbxFirebaseUserExternalConnectionsStore, DbxFirestoreContextService, DbxLimitedFirebaseDocumentLoaderInstance, FIREBASE_APP_CHECK_TOKEN, FIREBASE_APP_TOKEN, FIREBASE_AUTH_TOKEN, FIREBASE_FIRESTORE_TOKEN, FIREBASE_FUNCTIONS_TOKEN, FIREBASE_NOTIFICATION_ITEM_WIDGET_TYPE_PREFIX, FIREBASE_PROVIDER_ID_TO_LOGIN_METHOD_TYPE_MAP, FIREBASE_STORAGE_TOKEN, FlatFirestoreModelKeyPipe, IMPORTS_AND_EXPORTS, LOGIN_METHOD_TYPE_TO_FIREBASE_PROVIDER_ID_MAP, NOTIFICATION_DELIVERY_METHOD_ICONS, NOTIFICATION_DELIVERY_METHOD_LABELS, NOTIFICATION_DELIVERY_METHOD_SHORT_LABELS, NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_LABELS, NOTIFICATION_DELIVERY_METHOD_TEST_MESSAGE_NOUNS, NOTIFICATION_HEALTH_CHECK_PROBE_ISSUE_OUTCOMES, NOTIFICATION_HEALTH_CHECK_STATUS_COLORS, NOTIFICATION_HEALTH_CHECK_STATUS_ICONS, NOTIFICATION_HEALTH_CHECK_STATUS_LABELS, NotificationBoxCollectionStore, NotificationBoxDocumentStore, NotificationCollectionStore, NotificationDocumentStore, NotificationSummaryCollectionStore, NotificationSummaryDocumentStore, NotificationUserCollectionStore, NotificationUserDocumentStore, OAUTH_FIREBASE_LOGIN_METHOD_CATEGORY, StorageFileCollectionStore, StorageFileDocumentStore, StorageFileGroupDocumentStore, StorageFileUploadFilesError, SystemStateCollectionStore, SystemStateDocumentStore, TwoWayFlatFirestoreModelKeyPipe, UserExternalConnectionDocumentStore, authRolesObsWithClaimsService, authUserInfoFromAuthUser, authUserStateFromFirebaseAuthServiceFunction, createDbxFirebaseAppCheck, createDbxFirebaseAuth, createDbxFirebaseFirestore, createDbxFirebaseFunctions, createDbxFirebaseStorage, dbxFirebaseAuthContextInfo, dbxFirebaseCollectionChangeTrigger, dbxFirebaseCollectionChangeTriggerForStore, dbxFirebaseCollectionChangeTriggerForWatcher, dbxFirebaseCollectionChangeWatcher, dbxFirebaseCollectionLoaderInstance, dbxFirebaseCollectionLoaderInstanceWithCollection, dbxFirebaseDocumentLoaderInstance, dbxFirebaseDocumentLoaderInstanceWithAccessor, dbxFirebaseDocumentStoreContextModelEntitiesSourceFactory, dbxFirebaseExternalConnectionProviderForEntry, dbxFirebaseExternalConnectionRowStatusForEntry, dbxFirebaseExternalConnectionRows, dbxFirebaseIdRouteParamRedirect, dbxFirebaseInContextFirebaseModelServiceInstance, dbxFirebaseInContextFirebaseModelServiceInstanceFactory, dbxFirebaseKeyRouteParamRedirect, dbxFirebaseKnownExternalConnectionProvider, dbxFirebaseModelContextServiceInfoInstanceFactory, dbxFirebaseModelEntityWidgetInjectionConfigFactory, dbxFirebaseModelTypesServiceInstance, dbxFirebaseModelTypesServiceInstancePairForKeysFactory, dbxFirebaseSourceSelectLoadSource, dbxFirebaseStorageFileDownloadServiceCustomSourceFromObs, dbxFirebaseStorageFileImageCompressionFileModifier, dbxFirebaseStorageProvidersContextConfigFactory, dbxLimitedFirebaseDocumentLoaderInstance, dbxLimitedFirebaseDocumentLoaderInstanceWithAccessor, dbxWidgetTypeForNotificationTemplateType, defaultDbxFirebaseAuthServiceDelegateWithClaimsService, defaultDbxFirebaseStorageFileDownloadStorageAccessorFactory, developmentFirebaseServerSchedulerWidgetEntry, enableAppCheckDebugTokenGeneration, externalConnectionsLoadingStateFromDocumentLoadingState, firebaseAuthState, firebaseAuthTokenFromUser, firebaseCollectionStoreCreateFunction, firebaseCollectionStoreCrudFunction, firebaseContextServiceEntityMap, firebaseDocumentStoreCreateFunction, firebaseDocumentStoreCrudFunction, firebaseDocumentStoreDeleteFunction, firebaseDocumentStoreInvokeFunction, firebaseDocumentStoreReadFunction, firebaseDocumentStoreUpdateFunction, firebaseIdToken, firebaseProviderIdToLoginMethodType, isDbxFirebaseModelEntityWithStore, isStandaloneWebApp, linkDocumentStoreToParentContextStores, loginMethodTypeToFirebaseProviderId, modelDoesNotExistError, navigateAndWaitForPageToLeave, notificationHealthCheckProbeIssueLabel, parseDbxFirebaseEmulatorsConfig, provideDbxFirebase, provideDbxFirebaseAnalyticsUserEventsListenerService, provideDbxFirebaseApp, provideDbxFirebaseAuth, provideDbxFirebaseAuthImpersonation, provideDbxFirebaseCollectionStoreDirective, provideDbxFirebaseCollectionWithParentStoreDirective, provideDbxFirebaseDevelopment, provideDbxFirebaseDocumentStoreContextStore, provideDbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreTwoWayKeyProvider, provideDbxFirebaseExternalConnections, provideDbxFirebaseFunctions, provideDbxFirebaseLogin, provideDbxFirebaseModelContextService, provideDbxFirebaseModelEntitiesWidgetService, provideDbxFirebaseNotifications, provideDbxFirebaseStorageFileService, provideDbxFirestoreCollection, provideNotificationFirestoreCollections, provideStorageFileFirestoreCollections, provideSystemStateFirestoreCollections, providedDbxFirebaseStorage, readDbxAnalyticsUserPropertiesFromAuthUserInfo, readValueFromIdToken, resolveDbxFirebaseAuthFlow, setParentStoreEffect, shouldCreateUserExternalConnectionForDocumentLoadingState, stateFromTokenForLoggedInUserFunction, storageFileUploadFiles, storageFileUploadHandler };
8499
10764
  //# sourceMappingURL=dereekb-dbx-firebase.mjs.map