You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
8.7 KiB

  1. (function (root, factory) {
  2. 'use strict';
  3. if (typeof define === 'function' && define.amd) {
  4. define(['angular'], factory);
  5. } else if (root.hasOwnProperty('angular')) {
  6. // Browser globals (root is window), we don't register it.
  7. factory(root.angular);
  8. } else if (typeof exports === 'object') {
  9. module.exports = factory(require('angular'));
  10. }
  11. }(this , function (angular) {
  12. 'use strict';
  13. // In cases where Angular does not get passed or angular is a truthy value
  14. // but misses .module we can fall back to using window.
  15. angular = (angular && angular.module ) ? angular : window.angular;
  16. function isStorageSupported($window, storageType) {
  17. // Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied"
  18. // when accessing window.localStorage. This happens before you try to do anything with it. Catch
  19. // that error and allow execution to continue.
  20. // fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari
  21. // when "Block cookies": "Always block" is turned on
  22. var supported;
  23. try {
  24. supported = $window[storageType];
  25. }
  26. catch(err) {
  27. supported = false;
  28. }
  29. // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage and sessionStorage
  30. // is available, but trying to call .setItem throws an exception below:
  31. // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
  32. if(supported) {
  33. var key = '__' + Math.round(Math.random() * 1e7);
  34. try {
  35. $window[storageType].setItem(key, key);
  36. $window[storageType].removeItem(key, key);
  37. }
  38. catch(err) {
  39. supported = false;
  40. }
  41. }
  42. return supported;
  43. }
  44. /**
  45. * @ngdoc overview
  46. * @name ngStorage
  47. */
  48. return angular.module('ngStorage', [])
  49. /**
  50. * @ngdoc object
  51. * @name ngStorage.$localStorage
  52. * @requires $rootScope
  53. * @requires $window
  54. */
  55. .provider('$localStorage', _storageProvider('localStorage'))
  56. /**
  57. * @ngdoc object
  58. * @name ngStorage.$sessionStorage
  59. * @requires $rootScope
  60. * @requires $window
  61. */
  62. .provider('$sessionStorage', _storageProvider('sessionStorage'));
  63. function _storageProvider(storageType) {
  64. var providerWebStorage = isStorageSupported(window, storageType);
  65. return function () {
  66. var storageKeyPrefix = 'ngStorage-';
  67. this.setKeyPrefix = function (prefix) {
  68. if (typeof prefix !== 'string') {
  69. throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.');
  70. }
  71. storageKeyPrefix = prefix;
  72. };
  73. var serializer = angular.toJson;
  74. var deserializer = angular.fromJson;
  75. this.setSerializer = function (s) {
  76. if (typeof s !== 'function') {
  77. throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.');
  78. }
  79. serializer = s;
  80. };
  81. this.setDeserializer = function (d) {
  82. if (typeof d !== 'function') {
  83. throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.');
  84. }
  85. deserializer = d;
  86. };
  87. this.supported = function() {
  88. return !!providerWebStorage;
  89. };
  90. // Note: This is not very elegant at all.
  91. this.get = function (key) {
  92. return providerWebStorage && deserializer(providerWebStorage.getItem(storageKeyPrefix + key));
  93. };
  94. // Note: This is not very elegant at all.
  95. this.set = function (key, value) {
  96. return providerWebStorage && providerWebStorage.setItem(storageKeyPrefix + key, serializer(value));
  97. };
  98. this.remove = function (key) {
  99. providerWebStorage && providerWebStorage.removeItem(storageKeyPrefix + key);
  100. }
  101. this.$get = [
  102. '$rootScope',
  103. '$window',
  104. '$log',
  105. '$timeout',
  106. '$document',
  107. function(
  108. $rootScope,
  109. $window,
  110. $log,
  111. $timeout,
  112. $document
  113. ){
  114. // The magic number 10 is used which only works for some keyPrefixes...
  115. // See https://github.com/gsklee/ngStorage/issues/137
  116. var prefixLength = storageKeyPrefix.length;
  117. // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
  118. // Note: recheck mainly for testing (so we can use $window[storageType] rather than window[storageType])
  119. var isSupported = isStorageSupported($window, storageType),
  120. webStorage = isSupported || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}),
  121. $storage = {
  122. $default: function(items) {
  123. for (var k in items) {
  124. angular.isDefined($storage[k]) || ($storage[k] = angular.copy(items[k]) );
  125. }
  126. $storage.$sync();
  127. return $storage;
  128. },
  129. $reset: function(items) {
  130. for (var k in $storage) {
  131. '$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k));
  132. }
  133. return $storage.$default(items);
  134. },
  135. $sync: function () {
  136. for (var i = 0, l = webStorage.length, k; i < l; i++) {
  137. // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
  138. (k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k)));
  139. }
  140. },
  141. $apply: function() {
  142. var temp$storage;
  143. _debounce = null;
  144. if (!angular.equals($storage, _last$storage)) {
  145. temp$storage = angular.copy(_last$storage);
  146. angular.forEach($storage, function(v, k) {
  147. if (angular.isDefined(v) && '$' !== k[0]) {
  148. webStorage.setItem(storageKeyPrefix + k, serializer(v));
  149. delete temp$storage[k];
  150. }
  151. });
  152. for (var k in temp$storage) {
  153. webStorage.removeItem(storageKeyPrefix + k);
  154. }
  155. _last$storage = angular.copy($storage);
  156. }
  157. },
  158. $supported: function() {
  159. return !!isSupported;
  160. }
  161. },
  162. _last$storage,
  163. _debounce;
  164. $storage.$sync();
  165. _last$storage = angular.copy($storage);
  166. $rootScope.$watch(function() {
  167. _debounce || (_debounce = $timeout($storage.$apply, 100, false));
  168. });
  169. // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
  170. $window.addEventListener && $window.addEventListener('storage', function(event) {
  171. if (!event.key) {
  172. return;
  173. }
  174. // Reference doc.
  175. var doc = $document[0];
  176. if ( (!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength) ) {
  177. event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)];
  178. _last$storage = angular.copy($storage);
  179. $rootScope.$apply();
  180. }
  181. });
  182. $window.addEventListener && $window.addEventListener('beforeunload', function() {
  183. $storage.$apply();
  184. });
  185. return $storage;
  186. }
  187. ];
  188. };
  189. }
  190. }));