Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
034134a08f | |||
7a9ab68402 | |||
4e6ab26f0d | |||
c3d6c79fe3 | |||
2b1e7d6816 | |||
e14e1ffa74 | |||
414128db90 | |||
132f23a028 | |||
f27014af14 |
86
.github/workflows/build.yaml
vendored
Normal file
86
.github/workflows/build.yaml
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
name: Build
|
||||
run-name: Build
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build NPM Project
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node
|
||||
volumes:
|
||||
- '/mnt/swarm/gitea/runner/cache:/cache'
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: ztimson/actions/clone@develop
|
||||
|
||||
- name: Restore node_modules
|
||||
uses: ztimson/actions/cache/restore@develop
|
||||
with:
|
||||
key: node_modules
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm i
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build
|
||||
|
||||
- name: Test
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: ztimson/actions/cache@develop
|
||||
with:
|
||||
key: node_modules
|
||||
pattern: node_modules
|
||||
|
||||
- name: Cache Artifacts
|
||||
uses: ztimson/actions/cache@develop
|
||||
with:
|
||||
pattern: dist
|
||||
tag:
|
||||
name: Tag Version
|
||||
needs: build
|
||||
if: ${{github.ref_name}} == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
container: node
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: ztimson/actions/clone@develop
|
||||
|
||||
- name: Get Version Number
|
||||
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
|
||||
|
||||
- name: Tag Version
|
||||
uses: ztimson/actions/tag@develop
|
||||
with:
|
||||
tag: ${{env.VERSION}}
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
needs: build
|
||||
if: ${{github.ref_name}} == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node
|
||||
volumes:
|
||||
- '/mnt/swarm/gitea/runner/cache:/cache'
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: ztimson/actions/clone@develop
|
||||
|
||||
- name: Restore Artifacts
|
||||
uses: ztimson/actions/cache/restore@develop
|
||||
|
||||
- name: Upload to Registry
|
||||
uses: ztimson/actions/npm/publish@develop
|
||||
|
||||
- name: Upload to NPM
|
||||
uses: ztimson/actions/npm/publish@develop
|
||||
with:
|
||||
owner: ztimson
|
||||
registry: https://registry.npmjs.org/
|
||||
token: ${{secrets.NPM_TOKEN}}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
||||
|
||||
# compiled output
|
||||
/tmp
|
||||
/out-tsc
|
||||
/lib
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2023 Zakary Timson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
158
README.md
158
README.md
@ -1,26 +1,126 @@
|
||||
# WebStorage Decorators
|
||||
A Javascript library that adds property decorators to sync a class property with the local or session storage. Useful
|
||||
for persisting themes or local settings over reloads or maintaining filters/search options in the current user session.
|
||||
<!-- Header -->
|
||||
<div id="top" align="center">
|
||||
<br />
|
||||
|
||||
<!-- Logo -->
|
||||
<img src="https://git.zakscode.com/repo-avatars/60b47463c877db0d03d41f265a24df35882f246e95df786de38a19fbb617d310" alt="Logo" width="200" height="200">
|
||||
|
||||
<!-- Title -->
|
||||
### webstorage-decorators
|
||||
|
||||
<!-- Description -->
|
||||
TypeScript: Sync variables with localStorage
|
||||
|
||||
<!-- Repo badges -->
|
||||
[](https://git.zakscode.com/ztimson/webstorage-decorators/tags)
|
||||
[](https://git.zakscode.com/ztimson/webstorage-decorators/pulls)
|
||||
[](https://git.zakscode.com/ztimson/webstorage-decorators/issues)
|
||||
|
||||
<!-- Links -->
|
||||
|
||||
---
|
||||
<div>
|
||||
<a href="https://git.zakscode.com/ztimson/webstorage-decorators/releases" target="_blank">Release Notes</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/webstorage-decorators/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
- [WebStorage-Decorators](#top)
|
||||
- [About](#about)
|
||||
- [Examples](#examples)
|
||||
- [Built With](#built-with)
|
||||
- [Setup](#setup)
|
||||
- [Production](#production)
|
||||
- [Development](#development)
|
||||
- [Documentation](#documentation)
|
||||
- [Decorators](#decorators)
|
||||
- [WebStorageOptions](#webstorageoptions)
|
||||
- [License](#license)
|
||||
|
||||
## About
|
||||
|
||||
**Deprecated:** Please use the replacement library [var-persist](https://git.zakscode.com/ztimson/var-persist).
|
||||
|
||||
WebStorage-Decorators is a library that adds property decorators to sync a class property with the local or session storage. It is useful for persisting state through reloads without making any changes to existing code.
|
||||
|
||||
This library has some caveats that [var-persist](https://git.zakscode.com/ztimson/var-persist) rectifies:
|
||||
- Only supports decorators
|
||||
- Impure functions can make changes to data which do not get synced
|
||||
- [Proto]types and functions are lost
|
||||
|
||||
**Disclaimer:** JavaScript's decorators are currently undergoing changes to the API overseen by [TC39](https://tc39.es) and currently have no support for property decorators. [Experimental decorators](https://www.typescriptlang.org/tsconfig#experimentalDecorators) must be enabled to work properly.
|
||||
|
||||
### Examples
|
||||
|
||||
## Quick Setup
|
||||
1. Install with: `npm install --save webstorage-decorators`
|
||||
2. Add the decorator to your property and use as normal!
|
||||
```typescript
|
||||
import {LocalStorage, SessionStorage} from 'webstorage-decorators';
|
||||
|
||||
export class MyCustomClass {
|
||||
@LocalStorage({key: 'site_theme', default: 'light_theme'}) theme: string;
|
||||
@SessionStorage({encryptWith: config.entryptionKey}) thisUser: User;
|
||||
@LocalStorage('light_theme', {key: 'site_theme'}) theme: string;
|
||||
@SessionStorage(null, {encryptWith: config.entryptionKey}) thisUser: User;
|
||||
@SessionStorage() searchBar: string;
|
||||
|
||||
constructor() {
|
||||
constructor() {
|
||||
console.log(this.theme, localStorage.getItem('theme')); // Output: 'light_theme', 'light_theme'
|
||||
console.log(this.user, localStorage.getItem('user')); // Output: null, undefined
|
||||
user = {first: 'John', last: 'Smith', ...}
|
||||
console.log(this.user, this.user == localStorage.getItem('user')); // Output: {first: 'John', last: 'Smith', ...}, true
|
||||
console.log(this.user, localStorage.getItem('user')); // Output: {first: 'John', last: 'Smith', ...}, **Some encrypted value**
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Built With
|
||||
[](https://typescriptlang.org/)
|
||||
|
||||
## Setup
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="production" style="display: inline">
|
||||
Production
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
|
||||
#### Instructions
|
||||
1. Install persist: `npm i webstorage-decorators`
|
||||
2. Enable decorators inside `tsconfig.json`:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Import & use, see [examples above](#examples)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="development" style="display: inline">
|
||||
Development
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
|
||||
#### Instructions
|
||||
1. Install the dependencies: `npm i`
|
||||
2. Build library & docs: `npm build`
|
||||
3. Run unit tests: `npm test`
|
||||
|
||||
</details>
|
||||
|
||||
## Documentation
|
||||
|
||||
### Decorators
|
||||
@ -35,42 +135,10 @@ export class MyCustomClass {
|
||||
| Options | Description |
|
||||
|---------|-------------|
|
||||
| default | Default value, same as decorator's first argument |
|
||||
| encryptWith | Secret key to encrypt stored values with |
|
||||
| key | Key to reference value inside local/session storage (Defaults to the property name) |
|
||||
|
||||
## Caveats
|
||||
## License
|
||||
|
||||
### Custom Functions
|
||||
You can technically store anything inside local/session storage however everything is serialized using javascript's JSON,
|
||||
so anything extra (prototypes, functions, etc) will be lost. However if you provide a default value, it will be copied &
|
||||
the data injected, giving you a workaround to accessing static properties (Does not work with arrays).
|
||||
Copyright © 2023 Zakary Timson | Available under MIT Licensing
|
||||
|
||||
```typescript
|
||||
class Person {
|
||||
constructor(public first: string, public last: string) { }
|
||||
fullName() { return `${this.last}, ${this.first}`; }
|
||||
}
|
||||
|
||||
LocalStorage.setItem('example', '{"first": "John", "last": "Smith"}');
|
||||
@LocalStorage(null) example!: Person;
|
||||
console.log(example.fullName()) // ERROR: fullName function doesn't exist
|
||||
|
||||
LocalStorage.setItem('example2', '{"first": "John", "last": "Smith"}');
|
||||
@LocalStorage(new Person(null, null)) example2!: Person;
|
||||
console.log(example2.fullName()) // Works because we have a default object to copy type from
|
||||
```
|
||||
|
||||
### Impure Functions
|
||||
Impure functions don't use the Object's setter preventing the storage from being updated. To prevent this use a pure
|
||||
function or save it manually by reading the variable. (Reading triggers change detection & save if there are differences)
|
||||
```typescript
|
||||
@LocalStorage([1, 2]) example: number[];
|
||||
example.push(3) // Impure & won't update storage
|
||||
console.log(localStorage.getItem('example')) // Output: [1, 2];
|
||||
example; // Trigger save
|
||||
console.log(localStorage.getItem('example')) // Output: [1, 2, 3];
|
||||
|
||||
// OR
|
||||
|
||||
example = example.concat([3]); // Pure function requires you to use the setter triggering automatic saving
|
||||
```
|
||||
See the [license](./LICENSE) for more information.
|
||||
|
@ -1,12 +1,16 @@
|
||||
module.exports = {
|
||||
"reporters": ["default"],
|
||||
"roots": [
|
||||
"<rootDir>/tests"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
||||
],
|
||||
"transform": {
|
||||
".+\\.(ts)$": "ts-jest"
|
||||
},
|
||||
}
|
||||
"reporters": ["default", "jest-junit"],
|
||||
"roots": [
|
||||
"<rootDir>/tests"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
||||
],
|
||||
"transform": {
|
||||
".+\\.(ts)$": "ts-jest"
|
||||
},
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts'
|
||||
],
|
||||
};
|
||||
|
1
lib/index.d.ts
vendored
1
lib/index.d.ts
vendored
@ -1 +0,0 @@
|
||||
export * from './webstorage';
|
@ -1,2 +0,0 @@
|
||||
export * from './webstorage';
|
||||
//# sourceMappingURL=index.js.map
|
@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
41
lib/webstorage.d.ts
vendored
41
lib/webstorage.d.ts
vendored
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Options to be used with WebStorage decorators
|
||||
*/
|
||||
export interface WebStorageOptions {
|
||||
/** Default value to provide if storage is empty */
|
||||
default?: any;
|
||||
/** Key to prevent plain text storage **/
|
||||
encryptWith?: string;
|
||||
/** Key to save under */
|
||||
key?: string;
|
||||
}
|
||||
/**
|
||||
* Automatically syncs localStorage with the decorated property.
|
||||
*
|
||||
* **Example**
|
||||
* ```
|
||||
* class Example {
|
||||
* @LocalStorage() lastLogin: string;
|
||||
* @LocalStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param defaultValue Default value to return if property does no exist inside localStorage.
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
export declare function LocalStorage(defaultValue?: any, opts?: WebStorageOptions): (target: object, key: string) => void;
|
||||
/**
|
||||
* Automatically syncs sessionStorage with the decorated property.
|
||||
*
|
||||
* **Example**
|
||||
* ```
|
||||
* class Example {
|
||||
* @SessionStorage() lastLogin: string;
|
||||
* @SessionStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
export declare function SessionStorage(defaultValue?: any, opts?: WebStorageOptions): (target: object, key: string) => void;
|
@ -1,98 +0,0 @@
|
||||
import * as crypto from 'crypto-js';
|
||||
/**
|
||||
* Automatically syncs localStorage with the decorated property.
|
||||
*
|
||||
* **Example**
|
||||
* ```
|
||||
* class Example {
|
||||
* @LocalStorage() lastLogin: string;
|
||||
* @LocalStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param defaultValue Default value to return if property does no exist inside localStorage.
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
export function LocalStorage(defaultValue, opts = {}) {
|
||||
opts.default = defaultValue;
|
||||
return decoratorBuilder(localStorage, opts);
|
||||
}
|
||||
/**
|
||||
* Automatically syncs sessionStorage with the decorated property.
|
||||
*
|
||||
* **Example**
|
||||
* ```
|
||||
* class Example {
|
||||
* @SessionStorage() lastLogin: string;
|
||||
* @SessionStorage(false, {key: '_hideMenu'}) hideMenu: boolean;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
export function SessionStorage(defaultValue, opts = {}) {
|
||||
opts.default = defaultValue;
|
||||
return decoratorBuilder(sessionStorage, opts);
|
||||
}
|
||||
/**
|
||||
* **Internal use only**
|
||||
*
|
||||
* Fetch variable from storage & take care of any defaults, object definitions, encryption & serialization
|
||||
*
|
||||
* @param storage Web Storage API
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
function fromStorage(storage, opts) {
|
||||
let storedVal = storage.getItem(opts.key);
|
||||
if (storedVal == null || storedVal == "" || storedVal == "undefined") {
|
||||
if (opts.default == null)
|
||||
return null;
|
||||
if (opts.default != 'object')
|
||||
return opts.default;
|
||||
if (opts.default.constructor != null)
|
||||
return Object.assign(new opts.default.constructor(), opts.default);
|
||||
let temp = Object.assign({}, opts.default);
|
||||
Object.setPrototypeOf(temp, Object.getPrototypeOf(opts.default));
|
||||
return temp;
|
||||
}
|
||||
storedVal = JSON.parse(storedVal);
|
||||
if (opts.encryptWith != null)
|
||||
storedVal = JSON.parse(crypto.AES.decrypt(storedVal, opts.encryptWith).toString(crypto.enc.Utf8));
|
||||
if (typeof storedVal != 'object' || !Array.isArray(storedVal))
|
||||
return storedVal;
|
||||
if (opts.default != null && opts.default.constructor != null)
|
||||
return Object.assign(new opts.default.constructor(), opts.default, storedVal);
|
||||
return Object.assign({}, opts.default, storedVal);
|
||||
}
|
||||
/**
|
||||
* **Internal use only**
|
||||
*
|
||||
* Overrides the properties getter/setter methods to read/write from the provided storage endpoint.
|
||||
*
|
||||
* @param storage Web Storage API
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
function decoratorBuilder(storage, opts) {
|
||||
return function (target, key) {
|
||||
if (!opts.key)
|
||||
opts.key = key;
|
||||
let field = fromStorage(storage, opts);
|
||||
Object.defineProperty(target, key, {
|
||||
get: function () {
|
||||
if (field != fromStorage(storage, { key: opts.key, encryptWith: opts.encryptWith }))
|
||||
target[key] = field;
|
||||
return field;
|
||||
},
|
||||
set: function (value) {
|
||||
field = value;
|
||||
if (value == null)
|
||||
storage.removeItem(opts.key);
|
||||
if (opts.encryptWith != null)
|
||||
value = crypto.AES.encrypt(JSON.stringify(value), opts.encryptWith).toString();
|
||||
storage.setItem(opts.key, JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=webstorage.js.map
|
@ -1 +0,0 @@
|
||||
{"version":3,"file":"webstorage.js","sourceRoot":"","sources":["../src/webstorage.ts"],"names":[],"mappings":"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"}
|
3789
package-lock.json
generated
Normal file
3789
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@ -1,34 +1,36 @@
|
||||
{
|
||||
"name": "webstorage-decorators",
|
||||
"version": "3.2.5",
|
||||
"description": "Decorators to sync class properties to Local/Session storage",
|
||||
"repository": "https://github.com/ztimson/WebstorageDecorators",
|
||||
"main": "./lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --verbose",
|
||||
"watch": "npm run build && tsc --watch"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"keywords": [
|
||||
"Decorators",
|
||||
"LocalStorage",
|
||||
"SessionStorage",
|
||||
"WebStorage"
|
||||
],
|
||||
"author": "Zak Timson",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"crypto-js": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.15",
|
||||
"jest": "^26.6.0",
|
||||
"ts-jest": "^26.4.1",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
"name": "webstorage-decorators",
|
||||
"version": "4.3.1",
|
||||
"description": "Decorators to sync class properties to Local/Session storage",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.zakscode.com/ztimson/webstorage-decorators"
|
||||
},
|
||||
"author": "Zak Timson",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --verbose",
|
||||
"test:coverage": "npx jest --verbose --coverage",
|
||||
"watch": "npm run build && tsc --watch"
|
||||
},
|
||||
"keywords": [
|
||||
"Decorators",
|
||||
"LocalStorage",
|
||||
"SessionStorage",
|
||||
"WebStorage"
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
"jest": "^29.7.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
@ -1,13 +1,9 @@
|
||||
import * as crypto from 'crypto-js';
|
||||
|
||||
/**
|
||||
* Options to be used with WebStorage decorators
|
||||
*/
|
||||
export interface WebStorageOptions {
|
||||
/** Default value to provide if storage is empty */
|
||||
default?: any;
|
||||
/** Key to prevent plain text storage **/
|
||||
encryptWith?: string;
|
||||
/** Key to save under */
|
||||
key?: string;
|
||||
}
|
||||
@ -45,7 +41,7 @@ export function LocalStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
|
||||
* @param defaultValue Default value to return if property does no exist inside sessionStorage.
|
||||
* @param opts Any additional options
|
||||
*/
|
||||
export function SessionStorage(defaultValue?, opts: WebStorageOptions = {}) {
|
||||
export function SessionStorage(defaultValue?: any, opts: WebStorageOptions = {}) {
|
||||
opts.default = defaultValue;
|
||||
return decoratorBuilder(sessionStorage, opts);
|
||||
}
|
||||
@ -69,9 +65,9 @@ function fromStorage(storage: Storage, opts: WebStorageOptions) {
|
||||
return temp;
|
||||
}
|
||||
storedVal = JSON.parse(<string>storedVal);
|
||||
if(opts.encryptWith != null) storedVal = JSON.parse(crypto.AES.decrypt(<string>storedVal, opts.encryptWith).toString(crypto.enc.Utf8));
|
||||
if(typeof storedVal != 'object' || !Array.isArray(storedVal)) return storedVal;
|
||||
if(opts.default != null && opts.default.constructor != null) return Object.assign(new opts.default.constructor(), opts.default, storedVal);
|
||||
if(opts.default != null && opts.default.constructor != null)
|
||||
return Object.assign(new opts.default.constructor(), opts.default, storedVal);
|
||||
return Object.assign({}, opts.default, storedVal);
|
||||
}
|
||||
|
||||
@ -89,13 +85,12 @@ function decoratorBuilder(storage: Storage, opts: WebStorageOptions) {
|
||||
let field = fromStorage(storage, opts);
|
||||
Object.defineProperty(target, key, {
|
||||
get: function() {
|
||||
if(field != fromStorage(storage, {key: opts.key, encryptWith: opts.encryptWith})) target[key] = field;
|
||||
if(field != fromStorage(storage, {key: opts.key})) (<any>target)[key] = field;
|
||||
return field;
|
||||
},
|
||||
set: function(value?) {
|
||||
field = value;
|
||||
if(value == null) storage.removeItem(<string>opts.key);
|
||||
if(opts.encryptWith != null) value = <any>crypto.AES.encrypt(JSON.stringify(value), opts.encryptWith).toString();
|
||||
storage.setItem(<string>opts.key, JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
|
@ -1,8 +1,27 @@
|
||||
import {LocalStorage, SessionStorage} from "../src";
|
||||
|
||||
const CUSTOM_KEY = '_MY_KEY'
|
||||
const ENCRYPTION_KEY = 'abc123';
|
||||
// Mocks ===============================================================================================================
|
||||
class StorageMock implements Storage {
|
||||
private store: {[key: string]: string} = {}
|
||||
|
||||
get length() { return Object.keys(this.store).length; }
|
||||
|
||||
clear() { this.store = {}; }
|
||||
getItem(key: string) { return this.store[key]; }
|
||||
setItem(key: string, value: string) { this.store[key] = value; }
|
||||
removeItem(key: string) { delete this.store[key]; }
|
||||
key(index: number) { return Object.keys(this.store)[index]; }
|
||||
}
|
||||
|
||||
(<any>global).localStorage = new StorageMock();
|
||||
(<any>global).sessionStorage = new StorageMock()
|
||||
var localStorage: StorageMock;
|
||||
localStorage = (<any>global).localStorage;
|
||||
var sessionStorage: StorageMock;
|
||||
sessionStorage = (<any>global).sessionStorage;
|
||||
|
||||
// Test Data ===========================================================================================================
|
||||
const CUSTOM_KEY = '_MY_KEY'
|
||||
class TestType {
|
||||
constructor(public first: string, public last: string) { }
|
||||
fullName() { return `${this.last}, ${this.first}`; }
|
||||
@ -12,16 +31,15 @@ class TestStorage {
|
||||
@LocalStorage() localStorage: any;
|
||||
@LocalStorage({a: true, b: 'test', c: 3.14}) defaultedLocalStorage: any;
|
||||
@LocalStorage(null, {key: CUSTOM_KEY}) customLocalStorage: any;
|
||||
@LocalStorage(null, {encryptWith: ENCRYPTION_KEY}) encryptedLocalStorage: any;
|
||||
@LocalStorage(new TestType('John', 'Smith')) objectLocalStorage!: TestType;
|
||||
@SessionStorage() sessionStorage: any;
|
||||
@SessionStorage({a: true, b: 'test', c: 3.14}) defaultedSessionStorage: any;
|
||||
@SessionStorage(null, {key: CUSTOM_KEY}) customSessionStorage: any;
|
||||
@SessionStorage(null, {encryptWith: ENCRYPTION_KEY}) encryptedSessionStorage: any;
|
||||
@SessionStorage(new TestType('John', 'Smith')) objectSessionStorage!: TestType;
|
||||
}
|
||||
|
||||
describe('Webstorage Decorators', () => {
|
||||
// Tests ===============================================================================================================
|
||||
describe('WebStorage Decorators', () => {
|
||||
let testComponent: TestStorage;
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
@ -67,12 +85,6 @@ describe('Webstorage Decorators', () => {
|
||||
expect(localStorage.getItem(CUSTOM_KEY)).toBe(JSON.stringify(testValue));
|
||||
expect(testComponent.customLocalStorage).toBe(testValue);
|
||||
});
|
||||
test('Encrypted', () => {
|
||||
const testValue = Math.random();
|
||||
testComponent.encryptedLocalStorage = testValue;
|
||||
expect(localStorage.getItem('encryptedLocalStorage')).not.toBe(JSON.stringify(testValue));
|
||||
expect(testComponent.encryptedLocalStorage).toBe(testValue);
|
||||
});
|
||||
test('Impure Functions', () => {
|
||||
testComponent.localStorage = [1];
|
||||
testComponent.localStorage.push(2);
|
||||
@ -128,12 +140,6 @@ describe('Webstorage Decorators', () => {
|
||||
expect(sessionStorage.getItem(CUSTOM_KEY)).toBe(JSON.stringify(testValue));
|
||||
expect(testComponent.customSessionStorage).toBe(testValue);
|
||||
});
|
||||
test('Encrypted', () => {
|
||||
const testValue = Math.random();
|
||||
testComponent.encryptedSessionStorage = testValue;
|
||||
expect(sessionStorage.getItem('encryptedSessionStorage')).not.toBe(JSON.stringify(testValue));
|
||||
expect(testComponent.encryptedSessionStorage).toBe(testValue);
|
||||
});
|
||||
test('Impure Functions', () => {
|
||||
testComponent.sessionStorage = [1];
|
||||
testComponent.sessionStorage.push(2);
|
||||
|
@ -1,21 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"module": "es2020",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "lib",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"strict": true,
|
||||
"target": "es2015"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user