vendor: 32,933 bytes, lines 1-996
1/* 2 * ----------------------------- JSTORAGE ------------------------------------- 3 * Simple local storage wrapper to save data on the browser side, supporting 4 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ 5 * 6 * Author: Andris Reinman, [email protected] 7 * Project homepage: www.jstorage.info 8 * 9 * Licensed under Unlicense: 10 * 11 * This is free and unencumbered software released into the public domain. 12 * 13 * Anyone is free to copy, modify, publish, use, compile, sell, or 14 * distribute this software, either in source code form or as a compiled 15 * binary, for any purpose, commercial or non-commercial, and by any 16 * means. 17 * 18 * In jurisdictions that recognize copyright laws, the author or authors 19 * of this software dedicate any and all copyright interest in the 20 * software to the public domain. We make this dedication for the benefit 21 * of the public at large and to the detriment of our heirs and 22 * successors. We intend this dedication to be an overt act of 23 * relinquishment in perpetuity of all present and future rights to this 24 * software under copyright law. 25 * 26 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 27 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 30 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 * OTHER DEALINGS IN THE SOFTWARE. 33 * 34 * For more information, please refer to <http://unlicense.org/> 35 */ 36 37/* global ActiveXObject: false */ 38/* jshint browser: true */ 39 40(function() { 41 'use strict'; 42 43 var 44 /* jStorage version */ 45 JSTORAGE_VERSION = '0.4.12', 46 47 /* detect a dollar object or create one if not found */ 48 $ = window.jQuery || window.$ || (window.$ = {}), 49 50 /* check for a JSON handling support */ 51 JSON = { 52 parse: window.JSON && (window.JSON.parse || window.JSON.decode) || 53 String.prototype.evalJSON && function(str) { 54 return String(str).evalJSON(); 55 } || 56 $.parseJSON || 57 $.evalJSON, 58 stringify: Object.toJSON || 59 window.JSON && (window.JSON.stringify || window.JSON.encode) || 60 $.toJSON 61 }; 62 63 // Break if no JSON support was found 64 if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') { 65 throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page'); 66 } 67 68 var 69 /* This is the object, that holds the cached values */ 70 _storage = { 71 __jstorage_meta: { 72 CRC32: {} 73 } 74 }, 75 76 /* Actual browser storage (localStorage or globalStorage['domain']) */ 77 _storage_service = { 78 jStorage: '{}' 79 }, 80 81 /* DOM element for older IE versions, holds userData behavior */ 82 _storage_elm = null, 83 84 /* How much space does the storage take */ 85 _storage_size = 0, 86 87 /* which backend is currently used */ 88 _backend = false, 89 90 /* onchange observers */ 91 _observers = {}, 92 93 /* timeout to wait after onchange event */ 94 _observer_timeout = false, 95 96 /* last update time */ 97 _observer_update = 0, 98 99 /* pubsub observers */ 100 _pubsub_observers = {}, 101 102 /* skip published items older than current timestamp */ 103 _pubsub_last = +new Date(), 104 105 /* Next check for TTL */ 106 _ttl_timeout, 107 108 /** 109 * XML encoding and decoding as XML nodes can't be JSON'ized 110 * XML nodes are encoded and decoded if the node is the value to be saved 111 * but not if it's as a property of another object 112 * Eg. - 113 * $.jStorage.set('key', xmlNode); // IS OK 114 * $.jStorage.set('key', {xml: xmlNode}); // NOT OK 115 */ 116 _XMLService = { 117 118 /** 119 * Validates a XML node to be XML 120 * based on jQuery.isXML function 121 */ 122 isXML: function(elm) { 123 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; 124 return documentElement ? documentElement.nodeName !== 'HTML' : false; 125 }, 126 127 /** 128 * Encodes a XML node to string 129 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ 130 */ 131 encode: function(xmlNode) { 132 if (!this.isXML(xmlNode)) { 133 return false; 134 } 135 try { // Mozilla, Webkit, Opera 136 return new XMLSerializer().serializeToString(xmlNode); 137 } catch (E1) { 138 try { // IE 139 return xmlNode.xml; 140 } catch (E2) {} 141 } 142 return false; 143 }, 144 145 /** 146 * Decodes a XML node from string 147 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ 148 */ 149 decode: function(xmlString) { 150 var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) || 151 (window.ActiveXObject && function(_xmlString) { 152 var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); 153 xml_doc.async = 'false'; 154 xml_doc.loadXML(_xmlString); 155 return xml_doc; 156 }), 157 resultXML; 158 if (!dom_parser) { 159 return false; 160 } 161 resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml'); 162 return this.isXML(resultXML) ? resultXML : false; 163 } 164 }; 165 166 167 ////////////////////////// PRIVATE METHODS //////////////////////// 168 169 /** 170 * Initialization function. Detects if the browser supports DOM Storage 171 * or userData behavior and behaves accordingly. 172 */ 173 function _init() { 174 /* Check if browser supports localStorage */ 175 var localStorageReallyWorks = false; 176 if ('localStorage' in window) { 177 try { 178 window.localStorage.setItem('_tmptest', 'tmpval'); 179 localStorageReallyWorks = true; 180 window.localStorage.removeItem('_tmptest'); 181 } catch (BogusQuotaExceededErrorOnIos5) { 182 // Thanks be to iOS5 Private Browsing mode which throws 183 // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 184 } 185 } 186 187 if (localStorageReallyWorks) { 188 try { 189 if (window.localStorage) { 190 _storage_service = window.localStorage; 191 _backend = 'localStorage'; 192 _observer_update = _storage_service.jStorage_update; 193 } 194 } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ } 195 } 196 /* Check if browser supports globalStorage */ 197 else if ('globalStorage' in window) { 198 try { 199 if (window.globalStorage) { 200 if (window.location.hostname == 'localhost') { 201 _storage_service = window.globalStorage['localhost.localdomain']; 202 } else { 203 _storage_service = window.globalStorage[window.location.hostname]; 204 } 205 _backend = 'globalStorage'; 206 _observer_update = _storage_service.jStorage_update; 207 } 208 } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ } 209 } 210 /* Check if browser supports userData behavior */ 211 else { 212 _storage_elm = document.createElement('link'); 213 if (_storage_elm.addBehavior) { 214 215 /* Use a DOM element to act as userData storage */ 216 _storage_elm.style.behavior = 'url(#default#userData)'; 217 218 /* userData element needs to be inserted into the DOM! */ 219 document.getElementsByTagName('head')[0].appendChild(_storage_elm); 220 221 try { 222 _storage_elm.load('jStorage'); 223 } catch (E) { 224 // try to reset cache 225 _storage_elm.setAttribute('jStorage', '{}'); 226 _storage_elm.save('jStorage'); 227 _storage_elm.load('jStorage'); 228 } 229 230 var data = '{}'; 231 try { 232 data = _storage_elm.getAttribute('jStorage'); 233 } catch (E5) {} 234 235 try { 236 _observer_update = _storage_elm.getAttribute('jStorage_update'); 237 } catch (E6) {} 238 239 _storage_service.jStorage = data; 240 _backend = 'userDataBehavior'; 241 } else { 242 _storage_elm = null; 243 return; 244 } 245 } 246 247 // Load data from storage 248 _load_storage(); 249 250 // remove dead keys 251 _handleTTL(); 252 253 // start listening for changes 254 _setupObserver(); 255 256 // initialize publish-subscribe service 257 _handlePubSub(); 258 259 // handle cached navigation 260 if ('addEventListener' in window) { 261 window.addEventListener('pageshow', function(event) { 262 if (event.persisted) { 263 _storageObserver(); 264 } 265 }, false); 266 } 267 } 268 269 /** 270 * Reload data from storage when needed 271 */ 272 function _reloadData() { 273 var data = '{}'; 274 275 if (_backend == 'userDataBehavior') { 276 _storage_elm.load('jStorage'); 277 278 try { 279 data = _storage_elm.getAttribute('jStorage'); 280 } catch (E5) {} 281 282 try { 283 _observer_update = _storage_elm.getAttribute('jStorage_update'); 284 } catch (E6) {} 285 286 _storage_service.jStorage = data; 287 } 288 289 _load_storage(); 290 291 // remove dead keys 292 _handleTTL(); 293 294 _handlePubSub(); 295 } 296 297 /** 298 * Sets up a storage change observer 299 */ 300 function _setupObserver() { 301 if (_backend == 'localStorage' || _backend == 'globalStorage') { 302 if ('addEventListener' in window) { 303 window.addEventListener('storage', _storageObserver, false); 304 } else { 305 document.attachEvent('onstorage', _storageObserver); 306 } 307 } else if (_backend == 'userDataBehavior') { 308 setInterval(_storageObserver, 1000); 309 } 310 } 311 312 /** 313 * Fired on any kind of data change, needs to check if anything has 314 * really been changed 315 */ 316 function _storageObserver() { 317 var updateTime; 318 // cumulate change notifications with timeout 319 clearTimeout(_observer_timeout); 320 _observer_timeout = setTimeout(function() { 321 322 if (_backend == 'localStorage' || _backend == 'globalStorage') { 323 updateTime = _storage_service.jStorage_update; 324 } else if (_backend == 'userDataBehavior') { 325 _storage_elm.load('jStorage'); 326 try { 327 updateTime = _storage_elm.getAttribute('jStorage_update'); 328 } catch (E5) {} 329 } 330 331 if (updateTime && updateTime != _observer_update) { 332 _observer_update = updateTime; 333 _checkUpdatedKeys(); 334 } 335 336 }, 25); 337 } 338 339 /** 340 * Reloads the data and checks if any keys are changed 341 */ 342 function _checkUpdatedKeys() { 343 var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)), 344 newCrc32List; 345 346 _reloadData(); 347 newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)); 348 349 var key, 350 updated = [], 351 removed = []; 352 353 for (key in oldCrc32List) { 354 if (oldCrc32List.hasOwnProperty(key)) { 355 if (!newCrc32List[key]) { 356 removed.push(key); 357 continue; 358 } 359 if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') { 360 updated.push(key); 361 } 362 } 363 } 364 365 for (key in newCrc32List) { 366 if (newCrc32List.hasOwnProperty(key)) { 367 if (!oldCrc32List[key]) { 368 updated.push(key); 369 } 370 } 371 } 372 373 _fireObservers(updated, 'updated'); 374 _fireObservers(removed, 'deleted'); 375 } 376 377 /** 378 * Fires observers for updated keys 379 * 380 * @param {Array|String} keys Array of key names or a key 381 * @param {String} action What happened with the value (updated, deleted, flushed) 382 */ 383 function _fireObservers(keys, action) { 384 keys = [].concat(keys || []); 385 386 var i, j, len, jlen; 387 388 if (action == 'flushed') { 389 keys = []; 390 for (var key in _observers) { 391 if (_observers.hasOwnProperty(key)) { 392 keys.push(key); 393 } 394 } 395 action = 'deleted'; 396 } 397 for (i = 0, len = keys.length; i < len; i++) { 398 if (_observers[keys[i]]) { 399 for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) { 400 _observers[keys[i]][j](keys[i], action); 401 } 402 } 403 if (_observers['*']) { 404 for (j = 0, jlen = _observers['*'].length; j < jlen; j++) { 405 _observers['*'][j](keys[i], action); 406 } 407 } 408 } 409 } 410 411 /** 412 * Publishes key change to listeners 413 */ 414 function _publishChange() { 415 var updateTime = (+new Date()).toString(); 416 417 if (_backend == 'localStorage' || _backend == 'globalStorage') { 418 try { 419 _storage_service.jStorage_update = updateTime; 420 } catch (E8) { 421 // safari private mode has been enabled after the jStorage initialization 422 _backend = false; 423 } 424 } else if (_backend == 'userDataBehavior') { 425 _storage_elm.setAttribute('jStorage_update', updateTime); 426 _storage_elm.save('jStorage'); 427 } 428 429 _storageObserver(); 430 } 431 432 /** 433 * Loads the data from the storage based on the supported mechanism 434 */ 435 function _load_storage() { 436 /* if jStorage string is retrieved, then decode it */ 437 if (_storage_service.jStorage) { 438 try { 439 _storage = JSON.parse(String(_storage_service.jStorage)); 440 } catch (E6) { 441 _storage_service.jStorage = '{}'; 442 } 443 } else { 444 _storage_service.jStorage = '{}'; 445 } 446 _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; 447 448 if (!_storage.__jstorage_meta) { 449 _storage.__jstorage_meta = {}; 450 } 451 if (!_storage.__jstorage_meta.CRC32) { 452 _storage.__jstorage_meta.CRC32 = {}; 453 } 454 } 455 456 /** 457 * This functions provides the 'save' mechanism to store the jStorage object 458 */ 459 function _save() { 460 _dropOldEvents(); // remove expired events 461 try { 462 _storage_service.jStorage = JSON.stringify(_storage); 463 // If userData is used as the storage engine, additional 464 if (_storage_elm) { 465 _storage_elm.setAttribute('jStorage', _storage_service.jStorage); 466 _storage_elm.save('jStorage'); 467 } 468 _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; 469 } catch (E7) { /* probably cache is full, nothing is saved this way*/ } 470 } 471 472 /** 473 * Function checks if a key is set and is string or numberic 474 * 475 * @param {String} key Key name 476 */ 477 function _checkKey(key) { 478 if (typeof key != 'string' && typeof key != 'number') { 479 throw new TypeError('Key name must be string or numeric'); 480 } 481 if (key == '__jstorage_meta') { 482 throw new TypeError('Reserved key name'); 483 } 484 return true; 485 } 486 487 /** 488 * Removes expired keys 489 */ 490 function _handleTTL() { 491 var curtime, i, TTL, CRC32, nextExpire = Infinity, 492 changed = false, 493 deleted = []; 494 495 clearTimeout(_ttl_timeout); 496 497 if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') { 498 // nothing to do here 499 return; 500 } 501 502 curtime = +new Date(); 503 TTL = _storage.__jstorage_meta.TTL; 504 505 CRC32 = _storage.__jstorage_meta.CRC32; 506 for (i in TTL) { 507 if (TTL.hasOwnProperty(i)) { 508 if (TTL[i] <= curtime) { 509 delete TTL[i]; 510 delete CRC32[i]; 511 delete _storage[i]; 512 changed = true; 513 deleted.push(i); 514 } else if (TTL[i] < nextExpire) { 515 nextExpire = TTL[i]; 516 } 517 } 518 } 519 520 // set next check 521 if (nextExpire != Infinity) { 522 _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF)); 523 } 524 525 // save changes 526 if (changed) { 527 _save(); 528 _publishChange(); 529 _fireObservers(deleted, 'deleted'); 530 } 531 } 532 533 /** 534 * Checks if there's any events on hold to be fired to listeners 535 */ 536 function _handlePubSub() { 537 var i, len; 538 if (!_storage.__jstorage_meta.PubSub) { 539 return; 540 } 541 var pubelm, 542 _pubsubCurrent = _pubsub_last, 543 needFired = []; 544 545 for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) { 546 pubelm = _storage.__jstorage_meta.PubSub[i]; 547 if (pubelm[0] > _pubsub_last) { 548 _pubsubCurrent = pubelm[0]; 549 needFired.unshift(pubelm); 550 } 551 } 552 553 for (i = needFired.length - 1; i >= 0; i--) { 554 _fireSubscribers(needFired[i][1], needFired[i][2]); 555 } 556 557 _pubsub_last = _pubsubCurrent; 558 } 559 560 /** 561 * Fires all subscriber listeners for a pubsub channel 562 * 563 * @param {String} channel Channel name 564 * @param {Mixed} payload Payload data to deliver 565 */ 566 function _fireSubscribers(channel, payload) { 567 if (_pubsub_observers[channel]) { 568 for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) { 569 // send immutable data that can't be modified by listeners 570 try { 571 _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload))); 572 } catch (E) {} 573 } 574 } 575 } 576 577 /** 578 * Remove old events from the publish stream (at least 2sec old) 579 */ 580 function _dropOldEvents() { 581 if (!_storage.__jstorage_meta.PubSub) { 582 return; 583 } 584 585 var retire = +new Date() - 2000; 586 587 for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) { 588 if (_storage.__jstorage_meta.PubSub[i][0] <= retire) { 589 // deleteCount is needed for IE6 590 _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i); 591 break; 592 } 593 } 594 595 if (!_storage.__jstorage_meta.PubSub.length) { 596 delete _storage.__jstorage_meta.PubSub; 597 } 598 599 } 600 601 /** 602 * Publish payload to a channel 603 * 604 * @param {String} channel Channel name 605 * @param {Mixed} payload Payload to send to the subscribers 606 */ 607 function _publish(channel, payload) { 608 if (!_storage.__jstorage_meta) { 609 _storage.__jstorage_meta = {}; 610 } 611 if (!_storage.__jstorage_meta.PubSub) { 612 _storage.__jstorage_meta.PubSub = []; 613 } 614 615 _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]); 616 617 _save(); 618 _publishChange(); 619 } 620 621 622 /** 623 * JS Implementation of MurmurHash2 624 * 625 * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed) 626 * 627 * @author <a href='mailto:[email protected]'>Gary Court</a> 628 * @see http://github.com/garycourt/murmurhash-js 629 * @author <a href='mailto:[email protected]'>Austin Appleby</a> 630 * @see http://sites.google.com/site/murmurhash/ 631 * 632 * @param {string} str ASCII only 633 * @param {number} seed Positive integer only 634 * @return {number} 32-bit positive integer hash 635 */ 636 637 function murmurhash2_32_gc(str, seed) { 638 var 639 l = str.length, 640 h = seed ^ l, 641 i = 0, 642 k; 643 644 while (l >= 4) { 645 k = 646 ((str.charCodeAt(i) & 0xff)) | 647 ((str.charCodeAt(++i) & 0xff) << 8) | 648 ((str.charCodeAt(++i) & 0xff) << 16) | 649 ((str.charCodeAt(++i) & 0xff) << 24); 650 651 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 652 k ^= k >>> 24; 653 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 654 655 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; 656 657 l -= 4; 658 ++i; 659 } 660 661 switch (l) { 662 case 3: 663 h ^= (str.charCodeAt(i + 2) & 0xff) << 16; 664 /* falls through */ 665 case 2: 666 h ^= (str.charCodeAt(i + 1) & 0xff) << 8; 667 /* falls through */ 668 case 1: 669 h ^= (str.charCodeAt(i) & 0xff); 670 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 671 } 672 673 h ^= h >>> 13; 674 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 675 h ^= h >>> 15; 676 677 return h >>> 0; 678 } 679 680 ////////////////////////// PUBLIC INTERFACE ///////////////////////// 681 682 $.jStorage = { 683 /* Version number */ 684 version: JSTORAGE_VERSION, 685 686 /** 687 * Sets a key's value. 688 * 689 * @param {String} key Key to set. If this value is not set or not 690 * a string an exception is raised. 691 * @param {Mixed} value Value to set. This can be any value that is JSON 692 * compatible (Numbers, Strings, Objects etc.). 693 * @param {Object} [options] - possible options to use 694 * @param {Number} [options.TTL] - optional TTL value, in milliseconds 695 * @return {Mixed} the used value 696 */ 697 set: function(key, value, options) { 698 _checkKey(key); 699 700 options = options || {}; 701 702 // undefined values are deleted automatically 703 if (typeof value == 'undefined') { 704 this.deleteKey(key); 705 return value; 706 } 707 708 if (_XMLService.isXML(value)) { 709 value = { 710 _is_xml: true, 711 xml: _XMLService.encode(value) 712 }; 713 } else if (typeof value == 'function') { 714 return undefined; // functions can't be saved! 715 } else if (value && typeof value == 'object') { 716 // clone the object before saving to _storage tree 717 value = JSON.parse(JSON.stringify(value)); 718 } 719 720 _storage[key] = value; 721 722 _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); 723 724 this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange 725 726 _fireObservers(key, 'updated'); 727 return value; 728 }, 729 730 /** 731 * Looks up a key in cache 732 * 733 * @param {String} key - Key to look up. 734 * @param {mixed} def - Default value to return, if key didn't exist. 735 * @return {Mixed} the key value, default value or null 736 */ 737 get: function(key, def) { 738 _checkKey(key); 739 if (key in _storage) { 740 if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) { 741 return _XMLService.decode(_storage[key].xml); 742 } else { 743 return _storage[key]; 744 } 745 } 746 return typeof(def) == 'undefined' ? null : def; 747 }, 748 749 /** 750 * Deletes a key from cache. 751 * 752 * @param {String} key - Key to delete. 753 * @return {Boolean} true if key existed or false if it didn't 754 */ 755 deleteKey: function(key) { 756 _checkKey(key); 757 if (key in _storage) { 758 delete _storage[key]; 759 // remove from TTL list 760 if (typeof _storage.__jstorage_meta.TTL == 'object' && 761 key in _storage.__jstorage_meta.TTL) { 762 delete _storage.__jstorage_meta.TTL[key]; 763 } 764 765 delete _storage.__jstorage_meta.CRC32[key]; 766 767 _save(); 768 _publishChange(); 769 _fireObservers(key, 'deleted'); 770 return true; 771 } 772 return false; 773 }, 774 775 /** 776 * Sets a TTL for a key, or remove it if ttl value is 0 or below 777 * 778 * @param {String} key - key to set the TTL for 779 * @param {Number} ttl - TTL timeout in milliseconds 780 * @return {Boolean} true if key existed or false if it didn't 781 */ 782 setTTL: function(key, ttl) { 783 var curtime = +new Date(); 784 _checkKey(key); 785 ttl = Number(ttl) || 0; 786 if (key in _storage) { 787 788 if (!_storage.__jstorage_meta.TTL) { 789 _storage.__jstorage_meta.TTL = {}; 790 } 791 792 // Set TTL value for the key 793 if (ttl > 0) { 794 _storage.__jstorage_meta.TTL[key] = curtime + ttl; 795 } else { 796 delete _storage.__jstorage_meta.TTL[key]; 797 } 798 799 _save(); 800 801 _handleTTL(); 802 803 _publishChange(); 804 return true; 805 } 806 return false; 807 }, 808 809 /** 810 * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set 811 * 812 * @param {String} key Key to check 813 * @return {Number} Remaining TTL in milliseconds 814 */ 815 getTTL: function(key) { 816 var curtime = +new Date(), 817 ttl; 818 _checkKey(key); 819 if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) { 820 ttl = _storage.__jstorage_meta.TTL[key] - curtime; 821 return ttl || 0; 822 } 823 return 0; 824 }, 825 826 /** 827 * Deletes everything in cache. 828 * 829 * @return {Boolean} Always true 830 */ 831 flush: function() { 832 _storage = { 833 __jstorage_meta: { 834 CRC32: {} 835 } 836 }; 837 _save(); 838 _publishChange(); 839 _fireObservers(null, 'flushed'); 840 return true; 841 }, 842 843 /** 844 * Returns a read-only copy of _storage 845 * 846 * @return {Object} Read-only copy of _storage 847 */ 848 storageObj: function() { 849 function F() {} 850 F.prototype = _storage; 851 return new F(); 852 }, 853 854 /** 855 * Returns an index of all used keys as an array 856 * ['key1', 'key2',..'keyN'] 857 * 858 * @return {Array} Used keys 859 */ 860 index: function() { 861 var index = [], 862 i; 863 for (i in _storage) { 864 if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') { 865 index.push(i); 866 } 867 } 868 return index; 869 }, 870 871 /** 872 * How much space in bytes does the storage take? 873 * 874 * @return {Number} Storage size in chars (not the same as in bytes, 875 * since some chars may take several bytes) 876 */ 877 storageSize: function() { 878 return _storage_size; 879 }, 880 881 /** 882 * Which backend is currently in use? 883 * 884 * @return {String} Backend name 885 */ 886 currentBackend: function() { 887 return _backend; 888 }, 889 890 /** 891 * Test if storage is available 892 * 893 * @return {Boolean} True if storage can be used 894 */ 895 storageAvailable: function() { 896 return !!_backend; 897 }, 898 899 /** 900 * Register change listeners 901 * 902 * @param {String} key Key name 903 * @param {Function} callback Function to run when the key changes 904 */ 905 listenKeyChange: function(key, callback) { 906 _checkKey(key); 907 if (!_observers[key]) { 908 _observers[key] = []; 909 } 910 _observers[key].push(callback); 911 }, 912 913 /** 914 * Remove change listeners 915 * 916 * @param {String} key Key name to unregister listeners against 917 * @param {Function} [callback] If set, unregister the callback, if not - unregister all 918 */ 919 stopListening: function(key, callback) { 920 _checkKey(key); 921 922 if (!_observers[key]) { 923 return; 924 } 925 926 if (!callback) { 927 delete _observers[key]; 928 return; 929 } 930 931 for (var i = _observers[key].length - 1; i >= 0; i--) { 932 if (_observers[key][i] == callback) { 933 _observers[key].splice(i, 1); 934 } 935 } 936 }, 937 938 /** 939 * Subscribe to a Publish/Subscribe event stream 940 * 941 * @param {String} channel Channel name 942 * @param {Function} callback Function to run when the something is published to the channel 943 */ 944 subscribe: function(channel, callback) { 945 channel = (channel || '').toString(); 946 if (!channel) { 947 throw new TypeError('Channel not defined'); 948 } 949 if (!_pubsub_observers[channel]) { 950 _pubsub_observers[channel] = []; 951 } 952 _pubsub_observers[channel].push(callback); 953 }, 954 955 /** 956 * Publish data to an event stream 957 * 958 * @param {String} channel Channel name 959 * @param {Mixed} payload Payload to deliver 960 */ 961 publish: function(channel, payload) { 962 channel = (channel || '').toString(); 963 if (!channel) { 964 throw new TypeError('Channel not defined'); 965 } 966 967 _publish(channel, payload); 968 }, 969 970 /** 971 * Reloads the data from browser storage 972 */ 973 reInit: function() { 974 _reloadData(); 975 }, 976 977 /** 978 * Removes reference from global objects and saves it as jStorage 979 * 980 * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context 981 */ 982 noConflict: function(saveInGlobal) { 983 delete window.$.jStorage; 984 985 if (saveInGlobal) { 986 window.jStorage = this; 987 } 988 989 return this; 990 } 991 }; 992 993 // Initialize jStorage 994 _init(); 995 996})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.