├── .eslintignore ├── renovate.json ├── jest.config.js ├── .eslintrc ├── .gitignore ├── test ├── fixture │ ├── plugins │ │ └── init.js │ ├── pages │ │ └── index.vue │ ├── nuxt.config.ts │ └── tsconfig.json └── module.test.js ├── tsconfig.json ├── .editorconfig ├── src ├── runtime │ ├── plugin.js │ └── storage.ts ├── module.ts └── types.d.ts ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | src/runtime/plugin.js 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils' 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs/eslint-config-typescript" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist -------------------------------------------------------------------------------- /test/fixture/plugins/init.js: -------------------------------------------------------------------------------- 1 | export default function ({ $storage }) { 2 | $storage.syncUniversal('works', true) 3 | } 4 | -------------------------------------------------------------------------------- /test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "moduleResolution": "Node", 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "@types/node" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/module.test.js: -------------------------------------------------------------------------------- 1 | import { setupTest, get } from '@nuxt/test-utils' 2 | 3 | describe('My test', () => { 4 | setupTest({ 5 | configFile: 'nuxt.config.ts', 6 | server: true 7 | }) 8 | 9 | it('renders state', async () => { 10 | const { body } = await get('/') 11 | 12 | expect(body).toContain('{"works":true}') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtConfig } from '@nuxt/types' 2 | import storageModule from '../../src/module' 3 | 4 | export default { 5 | buildModules: ['@nuxt/typescript-build'], 6 | plugins: [ 7 | '~/plugins/init.js' 8 | ], 9 | modules: [ 10 | storageModule 11 | ], 12 | storage: { 13 | cookie: { 14 | prefix: '', 15 | options: { 16 | path: '/', 17 | expires: new Date() 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/runtime/plugin.js: -------------------------------------------------------------------------------- 1 | import { Storage } from '~storage' 2 | 3 | export default function nuxtUniversalStorage(ctx, inject) { 4 | let options = <%= JSON.stringify(options, null, 2) %> 5 | const cookie = <%= serialize(options.cookie) %> 6 | 7 | options = { 8 | ...options, 9 | cookie 10 | } 11 | 12 | // Create new instance of Storage class 13 | const storage = new Storage(ctx, options) 14 | 15 | // Inject into context as $storage 16 | ctx.$storage = storage 17 | inject('storage', storage) 18 | } 19 | -------------------------------------------------------------------------------- /test/fixture/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "lib": [ 7 | "ESNext", 8 | "ESNext.AsyncIterable", 9 | "DOM" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./*" 23 | ] 24 | }, 25 | "types": [ 26 | "@types/node", 27 | "@nuxt/types", 28 | "../../src/types" 29 | ] 30 | }, 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | node: [14] 19 | 20 | steps: 21 | - uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: checkout 26 | uses: actions/checkout@master 27 | 28 | - name: cache node_modules 29 | uses: actions/cache@v2 30 | with: 31 | path: node_modules 32 | key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} 33 | 34 | - name: Install dependencies 35 | if: steps.cache.outputs.cache-hit != 'true' 36 | run: yarn 37 | 38 | - name: Lint 39 | run: yarn lint 40 | 41 | - name: Build 42 | run: yarn build 43 | 44 | - name: Test 45 | run: yarn jest 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Alibaba Travels Co - Nuxt Community 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 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import type { Module } from '@nuxt/types' 2 | import defu from 'defu' 3 | export type { NuxtStorage } from './types' 4 | 5 | const defaults = { 6 | vuex: { 7 | namespace: 'storage' 8 | }, 9 | cookie: { 10 | prefix: '', 11 | options: { 12 | path: '/' 13 | } 14 | }, 15 | localStorage: { 16 | prefix: '' 17 | }, 18 | ignoreExceptions: false 19 | } 20 | 21 | type DeepPartial = { [P in keyof T]?: DeepPartial; } 22 | export type Options = typeof defaults 23 | export type Config = DeepPartial 24 | 25 | declare module '@nuxt/types' { 26 | interface Configuration { 27 | storage?: Config 28 | } 29 | } 30 | 31 | export default > function module (moduleOptions) { 32 | const { nuxt } = this 33 | 34 | const options: DeepPartial = defu(moduleOptions, { ...this.options.storage }, defaults) 35 | 36 | this.addPlugin({ 37 | src: require.resolve('./runtime/plugin'), 38 | fileName: 'storage.js', 39 | options 40 | }) 41 | 42 | nuxt.options.alias['~storage'] = require.resolve('./runtime/storage') 43 | nuxt.options.build.transpile.push(__dirname, '@nuxtjs/universal-storage') 44 | } 45 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | import { CookieSerializeOptions } from 'cookie' 2 | 3 | export interface NuxtStorage { 4 | getUniversal(key: string): any 5 | setUniversal(key: string, value: any): void 6 | syncUniversal(key: string, value: any): void 7 | removeUniversal(key: string): void 8 | 9 | getState(key: string): any 10 | setState(key: string, value: any): void 11 | removeState(key: string): void 12 | watchState(key: string, fn: Function): void 13 | 14 | getLocalStorage(key: string): any 15 | setLocalStorage(key: string, value: any): void 16 | removeLocalStorage(key: string): void 17 | 18 | getCookies(): { [key: string]: string } 19 | getCookie(key: string): any 20 | setCookie(key: string, value: any, options?: CookieSerializeOptions): void 21 | removeCookie(key: string, options?: CookieSerializeOptions): void 22 | } 23 | 24 | declare module 'vue/types/vue' { 25 | interface Vue { 26 | $storage: NuxtStorage 27 | } 28 | } 29 | 30 | declare module '@nuxt/vue-app' { 31 | interface Context { 32 | $storage: NuxtStorage 33 | } 34 | interface NuxtAppOptions { 35 | $storage: NuxtStorage 36 | } 37 | } 38 | 39 | // Nuxt 2.9+ 40 | declare module '@nuxt/types' { 41 | interface Context { 42 | $storage: NuxtStorage 43 | } 44 | interface NuxtAppOptions { 45 | $storage: NuxtStorage 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/universal-storage", 3 | "version": "0.5.9", 4 | "description": "Universal Storage Utilities for Nuxt", 5 | "repository": "nuxt-community/universal-storage-module", 6 | "license": "MIT", 7 | "main": "dist/module.js", 8 | "types": "dist/module.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "build": "siroc build && mkdist --src src/runtime --dist dist/runtime", 14 | "dev": "nuxt test/fixture", 15 | "lint": "eslint --ext .vue,.ts", 16 | "release": "yarn test && yarn build && standard-version && git push --follow-tags && npm publish", 17 | "test": "npm run lint && jest" 18 | }, 19 | "dependencies": { 20 | "cookie": "^0.5.0", 21 | "defu": "^6.1.2", 22 | "dotprop": "^1.2.1", 23 | "upath": "^2.0.1", 24 | "uri-js": "^4.4.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/preset-typescript": "^7.22.15", 28 | "@nuxt/test-utils": "latest", 29 | "@nuxt/types": "latest", 30 | "@nuxt/typescript-build": "^3.0.1", 31 | "@nuxtjs/eslint-config-typescript": "latest", 32 | "@types/cookie": "latest", 33 | "codecov": "latest", 34 | "eslint": "latest", 35 | "jest": "latest", 36 | "jsdom": "latest", 37 | "mkdist": "latest", 38 | "nuxt-edge": "latest", 39 | "siroc": "latest", 40 | "standard-version": "latest" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 2 Universal Storage Module 2 | 3 | [![npm (scoped with tag)](https://img.shields.io/npm/v/@nuxtjs/universal-storage/latest.svg)](https://npmjs.com/package/@nuxtjs/universal-storage) 4 | [![npm](https://img.shields.io/npm/dt/@nuxtjs/universal-storage.svg)](https://npmjs.com/package/@nuxtjs/universal-storage) 5 | [![GitHub Actions](https://github.com/nuxt-community/universal-storage-module/actions/workflows/ci.yml/badge.svg)](https://circleci.com/gh/nuxt-community/universal-storage-module) 6 | [![Codecov](https://img.shields.io/codecov/c/github/nuxt-community/universal-storage-module.svg)](https://codecov.io/gh/nuxt-community/universal-storage-module) 7 | [![Dependencies](https://david-dm.org/nuxt-community/universal-storage-module/status.svg)](https://david-dm.org/nuxt-community/universal-storage-module) 8 | [![js-standard-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](http://standardjs.com) 9 | 10 | > Universal Storage Utilities for Nuxt 2 based on [@nuxt-community/auth-module](https://github.com/nuxt-community/auth-module) 11 | 12 | [📖 **Release Notes**](./CHANGELOG.md) 13 | 14 | ## Setup 15 | 16 | - Add `@nuxtjs/universal-storage` dependency using yarn or npm to your project 17 | 18 | ```sh 19 | yarn add @nuxtjs/universal-storage 20 | ``` 21 | 22 | OR 23 | 24 | ```sh 25 | npm install @nuxtjs/universal-storage --save 26 | ``` 27 | 28 | - Add `@nuxtjs/universal-storage` to the `modules` section of your `nuxt.config.js` file 29 | 30 | ```js 31 | { 32 | modules: [ 33 | '@nuxtjs/universal-storage', 34 | ], 35 | 36 | storage: { 37 | 38 | } 39 | } 40 | ``` 41 | 42 | ## TypeScript 43 | 44 | Add the types to your "types" array in `tsconfig.json` 45 | 46 | ```json 47 | { 48 | "compilerOptions": { 49 | "types": [ 50 | "@nuxt/types", 51 | "@nuxtjs/universal-storage" 52 | ] 53 | } 54 | } 55 | ``` 56 | 57 | ## Usage 58 | 59 | ### Options 60 | 61 | Options are defined as following: 62 | 63 | ```js 64 | storage: { 65 | vuex, // boolean or {namespace} 66 | localStorage, // boolean or {prefix} 67 | cookie, // boolean or {prefix, options} 68 | initialState, // Object {} 69 | ignoreExceptions // 70 | } 71 | ``` 72 | 73 | and default to the following values: 74 | 75 | ```js 76 | { 77 | vuex: { 78 | namespace: 'storage' 79 | }, 80 | cookie: { 81 | prefix: '', 82 | options: { 83 | path: '/' 84 | } 85 | }, 86 | localStorage: { 87 | prefix: '' 88 | }, 89 | ignoreExceptions: false, 90 | } 91 | ``` 92 | 93 | ### Full synchronise on start with initialState as default 94 | 95 | Since version 0.4.0 this module allows full state synchronisation with `cookies`, `localStorage` and `initialState` as a default value. That allows for a very neat usage pattern: 96 | For example, if you have an `initialState` like the following in your `nuxt.config.js` file: 97 | 98 | ```js 99 | storage: { 100 | initialState: { testParam: false } 101 | } 102 | ``` 103 | 104 | then in my component I can simply declare (with decorators) 105 | 106 | ```js 107 | @State(s => s.storage.testParam) 108 | testParam 109 | ``` 110 | 111 | or (with mapState) 112 | 113 | ```js 114 | computed: mapState({ 115 | testParam: s => s.storage.testParam 116 | }) 117 | ``` 118 | 119 | Afterwards you can get the computed property `testParam` with whatever value it had in your last session and on change you just have to call `this.$storage.setUniversal("testParam", newVal)` to get the new value saved. 120 | 121 | ### Hidden settings (private state) 122 | 123 | > Private state is suitable to keep information not being exposed to the Vuex store. 124 | > This helps prevent stealing tokens from the SSR response HTML. 125 | 126 | If the key name starts with `_` then that value is kept separate in the memory storage and not exposed to the Vuex store like the rest of the values. 127 | 128 | For example: 129 | 130 | ```js 131 | $storage.setState("_password", "alpha1") 132 | ``` 133 | 134 | ### Api 135 | 136 | - `$storage.getUniversal(key)` 137 | 138 | - `$storage.setUniversal(key, value)` 139 | 140 | - `$storage.syncUniversal(key, defaultValue)` 141 | 142 | - `$storage.removeUniversal(key)` 143 | 144 | - `$storage.getState(key)` 145 | 146 | - `$storage.setState(key, value)` 147 | 148 | - `$storage.removeState(key)` 149 | 150 | - `$storage.watchState(key, fn)` 151 | 152 | - `$storage.getLocalStorage(key)` 153 | 154 | - `$storage.setLocalStorage(key, value)` 155 | 156 | - `$storage.removeLocalStorage(key)` 157 | 158 | - `$storage.getCookies()` 159 | 160 | - `$storage.getCookie(key)` 161 | 162 | - `$storage.setCookie(key, value)` 163 | 164 | - `$storage.removeCookie(key)` 165 | 166 | ## Development 167 | 168 | - Clone this repository 169 | - Install dependencies using `yarn install` or `npm install` 170 | - Start development server using `yarn run dev` or `npm run dev` 171 | - Point your browser to `http://localhost:3000` 172 | 173 | ## Roadmap 174 | 175 | - Add Encryption 176 | - Universal Session Handling 177 | 178 | ## License 179 | 180 | [MIT License](./LICENSE) 181 | -------------------------------------------------------------------------------- /src/runtime/storage.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import getProp from 'dotprop' 3 | import { parse as parseCookie, serialize as serializeCookie } from 'cookie' 4 | import type { NuxtStorage } from '../types' 5 | 6 | // Based on https://github.com/nuxt-community/auth-module/blob/dev/lib/core/storage.js 7 | 8 | export class Storage implements NuxtStorage { 9 | private ctx: any 10 | private options: any 11 | private _useVuex: boolean 12 | private state: any 13 | private _state: any 14 | 15 | constructor (ctx, options) { 16 | this.ctx = ctx 17 | this.options = options 18 | 19 | this._initState(options.initialState) 20 | } 21 | 22 | // ------------------------------------ 23 | // Universal 24 | // ------------------------------------ 25 | 26 | setUniversal (key, value) { 27 | // Local state 28 | this.setState(key, value) 29 | 30 | // Cookies 31 | this.setCookie(key, value) 32 | 33 | // Local Storage 34 | this.setLocalStorage(key, value) 35 | 36 | return value 37 | } 38 | 39 | getUniversal (key) { 40 | // Local state 41 | let value = this.getState(key) 42 | 43 | // Cookies 44 | if (isUnset(value)) { 45 | value = this.getCookie(key) 46 | } 47 | 48 | // Local Storage 49 | if (isUnset(value)) { 50 | value = this.getLocalStorage(key) 51 | } 52 | 53 | return value 54 | } 55 | 56 | syncUniversal (key, defaultValue) { 57 | let value = this.getUniversal(key) 58 | 59 | if (isUnset(value) && isSet(defaultValue)) { 60 | value = defaultValue 61 | } 62 | 63 | if (isSet(value)) { 64 | this.setUniversal(key, value) 65 | } 66 | 67 | return value 68 | } 69 | 70 | removeUniversal (key) { 71 | this.removeState(key) 72 | this.removeLocalStorage(key) 73 | this.removeCookie(key) 74 | } 75 | 76 | // ------------------------------------ 77 | // Local state (reactive) 78 | // ------------------------------------ 79 | 80 | _initState (initData) { 81 | // Private state is suitable to keep information not being exposed to Vuex store 82 | // This helps prevent stealing token from SSR response HTML 83 | Vue.set(this, '_state', {}) 84 | 85 | // Use vuex for local state's if possible 86 | this._useVuex = this.options.vuex && this.ctx.store 87 | 88 | if (this._useVuex) { 89 | const storeModule = { 90 | namespaced: true, 91 | state: () => ({}), 92 | mutations: { 93 | SET (state, payload) { 94 | Vue.set(state, payload.key, payload.value) 95 | } 96 | } 97 | } 98 | 99 | this.ctx.store.registerModule(this.options.vuex.namespace, storeModule, { 100 | preserveState: Boolean(this.ctx.store.state[this.options.vuex.namespace]) 101 | }) 102 | 103 | this.state = this.ctx.store.state[this.options.vuex.namespace] 104 | } else { 105 | Vue.set(this, 'state', {}) 106 | } 107 | 108 | // Synchronise initial values 109 | for (const key in initData) { 110 | this.syncUniversal(key, initData[key]) 111 | } 112 | } 113 | 114 | setState (key, value) { 115 | if (key[0] === '_') { 116 | Vue.set(this._state, key, value) 117 | } else if (this._useVuex) { 118 | this.ctx.store.commit(this.options.vuex.namespace + '/SET', { 119 | key, 120 | value 121 | }) 122 | } else { 123 | Vue.set(this.state, key, value) 124 | } 125 | 126 | return value 127 | } 128 | 129 | getState (key) { 130 | if (key[0] !== '_') { 131 | return this.state[key] 132 | } else { 133 | return this._state[key] 134 | } 135 | } 136 | 137 | watchState (key, fn) { 138 | if (this._useVuex) { 139 | return this.ctx.store.watch( 140 | state => getProp(state[this.options.vuex.namespace], key), 141 | fn 142 | ) 143 | } 144 | } 145 | 146 | removeState (key) { 147 | this.setState(key, undefined) 148 | } 149 | 150 | // ------------------------------------ 151 | // Local storage 152 | // ------------------------------------ 153 | 154 | setLocalStorage (key, value) { 155 | if (typeof localStorage === 'undefined' || !this.options.localStorage) { 156 | return 157 | } 158 | 159 | const _key = this.options.localStorage.prefix + key 160 | 161 | try { 162 | if (isObjectOrArray(value)) { 163 | localStorage.setItem(_key, JSON.stringify(value)) 164 | } else { 165 | localStorage.setItem(_key, value) 166 | } 167 | } catch (e) { 168 | if (!this.options.ignoreExceptions) { 169 | throw e 170 | } 171 | } 172 | 173 | return value 174 | } 175 | 176 | getLocalStorage (key) { 177 | if (typeof localStorage === 'undefined' || !this.options.localStorage) { 178 | return 179 | } 180 | 181 | const _key = this.options.localStorage.prefix + key 182 | 183 | const value = localStorage.getItem(_key) 184 | 185 | return decodeValue(value) 186 | } 187 | 188 | removeLocalStorage (key) { 189 | if (typeof localStorage === 'undefined' || !this.options.localStorage) { 190 | return 191 | } 192 | const _key = this.options.localStorage.prefix + key 193 | localStorage.removeItem(_key) 194 | } 195 | 196 | // ------------------------------------ 197 | // Cookies 198 | // ------------------------------------ 199 | getCookies () { 200 | const cookieStr = process.client 201 | ? document.cookie 202 | : this.ctx.req.headers.cookie 203 | 204 | return parseCookie(cookieStr || '') || {} 205 | } 206 | 207 | setCookie (key, value, options = {}) { 208 | if (!this.options.cookie) { 209 | return 210 | } 211 | 212 | const _key = this.options.cookie.prefix + key 213 | const _options = Object.assign({}, this.options.cookie.options, options) 214 | const _value = encodeValue(value) 215 | 216 | const serializedCookie = serializeCookie(_key, _value, _options) 217 | 218 | if (process.client) { 219 | // Set in browser 220 | document.cookie = serializedCookie 221 | } else if (process.server && this.ctx.res) { 222 | // Send Set-Cookie header from server side 223 | this.ctx.res.setHeader('Set-Cookie', [serializedCookie]) 224 | } 225 | 226 | return value 227 | } 228 | 229 | getCookie (key) { 230 | if (!this.options.cookie || (process.server && !this.ctx.req)) { 231 | return 232 | } 233 | 234 | const _key = this.options.cookie.prefix + key 235 | 236 | const cookies = this.getCookies() 237 | 238 | const value = cookies[_key] ? decodeURIComponent(cookies[_key]) : undefined 239 | 240 | return decodeValue(value) 241 | } 242 | 243 | removeCookie (key, options?) { 244 | this.setCookie(key, undefined, { maxAge: -1, ...options }) 245 | } 246 | } 247 | 248 | // ------------------------------------ 249 | // Utils 250 | // ------------------------------------ 251 | 252 | function isUnset (o) { 253 | return typeof o === 'undefined' || o === null 254 | } 255 | 256 | function isSet (o) { 257 | return !isUnset(o) 258 | } 259 | 260 | function isObjectOrArray (obj) { 261 | const type = Object.prototype.toString.call(obj) 262 | return type === '[object Object]' || type === '[object Array]' 263 | } 264 | 265 | function encodeValue (val) { 266 | if (typeof val === 'string') { 267 | return val 268 | } 269 | return JSON.stringify(val) 270 | } 271 | 272 | function decodeValue (val) { 273 | // Find current value's type 274 | const type = Object.prototype.toString.call(val) 275 | 276 | // If value is an object, return object 277 | if (type === '[object Object]') { 278 | return val 279 | } 280 | 281 | // If value is somehow undefined, return as is (erroneous code) 282 | if (type === 'undefined') { 283 | return val 284 | } 285 | 286 | // Finally try to parse it as json, or fallback to original value 287 | try { 288 | return JSON.parse(val) 289 | } catch (error) { 290 | return val 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.5.9](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.8...v0.5.9) (2021-04-11) 6 | 7 | ### [0.5.8](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.7...v0.5.8) (2021-04-10) 8 | 9 | 10 | ### Bug Fixes 11 | 12 | * **readme:** update build badge to github actions ([a3c55b9](https://github.com/nuxt-community/universal-storage-module/commit/a3c55b979ce37d5c559fe08dccfe3ed0d34b3a15)) 13 | 14 | ### [0.5.7](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.6...v0.5.7) (2021-01-17) 15 | 16 | 17 | ### Features 18 | 19 | * typescript rewrite ([971d5e3](https://github.com/nuxt-community/universal-storage-module/commit/971d5e360e44c842a621c616c5db749fa37b2215)) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * remove console.log ([#165](https://github.com/nuxt-community/universal-storage-module/issues/165)) ([d61dd13](https://github.com/nuxt-community/universal-storage-module/commit/d61dd139197b19547809b353038c7b6be7f92dd6)) 25 | * set-cookie should be an array ([#167](https://github.com/nuxt-community/universal-storage-module/issues/167)) ([9fb9609](https://github.com/nuxt-community/universal-storage-module/commit/9fb9609e1af093628b30db81db8188fd514ff3f7)) 26 | 27 | ### [0.5.6](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.5...v0.5.6) (2020-12-26) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * support cookie expires date object ([0c28fee](https://github.com/nuxt-community/universal-storage-module/commit/0c28feeb49f67864e45a9815323be16e70e33bed)) 33 | 34 | ### [0.5.5](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.4...v0.5.5) (2020-05-06) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **test:** change modules import ([0b174a8](https://github.com/nuxt-community/universal-storage-module/commit/0b174a8)) 40 | 41 | 42 | 43 | ### [0.5.4](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.3...v0.5.4) (2020-02-06) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * broken test due to dep upgrade ([c2a248f](https://github.com/nuxt-community/universal-storage-module/commit/c2a248f)) 49 | 50 | 51 | 52 | ### [0.5.3](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.2...v0.5.3) (2020-02-06) 53 | 54 | 55 | 56 | ### [0.5.2](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.1...v0.5.2) (2020-02-06) 57 | 58 | 59 | 60 | ### [0.5.1](https://github.com/nuxt-community/universal-storage-module/compare/v0.5.0...v0.5.1) (2020-02-06) 61 | 62 | 63 | 64 | ## [0.5.0](https://github.com/nuxt-community/universal-storage-module/compare/v0.4.0...v0.5.0) (2020-01-24) 65 | 66 | 67 | ### Features 68 | 69 | * full sync on plugin initialization ([#15](https://github.com/nuxt-community/universal-storage-module/issues/15)) ([b27e534](https://github.com/nuxt-community/universal-storage-module/commit/b27e534)) 70 | 71 | 72 | ### Tests 73 | 74 | * working test ([9f91fa7](https://github.com/nuxt-community/universal-storage-module/commit/9f91fa7)) 75 | 76 | 77 | ### BREAKING CHANGES 78 | 79 | * State will be fully synced on plugin initialization 80 | 81 | 82 | 83 | ## [0.4.0](https://github.com/nuxt-community/universal-storage-module/compare/v0.3.1...v0.4.0) (2019-07-11) 84 | 85 | 86 | ### Features 87 | 88 | * register $storage to root context ([#30](https://github.com/nuxt-community/universal-storage-module/issues/30)) ([99d3450](https://github.com/nuxt-community/universal-storage-module/commit/99d3450)) 89 | 90 | 91 | 92 | ### [0.3.1](https://github.com/nuxt-community/universal-storage-module/compare/v0.3.0...v0.3.1) (2019-07-09) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * **types:** ship @types/cookie dependency ([48655ed](https://github.com/nuxt-community/universal-storage-module/commit/48655ed)) 98 | 99 | 100 | 101 | ## [0.3.0](https://github.com/nuxt-community/universal-storage-module/compare/v0.2.1...v0.3.0) (2019-07-08) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * lint fix ([254de4d](https://github.com/nuxt-community/universal-storage-module/commit/254de4d)) 107 | 108 | 109 | ### Features 110 | 111 | * add type definitions ([bccb226](https://github.com/nuxt-community/universal-storage-module/commit/bccb226)) 112 | 113 | 114 | 115 | ### [0.2.1](https://github.com/nuxt-community/universal-storage-module/compare/v0.2.0...v0.2.1) (2019-06-27) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * add missing dependency ([#23](https://github.com/nuxt-community/universal-storage-module/issues/23)) ([762ebac](https://github.com/nuxt-community/universal-storage-module/commit/762ebac)) 121 | * removeUniversal on ssr ([#25](https://github.com/nuxt-community/universal-storage-module/issues/25)) ([71f2857](https://github.com/nuxt-community/universal-storage-module/commit/71f2857)) 122 | 123 | 124 | 125 | 126 | # [0.2.0](https://github.com/nuxt-community/universal-storage-module/compare/v0.1.4...v0.2.0) (2019-02-13) 127 | 128 | 129 | ### Features 130 | 131 | * add ignore exceptions ([#5](https://github.com/nuxt-community/universal-storage-module/issues/5)) ([b28d193](https://github.com/nuxt-community/universal-storage-module/commit/b28d193)) 132 | 133 | 134 | 135 | 136 | ## [0.1.4](https://github.com/nuxt-community/universal-storage-module/compare/v0.1.3...v0.1.4) (2018-08-11) 137 | 138 | 139 | ### Bug Fixes 140 | 141 | * **storage:** correctly serialize arrays into localStorage ([bb43de6](https://github.com/nuxt-community/universal-storage-module/commit/bb43de6)) 142 | 143 | 144 | 145 | 146 | ## [0.1.3](https://github.com/nuxt-community/universal-storage-module/compare/v0.1.2...v0.1.3) (2018-08-05) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * **storage:** check has key of cookie before decode it ([#1](https://github.com/nuxt-community/universal-storage-module/issues/1)) ([50fe99e](https://github.com/nuxt-community/universal-storage-module/commit/50fe99e)) 152 | 153 | 154 | 155 | 156 | ## [0.1.2](https://github.com/nuxt-community/universal-storage-module/compare/v0.1.1...v0.1.2) (2018-07-18) 157 | 158 | 159 | ### Bug Fixes 160 | 161 | * defaults ([df20d82](https://github.com/nuxt-community/universal-storage-module/commit/df20d82)) 162 | 163 | 164 | 165 | 166 | ## [0.1.1](https://github.com/nuxt-community/universal-storage-module/compare/v0.1.0...v0.1.1) (2018-07-15) 167 | 168 | 169 | 170 | 171 | # [0.1.0](https://github.com/nuxt-community/universal-storage-module/compare/v0.0.1...v0.1.0) (2018-07-15) 172 | 173 | 174 | ### Features 175 | 176 | * add support for saving different datatypes ([dd53b8b](https://github.com/nuxt-community/universal-storage-module/commit/dd53b8b)) 177 | 178 | 179 | 180 | 181 | ## 0.0.1 (2018-07-11) 182 | 183 | 184 | 185 | 186 | ## [0.3.1](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.3.0...v0.3.1) (2018-07-11) 187 | 188 | 189 | 190 | 191 | # [0.3.0](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.2.2...v0.3.0) (2018-07-08) 192 | 193 | 194 | ### Features 195 | 196 | * **example:** add more details and button to test your luck ([73cc700](https://github.com/alibaba-aero/nuxt-google-optimize/commit/73cc700)) 197 | 198 | 199 | 200 | 201 | ## [0.2.2](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.2.1...v0.2.2) (2018-07-07) 202 | 203 | 204 | ### Bug Fixes 205 | 206 | * **plugin:** handle dynamically disabled experiments ([c6620d0](https://github.com/alibaba-aero/nuxt-google-optimize/commit/c6620d0)) 207 | 208 | 209 | 210 | 211 | ## [0.2.1](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.2.0...v0.2.1) (2018-07-07) 212 | 213 | 214 | ### Bug Fixes 215 | 216 | * **plugin:** currently handle empty experiments ([31caafa](https://github.com/alibaba-aero/nuxt-google-optimize/commit/31caafa)) 217 | 218 | 219 | 220 | 221 | # [0.2.0](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.1.0...v0.2.0) (2018-07-07) 222 | 223 | 224 | ### Features 225 | 226 | * **module:** pushPlugin option ([87ae7e1](https://github.com/alibaba-aero/nuxt-google-optimize/commit/87ae7e1)) 227 | 228 | 229 | 230 | 231 | # [0.1.0](https://github.com/alibaba-aero/nuxt-google-optimize/compare/v0.0.1...v0.1.0) (2018-07-07) 232 | 233 | 234 | ### Features 235 | 236 | * **experiment:** accept isEligible function ([7193621](https://github.com/alibaba-aero/nuxt-google-optimize/commit/7193621)) 237 | 238 | 239 | 240 | 241 | ## 0.0.1 (2018-07-07) 242 | --------------------------------------------------------------------------------