Skip to content

多頁面共享sessionStorage並同步至vuex - 範例

Notifications You must be signed in to change notification settings

guahsu/Vue-sessionStorage

Repository files navigation

範例連結

https://guahsu.io/Vue-sessionStorage/dist/

多頁籤共享sessionStorage並同步至vuex

再vue專案中,如果有使用到vuex來做狀態管理,
那很常會遇到一件事情就是重新整理後就遺失了原本的狀態,
這裡紀錄我的解決方案:使多頁籤共享SessionStorage並透過同步達到Vuex資料的持久化。

常見的登入判斷

我們常在頁面判斷(vue-route: beforeEach)中寫下了像這樣的程式:

router.beforeEach((to, from, next) => {
  const token = // 各種存放token的地方
  // 頁面跳轉前如果沒token就把帶到登入頁
  if (!token) {
    next({ path: '/login' })
  } else {
    next()
  }
})

Token存哪?

登入時取回的token我們有幾個地方可以選擇存放:

  1. Vuex
    優點:可以跟系統內其他vuex互相統一使用vuex的api來操作token,頁面關閉隨之銷毀
    缺點:當重新整理或開新分頁時就會被登出了(vuex被清除)

  2. Cookie
    優點:可由server端寫入,省掉前端寫入的工(?)
    缺點:可被user關閉、最高寫入4k,如果每次請求帶的話100張圖要get就會要多400k流量。

  3. LocalStorage
    優點:存放不會消失,不需要擔心頁面關閉或重整/新開分頁等問題
    缺點:因為永久存放(除非user手動清除),必須加上時效性自行判斷,或特殊操作時要將其手動清除

  4. SessionStorage
    優點:頁籤開著的時候會一直存在著,即使是重新整理,頁籤關閉時會自動銷毀被儲存的資訊
    缺點:只在當前頁籤存在,開新分頁並不會被傳遞

看起來不論選用哪個都會有優點或缺點,基於Vue專案Vuex是避不太了的基礎上,剩下只能擇一了。

Vuex + SessionStorage

這裡我選用Vuex + sessionStorage做搭配,
把重要的資訊(User資訊、Token..等)在sessionStorage中做一份備份,
當頁面重整時再透過sessionStorage重新賦值給Vuex,
來達到程式碼管理的統一性(各組件邏輯一樣都統一使用Vuex做操作),
也可以避免當頁面被關閉或重新開啟瀏覽器時要對LocalStorage資訊再做一次判斷與驗證的工。

但缺點如上面所說,當User開新分頁時,新長出來的頁籤並不會有sessionStorage所儲存的資料,
也就不會被同步到Vuex,為了解決這個問題我們得想辦法讓sessionStorage的資訊可以被分享傳遞到新的頁籤中。

sessionStorage多頁籤共享

我在查找方案時看到這篇文章Sharing sessionStorage between tabs for secure multi-tab authentication
網路上也有一些譯文,對我幫助非常多,我最終的解決方案也是基於這篇文章來做的。

主要的作法簡述

我有寫一個簡單的的登入範例,可以參考上方的連結

  1. 登入的邏輯取回token時,將token存於sessionStorage中
userLogin () {
  // ...登入邏輯
  xxxApi.userLogin().then(res => {
    window.sessionStorage.setItem('token', res.token)
    // ...跳轉至首頁
  })
}
  1. 再Vue專案裡的index.html中加上這段,我替程式碼加上了註解
/* 註:此範例為複製全部sessionStorage內資料 */
(() => {
  // 判斷當前頁面是否存在sessionStorage
  if (!window.sessionStorage.length) {
    // 若不存在則加上一個localStorage Item, Key = getSessionStorageData
    window.localStorage.setItem('getSessionStorageData', Date.now())
  }
  // 增加window監聽事件'storage'
  window.addEventListener('storage', (event) => {
    // 如果偵聽到的事件是key是getSessionStorageData
    if (event.key === 'getSessionStorageData') {
      // 再新增一個localstorage Item, key = sessionStorageData,value就是當前的sessionStorage
      window.localStorage.setItem('sessionStorageData', JSON.stringify(window.sessionStorage))
      // 刪除localstorage中key = sessionStorageData的item
      window.localStorage.removeItem('sessionStorageData')
    }
    // 如果偵聽到的事件是key是sessionStorageData, 且當前的sessionStorage是空的
    if (event.key === 'sessionStorageData' && !window.sessionStorage.length) {
      // 把sessionStorageData的資料parse出來
      const data = JSON.parse(event.newValue)
      // 賦值到當前頁面的sessionStorage中
      for (let key in data) {
        window.sessionStorage.setItem(key, data[key])
      }
    }
  })
})()

