UNPKG

4.37 kBJavaScriptView Raw
1import deepEqual from 'deep-equal';
2
3const DEFAULT_CHECK_DELAY = 3000;
4const COOKIE_EXPIRES = 365;
5const QUOTA = 4093;
6// eslint-disable-next-line no-magic-numbers
7const SECONDS_IN_DAY = 24 * 60 * 60 * 1000;
8
9/**
10 * @prop {string} cookieName
11 *
12 * @param {{cookieName: string}} config
13 * @param {{checkDelay: number}} config
14 * @param {{type: string}} config
15 * @return {FallbackStorage}
16 * @constructor
17 */
18export default class FallbackStorage {
19 static DEFAULT_COOKIE_NAME = 'localStorage';
20 static DEFAULT_SESSION_COOKIE_NAME = 'sessionStorage';
21 static DEFAULT_CHECK_DELAY = DEFAULT_CHECK_DELAY;
22 static COOKIE_EXPIRES = COOKIE_EXPIRES;
23
24 /**
25 * Maximum storage size
26 * @see http://browsercookielimits.squawky.net/
27 * @type {number}
28 */
29 static QUOTA = QUOTA;
30
31 /**
32 * @param {string} name
33 * @param {string} value
34 * @param {number} days
35 * @private
36 */
37 static _createCookie(name, value, days) {
38 let date;
39 let expires;
40
41 if (days) {
42 date = new Date();
43 date.setTime(date.getTime() + (days * SECONDS_IN_DAY));
44 expires = `; expires=${date.toGMTString()}`;
45 } else {
46 expires = ';';
47 }
48
49 document.cookie = `${name}=${value}${expires}; path=/`;
50 }
51
52 /**
53 *
54 * @param {string} name
55 * @return {string}
56 * @private
57 */
58 static _readCookie(name) {
59 const nameEQ = `${name}=`;
60 const cookies = document.cookie.split(';');
61
62 let cookie;
63 for (let i = 0; i < cookies.length; i++) {
64 cookie = cookies[i];
65 while (cookie.charAt(0) === ' ') {
66 cookie = cookie.substring(1, cookie.length);
67 }
68
69 if (cookie.indexOf(nameEQ) === 0) {
70 return cookie.substring(nameEQ.length, cookie.length);
71 }
72 }
73
74 return undefined;
75 }
76
77 constructor(config = {}) {
78 const session = config.type === 'session';
79
80 this.cookieName = config.cookieName ||
81 (
82 session
83 ? this.constructor.DEFAULT_SESSION_COOKIE_NAME
84 : this.constructor.DEFAULT_COOKIE_NAME
85 );
86 this.checkDelay = config.checkDelay || this.constructor.DEFAULT_CHECK_DELAY;
87 this.expires = session ? this.constructor.COOKIE_EXPIRES : null;
88 }
89
90 /**
91 * @return {Promise}
92 * @private
93 */
94 _read() {
95 return new Promise(resolve => {
96 const rawData = FallbackStorage._readCookie(this.cookieName);
97 resolve(JSON.parse(decodeURIComponent(rawData)));
98 }).catch(() => ({}));
99 }
100
101 /**
102 * @param data
103 * @return {Promise}
104 * @private
105 */
106 _write(data) {
107 return new Promise(resolve => {
108 const stringData = encodeURIComponent(JSON.stringify(data));
109 FallbackStorage.
110 _createCookie(this.cookieName, stringData === '{}' ? '' : stringData, this.expires);
111 return resolve(data);
112 });
113 }
114
115 /**
116 * @param {string} key
117 * @return {Promise}
118 */
119 get(key) {
120 return this._read().then(data => data[key] || null);
121 }
122
123 /**
124 * @param {string} key
125 * @param {object} value
126 * @return {Promise}
127 */
128 set(key, value) {
129 return this._read().then(data => {
130 if (key) {
131 if (value != null) {
132 data[key] = value;
133 } else {
134 Reflect.deleteProperty(data, key);
135 }
136 }
137
138 return this._write(data);
139 });
140 }
141
142 /**
143 * @param {string} key
144 * @return {Promise}
145 */
146 remove(key) {
147 return this.set(key, null);
148 }
149
150 /**
151 *
152 * @param {function(string, value)} callback
153 * @return {Promise}
154 */
155 each(callback) {
156 if (typeof callback !== 'function') {
157 return Promise.reject(new Error('Callback is not a function'));
158 }
159
160 return this._read().then(data => {
161 const promises = [];
162 for (const key in data) {
163 if (data.hasOwnProperty(key)) {
164 promises.push(callback(key, data[key]));
165 }
166 }
167 return Promise.all(promises);
168 });
169 }
170
171 /**
172 * @param {string} key
173 * @param {Function} callback
174 * @return {Function}
175 */
176 on(key, callback) {
177 let stop = false;
178
179 const checkForChange = oldValue => {
180 this.get(key).then(newValue => {
181 if (stop) {
182 return;
183 }
184
185 if (!deepEqual(oldValue, newValue)) {
186 callback(newValue);
187 }
188
189 window.setTimeout(() => checkForChange(oldValue), this.checkDelay);
190 });
191 };
192
193 this.get(key).then(checkForChange);
194
195 return () => {
196 stop = true;
197 };
198 }
199}