From d008e78a615915700e6c75de9cb5d708bbb67cb0 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:35:27 +0000 Subject: [PATCH 1/2] feat: migrate to ES modules This migrates the syntax to standard ES modules (`import`/`export`) and reworks `var` to be `let`/`const`. Publishing this means anyone on a Node older than 20, using CommonJS, will not be able to `require(...)` it. Older Node can `import` it fine, and all modern Node can `require` or `import` it either way. --- nodejs/decrypt-dh.js | 16 ++--- nodejs/decrypt.js | 12 ++-- nodejs/ece.js | 152 +++++++++++++++++++++---------------------- nodejs/encrypt-dh.js | 14 ++-- nodejs/encrypt.js | 12 ++-- nodejs/package.json | 3 +- nodejs/test.js | 105 +++++++++++++++--------------- 7 files changed, 150 insertions(+), 164 deletions(-) diff --git a/nodejs/decrypt-dh.js b/nodejs/decrypt-dh.js index d5e4635..51a4dd6 100644 --- a/nodejs/decrypt-dh.js +++ b/nodejs/decrypt-dh.js @@ -1,7 +1,5 @@ -'use strict'; - -var crypto = require('crypto'); -var ece = require('./ece.js'); +import crypto from 'node:crypto'; +import * as ece from './ece.js'; if (process.argv.length < 6) { console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') + @@ -9,7 +7,7 @@ if (process.argv.length < 6) { process.exit(2); } -var receiver = crypto.createECDH('prime256v1'); +const receiver = crypto.createECDH('prime256v1'); // node crypto is finicky about accessing the public key // 1. it can't generate the public key from the private key // 2. it barfs when you try to access the public key, even after you set it @@ -17,16 +15,16 @@ var receiver = crypto.createECDH('prime256v1'); receiver.generateKeys(); receiver.setPublicKey(Buffer.from(process.argv[4], 'base64url')); receiver.setPrivateKey(Buffer.from(process.argv[3], 'base64url')); -var keymap = {}; +const keymap = {}; -var params = { +const params = { version: 'aes128gcm', authSecret: process.argv[2], privateKey: receiver }; if (process.argv.length > 7) { - var extra = JSON.parse(process.argv[7]); + const extra = JSON.parse(process.argv[7]); Object.keys(extra).forEach(function(k) { params[k] = extra[k]; }); @@ -34,7 +32,7 @@ if (process.argv.length > 7) { keymap[params.keyid] = receiver; console.log("Params: " + JSON.stringify(params, null, 2)); -var result = ece.decrypt(Buffer.from(process.argv[5], 'base64url'), params); +const result = ece.decrypt(Buffer.from(process.argv[5], 'base64url'), params); console.log(result.toString('base64url')); console.log(result.toString('utf-8')); diff --git a/nodejs/decrypt.js b/nodejs/decrypt.js index abbba96..57f1419 100644 --- a/nodejs/decrypt.js +++ b/nodejs/decrypt.js @@ -1,7 +1,5 @@ -'use strict'; - -var crypto = require('crypto'); -var ece = require('./ece.js'); +import crypto from 'node:crypto'; +import * as ece from './ece.js'; if (process.argv.length < 4) { console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') + @@ -9,20 +7,20 @@ if (process.argv.length < 4) { process.exit(2); } -var params = { +const params = { version: 'aes128gcm', key: process.argv[2] }; if (process.argv.length > 4) { - var extra = JSON.parse(process.argv[4]); + const extra = JSON.parse(process.argv[4]); Object.keys(extra).forEach(function(k) { params[k] = extra[k]; }); } console.log("Params: " + JSON.stringify(params, null, 2)); -var result = ece.decrypt(Buffer.from(process.argv[3], 'base64url'), params); +const result = ece.decrypt(Buffer.from(process.argv[3], 'base64url'), params); console.log(result.toString('base64url')); console.log(result.toString('utf-8')); diff --git a/nodejs/ece.js b/nodejs/ece.js index 132fe81..3f65adf 100644 --- a/nodejs/ece.js +++ b/nodejs/ece.js @@ -1,4 +1,3 @@ -'use strict'; /* * Encrypted content coding * @@ -14,18 +13,18 @@ * This version is selected by default, unless you specify a |padSize| of 1. */ -var crypto = require('crypto'); +import crypto from 'node:crypto'; -var AES_GCM = 'aes-128-gcm'; -var PAD_SIZE = { 'aes128gcm': 1, 'aesgcm': 2 }; -var TAG_LENGTH = 16; -var KEY_LENGTH = 16; -var NONCE_LENGTH = 12; -var SHA_256_LENGTH = 32; -var MODE_ENCRYPT = 'encrypt'; -var MODE_DECRYPT = 'decrypt'; +const AES_GCM = 'aes-128-gcm'; +const PAD_SIZE = { 'aes128gcm': 1, 'aesgcm': 2 }; +const TAG_LENGTH = 16; +const KEY_LENGTH = 16; +const NONCE_LENGTH = 12; +const SHA_256_LENGTH = 32; +const MODE_ENCRYPT = 'encrypt'; +const MODE_DECRYPT = 'decrypt'; -var keylog; +let keylog; if (process.env.ECE_KEYLOG === '1') { keylog = function(m, k) { console.warn(m + ' [' + k.length + ']: ' + k.toString('base64url')); @@ -44,7 +43,7 @@ function decode(b) { } function HMAC_hash(key, input) { - var hmac = crypto.createHmac('sha256', key); + const hmac = crypto.createHmac('sha256', key); hmac.update(input); return hmac.digest(); } @@ -59,11 +58,11 @@ function HKDF_extract(salt, ikm) { function HKDF_expand(prk, info, l) { keylog('prk', prk); keylog('info', info); - var output = Buffer.alloc(0); - var T = Buffer.alloc(0); + let output = Buffer.alloc(0); + let T = Buffer.alloc(0); info = Buffer.from(info, 'ascii'); - var counter = 0; - var cbuf = Buffer.alloc(1); + let counter = 0; + const cbuf = Buffer.alloc(1); while (output.length < l) { cbuf.writeUIntBE(++counter, 0, 1); T = HMAC_hash(prk, Buffer.concat([T, info, cbuf])); @@ -78,7 +77,7 @@ function HKDF(salt, ikm, info, len) { } function info(base, context) { - var result = Buffer.concat([ + let result = Buffer.concat([ Buffer.from('Content-Encoding: ' + base + '\0', 'ascii'), context ]); @@ -87,14 +86,14 @@ function info(base, context) { } function lengthPrefix(buffer) { - var b = Buffer.concat([Buffer.alloc(2), buffer]); + const b = Buffer.concat([Buffer.alloc(2), buffer]); b.writeUIntBE(buffer.length, 0, 2); return b; } function extractDH(header, mode) { - var key = header.privateKey; - var senderPubKey, receiverPubKey; + const key = header.privateKey; + let senderPubKey, receiverPubKey; if (mode === MODE_ENCRYPT) { senderPubKey = key.getPublicKey(); receiverPubKey = header.dh; @@ -117,7 +116,7 @@ function extractDH(header, mode) { } function extractSecretAndContext(header, mode) { - var result = { secret: null, context: Buffer.alloc(0) }; + let result = { secret: null, context: Buffer.alloc(0) }; if (header.key) { result.secret = header.key; if (result.secret.length !== KEY_LENGTH) { @@ -147,7 +146,7 @@ function webpushSecret(header, mode) { } keylog('authsecret', header.authSecret); - var remotePubKey, senderPubKey, receiverPubKey; + let remotePubKey, senderPubKey, receiverPubKey; if (mode === MODE_ENCRYPT) { senderPubKey = header.privateKey.getPublicKey(); remotePubKey = receiverPubKey = header.dh; @@ -188,11 +187,9 @@ function extractSecret(header, mode, keyLookupCallback) { if (!header.privateKey) { // Lookup based on keyid - if (!keyLookupCallback) { - var key = header.keymap && header.keymap[header.keyid]; - } else { - var key = keyLookupCallback(header.keyid) - } + const key = keyLookupCallback + ? keyLookupCallback(header.keyid) + : header.keymap && header.keymap[header.keyid]; if (!key) { throw new Error('No saved key (keyid: "' + header.keyid + '")'); } @@ -206,12 +203,12 @@ function deriveKeyAndNonce(header, mode, lookupKeyCallback) { if (!header.salt) { throw new Error('must include a salt parameter for ' + header.version); } - var keyInfo; - var nonceInfo; - var secret; + let keyInfo; + let nonceInfo; + let secret; if (header.version === 'aesgcm') { // old - var s = extractSecretAndContext(header, mode, lookupKeyCallback); + const s = extractSecretAndContext(header, mode, lookupKeyCallback); keyInfo = info('aesgcm', s.context); nonceInfo = info('nonce', s.context); secret = s.secret; @@ -223,8 +220,8 @@ function deriveKeyAndNonce(header, mode, lookupKeyCallback) { } else { throw new Error('Unable to set context for mode ' + header.version); } - var prk = HKDF_extract(header.salt, secret); - var result = { + const prk = HKDF_extract(header.salt, secret); + let result = { key: HKDF_expand(prk, keyInfo, KEY_LENGTH), nonce: HKDF_expand(prk, nonceInfo, NONCE_LENGTH) }; @@ -235,14 +232,14 @@ function deriveKeyAndNonce(header, mode, lookupKeyCallback) { /* Parse command-line arguments. */ function parseParams(params) { - var header = {}; + const header = {}; header.version = params.version || 'aes128gcm'; header.rs = parseInt(params.rs, 10); if (isNaN(header.rs)) { header.rs = 4096; } - var overhead = PAD_SIZE[header.version]; + let overhead = PAD_SIZE[header.version]; if (header.version === 'aes128gcm') { overhead += TAG_LENGTH; } @@ -278,9 +275,9 @@ function parseParams(params) { } function generateNonce(base, counter) { - var nonce = Buffer.from(base); - var m = nonce.readUIntBE(nonce.length - 6, 6); - var x = ((m ^ counter) & 0xffffff) + + const nonce = Buffer.from(base); + const m = nonce.readUIntBE(nonce.length - 6, 6); + const x = ((m ^ counter) & 0xffffff) + ((((m / 0x1000000) ^ (counter / 0x1000000)) & 0xffffff) * 0x1000000); nonce.writeUIntBE(x, nonce.length - 6, 6); keylog('nonce' + counter, nonce); @@ -290,7 +287,7 @@ function generateNonce(base, counter) { /* Used when decrypting aes128gcm to populate the header values. Modifies the * header values in place and returns the size of the header. */ function readHeader(buffer, header) { - var idsz = buffer.readUIntBE(20, 1); + const idsz = buffer.readUIntBE(20, 1); header.salt = buffer.slice(0, KEY_LENGTH); header.rs = buffer.readUIntBE(KEY_LENGTH, 4); header.keyid = buffer.slice(21, 21 + idsz); @@ -298,13 +295,13 @@ function readHeader(buffer, header) { } function unpadLegacy(data, version) { - var padSize = PAD_SIZE[version]; - var pad = data.readUIntBE(0, padSize); + const padSize = PAD_SIZE[version]; + let pad = data.readUIntBE(0, padSize); if (pad + padSize > data.length) { throw new Error('padding exceeds block size'); } keylog('padding', data.slice(0, padSize + pad)); - var padCheck = Buffer.alloc(pad); + const padCheck = Buffer.alloc(pad); padCheck.fill(0); if (padCheck.compare(data.slice(padSize, padSize + pad)) !== 0) { throw new Error('invalid padding'); @@ -313,7 +310,7 @@ function unpadLegacy(data, version) { } function unpad(data, last) { - var i = data.length - 1; + let i = data.length - 1; while(i >= 0) { if (data[i]) { if (last) { @@ -334,10 +331,10 @@ function unpad(data, last) { function decryptRecord(key, counter, buffer, header, last) { keylog('decrypt', buffer); - var nonce = generateNonce(key.nonce, counter); - var gcm = crypto.createDecipheriv(AES_GCM, key.key, nonce); + const nonce = generateNonce(key.nonce, counter); + const gcm = crypto.createDecipheriv(AES_GCM, key.key, nonce); gcm.setAuthTag(buffer.slice(buffer.length - TAG_LENGTH)); - var data = gcm.update(buffer.slice(0, buffer.length - TAG_LENGTH)); + let data = gcm.update(buffer.slice(0, buffer.length - TAG_LENGTH)); data = Buffer.concat([data, gcm.final()]); keylog('decrypted', data); if (header.version !== 'aes128gcm') { @@ -363,22 +360,22 @@ function decryptRecord(key, counter, buffer, header, last) { * The |params.privateKey| includes the private key of the receiver. */ function decrypt(buffer, params, keyLookupCallback) { - var header = parseParams(params); + const header = parseParams(params); if (header.version === 'aes128gcm') { - var headerLength = readHeader(buffer, header); + const headerLength = readHeader(buffer, header); buffer = buffer.slice(headerLength); } - var key = deriveKeyAndNonce(header, MODE_DECRYPT, keyLookupCallback); - var start = 0; - var result = Buffer.alloc(0); + const key = deriveKeyAndNonce(header, MODE_DECRYPT, keyLookupCallback); + let start = 0; + let result = Buffer.alloc(0); - var chunkSize = header.rs; + let chunkSize = header.rs; if (header.version !== 'aes128gcm') { chunkSize += TAG_LENGTH; } - for (var i = 0; start < buffer.length; ++i) { - var end = start + chunkSize; + for (let i = 0; start < buffer.length; ++i) { + let end = start + chunkSize; if (header.version !== 'aes128gcm' && end === buffer.length) { throw new Error('Truncated payload'); } @@ -386,7 +383,7 @@ function decrypt(buffer, params, keyLookupCallback) { if (end - start <= TAG_LENGTH) { throw new Error('Invalid block: too small at ' + i); } - var block = decryptRecord(key, i, buffer.slice(start, end), + const block = decryptRecord(key, i, buffer.slice(start, end), header, end >= buffer.length); result = Buffer.concat([result, block]); start = end; @@ -397,12 +394,12 @@ function decrypt(buffer, params, keyLookupCallback) { function encryptRecord(key, counter, buffer, pad, header, last) { keylog('encrypt', buffer); pad = pad || 0; - var nonce = generateNonce(key.nonce, counter); - var gcm = crypto.createCipheriv(AES_GCM, key.key, nonce); + const nonce = generateNonce(key.nonce, counter); + const gcm = crypto.createCipheriv(AES_GCM, key.key, nonce); - var ciphertext = []; - var padSize = PAD_SIZE[header.version]; - var padding = Buffer.alloc(pad + padSize); + const ciphertext = []; + const padSize = PAD_SIZE[header.version]; + const padding = Buffer.alloc(pad + padSize); padding.fill(0); if (header.version !== 'aes128gcm') { @@ -422,7 +419,7 @@ function encryptRecord(key, counter, buffer, pad, header, last) { } gcm.final(); - var tag = gcm.getAuthTag(); + const tag = gcm.getAuthTag(); if (tag.length !== TAG_LENGTH) { throw new Error('invalid tag generated'); } @@ -431,8 +428,8 @@ function encryptRecord(key, counter, buffer, pad, header, last) { } function writeHeader(header) { - var ints = Buffer.alloc(5); - var keyid = Buffer.from(header.keyid || []); + const ints = Buffer.alloc(5); + const keyid = Buffer.from(header.keyid || []); if (keyid.length > 255) { throw new Error('keyid is too large'); } @@ -459,12 +456,12 @@ function encrypt(buffer, params, keyLookupCallback) { if (!Buffer.isBuffer(buffer)) { throw new Error('buffer argument must be a Buffer'); } - var header = parseParams(params); + const header = parseParams(params); if (!header.salt) { header.salt = crypto.randomBytes(KEY_LENGTH); } - var result; + let result; if (header.version === 'aes128gcm') { // Save the DH public key in the header unless keyid is set. if (header.privateKey && !header.keyid) { @@ -476,20 +473,20 @@ function encrypt(buffer, params, keyLookupCallback) { result = Buffer.alloc(0); } - var key = deriveKeyAndNonce(header, MODE_ENCRYPT, keyLookupCallback); - var start = 0; - var padSize = PAD_SIZE[header.version]; - var overhead = padSize; + const key = deriveKeyAndNonce(header, MODE_ENCRYPT, keyLookupCallback); + let start = 0; + const padSize = PAD_SIZE[header.version]; + let overhead = padSize; if (header.version === 'aes128gcm') { overhead += TAG_LENGTH; } - var pad = isNaN(parseInt(params.pad, 10)) ? 0 : parseInt(params.pad, 10); + let pad = isNaN(parseInt(params.pad, 10)) ? 0 : parseInt(params.pad, 10); - var counter = 0; - var last = false; + let counter = 0; + let last = false; while (!last) { // Pad so that at least one data byte is in a block. - var recordPad = Math.min(header.rs - overhead - 1, pad); + let recordPad = Math.min(header.rs - overhead - 1, pad); if (header.version !== 'aes128gcm') { recordPad = Math.min((1 << (padSize * 8)) - 1, recordPad); } @@ -498,7 +495,7 @@ function encrypt(buffer, params, keyLookupCallback) { } pad -= recordPad; - var end = start + header.rs - overhead - recordPad; + let end = start + header.rs - overhead - recordPad; if (header.version !== 'aes128gcm') { // The > here ensures that we write out a padding-only block at the end // of a buffer. @@ -507,7 +504,7 @@ function encrypt(buffer, params, keyLookupCallback) { last = end >= buffer.length; } last = last && pad <= 0; - var block = encryptRecord(key, counter, buffer.slice(start, end), + const block = encryptRecord(key, counter, buffer.slice(start, end), recordPad, header, last); result = Buffer.concat([result, block]); @@ -522,7 +519,4 @@ function isFunction(object) { return typeof(object) === 'function'; } -module.exports = { - decrypt: decrypt, - encrypt: encrypt -}; +export { decrypt, encrypt }; diff --git a/nodejs/encrypt-dh.js b/nodejs/encrypt-dh.js index af10fc8..f5bb8e9 100644 --- a/nodejs/encrypt-dh.js +++ b/nodejs/encrypt-dh.js @@ -1,7 +1,5 @@ -'use strict'; - -var crypto = require('crypto'); -var ece = require('./ece.js'); +import crypto from 'node:crypto'; +import * as ece from './ece.js'; if (process.argv.length < 5) { console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') + @@ -10,20 +8,20 @@ if (process.argv.length < 5) { } -var params = { +const params = { version: 'aes128gcm', authSecret: process.argv[2], dh: process.argv[3] }; if (process.argv.length > 5) { - var extra = JSON.parse(process.argv[5]); + const extra = JSON.parse(process.argv[5]); Object.keys(extra).forEach(function(k) { params[k] = extra[k]; }); } -var sender = crypto.createECDH('prime256v1'); +const sender = crypto.createECDH('prime256v1'); sender.generateKeys(); if (params.senderPrivate) { sender.setPrivateKey(Buffer.from(params.senderPrivate, 'base64url')); @@ -38,6 +36,6 @@ if (params.senderPublic) { params.privateKey = sender; console.log("Params: " + JSON.stringify(params, null, 2)); -var result = ece.encrypt(Buffer.from(process.argv[4], 'base64url'), params); +const result = ece.encrypt(Buffer.from(process.argv[4], 'base64url'), params); console.log("Encrypted Message: " + result.toString('base64url')); diff --git a/nodejs/encrypt.js b/nodejs/encrypt.js index 93e5740..20203c6 100644 --- a/nodejs/encrypt.js +++ b/nodejs/encrypt.js @@ -1,7 +1,5 @@ -'use strict'; - -var crypto = require('crypto'); -var ece = require('./ece.js'); +import crypto from 'node:crypto'; +import * as ece from './ece.js'; if (process.argv.length < 4) { console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') + @@ -9,19 +7,19 @@ if (process.argv.length < 4) { process.exit(2); } -var params = { +const params = { version: 'aes128gcm', key: process.argv[2] }; if (process.argv.length > 4) { - var extra = JSON.parse(process.argv[4]); + const extra = JSON.parse(process.argv[4]); Object.keys(extra).forEach(function(k) { params[k] = extra[k]; }); } console.log("Params: " + JSON.stringify(params, null, 2)); -var result = ece.encrypt(Buffer.from(process.argv[3], 'base64url'), params); +const result = ece.encrypt(Buffer.from(process.argv[3], 'base64url'), params); console.log("Encrypted Message: " + result.toString('base64url')); diff --git a/nodejs/package.json b/nodejs/package.json index 000b80b..6b0c671 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -24,5 +24,6 @@ }, "engines": { "node": ">=20" - } + }, + "type": "module" } diff --git a/nodejs/test.js b/nodejs/test.js index 56dcf6d..a2d0910 100644 --- a/nodejs/test.js +++ b/nodejs/test.js @@ -1,8 +1,7 @@ -'use strict'; - -var crypto = require('crypto'); -var ece = require('./ece.js'); -var assert = require('assert'); +import assert from 'node:assert'; +import crypto from 'node:crypto'; +import fs from 'node:fs'; +import * as ece from './ece.js'; function usage() { console.log('Usage: node test.js [args]'); @@ -14,20 +13,20 @@ function usage() { console.log(' "max=" sets the maximum input size'); console.log(' "dump[=file]" log info to ../encrypt_data.json or the specified file'); } -var args = process.argv.slice(2); -var minLen = 1; -var maxLen = 100; -var plaintext; -var dumpFile; -var dumpData = []; -var log = function() {}; +const args = process.argv.slice(2); +const minLen = 1; +let maxLen = 100; +let plaintext; +let dumpFile; +const dumpData = []; +let log = function() {}; args.forEach(function(arg) { if (arg === 'verbose') { log = console.log.bind(console); } else if (arg.substring(0, 5) === 'text=') { plaintext = Buffer.from(arg.substring(5), 'utf8'); } else if (arg.substring(0, 4) === 'max=') { - var v = parseInt(arg.substring(4), 10); + const v = parseInt(arg.substring(4), 10); if (!isNaN(v) && v > minLen) { maxLen = v; } @@ -42,9 +41,9 @@ args.forEach(function(arg) { }); function filterTests(fullList) { - var filtered = fullList.filter(function(t) { + const filtered = fullList.filter(function(t) { return args.some(function(f) { - var v = typeof t === 'function' ? t.name : t; + let v = typeof t === 'function' ? t.name : t; return f === v; }); }); @@ -59,7 +58,7 @@ function logbuf(msg, buf) { buf = Buffer.from(buf, 'base64url'); } log(msg + ': [' + buf.length + ']'); - for (var i = 0; i < buf.length; i += 48) { + for (let i = 0; i < buf.length; i += 48) { log(' ' + buf.slice(i, i + 48).toString('base64url')); } } @@ -67,7 +66,7 @@ function logbuf(msg, buf) { function reallySaveDump(data){ if (dumpFile && data.version) { function dumpFix(d) { - var r = {}; + const r = {}; Object.keys(d).forEach(function(k) { if (Buffer.isBuffer(d[k])) { r[k] = d[k].toString('base64url'); @@ -83,7 +82,7 @@ function reallySaveDump(data){ dumpData.push(dumpFix(data)); } } -var saveDump = reallySaveDump; +let saveDump = reallySaveDump; function validate() { ['hello', null, 1, NaN, [], {}].forEach(function(v) { @@ -99,7 +98,7 @@ function validate() { } function generateInput(min) { - var input; + let input; min = Math.max(minLen, min || 0); if (plaintext) { if (plaintext.length < min) { @@ -107,7 +106,7 @@ function generateInput(min) { } input = plaintext; } else { - var len = Math.floor((Math.random() * (maxLen - min) + min)); + const len = Math.floor((Math.random() * (maxLen - min) + min)); input = crypto.randomBytes(len); } logbuf('Input', input); @@ -142,9 +141,9 @@ function encryptDecrypt(input, encryptParams, decryptParams, keys) { encryptParams.salt = decryptParams.salt; logbuf('Salt', encryptParams.salt); - var encrypted = ece.encrypt(input, encryptParams); + let encrypted = ece.encrypt(input, encryptParams); logbuf('Encrypted', encrypted); - var decrypted = ece.decrypt(encrypted, decryptParams); + const decrypted = ece.decrypt(encrypted, decryptParams); logbuf('Decrypted', decrypted); assert.equal(Buffer.compare(input, decrypted), 0); @@ -161,8 +160,8 @@ function encryptDecrypt(input, encryptParams, decryptParams, keys) { } function useExplicitKey(version) { - var input = generateInput(); - var params = { + let input = generateInput(); + const params = { version: version, key: crypto.randomBytes(16) }; @@ -171,8 +170,8 @@ function useExplicitKey(version) { } function authenticationSecret(version) { - var input = generateInput(); - var params = { + let input = generateInput(); + const params = { version: version, key: crypto.randomBytes(16), authSecret: crypto.randomBytes(16) @@ -183,8 +182,8 @@ function authenticationSecret(version) { } function exactlyOneRecord(version) { - var input = generateInput(1); - var params = { + let input = generateInput(1); + const params = { version: version, key: crypto.randomBytes(16), rs: input.length + rsoverhead(version) @@ -195,8 +194,8 @@ function exactlyOneRecord(version) { // If rs only allows one octet of data in each record AND padding is requested, // then we need to ensure that padding is added without infinitely looping. function padTinyRecord(version) { - var input = generateInput(1); - var params = { + let input = generateInput(1); + const params = { version: version, key: crypto.randomBytes(16), rs: rsoverhead(version) + 1, @@ -212,16 +211,16 @@ function tooMuchPadding(version) { if (version === 'aes128gcm') { return; } - var padSize = rsoverhead(version); - var rs = Math.pow(256, padSize) + padSize + 1; - var input = generateInput(1); - var params = { + const padSize = rsoverhead(version); + const rs = Math.pow(256, padSize) + padSize + 1; + let input = generateInput(1); + const params = { version: version, key: crypto.randomBytes(16), rs: rs, pad: rs }; - var ok = false; + let ok = false; try { ece.encrypt(input, params); } catch (e) { @@ -238,22 +237,22 @@ function detectTruncation(version) { if (version === 'aes128gcm') { return; } - var input = generateInput(2); - var params = { + let input = generateInput(2); + const params = { version: version, key: crypto.randomBytes(16), rs: input.length + rsoverhead(version) - 1 }; - var headerLen = (version === 'aes128gcm') ? 21 : 0; - var encrypted = ece.encrypt(input, params); - var chunkLen = headerLen + params.rs; + const headerLen = (version === 'aes128gcm') ? 21 : 0; + let encrypted = ece.encrypt(input, params); + let chunkLen = headerLen + params.rs; if (version != 'aes128gcm') { chunkLen += 16; } assert.ok(chunkLen < encrypted.length); encrypted = encrypted.slice(0, chunkLen); logbuf('Encrypted', encrypted); - var ok = false; + let ok = false; try { ece.decrypt(encrypted, params); } catch (e) { @@ -267,7 +266,7 @@ function detectTruncation(version) { function useDH(version) { // the static key is used by the receiver - var staticKey = crypto.createECDH('prime256v1'); + const staticKey = crypto.createECDH('prime256v1'); staticKey.generateKeys(); assert.equal(staticKey.getPublicKey()[0], 4, 'is an uncompressed point'); @@ -275,20 +274,20 @@ function useDH(version) { logbuf('Receiver public', staticKey.getPublicKey()); // the ephemeral key is used by the sender - var ephemeralKey = crypto.createECDH('prime256v1'); + const ephemeralKey = crypto.createECDH('prime256v1'); ephemeralKey.generateKeys(); assert.equal(ephemeralKey.getPublicKey()[0], 4, 'is an uncompressed point'); logbuf('Sender private', ephemeralKey.getPrivateKey()); logbuf('Sender public', ephemeralKey.getPublicKey()); - var input = generateInput(); - var encryptParams = { + let input = generateInput(); + const encryptParams = { version: version, authSecret: crypto.randomBytes(16), dh: staticKey.getPublicKey() }; - var decryptParams = { + const decryptParams = { version: version, authSecret: encryptParams.authSecret }; @@ -303,7 +302,7 @@ function useDH(version) { // keyData is used for cross library verification dumps - var keyData = { + const keyData = { sender: { private: ephemeralKey.getPrivateKey().toString('base64url'), public: ephemeralKey.getPublicKey().toString('base64url') @@ -351,12 +350,12 @@ function checkExamples(version) { return v.args.version === version; }).forEach(function (v, i) { log('decrypt ' + v.args.version + ' example ' + (i + 1)); - var decrypted = ece.decrypt(v.ciphertext, v.args); + const decrypted = ece.decrypt(v.ciphertext, v.args); logbuf('decrypted', decrypted); assert.equal(Buffer.compare(v.plaintext, decrypted), 0); log('encrypt ' + v.args.version + ' example ' + (i + 1)); - var encrypted = ece.encrypt(v.plaintext, v.args); + let encrypted = ece.encrypt(v.plaintext, v.args); logbuf('encrypted', encrypted); assert.equal(Buffer.compare(v.ciphertext, encrypted), 0); }); @@ -378,15 +377,15 @@ function useCustomCallback(version) { } let input = generateInput() - var parameters = { + const parameters = { keyid: '123456', }; log('Testing custom function') - var encrypted = ece.encrypt(input, parameters, customCallback); + let encrypted = ece.encrypt(input, parameters, customCallback); logbuf('encrypted', encrypted) - var decrypted = ece.decrypt(encrypted, parameters, customCallback); + const decrypted = ece.decrypt(encrypted, parameters, customCallback); logbuf('decrypted', decrypted) assert.equal(Buffer.compare(decrypted, input), 0) @@ -418,5 +417,5 @@ filterTests([ 'aesgcm', 'aes128gcm' ]) log('All tests passed.'); if (dumpFile) { - require('fs').writeFileSync(dumpFile, JSON.stringify(dumpData, undefined, ' ')); + fs.writeFileSync(dumpFile, JSON.stringify(dumpData, undefined, ' ')); } From 4f3cb60b2a28f3a3881864d7c92af84231fa7c4d Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:47:31 +0000 Subject: [PATCH 2/2] feat: add typescript types Adds typescript as a dev dependency and introduces JSDoc types so we can emit our own type definitions. These are output to `types/`. --- .gitignore | 1 + nodejs/.npmignore | 3 + nodejs/ece.js | 29 +++ nodejs/package-lock.json | 412 +++++++++++++++++++++++++++++++++++++++ nodejs/package.json | 25 ++- nodejs/tsconfig.json | 25 +++ 6 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 nodejs/package-lock.json create mode 100644 nodejs/tsconfig.json diff --git a/.gitignore b/.gitignore index bd028cd..fc356e9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules /encrypt_data.json /venv/ +/nodejs/types diff --git a/nodejs/.npmignore b/nodejs/.npmignore index 271afe3..5dd8929 100644 --- a/nodejs/.npmignore +++ b/nodejs/.npmignore @@ -3,3 +3,6 @@ decrypt-dh.js encrypt.js encrypt-dh.js test.js +types/* +!types/ece.d.ts +tsconfig.json diff --git a/nodejs/ece.js b/nodejs/ece.js index 3f65adf..10f2746 100644 --- a/nodejs/ece.js +++ b/nodejs/ece.js @@ -24,6 +24,25 @@ const SHA_256_LENGTH = 32; const MODE_ENCRYPT = 'encrypt'; const MODE_DECRYPT = 'decrypt'; +/** + * @typedef {Object} Params + * @property {('aes128gcm'|'aesgcm')=} version + * @property {(number|string)=} rs + * @property {(string|Buffer)=} salt + * @property {(string|Buffer)=} keyid + * @property {(string|Buffer)=} key + * @property {crypto.ECDH=} privateKey + * @property {Object=} keymap + * @property {string=} keylabel + * @property {(string|Buffer)=} dh + * @property {(string|Buffer)=} authSecret + * @property {(number|string)=} pad Only used when encrypting. + */ + +/** + * @typedef {(keyid: (string|Buffer)) => Buffer} KeyLookupCallback + */ + let keylog; if (process.env.ECE_KEYLOG === '1') { keylog = function(m, k) { @@ -358,6 +377,11 @@ function decryptRecord(key, counter, buffer, header, last) { * |params.dh| needs to be provided with the public key of the sender. * * The |params.privateKey| includes the private key of the receiver. + * + * @param {Buffer} buffer + * @param {Params} params + * @param {KeyLookupCallback=} keyLookupCallback + * @return {Buffer} */ function decrypt(buffer, params, keyLookupCallback) { const header = parseParams(params); @@ -451,6 +475,11 @@ function writeHeader(header) { * For Diffie-Hellman (WebPush), |params.dh| includes the public key of the * receiver. |params.privateKey| is used to establish a shared secret. Key * pairs can be created using |crypto.createECDH()|. + * + * @param {Buffer} buffer + * @param {Params} params + * @param {KeyLookupCallback=} keyLookupCallback + * @return {Buffer} */ function encrypt(buffer, params, keyLookupCallback) { if (!Buffer.isBuffer(buffer)) { diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json new file mode 100644 index 0000000..9d91121 --- /dev/null +++ b/nodejs/package-lock.json @@ -0,0 +1,412 @@ +{ + "name": "http_ece", + "version": "1.2.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "http_ece", + "version": "1.2.1", + "license": "MIT", + "devDependencies": { + "@types/node": "^20.19.43", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/nodejs/package.json b/nodejs/package.json index 6b0c671..4004c63 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -8,22 +8,37 @@ "name": "Martin Thomson", "email": "martin.thomson@gmail.com" }, - "contributors": [{ - "name": "Marco Castelluccio", - "email": "mcastelluccio@mozilla.com" - }], + "contributors": [ + { + "name": "Marco Castelluccio", + "email": "mcastelluccio@mozilla.com" + } + ], "repository": { "type": "git", "url": "https://github.com/martinthomson/encrypted-content-encoding.git" }, "license": "MIT", "main": "./ece.js", + "types": "./types/ece.d.ts", + "exports": { + ".": { + "types": "./types/ece.d.ts", + "default": "./ece.js" + } + }, "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", "test": "node ./test.js", "test:verbose": "node ./test.js dump verbose" }, "engines": { "node": ">=20" }, - "type": "module" + "type": "module", + "devDependencies": { + "@types/node": "^20.19.43", + "typescript": "^7.0.2" + } } diff --git a/nodejs/tsconfig.json b/nodejs/tsconfig.json new file mode 100644 index 0000000..e65b913 --- /dev/null +++ b/nodejs/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "outDir": "./types", + "rootDir": "./", + "emitDeclarationOnly": true, + "module": "node18", + "types": ["node"], + "allowJs": true, + "sourceMap": false, + "declaration": true, + "declarationMap": false, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "erasableSyntaxOnly": true, + "skipLibCheck": true + }, + "include": ["*.js"] +}