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