整體流程簡述

  1. 判斷頁面是否存在sessionStorage,並加上store事件的偵聽
  2. 若不存在,就把原本的sessionStorage內的資料設定到localStorage中並取名為sessionStorageData
  3. 若storage事件偵聽到Key為sessionStorageData的事件,就把其內容透過for賦值到頁面sessionStorage中

比較難懂的是這段

if (event.key === 'getSessionStorageData') {
  // 再新增一個localstorage Item, key = sessionStorageData,value就是當前的sessionStorage
  window.localStorage.setItem('sessionStorageData', JSON.stringify(window.sessionStorage))
  // 刪除localstorage中key = sessionStorageData的item
  window.localStorage.removeItem('sessionStorageData')
}

為什麼設定完後又直接刪除?

注意,我們有為window添加偵聽事件window.addEventListener('storage', ..)
這意味著每次storage事件被戳到的時候都會執行,所以當setItem('sessionStorage')的當下,也會接收到事件,
代表著window.localStorage.removeItem('sessionStorage')其實是發生在sessionStorage被寫入之後的事情了,
也因此才能達到同一個步驟做寫入與刪除,不留下過程中的localStorage資訊。

或許你可能會跟我一樣好奇,新開的頁面為啥會有sessionStorage可以提供複製?

查詢API(Using the Web Storage API | MDN)後才知道,各頁籤只要是同域名,
有對storage動作時全部都會被連動,沒有詳閱過API真的還不知道storage事件可以做到同域名的狀態監測。

storage事件的測試

關於這點可以簡單地做一個測試,隨便開兩個同網域的頁籤後,分別都開啟開發者工具:

  1. 在A頁籤中寫下
    window.addEventListener('storage', (event) => console.log(event))
  2. 在B頁籤寫下
    window.localStorage.setItem(’storageTest’, ‘test’)
  3. 再回頭看A頁籤,就會發現已經被偵聽到storage事件了!

透過這樣的事件偵聽,就可以達到藉由localStorage來傳遞sessionStorage的目的。

sessionStorage與vuex的同步,修改登入判斷邏輯

所以現在,修改router.beforeEach中的邏輯讓前往頁面的同時使sessionStorage的資料與vuex做同步。

router.beforeEach((to, from, next) => {
  // 一開始登入時寫進去的token
  const token = window.sessionStorage.getItem('token')
  if (to.matched.some(res => res.meta.requiresAuth)) {
    if (token) {
      // 同步到vuex中
      store.dispatch('root/setToken', token)
      next()
    } else {
      next({ name: 'Login' })
    }
  } else {
    next()
  }
})

關於登出

我們也可以同樣利用storage的特性,
當登出時同步清除所有頁籤的sessionStorage並reload頁面:

(() => {
  if (!window.sessionStorage.length) {
    window.localStorage.setItem('getSessionStorageData', Date.now())
  }
  window.addEventListener('storage', (event) => {
    if (event.key === 'getSessionStorageData') {
      window.localStorage.setItem('sessionStorageData', JSON.stringify(window.sessionStorage))
      window.localStorage.removeItem('sessionStorageData')
    }
    if (event.key === 'sessionStorageData' && !window.sessionStorage.length) {
      const data = JSON.parse(event.newValue)
      for (let key in data) {
        window.sessionStorage.setItem(key, data[key])
      }
    }
    // ========== 加下面這段 ==========
    if (event.key === 'logout') {
      // 接收到logout事件時,進行sessionStorage的清除與頁面reload
      window.sessionStorage.clear()
      window.location.reload()
    }
  })
})()

其他

即便這樣看起來很OK了,但還是有些缺陷,如同原文作者有提到的:
於Chrome, FireFox的恢復分頁功能會將sessionStorage的資料一併恢復,
這可能會導致一些安全性的問題(不過相較於localStorage, 這應該不算問題?)

另外要注意的就是,這個資料是在分頁被開啟的同時獲取的,
意味著如果你的sessionStorage儲存著經常被異動的資料,必須得再多寫一些事件去做攔截與複製,
因為storage事件不會偵聽到其他頁面的sessionStorage異動。

以上為個人測試與實作的紀錄分享,如果有錯誤或更好的做法再請各位大大指教了:) !

參考資料

  1. Sharing sessionStorage between tabs for secure multi-tab authentication
  2. Using the Web Storage API | MDN

About

多頁面共享sessionStorage並同步至vuex - 範例

https://guahsu.io/Vue-sessionStorage/dist/

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages