@lumjs/encode 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,20 @@
2
2
 
3
3
  A bunch of encoding libraries.
4
4
 
5
- Among other things, it offers a pure JS implementation of my
6
- [Safe64](https://github.com/supernovus/lum.encode.php)
7
- data serialization and encoding format.
5
+ ## 2.x Release Notes
6
+
7
+ Version `2.0` is pretty much a complete rewrite, and as the major version
8
+ change indicates, is not 100% backwards compatible. I've tried to make the
9
+ migration process as painless as possible, but some changes will be required.
10
+
11
+ - Split the [Safe64] libraries into their own package.
12
+ - Rewrote the `base64` and `hash` libraries to use modern `ES2015+` APIs.
13
+ - Dropped the dependency on the legacy `crypto-js` package.
14
+ - The `base64` libraries now have additional `async` functions for working
15
+ with arbitrary data in addition to the synchronous ones for Unicode text.
16
+ - The `hash` library now uses `async` methods due to the `SubtleCrypto` API
17
+ that we're now using for generating the hashes.
18
+ - Minor cleanups in `base91` and `utils` modules.
8
19
 
9
20
  ## Official URLs
10
21
 
@@ -20,3 +31,7 @@ Timothy Totten <2010@totten.ca>
20
31
  ## License
21
32
 
22
33
  [MIT](https://spdx.org/licenses/MIT.html)
34
+
35
+ ---
36
+
37
+ [Safe64]: https://github.com/supernovus/lum.safe64-data.js
package/lib/base64.js CHANGED
@@ -1,55 +1,376 @@
1
1
 
2
- const {S,isObj} = require('@lumjs/core/types');
3
- const Base64 = require('crypto-js/enc-base64');
4
- const Utf8 = require('crypto-js/enc-utf8');
2
+ const {S,B,isObj} = require('@lumjs/core/types');
3
+
4
+ const D_MIME = 'application/octet-stream';
5
+ const D_ENC = 'utf-8';
6
+ const P_DATA = 'data:';
7
+ const P_B64 = ';base64,';
8
+ const R_PRE = /^data\:(.*?);base64,/;
5
9
 
6
10
  /**
7
11
  * Base64 functions.
8
12
  *
9
- * Provides friendlier wrappers around the `crypto-js` libraries.
13
+ * Several functions based on code from MDN guides:
14
+ * https://developer.mozilla.org/en-US/docs/Glossary/Base64
10
15
  *
11
16
  * @module @lumjs/encode/base64
12
17
  */
13
18
 
14
19
  /**
15
- * Encode data as a `Base64` string.
16
- *
17
- * @param {(string|WordArray)} rawdata - The data we want to encode.
20
+ * Make a Base64 string URL-safe.
18
21
  *
19
- * If this is a `string` we'll convert it into a `WordArray` using
20
- * the `stringFormat` object.
22
+ * Converts `+` to `-` and `/` to `_`.
23
+ * By default it also strips `=` padding characters.
21
24
  *
22
- * @param {object} [stringFormat=Utf8] The string format.
25
+ * @param {string} string - A Base64-encoded string
26
+ * @param {object} [options] Options
27
+ * @param {boolean} [options.useTildes=false] Use tildes?
23
28
  *
24
- * Can be any encoding module from the `crypto-js` library.
25
- * Default is `CryptoJS.enc.Utf8`
26
- *
27
- * @return {string} The encoded string.
29
+ * Replaces `=` with `~` characters instead of stripping them.
30
+ * This option is for backwards-compatibility with old code only,
31
+ * and there's no reason to use it these days.
32
+ *
33
+ * @returns {string}
28
34
  */
29
- exports.encode = function(rawdata, stringFormat=Utf8)
35
+ function urlize(string, options={})
30
36
  {
31
- const data = typeof rawdata === S ? stringFormat.parse(rawdata) : rawdata;
32
- return Base64.stringify(data);
37
+ string = string.replaceAll('+', '-');
38
+ string = string.replaceAll('/', '_');
39
+ string = string.replaceAll('=', options.useTildes ? '~' : '');
40
+ return string;
33
41
  }
34
42
 
35
43
  /**
36
- * Decode a `Base64` string back into raw data.
37
- *
38
- * @param {string} string - The Base64 string to decode.
44
+ * Undoes the effects of `urlize()`
45
+ *
46
+ * Doesn't matter if the string has actually been passed to `urlize()`,
47
+ * nor if the obsolete `options.useTildes` option was used when encoding.
48
+ *
49
+ * @param {string} string
50
+ * @returns {string}
51
+ */
52
+ function deurlize(string)
53
+ {
54
+ string = string.replaceAll('-', '+');
55
+ string = string.replaceAll('_', '/');
56
+ string = string.replaceAll('~', '=');
57
+ string += "===".substring((string.length+3)%4);
58
+ return string;
59
+ }
60
+
61
+ /**
62
+ * Convert a Base64-encoded string into a Uint8Array.
63
+ *
64
+ * This is a low-level function with no options.
65
+ * See `decodeText()` for a more full-featured function.
66
+ *
67
+ * @param {string} base64 - Base64 encoded-string
68
+ * @returns {Uint8Array}
69
+ */
70
+ function toBytes(base64)
71
+ {
72
+ const binString = atob(base64);
73
+ return Uint8Array.from(binString, (m) => m.codePointAt(0));
74
+ }
75
+
76
+ /**
77
+ * Convert a Uint8Array into Base64-encoded string.
78
+ *
79
+ * This is a low-level function with no options.
80
+ * See `encodeText()` for a more full-featured function.
81
+ *
82
+ * @param {Uint8Array} bytes - Byte array to convert
83
+ * @returns {string}
84
+ */
85
+ function fromBytes(bytes)
86
+ {
87
+ const binString = Array.from(bytes, (byte) =>
88
+ String.fromCodePoint(byte),
89
+ ).join("");
90
+ return btoa(binString);
91
+ }
92
+
93
+ /**
94
+ * Encode a string to Base64.
95
+ *
96
+ * Uses the `TextEncoder` API and `fromBytes()`.
97
+ * May optionally pass the output through `urlize()`.
98
+ *
99
+ * @param {string} data - Any valid (Unicode) string
100
+ * @param {(object|boolean)} [options] Options
101
+ *
102
+ * - If `boolean`, used as `options.url`
103
+ * - Passed to `urlize()` if `options.url` is `true`
104
+ *
105
+ * @param {boolean} [options.url=false] Urlize the output?
106
+ * If true, converts `+`, `/`, and `=` to URL-friendly alternatives.
107
+ *
108
+ * @returns {string} A Base64-encoded string
109
+ */
110
+ function encodeText(data, options={})
111
+ {
112
+ if (typeof options === B)
113
+ { // Assume the 'url' option.
114
+ options = {url: options};
115
+ }
116
+
117
+ const encoder = new TextEncoder();
118
+ const base64 = fromBytes(encoder.encode(data));
119
+ return options.url ? urlize(base64, options) : base64;
120
+ }
121
+
122
+ /**
123
+ * Decode a Base64 string into a Unicode string.
124
+ *
125
+ * Uses `toBytes()` and the `TextDecoder` API.
126
+ * Will pass input through `deurlize()` by default.
127
+ *
128
+ * @param {string} base64 - A Base64 string to decode
129
+ * @param {(object|boolean)} [options] Options
130
+ *
131
+ * - If `boolean`, used as `options.url`
132
+ * - Passed to `new TextDecoder()`
133
+ * - Passed to `decoder.decode()`
134
+ *
135
+ * @param {boolean} [options.url=true] Deurlize the output?
136
+ *
137
+ * Unless this is explicitly set as `false`, the `base64`
138
+ * string will be passed to `deurlize()` before being
139
+ * processed further.
140
+ *
141
+ * @returns {string} A Unicode string
142
+ */
143
+ function decodeText(base64, options={})
144
+ {
145
+ if (typeof options === B)
146
+ { // Assume the 'url' option.
147
+ options = {url: options};
148
+ }
149
+
150
+ if (options.url !== false)
151
+ { // Unless explicitly disabled, use deurlize() first.
152
+ base64 = deurlize(base64);
153
+ }
154
+
155
+ const encoding = options.encoding ?? D_ENC;
156
+ const decoder = new TextDecoder(encoding, options);
157
+ return decoder.decode(toBytes(base64), options);
158
+ }
159
+
160
+ /**
161
+ * Encode binary data into a Base64-encoded Data URL.
162
+ *
163
+ * @param {(File|Blob|Array|TypedArray|ArrayBuffer)} data - Data to encode
164
+ *
165
+ * If this is not a `Blob` or `File`, it will be converted into one.
166
+ *
167
+ * @param {object} [options] Options
168
+ *
169
+ * @param {object} [options.blob] Options for Blob instances.
170
+ *
171
+ * If specified, this will be passed to the `Blob()` constructor.
172
+ *
173
+ * Only used if `data` is not already a `Blob` or `File` instance,
174
+ * and `options.file` was not specified or set to `false`.
175
+ *
176
+ * @param {(object|boolean)} [options.file] Options for File instances.
39
177
  *
40
- * @param {(object|false)} [stringFormat=Utf8] The string format.
178
+ * If this is any non-false value, and `data` is not already a `Blob`,
179
+ * then we will convert `data` into a `File` instance instead of a `Blob`.
41
180
  *
42
- * Can be any encoder library from the `crypto-js` library.
43
- * Default is `CryptoJS.enc.Utf8`
181
+ * If this is an `object`, it will be passed to the `File()` constructor.
44
182
  *
45
- * If this is `false`, we'll return a `WordArray` object.
183
+ * @param {string} [options.file.name] Filename for the `File` instance.
184
+ *
185
+ * This is likely never needed, but is kept for completion sake.
186
+ *
187
+ * @returns {Promise<string>} Resolves to the Data URL
188
+ */
189
+ async function toDataUrl(data, options={})
190
+ {
191
+ if (!(data instanceof Blob))
192
+ { // Build a Blob or File instance out of the passed data.
193
+ if (!Array.isArray(data))
194
+ { // Wrap the data in an Array.
195
+ data = [data];
196
+ }
197
+
198
+ // Sources for our Blob/File options.
199
+ const optsrc = [{type: D_MIME}, options];
200
+
201
+ if (options.file)
202
+ { // Let's build a File.
203
+ if (isObj(options.file))
204
+ {
205
+ optsrc.push(options.file);
206
+ }
207
+ const fopts = Object.assign(...optsrc);
208
+ const fname = fopts.filename ?? fopts.name ?? '';
209
+ data = new File(data, fname, fopts);
210
+ }
211
+ else
212
+ { // Let's build a Blob.
213
+ if (isObj(options.blob))
214
+ {
215
+ optsrc.push(options.blob);
216
+ }
217
+ const bopts = Object.assign(...optsrc);
218
+ data = new Blob(data, bopts);
219
+ }
220
+ } // Ensure sufficient Blobiness.
221
+
222
+ return await new Promise((resolve, reject) =>
223
+ {
224
+ const reader = Object.assign(new FileReader(),
225
+ {
226
+ onload: () => resolve(reader.result),
227
+ onerror: () => reject(reader.error),
228
+ });
229
+ reader.readAsDataURL(data);
230
+ });
231
+
232
+ } // toDataUrl()
233
+
234
+ /**
235
+ * Decode a Data URL into arbitrary binary data.
236
+ *
237
+ * @param {string} dataUrl - A valid Data URL
238
+ * @param {object} [options] Options
239
+ * @param {boolean} [options.response=false] Return `Response`
240
+ * @param {boolean} [options.buffer=false] Return `ArrayBuffer`
46
241
  *
47
- * @return {(string|WordArray)} The decoded output.
242
+ * @returns {Promise<(Uint8Array|ArrayBuffer|Response)>} Promise of data
243
+ *
244
+ * By default this resolves to a `Uint8Array` instance.
245
+ *
246
+ * See `options.response` and `options.buffer` for alternative values that
247
+ * this may resolve to if requested.
248
+ *
249
+ */
250
+ async function fromDataUrl(dataUrl, options={})
251
+ {
252
+ const res = await fetch(dataUrl);
253
+ if (options.response) return res;
254
+ const buf = await res.arrayBuffer();
255
+ if (options.buffer) return buf;
256
+ return new Uint8Array(buf);
257
+ }
258
+
259
+ /**
260
+ * A wrapper around `toDataUrl()` that strips the Data URL header,
261
+ * leaving just the Base64 string, and can emit URL-safe strings.
262
+ *
263
+ * @param {mixed} data - See `toDataUrl()` for valid values
264
+ * @param {object} [options] Options
265
+ *
266
+ * - Passed to `toDataUrl()`
267
+ * - Passed to `urlize()` if `options.url` is `true`
268
+ *
269
+ * @param {boolean} [options.url=false] Use `urlize()` on encoded string?
270
+ *
271
+ * @returns {Promise<string>} Resolves to a Base64 string
272
+ */
273
+ async function encodeData(data, options={})
274
+ {
275
+ let base64 = await toDataUrl(data, options).replace(R_PRE, '');
276
+ return options.url ? urlize(base64, options) : base64;
277
+ }
278
+
279
+ /**
280
+ * A wrapper around `fromDataUrl()` that adds a Data URL header
281
+ * if necessary, and can handle URL-safe Base64 strings.
282
+ *
283
+ * @param {*} base64
284
+ * @param {*} options
285
+ *
286
+ * - Passed to `fromDataUrl()`
287
+ * - Passed to `deurlize()` if `options.url` is NOT set to `false`
288
+ *
289
+ * @param {boolean} [options.url=true] Use `deurlize()` on decoded string?
290
+ *
291
+ * @returns {Promise} See `fromDataUrl()` for more details
292
+ */
293
+ async function decodeData(base64, options={})
294
+ {
295
+ if (!R_PRE.test(base64))
296
+ { // Assume a raw base64 string.
297
+ if (options.url !== false)
298
+ { // Unless explicitly disabled, use deurlize() first.
299
+ base64 = deurlize(base64);
300
+ }
301
+ const type = (typeof options.type === S) ? options.type : D_MIME;
302
+ base64 = P_DATA+type+P_B64+base64;
303
+ }
304
+
305
+ return fromDataUrl(base64, options);
306
+ }
307
+
308
+ /**
309
+ * Encode data into a base64 string
310
+ *
311
+ * Uses `encodeText()` unless the `data` or `options` have specific
312
+ * values that indicate `encodeData()` should be used instead.
313
+ *
314
+ * @param {*} data - Data to encode
315
+ *
316
+ * If this is anything other than a `string`, `encodeData()` will be used.
317
+ *
318
+ * @param {object} [options] Options
319
+ *
320
+ * If either `options.blob` or `options.file` are specified,
321
+ * `encodeData()` will be used.
322
+ *
323
+ * @returns {(string|Promise<string>)}
324
+ * See `encodeText()` and `encodeData()` for details.
325
+ */
326
+ function encode(data, options={})
327
+ {
328
+ if (options.blob || options.file || (typeof data !== S))
329
+ {
330
+ return encodeData(data, options);
331
+ }
332
+ else
333
+ {
334
+ return encodeText(data, options);
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Decode a base64 string into data
340
+ *
341
+ * Uses `decodeText()` unless the `base64` or `options` have specific
342
+ * values that indicate `decodeData()` should be used.
343
+ *
344
+ * @param {string} base64 - Base64-encoded string (or a Data URL).
345
+ *
346
+ * If this begins with a Data URL header, `decodeData()` will be used.
347
+ *
348
+ * @param {object} [options] Options
349
+ *
350
+ * If either `options.response` or `options.buffer` are true,
351
+ * `decodeData()` will be used.
352
+ *
353
+ * @returns {mixed} See `decodeText()` and `decodeData()` for details;
354
+ * will always be a `Promise` if `decodeData()` was used.
48
355
  */
49
- exports.decode = function(string, stringFormat=Utf8)
356
+ function decode(base64, options={})
50
357
  {
51
- const data = Base64.parse(string);
52
- return (isObj(stringFormat) ? data.toString(stringFormat) : data);
358
+ if (options.response || options.buffer || R_PRE.test(base64))
359
+ {
360
+ return decodeData(base64, options);
361
+ }
362
+ else
363
+ {
364
+ return decodeText(base64, options);
365
+ }
53
366
  }
54
367
 
55
- exports.Utf8 = Utf8;
368
+ module.exports =
369
+ {
370
+ urlize, deurlize,
371
+ toBytes, fromBytes,
372
+ encodeText, decodeText,
373
+ toDataUrl, fromDataUrl,
374
+ encodeData, decodeData,
375
+ encode, decode,
376
+ }
package/lib/base91.js CHANGED
@@ -1,4 +1,4 @@
1
- const {S,U,B} = require('@lumjs/core/types');
1
+ const {S,B} = require('@lumjs/core/types');
2
2
 
3
3
  /**
4
4
  * A pure-Javascript base91 library.
@@ -169,17 +169,17 @@ exports.decode = function(data, opts={})
169
169
  opts = {string: opts};
170
170
  }
171
171
 
172
- if (opts.string && typeof Uint8Array !== U && typeof TextDecoder !== U)
172
+ if (opts.string)
173
173
  {
174
174
  const uint = Uint8Array.from(output);
175
175
  const td = new TextDecoder();
176
176
  return td.decode(uint);
177
177
  }
178
- if (opts.uint && typeof Uint8Array !== U)
178
+ if (opts.uint)
179
179
  {
180
180
  return Uint8Array.from(output);
181
181
  }
182
- else if (opts.buffer && typeof Buffer !== U)
182
+ else if (opts.buffer)
183
183
  {
184
184
  return new Buffer.from(output);
185
185
  }