每日一句來源:Daily English
There is no doubt that good things will come, and when it comes later, it will be a surprise. -- 無庸置疑,好事情總會到來。而當他來晚時,也不失是一種驚喜。
昨天我們知道了基本的推波方法,我們今天進一步修正登入登出的使用者token存取,不要造成不必要的推波。
我們整理一下昨天的邏輯,以及問題
關於第三點,我們要怎麼識別呢?
筆者參考了先前我們在使用Gmail寄信時的紀錄方法
這是筆者的Gmail登入的狀況,我們可以發現,在google他們記錄了我們的登入裝置,我們可以透過取得登入裝置來決定要刪除的token內容。
我們可以注意到google記錄了什麼,他記錄了裝置名稱、裝置地點、最後的使用時間
navigator.userAgent
取得接著我們開始實做
先修改cloud-messaging-service.ts
constructor(
private _http: BaseHttpService,
@Inject(PLATFORM_ID) private platformId: Object) { }
// 這個方法在ssr執行沒有任何意義,我們直接把它忽略掉
@onlyOnBrowser('platformId')
getPermission(user: DocumentHandler<UserModel>) {
return Observable.fromPromise(
this.messaging.requestPermission()
.then(() => {
console.log('允許授權推波!');
return this.messaging.getToken();
}))
.switchMap(token => {
console.log(token);
this.token = token;
return this.saveTokenLocal(token, user.collection('fcmTokens'));
})
.switchMap(() => {
console.log('set');
this.fcmTockenHandler = user.collection('fcmTokens').document(this.token);
return this.fcmTockenHandler.set({
token: this.token,
userAgent: navigator.userAgent
});
})
.catch((err) => {
console.log('不給推波', err);
return Observable.throw(new Error('不給推波'));
});
}
我們整體都使用Rx做包裝,以便我們管理時間順序,實作
接著時做saveTokenLocal
的方法
saveTokenLocal(token, tokensRef: CollectionHandler<{}>) {
const localToken = localStorage.getItem(tokenName);
const userAgent = navigator.userAgent;
localStorage.setItem(tokenName, token);
// if is empty, it maybe first time or manual delete
if (!localToken) {
// 取得這個人所有的token,把所有userAgent相同的刪除
console.log('!');
return tokensRef.get({ isKey: true, queryFn: ref => ref.where('userAgent', '==', userAgent) })
.take(1)
.map((tokens: any[]) => tokens.filter(obj => obj.id !== token) // 排除掉當前的token
.map((i: any) => tokensRef.delete(i.id))
);
} else if (localToken !== token) {
// 很少會發生這樣的情況,這個情況發生在使用者手動移除權限,但是沒有移除local storage
return tokensRef.delete(localToken);
}
// 若都沒發生就丟一個null出去
return Observable.of(null);
}
如此一來當使用者登入後我們的資料庫就不會有多餘的token了,但是這個方法有個缺陷,如果使用者用了兩台相同的裝置進行登入,那userAgent會取得相同的結果,會導致前一台的推波被蓋掉,因為太少會有這類狀況,這邊不考慮。
接著我們實作登出時把token清除,一樣不再ssr時執行
@onlyOnBrowser('platformId')
deleteToken() {
return this.fcmTockenHandler.delete()
.do(() => localStorage.removeItem(tokenName))
.mergeMap(() => Observable.fromPromise(this.messaging.deleteToken(this.token)));
}
接著我們到auth.services修改一下
this.fireUser$
.do(() => this._block.block('登入中'))
.switchMap(user => {
return this.updateUser(user);
})
.switchMap(key => {
this.currentUserHandler = this.userHandler.document<UserModel>(key);
return this.currentUserHandler.get();
})
.do(user => {
this._block.unblock();
this.user = user;
this.currentUser$.next(user);
})
// 這邊當我們取得使用者時,去導頁,並且要求權限
.filter(u => !!u)
.switchMap(user => {
this.returnUrl(user);
return this._cms.getPermission(this.currentUserHandler);
})
.subscribe();
登出部分
signOut() {
return Observable.fromPromise(this._afAuth.auth.signOut())
.do(() => this._router.navigate(environment.nonAuthenticationUrl))
.mergeMap(() => this._cms.deleteToken());
}
登出後導頁,並且同時清除token
如此就修正完畢,我們不會存有多餘的token在伺服器導致有多餘的推波了。
我們可以手動關閉授權,從新取得試試看,可以在這裡關閉
chrome://settings/content/notifications
把自己的網域移除掉即可。
今天我們修正了多餘的token的問題,避免有很多不必要的推波的浪費,但是我們實際上還是會需要手動清除的狀況,筆者建議大家要給使用者一個管理裝置的功能,一來可以讓使用者知道有哪些裝置是有登入過的,且尚未登出,二來是可以讓使用這手動管理登入的狀況、推波的情形,甚至透過realtime的特性,我們也可以讓使用者把其他裝置登出的功能(沒有重新整理的狀態下)。
今天筆者把專案升級到5.2了,有使用的夥伴要注意~