Skip to content

Commit 30a9d77

Browse files
committed
Add support for multiple params of content to CreateHash (for eleventy-img)
1 parent 15f46c7 commit 30a9d77

File tree

4 files changed

+63
-29
lines changed

4 files changed

+63
-29
lines changed

src/CreateHash-Node.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { createHash } = require("node:crypto");
2+
const { base64UrlSafe } = require("./Url.js");
3+
4+
// This can be removed when Node 20+ is the baseline (see CreateHash.js)
5+
module.exports = function createHashNodeCrypto(...content) {
6+
let hash = createHash("sha256");
7+
8+
for(let c of content) {
9+
hash.update(c);
10+
}
11+
12+
// Note that Node does include a `digest("base64url")` that is supposedly Node 14+ but curiously failed on Stackblitz’s Node 16.
13+
let base64 = hash.digest("base64");
14+
return base64UrlSafe(base64);
15+
}

src/CreateHash.js

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,43 @@
1-
// WebCrypto (crypto global) not available in Node 18.
2-
function createHashNodeCrypto(content) {
3-
// require only when necessary
4-
const { createHash } = require("node:crypto");
51

6-
let hash = createHash("sha256");
7-
hash.update(content);
8-
9-
// Note that Node does include a `digest("base64url")` that is supposedly Node 14+ but curiously failed on Stackblitz’s Node 16.
10-
let base64 = hash.digest("base64");
11-
return urlSafe(base64);
12-
}
13-
14-
function urlSafe(hashString = "") {
15-
return hashString.replace(/[=\+\/]/g, function(match) {
16-
if(match === "=") {
17-
return "";
18-
}
19-
if(match === "+") {
20-
return "-";
21-
}
22-
return "_";
23-
});
24-
}
2+
const { base64UrlSafe } = require("./Url.js");
253

264
function toBase64(bytes) {
275
let str = Array.from(bytes, (b) => String.fromCodePoint(b)).join("");
28-
// `btoa` is Node 16+
6+
7+
// `btoa` Node 16+
298
return btoa(str);
309
}
3110

11+
// Thanks https://evanhahn.com/the-best-way-to-concatenate-uint8arrays/ (Public domain)
12+
function mergeUint8Array(...arrays) {
13+
let totalLength = arrays.reduce(
14+
(total, uint8array) => total + uint8array.byteLength,
15+
0
16+
);
17+
18+
let result = new Uint8Array(totalLength);
19+
let offset = 0;
20+
arrays.forEach((uint8array) => {
21+
result.set(uint8array, offset);
22+
offset += uint8array.byteLength;
23+
});
24+
25+
return result;
26+
}
27+
3228
// same output as node:crypto above (though now async).
33-
module.exports = async function createHash(content) {
29+
module.exports = async function createHash(...content) {
3430
if(typeof globalThis.crypto === "undefined") {
3531
// Backwards compat with Node Crypto, since WebCrypto (crypto global) is Node 20+
36-
return createHashNodeCrypto(content);
32+
const createHashNodeCrypto = require("./CreateHash-Node.js");
33+
return createHashNodeCrypto(...content);
3734
}
3835

3936
let encoder = new TextEncoder();
40-
let data = encoder.encode(content);
37+
let input = mergeUint8Array(...content.map(c => encoder.encode(c)))
4138

4239
// `crypto` is Node 20+
43-
return crypto.subtle.digest("SHA-256", data).then(hashBuffer => {
44-
return urlSafe(toBase64(new Uint8Array(hashBuffer)));
40+
return crypto.subtle.digest("SHA-256", input).then(hashBuffer => {
41+
return base64UrlSafe(toBase64(new Uint8Array(hashBuffer)));
4542
});
4643
}

src/Url.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function base64UrlSafe(hashString = "") {
2+
return hashString.replace(/[=\+\/]/g, function(match) {
3+
if(match === "=") {
4+
return "";
5+
}
6+
if(match === "+") {
7+
return "-";
8+
}
9+
return "_";
10+
});
11+
}
12+
13+
module.exports = { base64UrlSafe };

test/CreateHashTest.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const assert = require("node:assert/strict")
22
const test = require("node:test");
33

44
const createHash = require("../src/CreateHash.js");
5+
const createHashNodeCrypto = require("../src/CreateHash-Node.js");
56

67
test("Basic usage", async (t) => {
78
const emptyHash = await createHash("");
@@ -11,4 +12,12 @@ test("Basic usage", async (t) => {
1112
assert.equal(emptyHash.includes("="), false);
1213

1314
assert.equal(await createHash("This is a test"), "x74e2QL7jdTUiZfGRS9dflCfvNvigIsWvPTtzkwH0U4");
15+
});
16+
17+
test("Multiple calls", async (t) => {
18+
assert.equal(await createHash(), createHashNodeCrypto());
19+
assert.equal(await createHash(""), createHashNodeCrypto(""));
20+
assert.equal(await createHash("a", "b"), createHashNodeCrypto("a", "b"));
21+
assert.equal(await createHash("a", "b", "c"), createHashNodeCrypto("a", "b", "c"));
22+
assert.equal(await createHash("abcdef", "def"), createHashNodeCrypto("abcdef", "def"));
1423
});

0 commit comments

Comments
 (0)