Storage Listener ile Bütün Sekme ve Pencerelerde Auto Login / Logout

Toprak Erzurumluoğlu
4 min readMar 26, 2024

--

Merhaba, bu yazımda oturum açmış ya da tam tersi oturumunu sonlandırmış bir kullanıcının açık olan bütün sekme veya pencerelere bu durumunu yansıtmak üzere bir örnek oluşturacağım.

Photo by magnezis magnestic on Unsplash

Normal şartlarda bir kullanıcının zaten oturum açmışsa login ekranını ya da oturumunu sonlandırmışsa da login olunarak erişelebilecek bir ekranı görmemesi gerekmektedir.

Ancak bazen ihtiyaç doğrultusunda diğer bütün sekme ya da pencerelerde de kişi oturumunu sonladırmışsa sistemden atmak isteriz. (Ki zaten sistemden atmasak da token ı artık geçersiz olduğu için bir işlem yapmak isterse sistem onu dışarı çıkaracaktır, fakat işlem yapmazsa bir süre o ekranda gezinmesi mümkün olabilir.) Bu gibi durumlar için javascript dünyasında kullanıcı bilgileri gibi bilgileri localStorage’da tuttuğumuz için localStorage’taki değişikliği dinleyerek bu işlemleri yapabiliriz.

Şimdi gelelim bunu nasıl yapacağımıza. Çok genel bir javascript kodunu ben Angular için uyarlayacağım. Her platform için rahatlıkla uygulayabilirsiniz.

Login, logout işlemleri olduğu için çok fazla kod olacak. Bütün kod kalabalığına burada yer vermeyeceğim. Uygulamanın tamamına github üzerinden ulaşabilirsiniz.

Amacım yazıyı çok fazla kod kalabalığına sokmadan sadece odaktaki konuyu anlatmaya çalışmak.

Uygulama Hakkında

  • Kullanıcının giriş yapması için Login Ekranımız var.
  • Oturum açan kullanıcının görebileceği Dashboard Ekranımız var.
  • Oturum açma / sonladırma işlemlerinin yapıldığu AuthService’ imiz var.
  • Oturum açmamış kullanıcının içeriye girememesi, oturum açmış kullanıcının da giriş ekranını görememesi için Guardlar yazıyoruz.
  • Oturum bilgilerini localStorage’da tuttuğumuz için sayfa yenilemelerinde initial metodlarımız var (AuthServis’teki oturum verilerini beslemek için)
  • Ve son olarak Storage Listener Servisimiz var oturum açıldığında ya da sonlandırıldığında localStorage’ ı dinleyerek ona göre işlemlerimizi yapacağız.

AuthService

Biraz AuthService’te yapılan işlemlerden bahsedeyim.

#user$: BehaviorSubject<IUser> = new BehaviorSubject<IUser>(undefined);

private ve abone olunabilir bir user bilgisi tutuyorum.

export const USER: string = 'user';
set user(user: IUser) {
this.#user$.next(user);
if (!!user) {
localStorage.setItem(USER, JSON.stringify(user));
} else {
localStorage.removeItem(USER);
}
}

set user ile #user$ datasını besliyorum. Kullanıcıyı da localStorage’ da ‘user’ keyi ile tutuyorum.

getUserByEmailAndPassword = (loginCredential: ILogin): IUser | undefined => {
const userWithPassword: IUser | undefined = USERS.find(
(user) => user.password === loginCredential.password
);

if (!userWithPassword) {
return undefined;
}
const { password, ...user } = userWithPassword;
this.user = user;
return user;
};

Ve kullanıcı login durumunu kontrol etmek için;

get isLoggedIn() {
return !!this.user;
}

kullanıcı varsa true yoksa false dönen bir get metodu yazıyorum.

Yine Auth Service’te APP Initialize’ da kullanmak üzere aşağıdaki gibi bir function yazıyorum. Amaç storage’daki bilgiyle Serviceteki user datasını doldurmak.

loginControl = () => {
const userAsString = localStorage.getItem(USER);
const user = this.utils.isJsonString<IUser>(userAsString);
this.user = user;
};

Ek olarak Initial’ StorageListenerService’ i de başlatıyorumki uygulama yüklenir yüklenmez bu dinleme başlasın.

StorageListenerService

Bu servis uygulama initial olunca çalışmaya başlayacak. Zaten bu servisin herhangi bir başka yerde bağımlılığı olmayacak. İşi yalnızca Storage’ ı dinlemek.

Aşağıdaki gibi bu işlemi başlatıyoruz.

window.addEventListener('storage', function(){
...
});

Bütün logic bu üç noktanın olduğu yerde olacak.

Şimdi bunu bir service içinde kullanalım. Aşağıdaki gibi servisin constructor’ına yazabiliriz bu metodu.

constructor() {
this.runListener();
}

runListener = () => {
window.addEventListener('storage', function(){
...
});
}

Ben storage’da yalnızca ‘user’ key’ini kontrol etmek istediğim için şu şekilde benim için uygun olacak.

constructor() {
this.runListener();
}

runListener = () => {
window.addEventListener('storage', this.listen);
}

listen = (event: StorageEvent) => {
const { key } = event;
if (key === USER) {
...
}
}

Bir oturum açma işlemi ya da sonlandırma işlemi oldu. Bunu localStorage’ dan anlıyoruz ama #user$ bilgisi sadece o pencerede dolu diğerlerinde dolu değil. Bu nedenle initialda çalıştırdığım loginControl’ ü burada da yapmam gerekiyor ki #user$ değişkenim dolsun.

constructor(
private auth: AuthService
) {
this.runListener();
}

runListener = () => {
window.addEventListener('storage', this.listen);
}

listen = (event: StorageEvent) => {
const { key } = event;
if (key === USER) {
this.auth.loginControl();
...
}
}

Artık AuthService’teki #user$ verimiz dolu, get isLoggedIn metodundan istediğimiz sonucu alabiliriz.

Artık kodumuzu nihayete erdirelim.

constructor(
private router: Router,
private auth: AuthService
) {
this.runListener();
}

runListener = () => {
window.addEventListener('storage', this.listen);
}

listen = (event: StorageEvent) => {
const { key } = event;
if (key === USER) {
this.auth.loginControl();
if (this.auth.isLoggedIn) {
this.router.navigateByUrl('dashboard');
} else {
this.router.navigateByUrl('login');
}
}
}

LocalStorage’ daki ‘user’ bilgisinde bir değişiklik olduğunda (şu an ekleme ve silme üzerine kurguladık ancak update ihtimali varsa onun için biraz revize etmek gerekecek) loginControl metodu #user$ datasını dolduracak ya da boşaltacak. isLoggedIn metodundan dönen true ya da false değerine göre de bütün pencere ve sekmeler duruma göre diğer sayfalara yönlenecektir.

Bu şekilde bir uygulama bana göre gayet kolay ve kullanıcı dostu. Umarım sizler için de faydalı ve açıklayıcı bir yazı olmuştur.

--

--

Responses (1)