@lumjs/encode 2.2.2 → 2.3.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.3.1] - 2026-01-14
10
+ ### Fixed
11
+ - Another broken reference in the HOTP library from its prototype stage.
12
+ - Forgot to add a `Base32` alias property to the default module.
13
+
14
+ ## [2.3.0] - 2026-01-13
15
+ ### Added
16
+ - New `base32` module with encoding and decoding of four variants of Base32.
17
+ - Tests for the `base32` module.
18
+
9
19
  ## [2.2.2] - 2026-01-13
10
20
  ### Fixed
11
21
  - A few typos and bugs in the HMAC/HOTP/TOTP libraries I added last time.
@@ -63,7 +73,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
63
73
  ### Added
64
74
  - Initial release.
65
75
 
66
- [Unreleased]: https://github.com/supernovus/lum.encode.js/compare/v2.2.2...HEAD
76
+ [Unreleased]: https://github.com/supernovus/lum.encode.js/compare/v2.3.1...HEAD
77
+ [2.3.1]: https://github.com/supernovus/lum.encode.js/compare/v2.3.0...v2.3.1
78
+ [2.3.0]: https://github.com/supernovus/lum.encode.js/compare/v2.2.2...v2.3.0
67
79
  [2.2.2]: https://github.com/supernovus/lum.encode.js/compare/v2.2.0...v2.2.2
68
80
  [2.2.0]: https://github.com/supernovus/lum.encode.js/compare/v2.1.0...v2.2.0
69
81
  [2.1.0]: https://github.com/supernovus/lum.encode.js/compare/v2.0.0...v2.1.0
