I have a service that detects the presence when the user is online, away and offline in firebase.
presence.service.ts
import { Injectable } from "#angular/core";
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFireDatabase } from "#angular/fire/database";
import * as firebase from "firebase/app";
import { tap, map, switchMap, first } from "rxjs/operators";
import { of } from "rxjs";
import { SuperUserService } from "./../services/superuser.service";
#Injectable({
providedIn: "root",
})
export class PresenceService {
constructor(
private afAuth: AngularFireAuth,
private db: AngularFireDatabase,
private superuser: SuperUserService
) {
console.log("Verificação de status em execução");
this.setName();
this.updateOnUser().subscribe();
this.updateOnDisconnect().subscribe();
this.updateOnAway();
}
getPresence(uid: string) {
return this.db.object(`status/${uid}`).valueChanges();
}
getUser() {
return this.afAuth.authState.pipe(first()).toPromise();
}
async setPresence(status: string) {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
status,
timestamp: this.timestamp,
});
}
}
async setName() {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
nome: this.superuser.user.displayName,
});
}
}
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
updateOnUser() {
const connection = this.db
.object(".info/connected")
.valueChanges()
.pipe(map((connected) => (connected ? "online" : "offline")));
return this.afAuth.authState.pipe(
switchMap((user) => (user ? connection : of("offline"))),
tap((status) => this.setPresence(status))
);
}
updateOnDisconnect() {
return this.afAuth.authState.pipe(
tap((user) => {
if (user) {
return this.db
.object(`status/${user.uid}`)
.query.ref.onDisconnect()
.update({
status: "offline",
timestamp: this.timestamp,
});
}
})
);
}
async signOut() {
await this.setPresence("offline");
await this.afAuth.signOut();
}
updateOnAway() {
document.onvisibilitychange = (e) => {
if (document.visibilityState === "hidden") {
this.setPresence("away");
} else {
this.setPresence("online");
}
};
}
}
Firebase path
With this in mind I am wanting to implement a way to bring up how many users are active (online and away) Ex: 15 active users
I tried this, but it only brings me if exist or not
ref.child("status").orderByChild("status").equalTo("online")
.once("value",snapshot => {
if (snapshot.exists()){
// if exist
}
});
If you want to show the number of online users with your current data structure, you can keep the same query as you have but then use snapshot.numChildren() to determine the number of online users it returned.
If you want to show counts of both online and offline users with your current data structure, you can either:
Use a second query for the offline users, and use the same approach as above to get the count.
Use a single read without a condition on the status field, and then count the individual nodes in your application code with:
ref.child("status")
.once("value",snapshot => {
let onlineCount=0, offlineCount=0;
snapshot.forEach((user) => {
const status = user.child("status").val();
if (status === "online) onlineCount++
else if (status === "offline) offlineCount++
else console.error(`Unexpected status '${status}' for user ${user.key}`);
})
console.log(`Online: ${onlineCount}, Offline: ${offlineCount}`);
});
If you're reading all these user profiles to simply show two counters in your app, you're wasting quite a bit of bandwidth (and thus money) for both yourself and your users. Consider storing the actual counts that you want to display in the database itself, and updating them as each user goes online/offline in something like Cloud Functions.
Related
Currently, I'm working on NestJS API. I'd like to prepare Permissions Guard and I have a problem with this. Users can have only one role, one role can have a lot of permissions. Permissions for roles are set on the Admin panel, so role permissions can be often changed. I cannot understand how can I deal with permissions in PermissionGuard. I know that I can check the current state of them in the database, but I think it's not the best way to do that because the database will be queried too often.
What should I do? Any idea?
Works nice. It's a JwtAuthGuard improvement and checking one permission.
import { CanActivate, ExecutionContext, Type, mixin } from '#nestjs/common';
import { EPermission } from '../path-with-your-enum-values';
import { JWTRequestPayload } from '../request-payload-type';
import { JwtAuthGuard } from './jwt-auth.guard';
export const PermissionGuard = (permission: EPermission): Type<CanActivate> => {
class PermissionGuardMixin extends JwtAuthGuard {
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const request = context.switchToHttp().getRequest<JWTRequestPayload>();
const user = request.user;
if (!user || !user.permissions) {
return false;
}
return user.permissions.includes(permission);
}
}
return mixin(PermissionGuardMixin);
};
And with controller:
#Post(':taskId/moderate')
#UseGuards(PermissionGuard(EPermission.MODERATE))
public async moderate(#Param('taskId') taskId: string): Promise<any> {
// ...
}
I have a Cloud Firestore collection of documents (metadata about files that I've uploaded), and I have a bucket of those files in Firebase Storage.
I want to delete a given file using my Angular app. I can delete the Firestore docs, but I'm unable to delete the corresponding files in Storage.
Compile error: Property 'then' does not exist on type 'Observable<any>'.
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs'; // I was experimenting with onPromise()
import { finalize, tap } from 'rxjs/operators';
import { AngularFireStorage } from '#angular/fire/storage';
import { StateService } from '../services/state.service';
// ...
export class AdminComponent implements OnInit {
isProgressVisible: boolean;
constructor(private storage: AngularFireStorage, public stateService: StateService) {
this.isProgressVisible = false;
}
// ...
deleteFile(file): void {
this.isProgressVisible = true;
let storagePath = file.storagePath; // ex: 'uploads/1598066351161_myfile.txt'
let delimiter = storagePath.indexOf('/');
let docID = file.storagePath.substring(delimiter + 1); // ex: '1598066351161_myfile.txt'
let self = this;
this.stateService.firebase.firestore().collection('files').doc(docID).delete().then(function () {
console.log('cloud firestore doc deleted');
let ref = self.storage.ref(storagePath); // delete the file from Storage
ref.delete().then(function () {
console.log('file deleted from storage!');
}).catch(function (error) {
console.error('Error deleting file from storage:', error);
});
}).catch(function (error) {
console.error('Error deleting cloud firestore doc:', error);
});
}
}
Any help is appreciated!
this.stateService.firebase.firestore().collection('files').doc(docID).delete() returns an observable which is not promise. You should either convert it to promise with toPromise() or use subscribe instead of then.
Check this post for more insights.
Just before adding a new user to firebase, I want to check if the displayName already exists for an other user. I'm storing the displayName in users:
root / users / document *(uid as id)* / fields *like uid, displayName, email, ...*
I've tried it like this:
import { Injectable, NgZone } from '#angular/core';
import { auth } from 'firebase/app';
import { User } from "./user";
import { Router } from "#angular/router";
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
import { AngularFireDatabase } from '#angular/fire/database';
import { ToastController } from '#ionic/angular';
...
constructor(
public firestore: AngularFirestore,
public firebase: AngularFireDatabase,
public ngFireAuth: AngularFireAuth,
public router: Router,
public ngZone: NgZone,
public toastController: ToastController
) {
this.getUserData();
this.newUser = {} as newUser;
}
...
check_displayName() {
var usersRef = this.firestore.collection("users");
usersRef.where(doc.forEach(doc => doc, '==', this.state.displayName)).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty)
return false
} else {
return true
console.log('displayName already exists')
}
})
}
But I get an issue:
ReferenceError: doc is not defined
How can I iterate through the docs when there are unique document-ids (uid)?
Thx for your hints
Can you try this ??
check_displayName(userNameToCheck) {
const usersRef = this.afStore.collection("users");
usersRef.where('displayName', '==', userNameToCheck).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty);
return false;
} else {
console.log('displayName already exists');
return true;
}
});
}
I think you need to get the snapshot first and check if it is empty. There is no meaning in iterating through all docs and increasing your Firestore Reads.
And regarding the forEach(), first you need to get the snapshot and then use something like snapshot.forEach(). But in your case, if the snapshot is empty the username is unique and you did you job with just 1 read cost.
Small Tip: I have not checked if username comparision is case-sensitive. So I keep another field named displayNameLower and then compare that with lower-cased version of username to check.
Something like; .where('displayNameLower', '==', userNameToCheck.toLowerCase())
The solutiion above by Dharmaraj is pretty okay. But I would like to add further to it. We can make use of limit() so that the firebase stops querying once any first matching document from collection is found.If fastens the querying ultimately.
const firebaseAdmin = require('firebase-admin');
firebaseAdmin.initializeApp();
const afStore = firebaseAdmin.firestore();
check_displayName(userNameToCheck) {
const userRef = this.afStore.collection("users");
userRef.where('displayName', '==', userNameToCheck).limit(1).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('displayName is unique', snapshot.empty)
return false
} else {
console.log('displayName already exists')
return true
}
})
.catch(err => {
console.log('Alert:: error in querying the users document');
});;
}
How could i persist or recall on my firestore server the data once i reload page for any reason?
Basically in one of the components i make request to some data which by default gets initialized on my login Service component,lets say:
USER Component
export class UserComponent implements OnInit {
resumeAccount: UserResumeAccount = {
amountInAccount: 0,
allDetails: null,
};
constructor(
private userLogged: LoginService) {}
ngOnInit(): void {
this.resumeAccount.allDetails = this.userLogged.resumeAccount.allDetails;
this.resumeAccount.amountInAccount = this.userLogged.resumeAccount.amountInAccount;
}
}
then on the service i deploy the logic about showing or not this user logged information once is logged accessing to the firebase /firestoree data .Once the user signs in the data shows up cprrectly , but if for any reason the page is reload all the data get lost despite of user being active according with method getUserState():
LoginService component
#Injectable()
export class LoginService {
//variables
userdata: UserDataModel = {
uid: '',
restData: '',
};
userResumeAccount: UserResumeAccount = {
amountInAccount: 0,
allDetails: null,
};
totalAmount: number = 0;
resumeAccount: UserResumeAccount = {
amountInAccount: 0,
allDetails: null,
};
//constructor
constructor(
private userLogin: AngularFireAuth,
private docCreator: AngularFirestore,
private router: Router
) {}
ngOnInit():void {
this.userLogin.setPersistence(auth.Auth.Persistence.SESSION);
}
//methods
async logInUser(SignUpData: LoginData) {
let { emailUser, passwordUser } = SignUpData;
await this.userLogin.setPersistence(auth.Auth.Persistence.SESSION);
let responseOk = await this.userLogin.signInWithEmailAndPassword(emailUser,passwordUser);
this.userdata.uid = await responseOk.user.uid;
this.userdata.restData = await responseOk.user;
let pathUserName = await this.docCreator==>path to the document inside the firestore
.collection('users')
.doc(this.userdata.uid);
await pathUserName========>getting the firestore data and asigning it to variables
.get()
.toPromise()
.then(async (result) => {
if (result.exists) {
this.resumeAccount.allDetails = await result.get('userName');
this.resumeAccount.amountInAccount = await this.totalAmount;
} else return console.log('not Document Found');
})
.then(() => {})
.catch((err) => {
console.log('Error getting document:', err);
});
}
getUserState() {
return this.userLogin.authState.pipe(map((userLoged) =>{return (console.log(userLoged),
userLoged)}));
}===>ON RELOAD THIS METHOD STILL SHOWS USER ACTIVE
}
Thus basically in this former section inside the method which triggers the sign in process i access the data stored in firestore ; also at the ngOnInit methods i establish a persistence method for the section pretending to keep data reached once the page reloads, but is wrong!,as well as its initialization at the beggining of the loginUser method too.
How could i improve this problem.Really thanks in advance
Method reauthenticateAndRetrieveDataWithCredential requires credential.
I tried this and it gives me an error:
const user = firebase.auth().currentUser;
const credential = firebase.auth.OAuthCredential;
await user.reauthenticateAndRetrieveDataWithCredential(credential);
await user.updateEmail(email);
return email;
Error message
reauthenticateAndRetrieveDataWithCredential failed: First argument "credential" must be a valid credential.
I only have oath authentication (no email + password). So I can't figure out what credential firebase need. Any help?
Edit:
For some reason my firebase.auth.OAuthCredential or (firebase.auth.AuthCredential) return undefined. User is signedIn/authed.
Ran into this issue in a Vue.js project where we have a firebase.js file that handles module imports and exports of const.
Hoping this will help someone save time when using a similar setup.
File: firebase.js
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/database'
// Initialize Firebase
const app = firebase.initializeApp(options)
export const fb = firebase
export const auth = app.auth()
export const db = app.database()
export const functions = app.functions()
Example of changePassword() in other script file or inside *.vue
import { fb, auth } from './firebase.js'
...
changePassword() {
if (yourFormValidation == true) {
let user = auth.currentUser
const credentials = fb.auth.EmailAuthProvider.credential(
user.email,
this.current_password
)
user.reauthenticateAndRetrieveDataWithCredential(credentials)
.then(() => {
user.updatePassword(this.new_password)
.then(() => {
console.log('your password was successfully changed.')
})
.catch(error => console.log(error))
})
.catch(error => console.log(error.message))
}
}
Where I was running into Cannot read property 'credential' of undefined"...
Importing only { auth } from './firebase.js and then calling auth.EmailAuthProvider().
How to access other classes found on firebase.auth()...
export const fb = firebase in firebase.js
import { fb, auth } from './firebase.js' where you are writing your function
Call fb.auth.EmailAuthProvider.credential() or other class needed
The documentation for reauthenticating the user shows this example:
var user = firebase.auth().currentUser;
var credential;
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateAndRetrieveDataWithCredential(credential).then(function() {
// User re-authenticated.
}).catch(function(error) {
// An error happened.
});
Your code fails to implement the comment correctly. You will need to show a prompt to the user to provide their credentials again, put those into the correct credential object type, and pass that in.
For example:
var credential = firebase.auth.EmailAuthProvider.credential(
email,
password
);
You need to use a subscription to watch for the changes. Use AngularFire to watch for when they are logged in and get the UID (assuming you are using the Authentication login in Firebase so that all data is saved using the UID as the tree path.
You can also add a set timeout to unsubscribe them after a given time
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { switchMap, map } from 'rxjs/operators';
import { Observable, pipe } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private myOAuthSubscription: Subscription;
private myDatasubscription: Subscription;
public userloggedin:boolean = false;
public uid:string = '';
private functionhasrun:boolean = false;
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase,
public _afAuth: AngularFireAuth,
) {
//check that the user is logged in
try {
this.myOAuthSubscription = this._afAuth.authState.subscribe(user => {
if (user && user.uid) {
console.log('loggedin = true');
this.userloggedin = true;
this.uid = String(user.uid);
if(this.functionhasrun==false){
this.functionhasrun = true;
this.funDoDB():
}
} else {
console.log('loggedin = false');
this.userloggedin = true;
this.uid = '';
}
});
} catch (e) {
console.error("fbData_subscription", e);
}
}
ngOnDestroy() {
this.myOAuthSubscription.unsubscribe();
this.myDatasubscription.unsubscribe();
}
private funDoDB(){
if(this.userloggedin == true){
try {
//subscription using AngulaFire
this.myDatasubscription = this._DB.list('myDataPath/' + this.uid).snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
}
npm install --save angularfire2 firebase
npm install -D rxjs#6.2.2 rxjs-compat#6.2.2