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

Detailed steps to install MYSQL8.0 on CentOS7.6

1. Generally, mariadb is installed by default in ...

Summary of JS tips for creating or filling arrays of arbitrary length

Table of contents Preface Direct filling method f...

Web Design Experience: Self-righteous Web Designers

1. Trash or Classic? Web technology updates very ...

Detailed deployment of docker+gitlab+gitlab-runner

environment Server: centos7 Client: window Deploy...

Use of Linux cal command

1. Command Introduction The cal (calendar) comman...

Vue implements scroll loading table

Table of contents Achieve results Rolling load kn...

Nginx uses Lua+Redis to dynamically block IP

1. Background In our daily website maintenance, w...

Node+socket realizes simple chat room function

This article shares the specific code of node+soc...

How to Rename a Group of Files at Once on Linux

In Linux, we usually use the mv command to rename...

100-1% of the content on the website is navigation

Website, (100-1)% of the content is navigation 1....

Summary of various common join table query examples in MySQL

This article uses examples to describe various co...

vue dynamic component

Table of contents 1. Component 2. keep-alive 2.1 ...

Detailed explanation of mysql.user user table in Mysql

MySQL is a multi-user managed database that can a...

Vue implements carousel animation

This article example shares the specific code of ...