UNPKG

13.6 kBJavaScriptView Raw
1/*
2Copyright (c) 2017 NAVER Corp.
3@egjs/persist project is licensed under the MIT license
4
5@egjs/persist JavaScript library
6
7
8@version 2.4.0
9*/
10(function (global, factory) {
11 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
12 typeof define === 'function' && define.amd ? define(factory) :
13 (global.eg = global.eg || {}, global.eg.Persist = factory());
14}(this, (function () { 'use strict';
15
16 var win = typeof window !== "undefined" && window || {};
17 var console$1 = win.console;
18 var document$1 = win.document;
19 var history = win.history;
20 var location = win.location;
21 var navigator = win.navigator;
22 var parseFloat = win.parseFloat;
23 var performance = win.performance;
24 var localStorage = null;
25 var sessionStorage = null;
26
27 try {
28 localStorage = win.localStorage;
29 sessionStorage = win.sessionStorage;
30 } catch (e) {}
31
32 var CONST_PERSIST = "___persist___";
33 var CONST_PERSIST_STATE = "state" + CONST_PERSIST;
34 var CONST_DEPTHS = "depths";
35 var CONST_LAST_URL = "lastUrl";
36 var navigation = performance && performance.navigation;
37 var TYPE_NAVIGATE = navigation && navigation.TYPE_NAVIGATE || 0;
38 var TYPE_RELOAD = navigation && navigation.TYPE_RELOAD || 1;
39 var TYPE_BACK_FORWARD = navigation && navigation.TYPE_BACK_FORWARD || 2;
40
41 var isSupportState = history && "replaceState" in history && "state" in history;
42
43 function isStorageAvailable(storage) {
44 if (!storage) {
45 return undefined;
46 }
47
48 var TMP_KEY = "__tmp__" + CONST_PERSIST;
49
50 try {
51 // In case of iOS safari private mode, calling setItem on storage throws error
52 storage.setItem(TMP_KEY, CONST_PERSIST); // In Chrome incognito mode, can not get saved value
53 // In IE8, calling storage.getItem occasionally makes "Permission denied" error
54
55 return storage.getItem(TMP_KEY) === CONST_PERSIST;
56 } catch (e) {
57 return false;
58 }
59 }
60
61 var storage = function () {
62 var strg;
63
64 if (isStorageAvailable(sessionStorage)) {
65 strg = sessionStorage;
66 } else if (isStorageAvailable(localStorage)) {
67 strg = localStorage;
68 }
69
70 return strg;
71 }();
72
73 function warnInvalidStorageValue() {
74 /* eslint-disable no-console */
75 console.warn("window.history or session/localStorage has no valid " + "format data to be handled in persist.");
76 /* eslint-enable no-console */
77 }
78
79 function getStorage() {
80 return storage;
81 }
82 /*
83 * Get state value
84 */
85
86
87 function getState(key) {
88 var state;
89 var stateStr;
90
91 if (storage) {
92 stateStr = storage.getItem(key);
93 } else if (history.state) {
94 if (typeof history.state === "object" && history.state !== null) {
95 stateStr = history.state[key];
96 } else {
97 warnInvalidStorageValue();
98 }
99 } else {
100 stateStr = history.state;
101 } // the storage is clean
102
103
104 if (stateStr === null) {
105 return {};
106 } // "null" is not a valid
107
108
109 var isValidStateStr = typeof stateStr === "string" && stateStr.length > 0 && stateStr !== "null";
110
111 try {
112 state = JSON.parse(stateStr); // like '[ ... ]', '1', '1.234', '"123"' is also not valid
113
114 var isValidType = !(typeof state !== "object" || state instanceof Array);
115
116 if (!isValidStateStr || !isValidType) {
117 throw new Error();
118 }
119 } catch (e) {
120 warnInvalidStorageValue();
121 state = {};
122 } // Note2 (Android 4.3) return value is null
123
124
125 return state;
126 }
127
128 function getStateByKey(key, valueKey) {
129 if (!isSupportState && !storage) {
130 return undefined;
131 }
132
133 var result = getState(key)[valueKey]; // some device returns "null" or undefined
134
135 if (result === "null" || typeof result === "undefined") {
136 result = null;
137 }
138
139 return result;
140 }
141 /*
142 * Set state value
143 */
144
145
146 function setState(key, state) {
147 if (storage) {
148 if (state) {
149 storage.setItem(key, JSON.stringify(state));
150 } else {
151 storage.removeItem(key);
152 }
153 } else {
154 try {
155 var historyState = !history || history.state == null ? {} : history.state;
156
157 if (history && typeof historyState === "object") {
158 historyState[key] = JSON.stringify(state);
159 history.replaceState(historyState, document.title, location.href);
160 } else {
161 /* eslint-disable no-console */
162 console.warn("To use a history object, it must be an object that is not a primitive type.");
163 /* eslint-enable no-console */
164 }
165 } catch (e) {
166 /* eslint-disable no-console */
167 console.warn(e.message);
168 /* eslint-enable no-console */
169 }
170 }
171
172 state ? win[CONST_PERSIST] = true : delete win[CONST_PERSIST];
173 }
174
175 function setStateByKey(key, valueKey, data) {
176 if (!isSupportState && !storage) {
177 return;
178 }
179
180 var beforeData = getState(key);
181 beforeData[valueKey] = data;
182 setState(key, beforeData);
183 }
184 /*
185 * flush current history state
186 */
187
188
189 function reset(key) {
190 setState(key, null);
191 }
192
193 var userAgent = navigator ? navigator.userAgent : "";
194
195 var isNeeded = function () {
196 var isIOS = new RegExp("iPhone|iPad", "i").test(userAgent);
197 var isMacSafari = new RegExp("Mac", "i").test(userAgent) && !new RegExp("Chrome", "i").test(userAgent) && new RegExp("Apple", "i").test(userAgent);
198 var isAndroid = new RegExp("Android ", "i").test(userAgent);
199 var isWebview = new RegExp("wv; |inapp;", "i").test(userAgent);
200 var androidVersion = isAndroid ? parseFloat(new RegExp("(Android)\\s([\\d_\\.]+|\\d_0)", "i").exec(userAgent)[2]) : undefined;
201 return !(isIOS || isMacSafari || isAndroid && (androidVersion <= 4.3 && isWebview || androidVersion < 3));
202 }(); // In case of IE8, TYPE_BACK_FORWARD is undefined.
203
204
205 function getNavigationType() {
206 return performance && performance.navigation && performance.navigation.type;
207 }
208
209 function getUrl() {
210 return location ? location.href.split("#")[0] : "";
211 }
212
213 function getStorageKey(name) {
214 return name + CONST_PERSIST;
215 }
216
217 /* eslint-disable no-use-before-define */
218 var currentUrl = "";
219
220 function execRec(obj, path, func) {
221 var _obj = obj;
222
223 if (!_obj) {
224 _obj = isNaN(path[0]) ? {} : [];
225 }
226
227 var head = path.shift();
228
229 if (path.length === 0) {
230 if (_obj instanceof Array && isNaN(head)) {
231 console$1.warn("Don't use key string on array");
232 }
233
234 func(_obj, head);
235 return _obj;
236 }
237
238 _obj[head] = execRec(_obj[head], path, func);
239 return _obj;
240 }
241
242 function setPersistState(key, value) {
243 try {
244 setStateByKey(CONST_PERSIST_STATE, key, value);
245 } catch (e) {
246 if (clearFirst()) {
247 if (key === CONST_LAST_URL) {
248 setPersistState(key, value);
249 } else if (key === CONST_DEPTHS) {
250 setPersistState(key, value && value.slice(1));
251 }
252 } else {
253 // There is no more size to fit in.
254 throw e;
255 }
256 }
257 }
258
259 function getPersistState(key) {
260 return getStateByKey(CONST_PERSIST_STATE, key);
261 }
262
263 function updateDepth(type) {
264 if (type === void 0) {
265 type = 0;
266 }
267
268 var url = getUrl();
269
270 if (currentUrl === url) {
271 return;
272 } // url is not the same for the first time, pushState, or replaceState.
273
274
275 currentUrl = url;
276 var depths = getPersistState(CONST_DEPTHS) || [];
277
278 if (type === TYPE_BACK_FORWARD) {
279 // Change current url only
280 var currentIndex = depths.indexOf(currentUrl);
281 ~currentIndex && setPersistState(CONST_LAST_URL, currentUrl);
282 } else {
283 var prevLastUrl = getPersistState(CONST_LAST_URL);
284 reset(getStorageKey(currentUrl));
285
286 if (type === TYPE_NAVIGATE && url !== prevLastUrl) {
287 // Remove all url lists with higher index than current index
288 var prevLastIndex = depths.indexOf(prevLastUrl);
289 var removedList = depths.splice(prevLastIndex + 1, depths.length);
290 removedList.forEach(function (removedUrl) {
291 reset(getStorageKey(removedUrl));
292 }); // If the type is NAVIGATE and there is information about current url, delete it.
293
294 var _currentIndex = depths.indexOf(currentUrl);
295
296 ~_currentIndex && depths.splice(_currentIndex, 1);
297 } // Add depth for new address.
298
299
300 if (depths.indexOf(url) < 0) {
301 depths.push(url);
302 }
303
304 setPersistState(CONST_DEPTHS, depths);
305 setPersistState(CONST_LAST_URL, url);
306 }
307 }
308
309 function clearFirst() {
310 var depths = getPersistState(CONST_DEPTHS) || [];
311 var removed = depths.splice(0, 1);
312
313 if (!removed.length) {
314 // There is an error because there is no depth to add data.
315 return false;
316 }
317
318 var removedUrl = removed[0];
319 reset(getStorageKey(removedUrl));
320
321 if (currentUrl === removedUrl) {
322 currentUrl = "";
323 setPersistState(CONST_LAST_URL, "");
324
325 if (!depths.length) {
326 // I tried to add myself, but it didn't add up, so I got an error.
327 return false;
328 }
329 }
330
331 setPersistState(CONST_DEPTHS, depths); // Clear the previous record and try to add data again.
332
333 return true;
334 }
335
336 function _clear() {
337 var depths = getPersistState(CONST_DEPTHS) || [];
338 depths.forEach(function (url) {
339 reset(getStorageKey(url));
340 });
341 reset(CONST_PERSIST_STATE);
342 currentUrl = "";
343 }
344
345 if ("onpopstate" in win) {
346 win.addEventListener("popstate", function () {
347 // popstate event occurs when backward or forward
348 updateDepth(TYPE_BACK_FORWARD);
349 });
350 }
351 /**
352 * Get or store the current state of the web page using JSON.
353 * @ko 웹 페이지의 현재 상태를 JSON 형식으로 저장하거나 읽는다.
354 * @alias eg.Persist
355 *
356 * @support {"ie": "9+", "ch" : "latest", "ff" : "latest", "sf" : "latest" , "edge" : "latest", "ios" : "7+", "an" : "2.3+ (except 3.x)"}
357 */
358
359
360 var Persist =
361 /*#__PURE__*/
362 function () {
363 var Persist =
364 /*#__PURE__*/
365 function () {
366 /**
367 * @static
368 * Clear all information in Persist
369 */
370 Persist.clear = function clear() {
371 _clear();
372 };
373 /**
374 * @static
375 * Return whether you need "Persist" module by checking the bfCache support of the current browser
376 * @return {Boolean}
377 */
378
379
380 Persist.isNeeded = function isNeeded$$1() {
381 return isNeeded;
382 };
383 /**
384 * Constructor
385 * @param {String} key The key of the state information to be stored <ko>저장할 상태 정보의 키</ko>
386 **/
387
388
389 function Persist(key) {
390 this.key = key || "";
391 }
392 /**
393 * Read value
394 * @param {String?} path target path
395 * @return {String|Number|Boolean|Object|Array}
396 */
397
398
399 var _proto = Persist.prototype;
400
401 _proto.get = function get(path) {
402 // update url for pushState, replaceState
403 updateDepth(TYPE_NAVIGATE); // find path
404
405 var urlKey = getStorageKey(getUrl());
406 var globalState = getStateByKey(urlKey, this.key);
407
408 if (!path || path.length === 0) {
409 return globalState;
410 }
411
412 var pathToken = path.split(".");
413 var currentItem = globalState;
414 var isTargetExist = true;
415
416 for (var i = 0; i < pathToken.length; i++) {
417 if (!currentItem) {
418 isTargetExist = false;
419 break;
420 }
421
422 currentItem = currentItem[pathToken[i]];
423 }
424
425 if (!isTargetExist || currentItem == null) {
426 return null;
427 }
428
429 return currentItem;
430 };
431 /**
432 * Save value
433 * @param {String} path target path
434 * @param {String|Number|Boolean|Object|Array} value value to save
435 * @return {Persist}
436 */
437
438
439 _proto.set = function set(path, value) {
440 // update url for pushState, replaceState
441 updateDepth(TYPE_NAVIGATE); // find path
442
443 var key = this.key;
444 var urlKey = getStorageKey(getUrl());
445 var globalState = getStateByKey(urlKey, key);
446
447 try {
448 if (path.length === 0) {
449 setStateByKey(urlKey, key, value);
450 } else {
451 var allValue = execRec(globalState, path.split("."), function (obj, head) {
452 obj[head] = value;
453 });
454 setStateByKey(urlKey, key, allValue);
455 }
456 } catch (e) {
457 if (clearFirst(e)) {
458 this.set(path, value);
459 } else {
460 // There is no more size to fit in.
461 throw e;
462 }
463 }
464
465 return this;
466 };
467 /**
468 * Remove value
469 * @param {String} path target path
470 * @return {Persist}
471 */
472
473
474 _proto.remove = function remove(path) {
475 // update url for pushState, replaceState
476 updateDepth(TYPE_NAVIGATE); // find path
477
478 var key = this.key;
479 var urlKey = getStorageKey(getUrl());
480 var globalState = getStateByKey(urlKey, key);
481
482 try {
483 if (path.length === 0) {
484 setStateByKey(urlKey, key, null);
485 } else {
486 var value = execRec(globalState, path.split("."), function (obj, head) {
487 if (typeof obj === "object") {
488 delete obj[head];
489 }
490 });
491 setStateByKey(urlKey, key, value);
492 }
493 } catch (e) {
494 if (clearFirst(e)) {
495 this.remove(path);
496 } else {
497 // There is no more size to fit in.
498 throw e;
499 }
500 }
501
502 return this;
503 };
504
505 return Persist;
506 }();
507
508 Persist.VERSION = "2.4.0";
509 Persist.StorageManager = {
510 reset: reset,
511 setStateByKey: setStateByKey,
512 getStateByKey: getStateByKey,
513 getStorage: getStorage
514 };
515 return Persist;
516 }(); // If navigation's type is not TYPE_BACK_FORWARD, delete information about current url.
517
518
519 updateDepth(getNavigationType());
520
521 Persist.updateDepth = updateDepth;
522
523 return Persist;
524
525})));
526//# sourceMappingURL=persist.js.map