index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { createBlob } from './createBlob';
  2. const BLOB_TYPE_PREFIX_REGEX = /^~~local_forage_type~([^~]+)~/;
  3. const SERIALIZED_MARKER_LENGTH = "__lfsc__:" /* SERIALIZED_MARKER */.length;
  4. const TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + "arbf" /* TYPE_ARRAYBUFFER */.length;
  5. //tslint:disable:no-magic-numbers no-bitwise prefer-switch no-unbound-method
  6. const toString = Object.prototype.toString;
  7. export function stringToBuffer(serializedString) {
  8. // Fill the string into a ArrayBuffer.
  9. let bufferLength = serializedString.length * 0.75;
  10. const len = serializedString.length;
  11. if (serializedString[serializedString.length - 1] === '=') {
  12. bufferLength--;
  13. if (serializedString[serializedString.length - 2] === '=') {
  14. bufferLength--;
  15. }
  16. }
  17. const buffer = new ArrayBuffer(bufferLength);
  18. const bytes = new Uint8Array(buffer);
  19. for (let i = 0, p = 0; i < len; i += 4) {
  20. const encoded1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */.indexOf(serializedString[i]);
  21. const encoded2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */.indexOf(serializedString[i + 1]);
  22. const encoded3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */.indexOf(serializedString[i + 2]);
  23. const encoded4 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */.indexOf(serializedString[i + 3]);
  24. bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
  25. bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
  26. bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
  27. }
  28. return buffer;
  29. }
  30. /**
  31. * Converts a buffer to a string to store, serialized, in the backend
  32. * storage library.
  33. */
  34. export function bufferToString(buffer) {
  35. // base64-arraybuffer
  36. const bytes = new Uint8Array(buffer);
  37. let base64String = '';
  38. for (let i = 0; i < bytes.length; i += 3) {
  39. /*jslint bitwise: true */
  40. base64String += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */[bytes[i] >> 2];
  41. base64String += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
  42. base64String +=
  43. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
  44. base64String += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" /* BASE_CHARS */[bytes[i + 2] & 63];
  45. }
  46. if (bytes.length % 3 === 2) {
  47. base64String = base64String.substring(0, base64String.length - 1) + '=';
  48. }
  49. else if (bytes.length % 3 === 1) {
  50. base64String = base64String.substring(0, base64String.length - 2) + '==';
  51. }
  52. return base64String;
  53. }
  54. /**
  55. * Serialize a value, afterwards executing a callback (which usually
  56. * instructs the `setItem()` callback/promise to be executed). This is how
  57. * we store binary data with localStorage.
  58. * @param value
  59. * @param callback
  60. */
  61. export function serialize(value, callback) {
  62. let valueType = '';
  63. if (value) {
  64. valueType = toString.call(value);
  65. }
  66. // Cannot use `value instanceof ArrayBuffer` or such here, as these
  67. // checks fail when running the tests using casper.js...
  68. if (value && (valueType === '[object ArrayBuffer]' ||
  69. (value.buffer && toString.call(value.buffer) === '[object ArrayBuffer]'))) {
  70. // Convert binary arrays to a string and prefix the string with
  71. // a special marker.
  72. let buffer;
  73. let marker = "__lfsc__:" /* SERIALIZED_MARKER */;
  74. if (value instanceof ArrayBuffer) {
  75. buffer = value;
  76. marker += "arbf" /* TYPE_ARRAYBUFFER */;
  77. }
  78. else {
  79. buffer = value.buffer;
  80. if (valueType === '[object Int8Array]') {
  81. marker += "si08" /* TYPE_INT8ARRAY */;
  82. }
  83. else if (valueType === '[object Uint8Array]') {
  84. marker += "ui08" /* TYPE_UINT8ARRAY */;
  85. }
  86. else if (valueType === '[object Uint8ClampedArray]') {
  87. marker += "uic8" /* TYPE_UINT8CLAMPEDARRAY */;
  88. }
  89. else if (valueType === '[object Int16Array]') {
  90. marker += "si16" /* TYPE_INT16ARRAY */;
  91. }
  92. else if (valueType === '[object Uint16Array]') {
  93. marker += "ur16" /* TYPE_UINT16ARRAY */;
  94. }
  95. else if (valueType === '[object Int32Array]') {
  96. marker += "si32" /* TYPE_INT32ARRAY */;
  97. }
  98. else if (valueType === '[object Uint32Array]') {
  99. marker += "ui32" /* TYPE_UINT32ARRAY */;
  100. }
  101. else if (valueType === '[object Float32Array]') {
  102. marker += "fl32" /* TYPE_FLOAT32ARRAY */;
  103. }
  104. else if (valueType === '[object Float64Array]') {
  105. marker += "fl64" /* TYPE_FLOAT64ARRAY */;
  106. }
  107. else {
  108. callback(new Error('Failed to get type for BinaryArray'));
  109. }
  110. }
  111. callback(marker + bufferToString(buffer));
  112. }
  113. else if (valueType === '[object Blob]') {
  114. // Convert the blob to a binaryArray and then to a string.
  115. const fileReader = new FileReader();
  116. fileReader.onload = function () {
  117. // Backwards-compatible prefix for the blob type.
  118. //tslint:disable-next-line:restrict-plus-operands
  119. const str = `${"~~local_forage_type~" /* BLOB_TYPE_PREFIX */ + value.type}~${bufferToString(this.result)}`;
  120. callback("__lfsc__:" /* SERIALIZED_MARKER */ + "blob" /* TYPE_BLOB */ + str);
  121. };
  122. fileReader.readAsArrayBuffer(value);
  123. }
  124. else {
  125. try {
  126. callback(JSON.stringify(value));
  127. }
  128. catch (e) {
  129. console.error('Couldn\'t convert value into a JSON string: ', value);
  130. callback(null, e);
  131. }
  132. }
  133. }
  134. /**
  135. * Deserialize data we've inserted into a value column/field. We place
  136. * special markers into our strings to mark them as encoded; this isn't
  137. * as nice as a meta field, but it's the only sane thing we can do whilst
  138. * keeping localStorage support intact.
  139. *
  140. * Oftentimes this will just deserialize JSON content, but if we have a
  141. * special marker (SERIALIZED_MARKER, defined above), we will extract
  142. * some kind of arraybuffer/binary data/typed array out of the string.
  143. * @param value
  144. */
  145. export function deserialize(value) {
  146. // If we haven't marked this string as being specially serialized (i.e.
  147. // something other than serialized JSON), we can just return it and be
  148. // done with it.
  149. if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== "__lfsc__:" /* SERIALIZED_MARKER */) {
  150. return JSON.parse(value);
  151. }
  152. // The following code deals with deserializing some kind of Blob or
  153. // TypedArray. First we separate out the type of data we're dealing
  154. // with from the data itself.
  155. let serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH);
  156. const type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH);
  157. let blobType;
  158. // Backwards-compatible blob type serialization strategy.
  159. // DBs created with older versions of localForage will simply not have the blob type.
  160. if (type === "blob" /* TYPE_BLOB */ && BLOB_TYPE_PREFIX_REGEX.test(serializedString)) {
  161. const matcher = serializedString.match(BLOB_TYPE_PREFIX_REGEX);
  162. blobType = matcher[1];
  163. serializedString = serializedString.substring(matcher[0].length);
  164. }
  165. const buffer = stringToBuffer(serializedString);
  166. // Return the right type based on the code/type set during
  167. // serialization.
  168. switch (type) {
  169. case "arbf" /* TYPE_ARRAYBUFFER */:
  170. return buffer;
  171. case "blob" /* TYPE_BLOB */:
  172. return createBlob([buffer], { type: blobType });
  173. case "si08" /* TYPE_INT8ARRAY */:
  174. return new Int8Array(buffer);
  175. case "ui08" /* TYPE_UINT8ARRAY */:
  176. return new Uint8Array(buffer);
  177. case "uic8" /* TYPE_UINT8CLAMPEDARRAY */:
  178. return new Uint8ClampedArray(buffer);
  179. case "si16" /* TYPE_INT16ARRAY */:
  180. return new Int16Array(buffer);
  181. case "ur16" /* TYPE_UINT16ARRAY */:
  182. return new Uint16Array(buffer);
  183. case "si32" /* TYPE_INT32ARRAY */:
  184. return new Int32Array(buffer);
  185. case "ui32" /* TYPE_UINT32ARRAY */:
  186. return new Uint32Array(buffer);
  187. case "fl32" /* TYPE_FLOAT32ARRAY */:
  188. return new Float32Array(buffer);
  189. case "fl64" /* TYPE_FLOAT64ARRAY */:
  190. return new Float64Array(buffer);
  191. default:
  192. throw new Error('Unkown type: ' + type);
  193. }
  194. }
  195. //# sourceMappingURL=index.js.map