package/lib/base32.js ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Base32 encoding in pure JS.
3
+ *
4
+ * Originally based on an implementation from Richard Thiessen:
5
+ * https://gist.github.com/RichardThiessen/5a4e32d57aafd5430c09122f23e4b757
6
+ *
7
+ * With a lot of my own tweaks and enhancements obviously.
8
+ *
9
+ * @module @lumjs/encode/base32
10
+ */
11
+ 'use strict';
12
+
13
+ const { isObj } = require('@lumjs/core/types');
14
+ const cp = Object.assign;
15
+ const lock = Object.freeze;
16
+
17
+ /**
18
+ * Supported Base32 variants.
19
+ *
20
+ * Unlike Base64 which typically only has two versions (which only have minor
21
+ * differences), Base32 has multiple variants which use vastly different
22
+ * encoding alphabets. We support a handful of well known variants.
23
+ *
24
+ * For every lowercase property name in this object, there is
25
+ * also an uppcase alias that is the exact same type definition.
26
+ *
27
+ * This namespace object is also exported as `TYPES`.
28
+ *
29
+ * @alias module:@lumjs/encode/base32.Base32Types
30
+ */
31
+ const Base32Types = {
32
+ /** The RFC4648 standard format. */
33
+ rfc4648: lock(['ABCDEFGHIJKLMNOPQRSTUVWXYZ234567']),
34
+ /** The zbase32 variant. */
35
+ zbase32: lock(['ybndrfg8ejkmcpqxot1uwisza345h769']),
36
+ /** The geohash variant. */
37
+ geohash: lock(['0123456789bcdefghjkmnpqrstuvwxyz']),
38
+ /** Douglas Crockford's variant. */
39
+ crockford: lock(['0123456789ABCDEFGHJKMNPQRSTVWXYZ', 'O0', 'I1', 'L1']),
40
+ }
41
+
42
+ /**
43
+ * An alias to RFC4648 recognizing that it is the default variant.
44
+ */
45
+ Base32Types.default = Base32Types.rfc4648;
46
+
47
+ for (let lcid in Base32Types) {
48
+ let ucid = lcid.toUpperCase();
49
+ Base32Types[ucid] = Base32Types[lcid];
50
+ }
51
+
52
+ lock(Base32Types);
53
+
54
+ const UTF8 = 'utf-8';
55
+ const encodeText = s => new TextEncoder(UTF8).encode(s);
56
+ const decodeText = b => new TextDecoder(UTF8).decode(b);
57
+
58
+ function _opts(opts, bopt) {
59
+ if (typeof opts === 'string' || Array.isArray(opts)) {
60
+ opts = { type: opts };
61
+ }
62
+ else if (typeof opts === 'boolean') {
63
+ return { [bopt]: opts, type: Base32Types.default };
64
+ }
65
+ else if (!isObj(opts)) {
66
+ return { type: Base32Types.default }
67
+ }
68
+
69
+ if (typeof opts.type === 'string') {
70
+ if (Array.isArray(Base32Types[opts.type])) {
71
+ opts.type = Base32Types[opts.type];
72
+ }
73
+ else {
74
+ console.error("unknown base32 type", opts.type, opts);
75
+ opts.type = Base32Types.default;
76
+ }
77
+ }
78
+ else if (Array.isArray(opts.type)) {
79
+ if (typeof opts.type[0] !== 'string' || opts.type[0].length !== 32) {
80
+ console.error("invalid base32 type def", opts.type, opts);
81
+ opts.type = Base32Types.default;
82
+ }
83
+ }
84
+ else {
85
+ if (opts.type !== undefined) {
86
+ console.error("invalid base32 type option value", opts.type, opts);
87
+ }
88
+ opts.type = Base32Types.default;
89
+ }
90
+
91
+ return opts;
92
+ }
93
+
94
+ function _b32_map(typedef) {
95
+ let alphabet = typedef[0].toUpperCase();
96
+ let aliases = typedef.slice(1);
97
+ let lookup = new Map(alphabet.split("").map((c, i) => [c, i]));
98
+ aliases.map(x => lookup.set(x[0], lookup.get(x[1])));
99
+ return lookup;
100
+ }
101
+
102
+ /**
103
+ * Decode a Base32-encoded string.
104
+ * @param {string} str - Base32 string.
105
+ * @param {(object|string)} [opts] Options.
106
+ *
107
+ * If this is a boolean it will be used as `opts.string`.
108
+ *
109
+ * If this is a string or one of the `Base32Types` properties,
110
+ * it will be used as `opts.type`.
111
+ *
112
+ * @param {(string|Array)} [opts.type] Base32 variant to use.
113
+ *
114
+ * If this is a string it must be the name (uppercase or lowercase)
115
+ * of one of the properties in the `Base32Types` object.
116
+ *
117
+ * If it is an Array it is expected that it is one of the properties
118
+ * from the Base32Types, or a compatible type def where the first
119
+ * item in the array is a string consisting of exactly 32 unique characters.
120
+ *
121
+ * @param {boolean} [opts.string=false] Return a UTF-8 string?
122
+ *
123
+ * If this is true then we will assume the original encoded value was a UTF-8
124
+ * string, and will decode it as such using a TextDecoder instance.
125
+ *
126
+ * If this is false (the default) we will return a Uint8Array.
127
+ *
128
+ * @returns {(Uint8Array|string)}
129
+ * @alias module:@lumjs/encode/base32.decode
130
+ */
131
+ function base32Decode(str, opts) {
132
+ opts = _opts(opts, 'string');
133
+ let lookup = _b32_map(opts.type);
134
+ // remove whitespace and padding
135
+ str = str.replace(/\s+/g, "").replace(/=*$/g, "");
136
+ let vals = str.toUpperCase().split("").map(c => lookup.get(c));
137
+ if (vals.indexOf(undefined) != -1) {
138
+ throw new RangeError("Base32: string includes non-Base32 characters");
139
+ }
140
+
141
+ let bytes = new Uint8Array((vals.length * 5 / 8) | 0);
142
+ for (let i = 0; i < vals.length; i += 8) {
143
+ //do all, missing indices will fail silently
144
+ let b_o = (i / 8 * 5) | 0;
145
+ bytes[b_o + 0] = (vals[i + 0] << 3) | (vals[i + 1] >> 2);
146
+ bytes[b_o + 1] = (vals[i + 1] << 6) | (vals[i + 2] << 1) | (vals[i + 3] >> 4);
147
+ bytes[b_o + 2] = (vals[i + 3] << 4) | (vals[i + 4] >> 1);
148
+ bytes[b_o + 3] = (vals[i + 4] << 7) | (vals[i + 5] << 2) | (vals[i + 6] >> 3);
149
+ bytes[b_o + 4] = (vals[i + 6] << 5) | (vals[i + 7] >> 0);
150
+ }
151
+
152
+ return opts.string ? decodeText(bytes) : bytes;
153
+ }
154
+
155
+ /**
156
+ * Encode a value into a Base32 string.
157
+ *
158
+ * @param {(ArrayBuffer|TypedArray|string)} data - Data to be encoded.
159
+ *
160
+ * If this is a string it will be converted into a Uint8Array using a
161
+ * TextEncoder instance.
162
+ *
163
+ * @param {(object|string|boolean)} [opts] Options.
164
+ *
165
+ * If this is a boolean it will be used as `opts.pad`.
166
+ *
167
+ * If this is a string or one of the `Base32Types` properties,
168
+ * it will be used as `opts.type`.
169
+ *
170
+ * @param {(string|Array)} [opts.type] Base32 variant to use.
171
+ *
172
+ * If this is a string it must be the name (uppercase or lowercase)
173
+ * of one of the properties in the `Base32Types` object.
174
+ *
175
+ * If it is an Array it is expected that it is one of the properties
176
+ * from the Base32Types, or a compatible type def where the first
177
+ * item in the array is a string consisting of exactly 32 unique characters.
178
+ *
179
+ * @param {boolean} [opts.pad=true] Add `=` padding characters?
180
+ *
181
+ * Base32 (like Base64) uses `=` as a padding character to ensure the length
182
+ * of the encoded strings are divisible by 8. As that is a part of
183
+ * the standard specification, this option defaults to true.
184
+ *
185
+ * If you explicitly set this to false the string won't have any padding
186
+ * added to it, regardless of its length.
187
+ *
188
+ * @returns {string}
189
+ * @alias module:@lumjs/encode/base32.encode
190
+ */
191
+ function base32Encode(buf, opts) {
192
+ opts = _opts(opts, 'pad');
193
+ let alphabet = opts.type[0];
194
+ if (buf.byteLength === undefined) { //not Arraybuffer?
195
+ buf = encodeText(buf);
196
+ }
197
+
198
+ //may need extra zero at end of buf for some bits of last value
199
+ let bytes = new Uint8Array(buf.byteLength + 1);
200
+ bytes.set(buf, 0)
201
+ let out = new Uint8Array(((buf.byteLength * 8 + 4) / 5) | 0);
202
+
203
+ for (let i = 0; i < out.length; i += 8) {
204
+ //do all,missing indices will fail silently
205
+ let b_o = (i / 8 * 5) | 0;
206
+ out[i + 0] = (bytes[b_o + 0] >> 3);
207
+ out[i + 1] = (bytes[b_o + 0] << 2) | (bytes[b_o + 1] >> 6);
208
+ out[i + 2] = (bytes[b_o + 1] >> 1);
209
+ out[i + 3] = (bytes[b_o + 1] << 4) | (bytes[b_o + 2] >> 4);
210
+ out[i + 4] = (bytes[b_o + 2] << 1) | (bytes[b_o + 3] >> 7);
211
+ out[i + 5] = (bytes[b_o + 3] >> 2);
212
+ out[i + 6] = (bytes[b_o + 3] << 3) | (bytes[b_o + 4] >> 5);
213
+ out[i + 7] = (bytes[b_o + 4] >> 0);
214
+ }
215
+
216
+ let str = Array.from(out.entries(), x => alphabet[x[1] & 31]).join("");
217
+ if (opts.pad ?? true) {
218
+ str = str.padEnd(out.length + (8 - (out.length % 8)) % 8, "=");
219
+ }
220
+ return str;
221
+ }
222
+
223
+ module.exports = {
224
+ Base32Types, TYPES: Base32Types,
225
+ base32Encode, encode: base32Encode,
226
+ base32Decode, decode: base32Decode,
227
+ }
package/lib/hotp.js CHANGED
@@ -78,7 +78,9 @@ class HOTP {
78
78
  v1,
79
79
  v2,
80
80
  code,
81
- toString,
81
+ toString() {
82
+ return this.code;
83
+ },
82
84
  }
83
85
 
84
86
  if (opts.debug) console.debug(res.code, res);
package/lib/index.js CHANGED
@@ -30,6 +30,12 @@ def(exports, 'ord', util.ord, E);
30
30
  */
31
31
  def(exports, 'numByteArray', util.numByteArray, E);
32
32
 
33
+ /**
34
+ * @name module:@lumjs/encode.Base32
35
+ * @see {@link module:@lumjs/encode/base32}
36
+ */
37
+ lazy(exports, 'Base32', () => require('./base32'), E);
38
+
33
39
  /**
34
40
  * @name module:@lumjs/encode.Base64
35
41
  * @see {@link module:@lumjs/encode/base64}
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@lumjs/encode",
3
- "version": "2.2.2",
3
+ "version": "2.3.1",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {
7
7
  ".": "./lib/index.js",
8
+ "./base32": "./lib/base32.js",
8
9
  "./base64": "./lib/base64.js",
9
10
  "./base91": "./lib/base91.js",
10
11
  "./hash": "./lib/hash.js",