3

I am using Ionic2 with Angularfire2 to access Firebase Authentication.

I access the following rxjs/Observable:

chats.ts

  this.firelist = this.dataService.findChats();
  this.subscription = this.firelist.subscribe(chatItems => {
     ...
  });

  ngDestroy() {
    this.subscription.unsubscribe();
  }

dataService.ts

findChats(): Observable<any[]> {
        this.firebaseDataService.findChats().forEach(firebaseItems => {
           ...
           observer.next(mergedItems);
        });
}

firebaseDataService.ts

findChats(): Observable<any[]> { // populates the firelist
    return this.af.database.list('/chat/', {
        query: {
            orderByChild: 'negativtimestamp'
        }
    }).map(items => {
        const filtered = items.filter(
            item => (item.memberId1 === this.me.uid || item.memberId2 === this.me.uid)
        );
        return filtered;
    });
}

Also, in Firebase Authentication, I have the following Realtime Database Rules:

  "rules": {
      ".read": "auth != null",
      ".write": "auth != null",
  }

Problem

This works perfectly while a user is logged in (i.e. auth != null), but a soon as a user logs out, and auth == null, I get the following error:

ERROR Error: Uncaught (in promise): Error: permission_denied at /chat: Client doesn't have permission to access the desired data. Error: permission_denied at /chat: Client doesn't have permission to access the desired data.

Question

As you can see in the above code, I try to unsubscribe from the Firebase Authentication Service, but must be doing it incorrectly. If anyone can advise how I should unsubscribe in order to stop my app querying the Firebase database, I would appreciate the help.

KENdi
  • 7,576
  • 2
  • 16
  • 31
Richard
  • 8,193
  • 28
  • 107
  • 228
  • Why don't you do `this.firelist.unsubscribe()` – Vamshi Jul 03 '17 at 08:10
  • Hi Skeptor, thanks for the feedback. `this.firelist` is a `rxjs/Observable`, and the api does not have an `unsubscribe` function. It does however, return a `Subscription` that does have one, so that's what I unsuccessfully try as shown above. http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html – Richard Jul 03 '17 at 08:15
  • I never unsubscribe so not sure about the syntax . But in my opinion you need to watch auth object status for every http call , instead of unsubscribing. I will provide a different way to write the same code and how i do in my app. ( I use aws, but they are almost same ) – Vamshi Jul 03 '17 at 08:30
  • Thank you, appreciate the assistance. – Richard Jul 03 '17 at 08:31
  • What I meant is , if user is loggedout , any http call will fail and user will automatically redirected to login page . You need not manually unsubscribe. I will add my code in couple of hours , am not at my desk right now . – Vamshi Jul 03 '17 at 08:31
  • 1
    What happens if a hacker logs in, and tries to access another users messages? That's where the Firebase Rules come in handy. You can only allow access to the messages that match the users `uid`. You can do something like: ` "chat": { "$key": { ".read": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid || true", ".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid || true" } },` – Richard Jul 03 '17 at 08:32

2 Answers2

1

This is not an exact answer to the question, but my argument is we will never come to this issue if we do this other way.

P.S : I never used firebase , I use AWS so the firebase code might not be precise, we discuss about it .

Here is my version:

findChats(): Observable<any[]> { // populates the firelist
    return 
      this.authService.getCurrentUser()
        .flatMap(user => user === null ? 
          Observable.empty():        
          this.af.database.list('/chat/', {
             query: {
                orderByChild: 'negativtimestamp'
             }
           })
          .map(items => 
             return items.filter(
               item => (
                  item.memberId1 === this.me.uid || 
                  item.memberId2 === this.me.uid
               )
             ) 
          )
     );
}

Auth Service

public currentUser = new BehaviorSubject(null);

constructor(){

    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
           // User is signed in.
           currentUser.next(user);
        } else {
         // No user is signed in.
           currentUser.next(null);
        }
    });

}    

The code is very raw but I hope it conveys the idea. Here I am observing on the user authentication status and doing flatMap to fetch data. If user logs out, auth status change to null so in the UI data changes to empty list. Even if the user is not logged out , still he cannot see the data.


UPDATE :

Refactoring other answer:

findChats(){
   return Observable.merge(
       this.firebaseDataService
           .findChats()
           .flatMap(chats => Observable.from(chats)),
       this.localDataService
           .findChats()
           .flatMap(chats => Observable.from(chats))
   )
   .filter(chat => !!chat)
   .toArray()
   .map(chats => [
      ...chats.sort(
        (a, b) => 
           parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp))
      ]
   );  
}

Except when you are caching response using Subject never use subscribe inside service.

Vamshi
  • 9,194
  • 4
  • 38
  • 54
0

The unsubscribe is working correctly. The problem turns out that the following function I used, is causing it to subscribe twice for some reason:

findChats(): Observable<any[]> {
    return Observable.create((observer) => {
        this.chatSubscription2 = this.firebaseDataService.findChats().subscribe(firebaseItems => {
            this.localDataService.findChats().then((localItems: any[]) => {
                let mergedItems: any[] = [];
                if (localItems && localItems != null && firebaseItems && firebaseItems != null) {
                    for (let i: number = 0; i < localItems.length; i++) {
                        if (localItems[i] === null) {
                            localItems.splice(i, 1);
                        }
                    }
                    mergedItems = this.arrayUnique(firebaseItems.concat(localItems), true);
                } else if (firebaseItems && firebaseItems != null) {
                    mergedItems = firebaseItems;
                } else if (localItems && localItems != null) {
                    mergedItems = localItems;
                }
                mergedItems.sort((a, b) => {
                    return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
                });
                observer.next(mergedItems);
                this.checkChats(firebaseItems, localItems);
            });
        });
    });
}

For some reason the 3rd line above is called twice (even though the function is only called once). This is creating a second subscription, and the first subscription is never unsubscribed.

So it appears that the creation of the Observable is incorrect. I'm not sure what;s the correct way to create the Observable though.

Richard
  • 8,193
  • 28
  • 107
  • 228
  • I have posted a question on how to create the `Observable` so that there's only one subscription here: https://stackoverflow.com/questions/44883886/observable-create-is-called-twice – Richard Jul 03 '17 at 11:00
  • You should not use `Observable.create` . Directly return `this.firebaseDataService` after doing some transformations using map . – Vamshi Jul 03 '17 at 11:07
  • I'm not sure how to do that because the list that makes use of the `Observable` is currently subscribing to it, i.e. it needs to update dynamically. – Richard Jul 03 '17 at 11:53
  • 1
    I will provide an update to my answer with refactored method – Vamshi Jul 03 '17 at 12:32