@sneat/auth-core 0.27.18 → 0.27.20

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.
@@ -5,7 +5,7 @@ Firestore as AngularFirestore, collection, doc, onSnapshot, } from 'firebase/fir
5
5
  import { SneatApiService } from '@sneat/api';
6
6
  import { ErrorLogger, SneatUrlOperationBlocker, } from '@sneat/core';
7
7
  import { initialSneatAuthState, SneatAuthStateService, } from '../sneat-auth-state-service';
8
- import { BehaviorSubject, ReplaySubject } from 'rxjs';
8
+ import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
9
9
  import { UserRecordService, } from './user-record.service';
10
10
  import * as i0 from "@angular/core";
11
11
  const UsersCollection = 'users';
@@ -59,6 +59,26 @@ export class SneatUserService {
59
59
  setUserCountry(countryID) {
60
60
  return this.sneatApiService.post('users/set_user_country', { countryID });
61
61
  }
62
+ /**
63
+ * Watches another user's readable profile record without changing the
64
+ * authenticated user's state. Firestore rules remain the authorization
65
+ * boundary (for example, shared-Space membership); callers only depend on
66
+ * this user-domain service rather than the persistence SDK.
67
+ */
68
+ watchUserRecordByID(userID) {
69
+ return new Observable((subscriber) => {
70
+ if (!userID || this.operationBlocker.isBlocked('server-requests')) {
71
+ subscriber.next(null);
72
+ subscriber.complete();
73
+ return undefined;
74
+ }
75
+ const userDocRef = this.userDocRef(userID);
76
+ return runInInjectionContext(this.injector, () => onSnapshot(userDocRef, {
77
+ next: (snapshot) => subscriber.next(snapshot.exists() ? snapshot.data() : null),
78
+ error: (error) => subscriber.error(error),
79
+ }));
80
+ });
81
+ }
62
82
  onUserSignedIn(authState) {
63
83
  const authUser = authState.user;
64
84
  // afUser.getIdToken().then(idToken => {
@@ -1 +1 @@
1
- {"version":3,"file":"sneat-user.service.js","sourceRoot":"","sources":["../../../../../../../libs/auth/core/src/lib/user/sneat-user.service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Injectable,\n inject,\n Injector,\n runInInjectionContext,\n} from '@angular/core';\nimport {\n // Action,\n Firestore as AngularFirestore,\n CollectionReference,\n DocumentSnapshot,\n collection,\n doc,\n onSnapshot,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { SneatApiService } from '@sneat/api';\nimport { IUserRecord } from '@sneat/auth-models';\nimport {\n ErrorLogger,\n IErrorLogger,\n SneatUrlOperationBlocker,\n} from '@sneat/core';\nimport {\n initialSneatAuthState,\n ISneatAuthState,\n ISneatAuthUser,\n SneatAuthStateService,\n} from '../sneat-auth-state-service';\nimport { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';\nimport {\n IInitUserRecordRequest,\n UserRecordService,\n} from './user-record.service';\n\nexport interface ISneatUserState extends ISneatAuthState {\n record?: IUserRecord | null; // undefined => not loaded yet, null = does not exists\n}\n\nconst UsersCollection = 'users';\n\n@Injectable({ providedIn: 'root' }) // TODO: lazy loading\nexport class SneatUserService {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly sneatApiService = inject(SneatApiService);\n private readonly userRecordService = inject(UserRecordService);\n private readonly operationBlocker = inject(SneatUrlOperationBlocker);\n\n // private userDocSubscription?: Subscription;\n private readonly injector = inject(Injector);\n private readonly userCollection: CollectionReference<IUserRecord>;\n private readonly userDocRef = (uid: string) =>\n runInInjectionContext(this.injector, () => doc(this.userCollection, uid));\n\n private uid?: string;\n private $userTitle?: string;\n\n private readonly userChanged$ = new ReplaySubject<string | undefined>(1);\n public readonly userChanged = this.userChanged$.asObservable();\n\n private readonly userState$ = new BehaviorSubject<ISneatUserState>(\n initialSneatAuthState,\n );\n\n public readonly userState = this.userState$.asObservable();\n\n private _unsubscribeFromUserDoc?: Unsubscribe;\n\n private unsubscribeFromUserDoc(_from: string) {\n if (this._unsubscribeFromUserDoc) {\n // console.log(\n // 'SneatUserService.unsubscribeFromUserDoc() called from ' + _from,\n // );\n this._unsubscribeFromUserDoc();\n this._unsubscribeFromUserDoc = undefined;\n }\n }\n\n constructor() {\n const afs = inject(AngularFirestore);\n const sneatAuthStateService = inject(SneatAuthStateService);\n this.userCollection = collection(\n afs,\n UsersCollection,\n ) as CollectionReference<IUserRecord>;\n sneatAuthStateService.authState.subscribe({\n next: this.onAuthStateChanged,\n error: this.errorLogger.logErrorHandler('failed to get sneat auth state'),\n });\n }\n\n public get currentUserID(): string | undefined {\n return this.uid;\n }\n\n // public get userTitle(): string | undefined {\n // \treturn this.$userTitle;\n // }\n\n public setUserCountry(countryID: string): Observable<void> {\n return this.sneatApiService.post('users/set_user_country', { countryID });\n }\n\n public onUserSignedIn(authState: ISneatAuthState): void {\n const authUser = authState.user;\n // afUser.getIdToken().then(idToken => {\n // \tconsole.log('Firebase idToken:', idToken);\n // }).catch(err => this.errorLoggerService.logError(err, 'Failed to get Firebase ID token'));\n if (authUser?.email && authUser.emailVerified) {\n this.$userTitle = authUser.email;\n }\n if (this.uid === authUser?.uid) {\n return;\n }\n this.unsubscribeFromUserDoc('onUserSignedIn()');\n if (!authUser) {\n if (this.userState$.value?.record !== null) {\n this.userState$.next({ ...this.userState$.value });\n }\n return;\n }\n const { uid } = authUser;\n this.uid = uid;\n this.userState$.next({\n ...authState,\n });\n this.userChanged$.next(uid);\n this.watchUserRecord(uid, authState);\n }\n\n private watchUserRecord(uid: string, authState: ISneatAuthState): void {\n // console.log(\n // `SneatUserService.watchUserRecord(uid=${uid}): Loading user record...`,\n // );\n this.unsubscribeFromUserDoc('whatUserRecord()');\n if (this.operationBlocker.isBlocked('server-requests')) {\n return;\n }\n\n // TODO: Remove - setTimeout() not needed but trying to troubleshoot user record issue\n setTimeout(() => {\n if (this.operationBlocker.isBlocked('server-requests')) {\n return;\n }\n try {\n const userDocRef = this.userDocRef(uid);\n this._unsubscribeFromUserDoc = runInInjectionContext(\n this.injector,\n () =>\n onSnapshot(userDocRef, {\n next: (userDocSnapshot) => {\n // console.log(\n // `SneatUserService.watchUserRecord(uid=${uid}) => userDocSnapshot:`,\n // userDocSnapshot,\n // );\n this.onAuthStateChanged(authState);\n this.userDocChanged(userDocSnapshot, authState);\n },\n error: (err) => {\n console.error(\n `SneatUserService.watchUserRecord(uid=${uid}) => failed:`,\n err,\n );\n },\n }),\n );\n } catch (err) {\n console.error(\n `SneatUserService.watchUserRecord(uid=${uid}) => Failed to setup watcher for user record::`,\n err,\n );\n return;\n }\n }, 100);\n }\n\n private onAuthStateChanged = (authState: ISneatAuthState): void => {\n // console.log('SneatUserService => authState changed:', authState);\n if (authState.user) {\n this.onUserSignedIn(authState);\n } else {\n this.userState$.next(authState);\n this.userChanged$.next(undefined);\n this.onUserSignedOut();\n }\n };\n\n private userDocChanged(\n userDocSnapshot: DocumentSnapshot<IUserRecord>,\n authState: ISneatAuthState,\n ): void {\n // console.log(\n // 'SneatUserService.userDocChanged() => userDocSnapshot.exists:',\n // userDocSnapshot.exists(),\n // 'authState:',\n // authState,\n // 'userDocSnapshot:',\n // userDocSnapshot,\n // );\n if (userDocSnapshot.ref.id !== this.uid) {\n console.error(\n 'userDocSnapshot.ref.id !== this.uid - Should always be equal as we unsubscribe if uid changes',\n );\n return;\n }\n // console.log('SneatUserService => userDocSnapshot.exists:', userDocSnapshot.exists)\n const authUser = authState.user;\n if (authUser && !userDocSnapshot.exists()) {\n this.initUserRecordFromAuthUser(authUser);\n }\n const userRecord: IUserRecord | null = userDocSnapshot.exists()\n ? (userDocSnapshot.data() as IUserRecord)\n : authUser\n ? { title: authUser.displayName || authUser.email || authUser.uid }\n : null;\n\n this.userState$.next({\n ...authState,\n record: userRecord,\n });\n }\n\n private initUserRecordFromAuthUser(authUser: ISneatAuthUser): void {\n let request: IInitUserRecordRequest = {\n email: authUser.email || undefined,\n emailIsVerified: authUser.emailVerified,\n authProvider: authUser?.providerId,\n };\n if (authUser?.displayName) {\n request = { ...request, names: { fullName: authUser.displayName } };\n }\n this.userRecordService.initUserRecord(request).subscribe({\n next: (_userDto) => {\n // User record created successfully - no additional action needed\n },\n error: this.errorLogger.logErrorHandler('failed to create user record'),\n });\n }\n\n private onUserSignedOut(): void {\n this.uid = undefined;\n this.unsubscribeFromUserDoc('onUserSignedOut()');\n }\n\n // private createUserRecord(userDocRef: DocumentReference, authUser: ISneatAuthUser): void {\n // \tif (this.userState$.value) {\n // \t\treturn;\n // \t}\n // \tthis.db.firestore.runTransaction(async tx => {\n // \t\tif (this.userState$.value) {\n // \t\t\treturn undefined;\n // \t\t}\n // \t\tconst u = await tx.get(userDocRef);\n // \t\tif (!u.exists) {\n // \t\t\tconst title = authUser.displayName || authUser.email || authUser.uid;\n // \t\t\tconst user: IUserRecord = authUser.email\n // \t\t\t\t? {title, email: authUser.email, emailVerified: authUser.emailVerified}\n // \t\t\t\t: {title};\n // \t\t\tawait tx.set(userDocRef, user);\n // \t\t\treturn user;\n // \t\t}\n // \t\treturn undefined;\n // \t}).then(user => {\n // \t\tif (user) {\n // \t\t\tconsole.log('user record created:', user);\n // \t\t}\n // \t\tif (!this.userState$.value) {\n // \t\t\tconst userState: ISneatUserState = {\n // \t\t\t\tstatus: AuthStatuses.authenticated,\n // \t\t\t\trecord: user,\n // \t\t\t\tuser: authUser,\n // \t\t\t};\n // \t\t\tthis.userState$.next(userState);\n // \t\t}\n // \t}).catch(this.errorLogger.logErrorHandler('failed to create user record'));\n // }\n}\n"]}
1
+ {"version":3,"file":"sneat-user.service.js","sourceRoot":"","sources":["../../../../../../../libs/auth/core/src/lib/user/sneat-user.service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Injectable,\n inject,\n Injector,\n runInInjectionContext,\n} from '@angular/core';\nimport {\n // Action,\n Firestore as AngularFirestore,\n CollectionReference,\n DocumentSnapshot,\n collection,\n doc,\n onSnapshot,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { SneatApiService } from '@sneat/api';\nimport { IUserRecord } from '@sneat/auth-models';\nimport {\n ErrorLogger,\n IErrorLogger,\n SneatUrlOperationBlocker,\n} from '@sneat/core';\nimport {\n initialSneatAuthState,\n ISneatAuthState,\n ISneatAuthUser,\n SneatAuthStateService,\n} from '../sneat-auth-state-service';\nimport { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';\nimport {\n IInitUserRecordRequest,\n UserRecordService,\n} from './user-record.service';\n\nexport interface ISneatUserState extends ISneatAuthState {\n record?: IUserRecord | null; // undefined => not loaded yet, null = does not exists\n}\n\nconst UsersCollection = 'users';\n\n@Injectable({ providedIn: 'root' }) // TODO: lazy loading\nexport class SneatUserService {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly sneatApiService = inject(SneatApiService);\n private readonly userRecordService = inject(UserRecordService);\n private readonly operationBlocker = inject(SneatUrlOperationBlocker);\n\n // private userDocSubscription?: Subscription;\n private readonly injector = inject(Injector);\n private readonly userCollection: CollectionReference<IUserRecord>;\n private readonly userDocRef = (uid: string) =>\n runInInjectionContext(this.injector, () => doc(this.userCollection, uid));\n\n private uid?: string;\n private $userTitle?: string;\n\n private readonly userChanged$ = new ReplaySubject<string | undefined>(1);\n public readonly userChanged = this.userChanged$.asObservable();\n\n private readonly userState$ = new BehaviorSubject<ISneatUserState>(\n initialSneatAuthState,\n );\n\n public readonly userState = this.userState$.asObservable();\n\n private _unsubscribeFromUserDoc?: Unsubscribe;\n\n private unsubscribeFromUserDoc(_from: string) {\n if (this._unsubscribeFromUserDoc) {\n // console.log(\n // 'SneatUserService.unsubscribeFromUserDoc() called from ' + _from,\n // );\n this._unsubscribeFromUserDoc();\n this._unsubscribeFromUserDoc = undefined;\n }\n }\n\n constructor() {\n const afs = inject(AngularFirestore);\n const sneatAuthStateService = inject(SneatAuthStateService);\n this.userCollection = collection(\n afs,\n UsersCollection,\n ) as CollectionReference<IUserRecord>;\n sneatAuthStateService.authState.subscribe({\n next: this.onAuthStateChanged,\n error: this.errorLogger.logErrorHandler('failed to get sneat auth state'),\n });\n }\n\n public get currentUserID(): string | undefined {\n return this.uid;\n }\n\n // public get userTitle(): string | undefined {\n // \treturn this.$userTitle;\n // }\n\n public setUserCountry(countryID: string): Observable<void> {\n return this.sneatApiService.post('users/set_user_country', { countryID });\n }\n\n /**\n * Watches another user's readable profile record without changing the\n * authenticated user's state. Firestore rules remain the authorization\n * boundary (for example, shared-Space membership); callers only depend on\n * this user-domain service rather than the persistence SDK.\n */\n public watchUserRecordByID(userID: string): Observable<IUserRecord | null> {\n return new Observable<IUserRecord | null>((subscriber) => {\n if (!userID || this.operationBlocker.isBlocked('server-requests')) {\n subscriber.next(null);\n subscriber.complete();\n return undefined;\n }\n const userDocRef = this.userDocRef(userID);\n return runInInjectionContext(this.injector, () =>\n onSnapshot(userDocRef, {\n next: (snapshot) =>\n subscriber.next(snapshot.exists() ? snapshot.data() : null),\n error: (error) => subscriber.error(error),\n }),\n );\n });\n }\n\n public onUserSignedIn(authState: ISneatAuthState): void {\n const authUser = authState.user;\n // afUser.getIdToken().then(idToken => {\n // \tconsole.log('Firebase idToken:', idToken);\n // }).catch(err => this.errorLoggerService.logError(err, 'Failed to get Firebase ID token'));\n if (authUser?.email && authUser.emailVerified) {\n this.$userTitle = authUser.email;\n }\n if (this.uid === authUser?.uid) {\n return;\n }\n this.unsubscribeFromUserDoc('onUserSignedIn()');\n if (!authUser) {\n if (this.userState$.value?.record !== null) {\n this.userState$.next({ ...this.userState$.value });\n }\n return;\n }\n const { uid } = authUser;\n this.uid = uid;\n this.userState$.next({\n ...authState,\n });\n this.userChanged$.next(uid);\n this.watchUserRecord(uid, authState);\n }\n\n private watchUserRecord(uid: string, authState: ISneatAuthState): void {\n // console.log(\n // `SneatUserService.watchUserRecord(uid=${uid}): Loading user record...`,\n // );\n this.unsubscribeFromUserDoc('whatUserRecord()');\n if (this.operationBlocker.isBlocked('server-requests')) {\n return;\n }\n\n // TODO: Remove - setTimeout() not needed but trying to troubleshoot user record issue\n setTimeout(() => {\n if (this.operationBlocker.isBlocked('server-requests')) {\n return;\n }\n try {\n const userDocRef = this.userDocRef(uid);\n this._unsubscribeFromUserDoc = runInInjectionContext(\n this.injector,\n () =>\n onSnapshot(userDocRef, {\n next: (userDocSnapshot) => {\n // console.log(\n // `SneatUserService.watchUserRecord(uid=${uid}) => userDocSnapshot:`,\n // userDocSnapshot,\n // );\n this.onAuthStateChanged(authState);\n this.userDocChanged(userDocSnapshot, authState);\n },\n error: (err) => {\n console.error(\n `SneatUserService.watchUserRecord(uid=${uid}) => failed:`,\n err,\n );\n },\n }),\n );\n } catch (err) {\n console.error(\n `SneatUserService.watchUserRecord(uid=${uid}) => Failed to setup watcher for user record::`,\n err,\n );\n return;\n }\n }, 100);\n }\n\n private onAuthStateChanged = (authState: ISneatAuthState): void => {\n // console.log('SneatUserService => authState changed:', authState);\n if (authState.user) {\n this.onUserSignedIn(authState);\n } else {\n this.userState$.next(authState);\n this.userChanged$.next(undefined);\n this.onUserSignedOut();\n }\n };\n\n private userDocChanged(\n userDocSnapshot: DocumentSnapshot<IUserRecord>,\n authState: ISneatAuthState,\n ): void {\n // console.log(\n // 'SneatUserService.userDocChanged() => userDocSnapshot.exists:',\n // userDocSnapshot.exists(),\n // 'authState:',\n // authState,\n // 'userDocSnapshot:',\n // userDocSnapshot,\n // );\n if (userDocSnapshot.ref.id !== this.uid) {\n console.error(\n 'userDocSnapshot.ref.id !== this.uid - Should always be equal as we unsubscribe if uid changes',\n );\n return;\n }\n // console.log('SneatUserService => userDocSnapshot.exists:', userDocSnapshot.exists)\n const authUser = authState.user;\n if (authUser && !userDocSnapshot.exists()) {\n this.initUserRecordFromAuthUser(authUser);\n }\n const userRecord: IUserRecord | null = userDocSnapshot.exists()\n ? (userDocSnapshot.data() as IUserRecord)\n : authUser\n ? { title: authUser.displayName || authUser.email || authUser.uid }\n : null;\n\n this.userState$.next({\n ...authState,\n record: userRecord,\n });\n }\n\n private initUserRecordFromAuthUser(authUser: ISneatAuthUser): void {\n let request: IInitUserRecordRequest = {\n email: authUser.email || undefined,\n emailIsVerified: authUser.emailVerified,\n authProvider: authUser?.providerId,\n };\n if (authUser?.displayName) {\n request = { ...request, names: { fullName: authUser.displayName } };\n }\n this.userRecordService.initUserRecord(request).subscribe({\n next: (_userDto) => {\n // User record created successfully - no additional action needed\n },\n error: this.errorLogger.logErrorHandler('failed to create user record'),\n });\n }\n\n private onUserSignedOut(): void {\n this.uid = undefined;\n this.unsubscribeFromUserDoc('onUserSignedOut()');\n }\n\n // private createUserRecord(userDocRef: DocumentReference, authUser: ISneatAuthUser): void {\n // \tif (this.userState$.value) {\n // \t\treturn;\n // \t}\n // \tthis.db.firestore.runTransaction(async tx => {\n // \t\tif (this.userState$.value) {\n // \t\t\treturn undefined;\n // \t\t}\n // \t\tconst u = await tx.get(userDocRef);\n // \t\tif (!u.exists) {\n // \t\t\tconst title = authUser.displayName || authUser.email || authUser.uid;\n // \t\t\tconst user: IUserRecord = authUser.email\n // \t\t\t\t? {title, email: authUser.email, emailVerified: authUser.emailVerified}\n // \t\t\t\t: {title};\n // \t\t\tawait tx.set(userDocRef, user);\n // \t\t\treturn user;\n // \t\t}\n // \t\treturn undefined;\n // \t}).then(user => {\n // \t\tif (user) {\n // \t\t\tconsole.log('user record created:', user);\n // \t\t}\n // \t\tif (!this.userState$.value) {\n // \t\t\tconst userState: ISneatUserState = {\n // \t\t\t\tstatus: AuthStatuses.authenticated,\n // \t\t\t\trecord: user,\n // \t\t\t\tuser: authUser,\n // \t\t\t};\n // \t\t\tthis.userState$.next(userState);\n // \t\t}\n // \t}).catch(this.errorLogger.logErrorHandler('failed to create user record'));\n // }\n}\n"]}
@@ -24,6 +24,13 @@ export declare class SneatUserService {
24
24
  constructor();
25
25
  get currentUserID(): string | undefined;
26
26
  setUserCountry(countryID: string): Observable<void>;
27
+ /**
28
+ * Watches another user's readable profile record without changing the
29
+ * authenticated user's state. Firestore rules remain the authorization
30
+ * boundary (for example, shared-Space membership); callers only depend on
31
+ * this user-domain service rather than the persistence SDK.
32
+ */
33
+ watchUserRecordByID(userID: string): Observable<IUserRecord | null>;
27
34
  onUserSignedIn(authState: ISneatAuthState): void;
28
35
  private watchUserRecord;
29
36
  private onAuthStateChanged;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sneat/auth-core",
3
- "version": "0.27.18",
3
+ "version": "0.27.20",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },