How to use Typescript to encapsulate local storage

How to use Typescript to encapsulate local storage

Preface

Local storage is a technology that is often used in front-end development, but the official API is inconvenient to use, and some functions do not provide us with corresponding APIs, such as setting expiration time. This article does not intend to introduce knowledge related to the concept of local storage, but aims to encapsulate a useful local storage class using typescript.

Local storage usage scenarios

  • Token storage after user login
  • Storage of user information
  • Communication between different pages
  • Persistence of project status management, such as redux persistence, vuex persistence, etc.
  • Performance optimization, etc.
  • ...

Problems in use

  • The official API is not very user-friendly (too lengthy), and all data is stored in the form of strings, and data type conversion is required for access.
    • localStorage.setItem(key, value)
    • ...
  • Unable to set expiration time
  • Stored in plain text, some relatively private information can be easily viewed by users in the browser
  • Same-origin projects share local storage space, which may cause data confusion

Solution

The solutions to the above problems are encapsulated in a class and exposed to users through a simple interface for direct calling. The class will encapsulate the following functionality:

  • Data type conversion
  • Expiration time
  • Data encryption
  • Unified naming convention

Functionality

 // storage.ts

enum StorageType {
  l = 'localStorage',
  s = 'sessionStorage'
}

class MyStorage {
  storage: Storage

  constructor(type: StorageType) {
    this.storage = type === StorageType.l ? window.localStorage : window.sessionStorage
  }

  set(
    key: string,
    value: any
  ) {
    const data = JSON.stringify(value)
    this.storage.setItem(key, data)
  }

  get(key: string) {
    const value = this.storage.getItem(key)
    if (value) {
      return JSON.parse(value)
  }

  delete(key: string) {
    this.storage.removeItem(key)
  }

  clear() {
    this.storage.clear()
  }
}

const LStorage = new MyStorage(StorageType.l)
const SStorage = new MyStorage(StorageType.s)

export { LStorage, SStorage }

The above code simply implements the basic functions of local storage, and completes the data type conversion operation during access internally. The usage is as follows:

 import { LStorage, SStorage } from './storage'

...

LStorage.set('data', { name: 'zhangsan' })
LStorage.get('data') // { name: 'zhangsan' }

Add expiration time

The idea of ​​setting the expiration time is: when setting, add the expires field to the data to record the data storage time. When getting, compare the retrieved expires with the current time. If the current time is greater than expires, it means it has expired. At this time, clear the data record and return null. The expires type can be boolean type and number type. The default is false, that is, no expiration time is set. When the user sets it to true, the default expiration time is 1 year. When the user sets it to a specific value, the expiration time is the value set by the user. The code is implemented as follows:

 interface IStoredItem {
  value: any
  expires?: number
}
...
set(
    key: string,
    value: any,
    expires: boolean | number = false,
  ) {
    const source: IStoredItem = { value: null }
    if (expires) {
    // The default expiration time is 1 year, which can be adjusted according to actual conditions source.expires =
        new Date().getTime() +
        (expires === true ? 1000 * 60 * 60 * 24 * 365 : expires)
    }
    source.value = value
    const data = JSON.stringify(source)
    this.storage.setItem(key, data)
  }
  
  get(key: string) {
    const value = this.storage.getItem(key)
    if (value) {
      const source: IStoredItem = JSON.parse(value)
      const expires = source.expires
      const now = new Date().getTime()
      if (expires && now > expires) {
        this.delete(key)
        return null
      }

      return source.value
    }
  }

Add data encryption

The crypto-js package is used for encryption. The two private methods encrypt and decrypt are encapsulated in the class to handle data encryption and decryption. Of course, users can also set whether to encrypt data through the encryption field. The default is true, which means that encryption is enabled by default. In addition, the current environment can be obtained through process.env.NODE_ENV. If it is a development environment, it will not be encrypted to facilitate development and debugging. The code is implemented as follows:

 import CryptoJS from 'crypto-js'

const SECRET_KEY = 'nkldsx@#45#VDss9'
const IS_DEV = process.env.NODE_ENV === 'development'
...
class MyStorage {
  ...
  
  private encrypt(data: string) {
    return CryptoJS.AES.encrypt(data, SECRET_KEY).toString()
  }

  private decrypt(data: string) {
    const bytes = CryptoJS.AES.decrypt(data, SECRET_KEY)
    return bytes.toString(CryptoJS.enc.Utf8)
  }
  
  set(
    key: string,
    value: any,
    expires: boolean | number = false,
    encryption = true
  ) {
    const source: IStoredItem = { value: null }
    if (expires) {
      source.expires =
        new Date().getTime() +
        (expires === true ? 1000 * 60 * 60 * 24 * 365 : expires)
    }
    source.value = value
    const data = JSON.stringify(source)
    this.storage.setItem(key, IS_DEV ? data : encryption ? this.encrypt(data) : data
    )
  }
  
  get(key: string, encryption = true) {
    const value = this.storage.getItem(key)
    if (value) {
      const source: IStoredItem = JSON.parse(value)
      const expires = source.expires
      const now = new Date().getTime()
      if (expires && now > expires) {
        this.delete(key)
        return null
      }

      return IS_DEV
        ? source.value
        : encryption
        ? this.decrypt(source.value)
        : source.value
    }
  }
  
}

Add naming conventions

You can standardize the naming by adding a prefix in front of the key, such as a composite key of project name_version number_key type. This naming convention can be set freely, either by a constant or by concatenating the name and version in package.json. The code is as follows:

 const config = require('../../package.json')

const PREFIX = config.name + '_' + config.version + '_'

...
class MyStorage {

  // Synthesize key
  private synthesisKey(key: string) {
    return PREFIX + key
  }
  
  ...
  
 set(
    key: string,
    value: any,
    expires: boolean | number = false,
    encryption = true
  ) {
    ...
    this.storage.setItem(
      this.synthesisKey(key),
      IS_DEV ? data : encryption ? this.encrypt(data) : data
    )
  }
  
  get(key: string, encryption = true) {
    const value = this.storage.getItem(this.synthesisKey(key))
    ...
  }

}

Complete code

 import CryptoJS from 'crypto-js'
const config = require('../../package.json')

enum StorageType {
  l = 'localStorage',
  s = 'sessionStorage'
}

interface IStoredItem {
  value: any
  expires?: number
}

const SECRET_KEY = 'nkldsx@#45#VDss9'
const PREFIX = config.name + '_' + config.version + '_'
const IS_DEV = process.env.NODE_ENV === 'development'

class MyStorage {
  storage: Storage

  constructor(type: StorageType) {
    this.storage =
      type === StorageType.l ? window.localStorage : window.sessionStorage
  }

  private encrypt(data: string) {
    return CryptoJS.AES.encrypt(data, SECRET_KEY).toString()
  }

  private decrypt(data: string) {
    const bytes = CryptoJS.AES.decrypt(data, SECRET_KEY)
    return bytes.toString(CryptoJS.enc.Utf8)
  }

  private synthesisKey(key: string) {
    return PREFIX + key
  }

  set(
    key: string,
    value: any,
    expires: boolean | number = false,
    encryption = true
  ) {
    const source: IStoredItem = { value: null }
    if (expires) {
      source.expires =
        new Date().getTime() +
        (expires === true ? 1000 * 60 * 60 * 24 * 365 : expires)
    }
    source.value = value
    const data = JSON.stringify(source)
    this.storage.setItem(
      this.synthesisKey(key),
      IS_DEV ? data : encryption ? this.encrypt(data) : data
    )
  }

  get(key: string, encryption = true) {
    const value = this.storage.getItem(this.synthesisKey(key))
    if (value) {
      const source: IStoredItem = JSON.parse(value)
      const expires = source.expires
      const now = new Date().getTime()
      if (expires && now > expires) {
        this.delete(key)
        return null
      }

      return IS_DEV
        ? source.value
        : encryption
        ? this.decrypt(source.value)
        : source.value
    }
  }

  delete(key: string) {
    this.storage.removeItem(this.synthesisKey(key))
  }

  clear() {
    this.storage.clear()
  }
}

const LStorage = new MyStorage(StorageType.l)
const SStorage = new MyStorage(StorageType.s)

export { LStorage, SStorage }

Summarize

This is the end of this article on how to use Typescript to encapsulate local storage. For more relevant Typescript encapsulation local storage content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

<<:  Detailed explanation of the solution to the problem of merging rows and columns in tables in HTML

>>:  Recommend a cool flashing alarm button

Recommend

How to set PATH environment variable in Linux system (3 methods)

1. In Windows system, many software installations...

Explanation of the steps for Tomcat to support https access

How to make tomcat support https access step: (1)...

How to install MySQL 5.7.28 binary mode under CentOS 7.4

Linux system version: CentOS7.4 MySQL version: 5....

How to use Docker to build a development environment (Windows and Mac)

Table of contents 1. Benefits of using Docker 2. ...

CentOS IP connection network implementation process diagram

1. Log in to the system and enter the directory: ...

JS asynchronous code unit testing magic Promise

Table of contents Preface Promise chaining MDN Er...

Token verification login in Vue project (front-end part)

This article example shares the specific code of ...

Vue realizes simple effect of running light

This article shares the specific code of Vue to a...

Tomcat8 uses cronolog to split Catalina.Out logs

background If the catalina.out log file generated...