├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc.cjs ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── src ├── __tests__ │ ├── e2e.test.ts │ └── sync.test.ts ├── index.ts └── sync.ts ├── tsconfig.json └── vite.config.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - uses: pnpm/action-setup@v2.2.4 17 | with: 18 | version: 8 19 | 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: '18' 23 | cache: 'pnpm' 24 | 25 | - run: pnpm install 26 | - run: pnpm format --check 27 | - run: pnpm build 28 | - run: pnpm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # redux-persist-transform-encrypt 3 | # 4 | 5 | # Compiled output 6 | /lib/ 7 | 8 | # 9 | # Node 10 | # 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directory 38 | node_modules 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | arrowParens: 'avoid', 4 | }; 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [5.1.1] - 2023-12-14 11 | 12 | ### Changed 13 | 14 | - Moved `default` to the end of `exports` in `package.json` 15 | 16 | ## [5.1.0] - 2023-12-09 17 | 18 | ### Changed 19 | 20 | - Replaced `main` and `types` with `exports` in `package.json` for better ESM compatibility 21 | 22 | ## [5.0.1] - 2023-10-30 23 | 24 | ### Changed 25 | 26 | - Updated dependencies 27 | - Includes upgrade to `crypto-js@4.2.0` 28 | 29 | ## [5.0.0] - 2023-03-25 30 | 31 | ### Changed 32 | 33 | - Moved package to ESM 34 | - See [here](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for more details 35 | - Updated dependencies 36 | - Test files no longer included in published package 37 | 38 | ## [4.0.0] - 2022-07-28 39 | 40 | ### Added 41 | 42 | - Added support for passing type parameters to `createTransform` 43 | - Added support for passing `TransformConfig` to `createTransform` 44 | 45 | ### Changed 46 | 47 | - Updated dependencies 48 | - Includes upgrade to `crypto-js@4.1.1` 49 | 50 | ## [3.0.1] - 2020-10-23 51 | 52 | ### Changed 53 | 54 | - Downgraded `crypto-js` to `3.1.9-1` to fix error in React Native ([#49](https://github.com/maxdeviant/redux-persist-transform-encrypt/issues/49)) 55 | 56 | ### Removed 57 | 58 | - Removed `readable-stream` dependency, as it is no longer used 59 | 60 | ## [3.0.0] - 2020-10-17 61 | 62 | ### Changed 63 | 64 | - Library has been rewritten in TypeScript 65 | - No more default export; must use the `encryptTransform` named import instead 66 | 67 | ### Removed 68 | 69 | - Removed asynchronous support, as it was never working properly in the first place 70 | - Asynchronous support is tracked by [#48](https://github.com/maxdeviant/redux-persist-transform-encrypt/issues/48) 71 | 72 | ## [2.0.1] - 2018-04-02 73 | 74 | ### Added 75 | 76 | - Added `onError` to allow for custom error handling 77 | ([@ekynoxe](https://github.com/ekynoxe) in 78 | [#20](https://github.com/maxdeviant/redux-persist-transform-encrypt/pull/20)) 79 | 80 | ## [2.0.0] - 2017-11-11 81 | 82 | ### Changed 83 | 84 | - Added support for `redux-persist@5` 85 | 86 | ## [1.0.2] - 2017-04-05 87 | 88 | ### Changed 89 | 90 | - Import only required modules from `crypto-js` 91 | ([@7rulnik](https://github.com/7rulnik) in 92 | [#14](https://github.com/maxdeviant/redux-persist-transform-encrypt/pull/14)) 93 | 94 | ## [1.0.1] - 2016-12-26 95 | 96 | ### Fixed 97 | 98 | - Fixed default export for sync transform 99 | 100 | ## [1.0.0] - 2016-12-24 101 | 102 | ### Changed 103 | 104 | - Separated sync and async transforms into separate exports 105 | ([@maxdeviant](https://github.com/maxdeviant) in 106 | [#11](https://github.com/maxdeviant/redux-persist-transform-encrypt/pull/11)) 107 | 108 | ## [0.2.0] - 2016-12-18 109 | 110 | ### Added 111 | 112 | - Added `createProgressiveEncryptor` which provides the ability to encrypt state 113 | progressively ([@stovmascript](https://github.com/stovmascript) in 114 | [#5](https://github.com/maxdeviant/redux-persist-transform-encrypt/pull/5)) 115 | 116 | ### Fixed 117 | 118 | - Updated `redux-persist` peer dependency to `v4.x.x` 119 | ([#8](https://github.com/maxdeviant/redux-persist-transform-encrypt/issues/8)) 120 | 121 | ## [0.1.2] - 2016-07-13 122 | 123 | ### Added 124 | 125 | - Added Changelog 126 | 127 | ### Fixed 128 | 129 | - Added direct `json-stringify-safe` dependency to fix failing Travis build 130 | 131 | ## [0.1.1] - 2016-07-13 132 | 133 | ### Fixed 134 | 135 | - Removed `.babelrc` from npm package 136 | ([#3](https://github.com/maxdeviant/redux-persist-transform-encrypt/issues/3)) 137 | 138 | ## [0.1.0] - 2016-05-15 139 | 140 | ### Added 141 | 142 | - Initial release 143 | 144 | [unreleased]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v5.1.1...HEAD 145 | [5.1.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v5.1.0...v5.1.1 146 | [5.1.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v5.0.1...v5.1.0 147 | [5.0.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v5.0.0...v5.0.1 148 | [5.0.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v4.0.0...v5.0.0 149 | [4.0.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v3.0.1...v4.0.0 150 | [3.0.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v3.0.0...v3.0.1 151 | [3.0.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v2.0.1...v3.0.0 152 | [2.0.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v2.0.0...v2.0.1 153 | [2.0.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v1.0.2...v2.0.0 154 | [1.0.2]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v1.0.1...v1.0.2 155 | [1.0.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v1.0.0...v1.0.1 156 | [1.0.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v0.2.0...v1.0.0 157 | [0.2.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v0.1.2...v0.2.0 158 | [0.1.2]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v0.1.1...v0.1.2 159 | [0.1.1]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/v0.1.0...v0.1.1 160 | [0.1.0]: https://github.com/maxdeviant/redux-persist-transform-encrypt/compare/576d7fc...v0.1.0 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2023 Marshall Bowers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-persist-transform-encrypt 2 | 3 | [![npm](https://img.shields.io/npm/v/redux-persist-transform-encrypt.svg?maxAge=3600)](https://www.npmjs.com/package/redux-persist-transform-encrypt) 4 | [![CI](https://github.com/maxdeviant/redux-persist-transform-encrypt/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/maxdeviant/redux-persist-transform-encrypt/actions/workflows/ci.yml) 5 | 6 | Encrypt your Redux store. 7 | 8 | ## Maintenance notice 9 | 10 | As of February 2, 2024, I will longer be maintaining `redux-persist-transform-encrypt`. 11 | 12 | I have been supporting it as best I can these past few years, but the reality of it is I have not used `redux-persist-transform-encrypt`, `redux-persist`, or Redux since 2017. 13 | 14 | Since I no longer use any of the technologies involved and don't have a good way of testing any potential changes, I am no longer in a position where I feel I can maintain this package to my desired standards. 15 | 16 | Additionally, `redux-persist` as a project also seems dead, despite an attempted change in management. 17 | 18 | ## Installation 19 | 20 | `redux-persist-transform-encrypt` must be used in conjunction with `redux-persist`, so make sure you have that installed as well. 21 | 22 | #### Yarn 23 | 24 | ```sh 25 | yarn add redux-persist-transform-encrypt 26 | ``` 27 | 28 | #### npm 29 | 30 | ```sh 31 | npm install redux-persist-transform-encrypt 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Synchronous 37 | 38 | ```js 39 | import { persistReducer } from 'redux-persist'; 40 | import { encryptTransform } from 'redux-persist-transform-encrypt'; 41 | 42 | const reducer = persistReducer( 43 | { 44 | transforms: [ 45 | encryptTransform({ 46 | secretKey: 'my-super-secret-key', 47 | onError: function (error) { 48 | // Handle the error. 49 | }, 50 | }), 51 | ], 52 | }, 53 | baseReducer 54 | ); 55 | ``` 56 | 57 | ### Asynchronous 58 | 59 | Asynchronous support was removed in v3.0.0, as it was never fully supported and is not able to be implemented correctly given the current constraints that `redux-persist` imposes on transforms. See [#48](https://github.com/maxdeviant/redux-persist-transform-encrypt/issues/48) for more details. 60 | 61 | ### Custom Error Handling 62 | 63 | The `onError` property given to the `encryptTransform` options is an optional 64 | function that receives an `Error` object as its only parameter. This allows 65 | custom error handling from the parent application. 66 | 67 | ## Secret Key Selection 68 | 69 | The `secretKey` provided to `encryptTransform` is used as a passphrase to generate a 256-bit AES key which is then used to encrypt the Redux store. 70 | 71 | You **SHOULD NOT** use a single secret key for all users of your application, as this negates any potential security benefits of encrypting the store in the first place. 72 | 73 | You **SHOULD NOT** hard-code or generate your secret key anywhere on the client, as this risks exposing the key since the JavaScript source is ultimately accessible to the end-user. 74 | 75 | If you are only interested in persisting the store over the course of a single session and then invalidating the store, consider using the user's access token or session key as the secret key. 76 | 77 | For long-term persistence, you will want to use a unique, deterministic key that is provided by the server. For example, the server could derive a hash from the user's ID and a salt (also stored server-side) and then return that hash to the client to use to decrypt the store. Placing this key retrieval behind authentication would prevent someone from accessing the encrypted store data if they are not authenticated as the user. 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-persist-transform-encrypt", 3 | "version": "5.1.1", 4 | "description": "Encrypt your Redux store.", 5 | "type": "module", 6 | "exports": { 7 | "types": "./lib/index.d.ts", 8 | "default": "./lib/index.js" 9 | }, 10 | "scripts": { 11 | "prepublishOnly": "pnpm run build", 12 | "format": "prettier --ignore-path .gitignore --write '**/*.+(js|json|md|ts|tsx)'", 13 | "build": "tsc -p . --outDir lib/", 14 | "test": "vitest run" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/maxdeviant/redux-persist-transform-encrypt.git" 19 | }, 20 | "keywords": [ 21 | "redux", 22 | "redux-persist", 23 | "redux-persist-transform", 24 | "encryption" 25 | ], 26 | "author": "Marshall Bowers ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/maxdeviant/redux-persist-transform-encrypt/issues" 30 | }, 31 | "homepage": "https://github.com/maxdeviant/redux-persist-transform-encrypt#readme", 32 | "files": [ 33 | "lib/", 34 | "index.d.ts", 35 | "!**/__tests__/" 36 | ], 37 | "peerDependencies": { 38 | "redux": ">3.0.0", 39 | "redux-persist": "^6.x.x" 40 | }, 41 | "dependencies": { 42 | "crypto-js": "^4.2.0", 43 | "json-stringify-safe": "^5.0.1" 44 | }, 45 | "devDependencies": { 46 | "@types/crypto-js": "^4.1.3", 47 | "@types/json-stringify-safe": "^5.0.2", 48 | "@types/node": "^18.15.8", 49 | "prettier": "^2.8.7", 50 | "redux": "^4.2.1", 51 | "redux-persist": "^6.0.0", 52 | "typescript": "^5.0.2", 53 | "vitest": "^0.34.6" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | crypto-js: 9 | specifier: ^4.2.0 10 | version: 4.2.0 11 | json-stringify-safe: 12 | specifier: ^5.0.1 13 | version: 5.0.1 14 | 15 | devDependencies: 16 | '@types/crypto-js': 17 | specifier: ^4.1.3 18 | version: 4.1.3 19 | '@types/json-stringify-safe': 20 | specifier: ^5.0.2 21 | version: 5.0.2 22 | '@types/node': 23 | specifier: ^18.15.8 24 | version: 18.15.8 25 | prettier: 26 | specifier: ^2.8.7 27 | version: 2.8.7 28 | redux: 29 | specifier: ^4.2.1 30 | version: 4.2.1 31 | redux-persist: 32 | specifier: ^6.0.0 33 | version: 6.0.0(redux@4.2.1) 34 | typescript: 35 | specifier: ^5.0.2 36 | version: 5.0.2 37 | vitest: 38 | specifier: ^0.34.6 39 | version: 0.34.6 40 | 41 | packages: 42 | 43 | /@babel/runtime@7.21.0: 44 | resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} 45 | engines: {node: '>=6.9.0'} 46 | dependencies: 47 | regenerator-runtime: 0.13.11 48 | dev: true 49 | 50 | /@esbuild/android-arm64@0.18.20: 51 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} 52 | engines: {node: '>=12'} 53 | cpu: [arm64] 54 | os: [android] 55 | requiresBuild: true 56 | dev: true 57 | optional: true 58 | 59 | /@esbuild/android-arm@0.18.20: 60 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} 61 | engines: {node: '>=12'} 62 | cpu: [arm] 63 | os: [android] 64 | requiresBuild: true 65 | dev: true 66 | optional: true 67 | 68 | /@esbuild/android-x64@0.18.20: 69 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} 70 | engines: {node: '>=12'} 71 | cpu: [x64] 72 | os: [android] 73 | requiresBuild: true 74 | dev: true 75 | optional: true 76 | 77 | /@esbuild/darwin-arm64@0.18.20: 78 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} 79 | engines: {node: '>=12'} 80 | cpu: [arm64] 81 | os: [darwin] 82 | requiresBuild: true 83 | dev: true 84 | optional: true 85 | 86 | /@esbuild/darwin-x64@0.18.20: 87 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} 88 | engines: {node: '>=12'} 89 | cpu: [x64] 90 | os: [darwin] 91 | requiresBuild: true 92 | dev: true 93 | optional: true 94 | 95 | /@esbuild/freebsd-arm64@0.18.20: 96 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} 97 | engines: {node: '>=12'} 98 | cpu: [arm64] 99 | os: [freebsd] 100 | requiresBuild: true 101 | dev: true 102 | optional: true 103 | 104 | /@esbuild/freebsd-x64@0.18.20: 105 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} 106 | engines: {node: '>=12'} 107 | cpu: [x64] 108 | os: [freebsd] 109 | requiresBuild: true 110 | dev: true 111 | optional: true 112 | 113 | /@esbuild/linux-arm64@0.18.20: 114 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} 115 | engines: {node: '>=12'} 116 | cpu: [arm64] 117 | os: [linux] 118 | requiresBuild: true 119 | dev: true 120 | optional: true 121 | 122 | /@esbuild/linux-arm@0.18.20: 123 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} 124 | engines: {node: '>=12'} 125 | cpu: [arm] 126 | os: [linux] 127 | requiresBuild: true 128 | dev: true 129 | optional: true 130 | 131 | /@esbuild/linux-ia32@0.18.20: 132 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} 133 | engines: {node: '>=12'} 134 | cpu: [ia32] 135 | os: [linux] 136 | requiresBuild: true 137 | dev: true 138 | optional: true 139 | 140 | /@esbuild/linux-loong64@0.18.20: 141 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} 142 | engines: {node: '>=12'} 143 | cpu: [loong64] 144 | os: [linux] 145 | requiresBuild: true 146 | dev: true 147 | optional: true 148 | 149 | /@esbuild/linux-mips64el@0.18.20: 150 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} 151 | engines: {node: '>=12'} 152 | cpu: [mips64el] 153 | os: [linux] 154 | requiresBuild: true 155 | dev: true 156 | optional: true 157 | 158 | /@esbuild/linux-ppc64@0.18.20: 159 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} 160 | engines: {node: '>=12'} 161 | cpu: [ppc64] 162 | os: [linux] 163 | requiresBuild: true 164 | dev: true 165 | optional: true 166 | 167 | /@esbuild/linux-riscv64@0.18.20: 168 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} 169 | engines: {node: '>=12'} 170 | cpu: [riscv64] 171 | os: [linux] 172 | requiresBuild: true 173 | dev: true 174 | optional: true 175 | 176 | /@esbuild/linux-s390x@0.18.20: 177 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} 178 | engines: {node: '>=12'} 179 | cpu: [s390x] 180 | os: [linux] 181 | requiresBuild: true 182 | dev: true 183 | optional: true 184 | 185 | /@esbuild/linux-x64@0.18.20: 186 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} 187 | engines: {node: '>=12'} 188 | cpu: [x64] 189 | os: [linux] 190 | requiresBuild: true 191 | dev: true 192 | optional: true 193 | 194 | /@esbuild/netbsd-x64@0.18.20: 195 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} 196 | engines: {node: '>=12'} 197 | cpu: [x64] 198 | os: [netbsd] 199 | requiresBuild: true 200 | dev: true 201 | optional: true 202 | 203 | /@esbuild/openbsd-x64@0.18.20: 204 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} 205 | engines: {node: '>=12'} 206 | cpu: [x64] 207 | os: [openbsd] 208 | requiresBuild: true 209 | dev: true 210 | optional: true 211 | 212 | /@esbuild/sunos-x64@0.18.20: 213 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} 214 | engines: {node: '>=12'} 215 | cpu: [x64] 216 | os: [sunos] 217 | requiresBuild: true 218 | dev: true 219 | optional: true 220 | 221 | /@esbuild/win32-arm64@0.18.20: 222 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} 223 | engines: {node: '>=12'} 224 | cpu: [arm64] 225 | os: [win32] 226 | requiresBuild: true 227 | dev: true 228 | optional: true 229 | 230 | /@esbuild/win32-ia32@0.18.20: 231 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} 232 | engines: {node: '>=12'} 233 | cpu: [ia32] 234 | os: [win32] 235 | requiresBuild: true 236 | dev: true 237 | optional: true 238 | 239 | /@esbuild/win32-x64@0.18.20: 240 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} 241 | engines: {node: '>=12'} 242 | cpu: [x64] 243 | os: [win32] 244 | requiresBuild: true 245 | dev: true 246 | optional: true 247 | 248 | /@jest/schemas@29.6.3: 249 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} 250 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 251 | dependencies: 252 | '@sinclair/typebox': 0.27.8 253 | dev: true 254 | 255 | /@jridgewell/sourcemap-codec@1.4.15: 256 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 257 | dev: true 258 | 259 | /@sinclair/typebox@0.27.8: 260 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 261 | dev: true 262 | 263 | /@types/chai-subset@1.3.4: 264 | resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} 265 | dependencies: 266 | '@types/chai': 4.3.9 267 | dev: true 268 | 269 | /@types/chai@4.3.9: 270 | resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} 271 | dev: true 272 | 273 | /@types/crypto-js@4.1.3: 274 | resolution: {integrity: sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==} 275 | dev: true 276 | 277 | /@types/json-stringify-safe@5.0.2: 278 | resolution: {integrity: sha512-NJdNTdatSiYvxdPLwgQImChxTfPECXYkdB6wC71dO3GCz2z/s/ZkkHXHaUlSVvnbsRUQg8Al3cT8Wp08wMF3zA==} 279 | dev: true 280 | 281 | /@types/node@18.15.8: 282 | resolution: {integrity: sha512-kzGNJZ57XEH7RdckxZ7wfRjB9hgZABF+NLgR1B2zogUvV0gmK0/60VYA4yb4oKZckPiiJlmmfpdqTfCN0VRX+Q==} 283 | dev: true 284 | 285 | /@vitest/expect@0.34.6: 286 | resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} 287 | dependencies: 288 | '@vitest/spy': 0.34.6 289 | '@vitest/utils': 0.34.6 290 | chai: 4.3.10 291 | dev: true 292 | 293 | /@vitest/runner@0.34.6: 294 | resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} 295 | dependencies: 296 | '@vitest/utils': 0.34.6 297 | p-limit: 4.0.0 298 | pathe: 1.1.1 299 | dev: true 300 | 301 | /@vitest/snapshot@0.34.6: 302 | resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} 303 | dependencies: 304 | magic-string: 0.30.5 305 | pathe: 1.1.1 306 | pretty-format: 29.7.0 307 | dev: true 308 | 309 | /@vitest/spy@0.34.6: 310 | resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} 311 | dependencies: 312 | tinyspy: 2.2.0 313 | dev: true 314 | 315 | /@vitest/utils@0.34.6: 316 | resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} 317 | dependencies: 318 | diff-sequences: 29.6.3 319 | loupe: 2.3.7 320 | pretty-format: 29.7.0 321 | dev: true 322 | 323 | /acorn-walk@8.2.0: 324 | resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} 325 | engines: {node: '>=0.4.0'} 326 | dev: true 327 | 328 | /acorn@8.11.2: 329 | resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 330 | engines: {node: '>=0.4.0'} 331 | hasBin: true 332 | dev: true 333 | 334 | /ansi-styles@5.2.0: 335 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 336 | engines: {node: '>=10'} 337 | dev: true 338 | 339 | /assertion-error@1.1.0: 340 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 341 | dev: true 342 | 343 | /cac@6.7.14: 344 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 345 | engines: {node: '>=8'} 346 | dev: true 347 | 348 | /chai@4.3.10: 349 | resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} 350 | engines: {node: '>=4'} 351 | dependencies: 352 | assertion-error: 1.1.0 353 | check-error: 1.0.3 354 | deep-eql: 4.1.3 355 | get-func-name: 2.0.2 356 | loupe: 2.3.7 357 | pathval: 1.1.1 358 | type-detect: 4.0.8 359 | dev: true 360 | 361 | /check-error@1.0.3: 362 | resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} 363 | dependencies: 364 | get-func-name: 2.0.2 365 | dev: true 366 | 367 | /crypto-js@4.2.0: 368 | resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} 369 | dev: false 370 | 371 | /debug@4.3.4: 372 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 373 | engines: {node: '>=6.0'} 374 | peerDependencies: 375 | supports-color: '*' 376 | peerDependenciesMeta: 377 | supports-color: 378 | optional: true 379 | dependencies: 380 | ms: 2.1.2 381 | dev: true 382 | 383 | /deep-eql@4.1.3: 384 | resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} 385 | engines: {node: '>=6'} 386 | dependencies: 387 | type-detect: 4.0.8 388 | dev: true 389 | 390 | /diff-sequences@29.6.3: 391 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 392 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 393 | dev: true 394 | 395 | /esbuild@0.18.20: 396 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} 397 | engines: {node: '>=12'} 398 | hasBin: true 399 | requiresBuild: true 400 | optionalDependencies: 401 | '@esbuild/android-arm': 0.18.20 402 | '@esbuild/android-arm64': 0.18.20 403 | '@esbuild/android-x64': 0.18.20 404 | '@esbuild/darwin-arm64': 0.18.20 405 | '@esbuild/darwin-x64': 0.18.20 406 | '@esbuild/freebsd-arm64': 0.18.20 407 | '@esbuild/freebsd-x64': 0.18.20 408 | '@esbuild/linux-arm': 0.18.20 409 | '@esbuild/linux-arm64': 0.18.20 410 | '@esbuild/linux-ia32': 0.18.20 411 | '@esbuild/linux-loong64': 0.18.20 412 | '@esbuild/linux-mips64el': 0.18.20 413 | '@esbuild/linux-ppc64': 0.18.20 414 | '@esbuild/linux-riscv64': 0.18.20 415 | '@esbuild/linux-s390x': 0.18.20 416 | '@esbuild/linux-x64': 0.18.20 417 | '@esbuild/netbsd-x64': 0.18.20 418 | '@esbuild/openbsd-x64': 0.18.20 419 | '@esbuild/sunos-x64': 0.18.20 420 | '@esbuild/win32-arm64': 0.18.20 421 | '@esbuild/win32-ia32': 0.18.20 422 | '@esbuild/win32-x64': 0.18.20 423 | dev: true 424 | 425 | /fsevents@2.3.2: 426 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 427 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 428 | os: [darwin] 429 | requiresBuild: true 430 | dev: true 431 | optional: true 432 | 433 | /get-func-name@2.0.2: 434 | resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} 435 | dev: true 436 | 437 | /json-stringify-safe@5.0.1: 438 | resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} 439 | dev: false 440 | 441 | /jsonc-parser@3.2.0: 442 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} 443 | dev: true 444 | 445 | /local-pkg@0.4.3: 446 | resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} 447 | engines: {node: '>=14'} 448 | dev: true 449 | 450 | /loupe@2.3.7: 451 | resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} 452 | dependencies: 453 | get-func-name: 2.0.2 454 | dev: true 455 | 456 | /magic-string@0.30.5: 457 | resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} 458 | engines: {node: '>=12'} 459 | dependencies: 460 | '@jridgewell/sourcemap-codec': 1.4.15 461 | dev: true 462 | 463 | /mlly@1.4.2: 464 | resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} 465 | dependencies: 466 | acorn: 8.11.2 467 | pathe: 1.1.1 468 | pkg-types: 1.0.3 469 | ufo: 1.3.1 470 | dev: true 471 | 472 | /ms@2.1.2: 473 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 474 | dev: true 475 | 476 | /nanoid@3.3.6: 477 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 478 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 479 | hasBin: true 480 | dev: true 481 | 482 | /p-limit@4.0.0: 483 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} 484 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 485 | dependencies: 486 | yocto-queue: 1.0.0 487 | dev: true 488 | 489 | /pathe@1.1.1: 490 | resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} 491 | dev: true 492 | 493 | /pathval@1.1.1: 494 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} 495 | dev: true 496 | 497 | /picocolors@1.0.0: 498 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 499 | dev: true 500 | 501 | /pkg-types@1.0.3: 502 | resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} 503 | dependencies: 504 | jsonc-parser: 3.2.0 505 | mlly: 1.4.2 506 | pathe: 1.1.1 507 | dev: true 508 | 509 | /postcss@8.4.31: 510 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 511 | engines: {node: ^10 || ^12 || >=14} 512 | dependencies: 513 | nanoid: 3.3.6 514 | picocolors: 1.0.0 515 | source-map-js: 1.0.2 516 | dev: true 517 | 518 | /prettier@2.8.7: 519 | resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} 520 | engines: {node: '>=10.13.0'} 521 | hasBin: true 522 | dev: true 523 | 524 | /pretty-format@29.7.0: 525 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} 526 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 527 | dependencies: 528 | '@jest/schemas': 29.6.3 529 | ansi-styles: 5.2.0 530 | react-is: 18.2.0 531 | dev: true 532 | 533 | /react-is@18.2.0: 534 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} 535 | dev: true 536 | 537 | /redux-persist@6.0.0(redux@4.2.1): 538 | resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==} 539 | peerDependencies: 540 | react: '>=16' 541 | redux: '>4.0.0' 542 | peerDependenciesMeta: 543 | react: 544 | optional: true 545 | dependencies: 546 | redux: 4.2.1 547 | dev: true 548 | 549 | /redux@4.2.1: 550 | resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} 551 | dependencies: 552 | '@babel/runtime': 7.21.0 553 | dev: true 554 | 555 | /regenerator-runtime@0.13.11: 556 | resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} 557 | dev: true 558 | 559 | /rollup@3.29.4: 560 | resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} 561 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 562 | hasBin: true 563 | optionalDependencies: 564 | fsevents: 2.3.2 565 | dev: true 566 | 567 | /siginfo@2.0.0: 568 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 569 | dev: true 570 | 571 | /source-map-js@1.0.2: 572 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 573 | engines: {node: '>=0.10.0'} 574 | dev: true 575 | 576 | /stackback@0.0.2: 577 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 578 | dev: true 579 | 580 | /std-env@3.4.3: 581 | resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} 582 | dev: true 583 | 584 | /strip-literal@1.3.0: 585 | resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} 586 | dependencies: 587 | acorn: 8.11.2 588 | dev: true 589 | 590 | /tinybench@2.5.1: 591 | resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} 592 | dev: true 593 | 594 | /tinypool@0.7.0: 595 | resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} 596 | engines: {node: '>=14.0.0'} 597 | dev: true 598 | 599 | /tinyspy@2.2.0: 600 | resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} 601 | engines: {node: '>=14.0.0'} 602 | dev: true 603 | 604 | /type-detect@4.0.8: 605 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 606 | engines: {node: '>=4'} 607 | dev: true 608 | 609 | /typescript@5.0.2: 610 | resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} 611 | engines: {node: '>=12.20'} 612 | hasBin: true 613 | dev: true 614 | 615 | /ufo@1.3.1: 616 | resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} 617 | dev: true 618 | 619 | /vite-node@0.34.6(@types/node@18.15.8): 620 | resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} 621 | engines: {node: '>=v14.18.0'} 622 | hasBin: true 623 | dependencies: 624 | cac: 6.7.14 625 | debug: 4.3.4 626 | mlly: 1.4.2 627 | pathe: 1.1.1 628 | picocolors: 1.0.0 629 | vite: 4.5.0(@types/node@18.15.8) 630 | transitivePeerDependencies: 631 | - '@types/node' 632 | - less 633 | - lightningcss 634 | - sass 635 | - stylus 636 | - sugarss 637 | - supports-color 638 | - terser 639 | dev: true 640 | 641 | /vite@4.5.0(@types/node@18.15.8): 642 | resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} 643 | engines: {node: ^14.18.0 || >=16.0.0} 644 | hasBin: true 645 | peerDependencies: 646 | '@types/node': '>= 14' 647 | less: '*' 648 | lightningcss: ^1.21.0 649 | sass: '*' 650 | stylus: '*' 651 | sugarss: '*' 652 | terser: ^5.4.0 653 | peerDependenciesMeta: 654 | '@types/node': 655 | optional: true 656 | less: 657 | optional: true 658 | lightningcss: 659 | optional: true 660 | sass: 661 | optional: true 662 | stylus: 663 | optional: true 664 | sugarss: 665 | optional: true 666 | terser: 667 | optional: true 668 | dependencies: 669 | '@types/node': 18.15.8 670 | esbuild: 0.18.20 671 | postcss: 8.4.31 672 | rollup: 3.29.4 673 | optionalDependencies: 674 | fsevents: 2.3.2 675 | dev: true 676 | 677 | /vitest@0.34.6: 678 | resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} 679 | engines: {node: '>=v14.18.0'} 680 | hasBin: true 681 | peerDependencies: 682 | '@edge-runtime/vm': '*' 683 | '@vitest/browser': '*' 684 | '@vitest/ui': '*' 685 | happy-dom: '*' 686 | jsdom: '*' 687 | playwright: '*' 688 | safaridriver: '*' 689 | webdriverio: '*' 690 | peerDependenciesMeta: 691 | '@edge-runtime/vm': 692 | optional: true 693 | '@vitest/browser': 694 | optional: true 695 | '@vitest/ui': 696 | optional: true 697 | happy-dom: 698 | optional: true 699 | jsdom: 700 | optional: true 701 | playwright: 702 | optional: true 703 | safaridriver: 704 | optional: true 705 | webdriverio: 706 | optional: true 707 | dependencies: 708 | '@types/chai': 4.3.9 709 | '@types/chai-subset': 1.3.4 710 | '@types/node': 18.15.8 711 | '@vitest/expect': 0.34.6 712 | '@vitest/runner': 0.34.6 713 | '@vitest/snapshot': 0.34.6 714 | '@vitest/spy': 0.34.6 715 | '@vitest/utils': 0.34.6 716 | acorn: 8.11.2 717 | acorn-walk: 8.2.0 718 | cac: 6.7.14 719 | chai: 4.3.10 720 | debug: 4.3.4 721 | local-pkg: 0.4.3 722 | magic-string: 0.30.5 723 | pathe: 1.1.1 724 | picocolors: 1.0.0 725 | std-env: 3.4.3 726 | strip-literal: 1.3.0 727 | tinybench: 2.5.1 728 | tinypool: 0.7.0 729 | vite: 4.5.0(@types/node@18.15.8) 730 | vite-node: 0.34.6(@types/node@18.15.8) 731 | why-is-node-running: 2.2.2 732 | transitivePeerDependencies: 733 | - less 734 | - lightningcss 735 | - sass 736 | - stylus 737 | - sugarss 738 | - supports-color 739 | - terser 740 | dev: true 741 | 742 | /why-is-node-running@2.2.2: 743 | resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} 744 | engines: {node: '>=8'} 745 | hasBin: true 746 | dependencies: 747 | siginfo: 2.0.0 748 | stackback: 0.0.2 749 | dev: true 750 | 751 | /yocto-queue@1.0.0: 752 | resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} 753 | engines: {node: '>=12.20'} 754 | dev: true 755 | -------------------------------------------------------------------------------- /src/__tests__/e2e.test.ts: -------------------------------------------------------------------------------- 1 | import { Action, legacy_createStore as createStore, Store } from 'redux'; 2 | import { 3 | Persistor, 4 | persistReducer, 5 | persistStore, 6 | Storage, 7 | } from 'redux-persist'; 8 | import { describe, expect, it } from 'vitest'; 9 | import { encryptTransform } from '../sync.js'; 10 | 11 | const inMemoryStorage = () => { 12 | const memory = new Map(); 13 | 14 | const storage: Storage = { 15 | getItem: key => 16 | new Promise(resolve => { 17 | resolve(memory.get(key)); 18 | }), 19 | setItem: (key, item) => 20 | new Promise(resolve => { 21 | memory.set(key, item); 22 | resolve(); 23 | }), 24 | removeItem: key => 25 | new Promise(resolve => { 26 | memory.delete(key); 27 | resolve(); 28 | }), 29 | }; 30 | 31 | return { storage, memory }; 32 | }; 33 | 34 | const persistStoreAsync = (store: Store) => 35 | new Promise(resolve => { 36 | const persistor = persistStore(store, void 0, () => resolve(persistor)); 37 | }); 38 | 39 | describe('end-to-end', () => { 40 | it('works with `redux-persist`', async () => { 41 | type CounterAction = Action<'INCREMENT'> | Action<'DECREMENT'>; 42 | 43 | const counter = (state = { count: 0 }, action: CounterAction) => { 44 | switch (action.type) { 45 | case 'INCREMENT': 46 | return { ...state, count: state.count + 1 }; 47 | case 'DECREMENT': 48 | return { ...state, count: state.count - 1 }; 49 | default: 50 | return state; 51 | } 52 | }; 53 | 54 | const { storage } = inMemoryStorage(); 55 | const transform = encryptTransform({ 56 | secretKey: 'e2e-test', 57 | }); 58 | 59 | const key = 'counter'; 60 | const persistedCounter = persistReducer( 61 | { 62 | key, 63 | storage, 64 | transforms: [transform], 65 | }, 66 | counter 67 | ); 68 | 69 | const store = createStore(persistedCounter); 70 | const persistor = await persistStoreAsync(store); 71 | 72 | store.dispatch({ type: 'INCREMENT' }); 73 | store.dispatch({ type: 'INCREMENT' }); 74 | 75 | await persistor.flush(); 76 | 77 | const rehydratedStore = createStore(persistedCounter); 78 | await persistStoreAsync(rehydratedStore); 79 | 80 | expect(rehydratedStore.getState()).toStrictEqual(store.getState()); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/__tests__/sync.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { encryptTransform } from '../sync.js'; 3 | 4 | describe('sync', () => { 5 | describe('configuration', () => { 6 | it('throws an error when not given any configuration', () => { 7 | expect(() => 8 | encryptTransform(void 0 as any) 9 | ).toThrowErrorMatchingInlineSnapshot( 10 | `"redux-persist-transform-encrypt: No configuration provided."` 11 | ); 12 | }); 13 | 14 | it('throws an error when not given a secret key', () => { 15 | expect(() => 16 | encryptTransform({ 17 | secretKey: void 0 as any, 18 | }) 19 | ).toThrowErrorMatchingInlineSnapshot( 20 | `"redux-persist-transform-encrypt: No secret key provided."` 21 | ); 22 | }); 23 | }); 24 | 25 | describe('inbound', () => { 26 | it('encrypts the inbound state', () => { 27 | const transform = encryptTransform({ 28 | secretKey: 'redux-is-awesome', 29 | }); 30 | 31 | const key = 'testState'; 32 | const state = { 33 | foo: 'bar', 34 | }; 35 | 36 | const newState = transform.in(state, key, state); 37 | expect(typeof newState).toBe('string'); 38 | expect(newState).not.toEqual(state); 39 | }); 40 | }); 41 | 42 | describe('outbound', () => { 43 | it('decrypts the outbound state', () => { 44 | const transform = encryptTransform({ 45 | secretKey: 'redux-is-awesome', 46 | }); 47 | 48 | const key = 'testState'; 49 | const state = { 50 | foo: 'bar', 51 | }; 52 | 53 | const encryptedState = transform.in(state, key, state); 54 | const decryptedState = transform.out(encryptedState, key, encryptedState); 55 | expect(decryptedState).toEqual(state); 56 | }); 57 | 58 | it('calls the error handler when the decryption fails', () => { 59 | const inboundTransform = encryptTransform({ 60 | secretKey: 'redux-is-awesome', 61 | }); 62 | 63 | const key = 'testState'; 64 | const state = { 65 | foo: 'bar', 66 | }; 67 | 68 | const encryptedState = inboundTransform.in(state, key, state); 69 | 70 | const handleError = vi.fn(); 71 | const transform = encryptTransform({ 72 | secretKey: 'different-secret', 73 | onError: handleError, 74 | }); 75 | 76 | transform.out(encryptedState, key, encryptedState); 77 | expect(handleError).toHaveBeenCalledWith( 78 | new Error( 79 | 'redux-persist-transform-encrypt: Could not decrypt state. Please verify that you are using the correct secret key.' 80 | ) 81 | ); 82 | }); 83 | }); 84 | 85 | describe('round trip', () => { 86 | it('works when the inbound state is already a JSON string', () => { 87 | const transform = encryptTransform({ 88 | secretKey: 'redux-is-awesome', 89 | }); 90 | 91 | const key = 'testState'; 92 | const state = JSON.stringify({ 93 | foo: 'bar', 94 | }); 95 | 96 | const encryptedState = transform.in(state, key, state); 97 | const decryptedState = transform.out(encryptedState, key, encryptedState); 98 | expect(decryptedState).toStrictEqual(state); 99 | }); 100 | 101 | it('works when the inbound state is a plain string', () => { 102 | const transform = encryptTransform({ 103 | secretKey: 'redux-is-awesome', 104 | }); 105 | 106 | const key = 'testState'; 107 | const state = 'Hello World'; 108 | 109 | const encryptedState = transform.in(state, key, state); 110 | const decryptedState = transform.out(encryptedState, key, encryptedState); 111 | expect(decryptedState).toStrictEqual(state); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sync.js'; 2 | -------------------------------------------------------------------------------- /src/sync.ts: -------------------------------------------------------------------------------- 1 | import Aes from 'crypto-js/aes.js'; 2 | import CryptoJsCore from 'crypto-js/core.js'; 3 | import stringify from 'json-stringify-safe'; 4 | import { createTransform } from 'redux-persist'; 5 | import type { TransformConfig } from 'redux-persist/lib/createTransform'; 6 | 7 | export interface EncryptTransformConfig { 8 | secretKey: string; 9 | onError?: (err: Error) => void; 10 | } 11 | 12 | const makeError = (message: string) => 13 | new Error(`redux-persist-transform-encrypt: ${message}`); 14 | 15 | export const encryptTransform = ( 16 | config: EncryptTransformConfig, 17 | transformConfig?: TransformConfig 18 | ) => { 19 | if (typeof config === 'undefined') { 20 | throw makeError('No configuration provided.'); 21 | } 22 | 23 | const { secretKey } = config; 24 | if (!secretKey) { 25 | throw makeError('No secret key provided.'); 26 | } 27 | 28 | const onError = 29 | typeof config.onError === 'function' ? config.onError : console.warn; 30 | 31 | return createTransform( 32 | (inboundState, _key) => 33 | Aes.encrypt(stringify(inboundState), secretKey).toString(), 34 | (outboundState, _key) => { 35 | if (typeof outboundState !== 'string') { 36 | return onError(makeError('Expected outbound state to be a string.')); 37 | } 38 | 39 | try { 40 | const decryptedString = Aes.decrypt(outboundState, secretKey).toString( 41 | CryptoJsCore.enc.Utf8 42 | ); 43 | if (!decryptedString) { 44 | throw new Error('Decrypted string is empty.'); 45 | } 46 | 47 | try { 48 | return JSON.parse(decryptedString); 49 | } catch { 50 | return onError(makeError('Failed to parse state as JSON.')); 51 | } 52 | } catch { 53 | return onError( 54 | makeError( 55 | 'Could not decrypt state. Please verify that you are using the correct secret key.' 56 | ) 57 | ); 58 | } 59 | }, 60 | transformConfig 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "module": "ESNext", 9 | "moduleResolution": "nodenext", 10 | "noImplicitReturns": true, 11 | "noUncheckedIndexedAccess": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "target": "ESNext" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | root: 'src/', 6 | clearMocks: true, 7 | }, 8 | }); 9 | --------------------------------------------------------------------------------