Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions.
firebaseのfirestoreをアプリケーションで利用しているときに、ログアウトすると、snapshot listenerのエラーが次のように出た場合の対処方法です。
Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions.
下ではVuexを使った例ですが、他のケースでも応用可能です。
Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions.
このエラーがログアウトのときに出る場合、snapshotの購読をやめる前にログアウトしてしまっている可能性があります。
手順1:onSnapshot()メソッドを使っている箇所を探す
onSnapshot()メソッドを使用してクエリの結果をリッスンしている場合、つまり購読(subscribe)している場合、ログアウトの前にかならず購読を解除(unsubscribe)する必要があります。
購読を解除するためには、onSnapshot()メソッドを変数に格納している必要があります。
それができているかを確認するために、まずはonSnapshot()メソッドを使っている箇所を探します。
手順2:onSnapshot()メソッドを変数に格納する
下の例では、playlistsというコレクションの中のplaylistIdというドキュメントの中にあるvideosというコレクションの中のドキュメントを作成日順で取得するメソッドです。
mutations: {
setUnsubscribe(state, unsubscribe) {
state.unsubscribe = unsubscribe
},
stopSnapshotListener(state) {
state.unsubscribe()
},
...
actions: {
...
getVideos({ state, commit }, playlistId) {
const collection = db.collection('playlists').doc(playlistId).collection('videos').orderBy('createdAt')
const unsubscribe = collection.onSnapshot(snap => {
let results = []
snap.docs.forEach(doc => {
results.push({...doc.data()})
})
state.videos = results
})
commit('setVideos', state.videos)
commit('setUnsubscribe', unsubscribe)
},
onSnapshot()メソッドの部分をunsubscribeという変数に格納しています。
Vuexの場合、stateに登録するときは関数なので初期値をオブジェクトで登録します。
// サブスク解除
unsubscribe: {},
手順3:snapshotの購読を解除する
snapshotの購読を解除するには、上で作成した変数をメソッドとして呼び出すだけです。
Vueの場合、Vueインスタンスが破壊される前のdeforeDestroyの時点で呼び出せばOKです。
beforeDestroy() {
his.$store.state.unsubscribe()
},
Vuexの場合、mutationsを使う場合は、上のようにセットして、commitで呼び出します。
beforeDestroy() {
// this.$store.state.unsubscribe()
this.$store.commit('stopSnapshotListener')
projectAuth.signOut()
},
それでもエラーが出る場合
うまく動作しない場合は、this.$store.state.unsubscribe()の値をコンソールで確認しましょう。
ファンクションが返ってくる場合は購読解除できていません。
beforeDestroy()でしっかり反応しているか確かめます。
もしファンクションが返って来ている場合、メソッドで定義してみましょう。
methods: {
async logout() {
(async () => {
// 関数のとき
if(typeof this.$store.state.unsubscribe === 'function') {
await this.$store.state.unsubscribe()
}
})()
.then(() => {
projectAuth.signOut()
})
.then(() => {
this.$store.commit('reset')
this.$store.state.step = 1
this.$router.push({ name: 'Form' })
})
.catch(err => console.log('エラー',err.message))
}
},
不格好ですが、非同期処理を順番に実行できるようにしています。
nullが返されると「this.$store.state.unsubscribe in not a function」と怒られるので、関数かどうかを確認して実行するようにしています。