From ed05f8f520c892b5a3d1580b3ef56ca52cc51c97 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Fri, 5 Dec 2025 22:11:57 +0530 Subject: [PATCH 01/19] first commit --- .../solutions/easy/1-counter.js | 8 +- .../solutions/easy/2-counter.js | 14 ++- .../solutions/hard/1-promisify-setTimeout.js | 12 ++- .../solutions/hard/2-sleep-completely.js | 14 ++- .../solutions/hard/3-promise-all.js | 52 +++++++++- .../solutions/hard/4-promise-chain.js | 91 ++++++++++++++++- "week-3/easy/The-Pok\303\251mon/index.html" | 16 +++ "week-3/easy/The-Pok\303\251mon/script.js" | 50 ++++++++++ week-3/easy/bg-color-changer/index.html | 21 ++++ week-3/easy/bg-color-changer/script.js | 12 +++ week-3/easy/quiz-app/index.html | 35 +++++++ week-3/easy/quiz-app/script.js | 99 +++++++++++++++++++ week-5/solution/backend/db/index.js | 6 +- week-5/solution/backend/package-lock.json | 69 ++++++------- week-5/solution/backend/package.json | 2 +- week-5/solution/backend/routes/user.js | 15 +++ 16 files changed, 469 insertions(+), 47 deletions(-) create mode 100644 "week-3/easy/The-Pok\303\251mon/index.html" create mode 100644 "week-3/easy/The-Pok\303\251mon/script.js" create mode 100644 week-3/easy/bg-color-changer/index.html create mode 100644 week-3/easy/bg-color-changer/script.js create mode 100644 week-3/easy/quiz-app/index.html create mode 100644 week-3/easy/quiz-app/script.js diff --git a/week-2/week-2-async-js/solutions/easy/1-counter.js b/week-2/week-2-async-js/solutions/easy/1-counter.js index da2806780..80cade2d8 100644 --- a/week-2/week-2-async-js/solutions/easy/1-counter.js +++ b/week-2/week-2-async-js/solutions/easy/1-counter.js @@ -1,9 +1,13 @@ let counter = 0; +// const updateCounter = () => { +// counter++; +// console.log(counter); +// }; -const updateCounter = () => { +function updateCounter() { counter++; console.log(counter); -}; +} setInterval(updateCounter, 1000); diff --git a/week-2/week-2-async-js/solutions/easy/2-counter.js b/week-2/week-2-async-js/solutions/easy/2-counter.js index 95896a264..38e3a69b7 100644 --- a/week-2/week-2-async-js/solutions/easy/2-counter.js +++ b/week-2/week-2-async-js/solutions/easy/2-counter.js @@ -1,10 +1,16 @@ let counter = 0; -const updateCounter = () => { - counter++; - console.log(counter); +// const updateCounter = () => { +// counter++; +// console.log(counter); + +// setTimeout(updateCounter, 1000); +// }; +function updateCounter() { + counter++; + console.log(counter); setTimeout(updateCounter, 1000); -}; +} updateCounter(); diff --git a/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js b/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js index 1362af622..94f15cc7a 100644 --- a/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js +++ b/week-2/week-2-async-js/solutions/hard/1-promisify-setTimeout.js @@ -7,4 +7,14 @@ function wait(n) { return p; } -module.exports = wait; \ No newline at end of file +module.exports = wait; + +function randomAfter3S(resolve, n) { + setTimeout(resolve, n*1000); +} + +function callbackAfter3S() { + console.log("promise succeeded after 3 seconds!") +} +p = new Promise((resolve) => randomAfter3S(resolve, 3)); +p.then(callbackAfter3S); \ No newline at end of file diff --git a/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js b/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js index 42e80a1a4..3cb056fea 100644 --- a/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js +++ b/week-2/week-2-async-js/solutions/hard/2-sleep-completely.js @@ -6,4 +6,16 @@ function sleep(milliseconds) { }); } -module.exports = sleep; \ No newline at end of file +module.exports = sleep; + +function randomAfterSleep(resolve, n) { + let startTime = new Date().getTime(); + while (new Date().getTime() < startTime + n*1000); + resolve(); +} + +function callbackAfterSleep() { + console.log("promise succeeded after sleeping for 3 seconds!") +} +p = new Promise((resolve) => randomAfterSleep(resolve, 3)); +p.then(callbackAfterSleep); \ No newline at end of file diff --git a/week-2/week-2-async-js/solutions/hard/3-promise-all.js b/week-2/week-2-async-js/solutions/hard/3-promise-all.js index 5ca428d5a..3ce15a9a6 100644 --- a/week-2/week-2-async-js/solutions/hard/3-promise-all.js +++ b/week-2/week-2-async-js/solutions/hard/3-promise-all.js @@ -25,5 +25,53 @@ function wait1(t) { return totalTime; } - module.exports = calculateTime; - \ No newline at end of file +module.exports = calculateTime; + + +// function randomAfter1S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter1S() { + console.log("promise succeeded after 1 seconds!") +} +let p1 = new Promise((resolve) => randomAfterNS(resolve, 1)); +p1.then(callbackAfter1S); + + + +// function randomAfter2S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter2S() { + console.log("promise succeeded after 2 seconds!") +} +let p2 = new Promise((resolve) => randomAfterNS(resolve, 2)); +p2.then(callbackAfter2S); + + + +// function randomAfter3S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter3S() { + console.log("promise succeeded after 3 seconds!") +} +let p3 = new Promise((resolve) => randomAfterNS(resolve, 3)); +p3.then(callbackAfter3S); + +console.log("hello") + +function randomAfterNS(resolve, n) { + setTimeout(resolve, n*1000); +} + +const startTime = Date.now(); + +function callback() { + console.log("all promises succeeded after " + (Date.now() - startTime)/1000 + " seconds!") +} + +Promise.all([p1, p2, p3]).then(callback); \ No newline at end of file diff --git a/week-2/week-2-async-js/solutions/hard/4-promise-chain.js b/week-2/week-2-async-js/solutions/hard/4-promise-chain.js index f3608d78d..92756d918 100644 --- a/week-2/week-2-async-js/solutions/hard/4-promise-chain.js +++ b/week-2/week-2-async-js/solutions/hard/4-promise-chain.js @@ -32,4 +32,93 @@ function call(t1, t2, t3) { }); } -module.exports = calculateTime; \ No newline at end of file +module.exports = calculateTime; + +// function randomAfter1S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter1S() { + console.log("promise succeeded after 1 seconds!") +} +let p1 = new Promise((resolve) => randomAfterNS(resolve, 1)); +// p1.then(callbackAfter1S); + + + +// function randomAfter2S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter2S() { + console.log("promise succeeded after 2 seconds!") +} +// let p2 = new Promise((resolve) => randomAfterNS(resolve, 2)); +// p2.then(callbackAfter2S); + + + +// function randomAfter3S(resolve, n) { +// setTimeout(resolve, n*1000); +// } + +function callbackAfter3S() { + console.log("promise succeeded after 3 seconds!") +} +// let p3 = new Promise((resolve) => randomAfterNS(resolve, 3)); +// p3.then(callbackAfter3S); + +console.log("hello") + +function randomAfterNS(resolve, n) { + setTimeout(resolve, n*1000); +} + +const startTime = Date.now(); + +function callback() { + console.log("all promises succeeded after " + (Date.now() - startTime)/1000 + " seconds!") +} + +p1 + .then(() => { + callbackAfter1S(); + return new Promise((resolve) => randomAfterNS(resolve, 2)); + }) + .then(() => { + callbackAfter2S(); + return new Promise((resolve) => randomAfterNS(resolve, 3)); + }) + .then(() => { + callbackAfter3S(); + callback(); + }); + + +p1 = new Promise((resolve) => randomAfterNS(resolve, 1)); +p1.then(() => { + callbackAfter1S(); + p2 = new Promise((resolve) => randomAfterNS(resolve, 2)); + p2.then(() => { + callbackAfter2S(); + p3 = new Promise((resolve) => randomAfterNS(resolve, 3)); + p3.then(() => { + callbackAfter3S(); + callback(); + }) + }) + }) + + async function solve() { + await new Promise((resolve) => randomAfterNS(resolve, 1)); + callbackAfter1S(); + await new Promise((resolve) => randomAfterNS(resolve, 2)); + callbackAfter2S(); + await new Promise((resolve) => randomAfterNS(resolve, 3)); + callbackAfter3S(); + callback(); + } + + solve() + + console.log("whatsup") \ No newline at end of file diff --git "a/week-3/easy/The-Pok\303\251mon/index.html" "b/week-3/easy/The-Pok\303\251mon/index.html" new file mode 100644 index 000000000..a8a7ab2f4 --- /dev/null +++ "b/week-3/easy/The-Pok\303\251mon/index.html" @@ -0,0 +1,16 @@ + + + + + index + + + + Hello world + +
+
+ + + + \ No newline at end of file diff --git "a/week-3/easy/The-Pok\303\251mon/script.js" "b/week-3/easy/The-Pok\303\251mon/script.js" new file mode 100644 index 000000000..dc19e10c4 --- /dev/null +++ "b/week-3/easy/The-Pok\303\251mon/script.js" @@ -0,0 +1,50 @@ +let score = 0; + +function startPokemon() { + document.querySelector("#input").innerHTML = "" + document.querySelector("#pokemon-list").innerHTML = "" + document.querySelector("#input").appendChild(submitPokemonId()) +} + +function submitPokemonId() { + const div = document.createElement("div"); + const form = document.createElement("form"); + const label = document.createElement("label"); + const input = document.createElement("input"); + const button = document.createElement("button"); + label.setAttribute("for", "pokemonId") + label.innerHTML = "Enter pokemon id" + input.setAttribute("id", "pokemonId") + input.setAttribute("placeholder", "Enter pokemon id") + button.innerHTML = "Submit" + button.type = "button"; + button.addEventListener("click", () => pokemonComponent(input.value)); + form.appendChild(label) + form.appendChild(input) + form.appendChild(button) + div.appendChild(form) + return div +} + +async function pokemonComponent(id) { + console.log(id) + document.querySelector("#pokemon-list").innerHTML = "" + try { + const response = await fetch("https://pokeapi.co/api/v2/pokemon-form/" + id + "/"); + const pokemonData = await response.json(); // <-- JSON stored here + const h1 = document.createElement("h1"); + const imgFront = document.createElement("img"); + const imgBack = document.createElement("img"); + h1.innerHTML = pokemonData.pokemon.name; + imgFront.setAttribute("src", pokemonData.sprites.front_default) + imgBack.setAttribute("src", pokemonData.sprites.back_default) + document.querySelector("#pokemon-list").appendChild(h1) + document.querySelector("#pokemon-list").appendChild(imgFront) + document.querySelector("#pokemon-list").appendChild(imgBack) + } catch (err) { + console.error("Error fetching Pokémon data:", err); + } +} + +// Attach event listener in JavaScript (module-safe) +document.getElementById("start-btn").addEventListener("click", startPokemon); \ No newline at end of file diff --git a/week-3/easy/bg-color-changer/index.html b/week-3/easy/bg-color-changer/index.html new file mode 100644 index 000000000..48a7cabae --- /dev/null +++ b/week-3/easy/bg-color-changer/index.html @@ -0,0 +1,21 @@ + + + + + + + index + + + + + Hello world + + + + +
hellooooo
+ + + + \ No newline at end of file diff --git a/week-3/easy/bg-color-changer/script.js b/week-3/easy/bg-color-changer/script.js new file mode 100644 index 000000000..642b06c3c --- /dev/null +++ b/week-3/easy/bg-color-changer/script.js @@ -0,0 +1,12 @@ +function changeBackground(colour) { + document.querySelector("body").style.backgroundColor = colour + render(); +} + +// react +function render() { + +} + +// THIS makes the default appear immediately +render(); \ No newline at end of file diff --git a/week-3/easy/quiz-app/index.html b/week-3/easy/quiz-app/index.html new file mode 100644 index 000000000..7382f4a96 --- /dev/null +++ b/week-3/easy/quiz-app/index.html @@ -0,0 +1,35 @@ + + + + + index + + + + Hello world + + + +
+ + + + + \ No newline at end of file diff --git a/week-3/easy/quiz-app/script.js b/week-3/easy/quiz-app/script.js new file mode 100644 index 000000000..8c4ea93d6 --- /dev/null +++ b/week-3/easy/quiz-app/script.js @@ -0,0 +1,99 @@ +import { quizData } from "./data.js"; + +let score = 0; + +function startQuiz() { + score = 0; + render(0); +} + +function submitAnswer(i) { + calculateScore(document.getElementById(quizData[i].correct).checked) + i = i + 1; + if (i == quizData.length) { + document.querySelector("#quiz-list").innerHTML = "Your final score is " + score + " out of " + quizData.length; + } + else + render(i) +} + +function calculateScore(i) { + score = score + i; + console.log("score") + console.log(score) +} + +function quizComponent(i) { + const div = document.createElement("div"); + const h1 = document.createElement("h1"); + const form = document.createElement("form"); + const label1 = document.createElement("label"); + const label2 = document.createElement("label"); + const label3 = document.createElement("label"); + const label4 = document.createElement("label"); + const input1 = document.createElement("input"); + const input2 = document.createElement("input"); + const input3 = document.createElement("input"); + const input4 = document.createElement("input"); + const button = document.createElement("button"); + h1.innerHTML = quizData[i].question; + button.innerHTML = "Submit" + button.type = "button"; + button.addEventListener("click", () => submitAnswer(i)); + + input1.setAttribute("type", "radio") + input1.setAttribute("name", "selection") + input1.setAttribute("value", "a") + input1.setAttribute("id", "a"); + + input2.setAttribute("type", "radio") + input2.setAttribute("name", "selection") + input2.setAttribute("value", "b") + input2.setAttribute("id", "b"); + + input3.setAttribute("type", "radio") + input3.setAttribute("name", "selection") + input3.setAttribute("value", "c") + input3.setAttribute("id", "c"); + + input4.setAttribute("type", "radio") + input4.setAttribute("name", "selection") + input4.setAttribute("value", "d") + input4.setAttribute("id", "d"); + + label1.setAttribute("for", "a") + label2.setAttribute("for", "b") + label3.setAttribute("for", "c") + label4.setAttribute("for", "d") + + div.appendChild(h1) + label1.innerHTML = quizData[i].a; + label2.innerHTML = quizData[i].b; + label3.innerHTML = quizData[i].c; + label4.innerHTML = quizData[i].d; + label1.appendChild(input1) + label2.appendChild(input2) + label3.appendChild(input3) + label4.appendChild(input4) + form.appendChild(label1) + form.appendChild(document.createElement("br")) + form.appendChild(label2) + form.appendChild(document.createElement("br")) + form.appendChild(label3) + form.appendChild(document.createElement("br")) + form.appendChild(label4) + form.appendChild(document.createElement("br")) + form.appendChild(button) + div.appendChild(form) + return div + +} + +// react +function render(i) { + document.querySelector("#quiz-list").innerHTML = "" + document.querySelector("#quiz-list").appendChild(quizComponent(i)) +} + +// Attach event listener in JavaScript (module-safe) +document.getElementById("start-btn").addEventListener("click", startQuiz); \ No newline at end of file diff --git a/week-5/solution/backend/db/index.js b/week-5/solution/backend/db/index.js index 0c7e3c4dd..d47b454ce 100644 --- a/week-5/solution/backend/db/index.js +++ b/week-5/solution/backend/db/index.js @@ -2,9 +2,13 @@ const mongoose = require("mongoose"); require('dotenv').config(); // Connect to MongoDB +// tavishiseth_db_user +// Kushalatolia@123 +// mongodb+srv://tavishiseth_db_user:Kushalatolia@123@cluster0.sleqxbs.mongodb.net/ const connectToDatabase = async () => { try { - await mongoose.connect(process.env.MONGO_URI); + // await mongoose.connect(process.env.MONGO_URI); + await mongoose.connect("mongodb+srv://tavishiseth_db_user:Kushalatolia@123@cluster0.sleqxbs.mongodb.net/todo-app-database"); console.log("Database connected"); } catch (error) { console.error("Database connection failed:", error); diff --git a/week-5/solution/backend/package-lock.json b/week-5/solution/backend/package-lock.json index 8598f9788..a59198517 100644 --- a/week-5/solution/backend/package-lock.json +++ b/week-5/solution/backend/package-lock.json @@ -13,13 +13,13 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.6.3" + "mongoose": "^8.20.1" } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -83,9 +83,9 @@ } }, "node_modules/bson": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", - "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -623,25 +623,25 @@ } }, "node_modules/mongodb": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", - "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -669,23 +669,24 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.3.tgz", - "integrity": "sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.1.tgz", + "integrity": "sha512-G+n3maddlqkQrP1nXxsI0q20144OSo+pe+HzRRGqaC4yK3FLYKqejqB9cbIi+SX7eoRsnG23LHGYNp8n7mWL2Q==", + "license": "MIT", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "6.8.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -1020,15 +1021,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/type-is": { @@ -1079,16 +1080,16 @@ } }, "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } } } diff --git a/week-5/solution/backend/package.json b/week-5/solution/backend/package.json index 006b08316..8442d75b5 100644 --- a/week-5/solution/backend/package.json +++ b/week-5/solution/backend/package.json @@ -14,6 +14,6 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.6.3" + "mongoose": "^8.20.1" } } diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js index d058e32a7..4bc77e7ba 100644 --- a/week-5/solution/backend/routes/user.js +++ b/week-5/solution/backend/routes/user.js @@ -3,6 +3,8 @@ const jwt = require('jsonwebtoken'); const { authenticateJwt, SECRET } = require("../middleware/user"); const { User } = require("../db"); const router = express.Router(); +//const app = express() +// app.use(express.json()) router.post('/signup', async (req, res) => { const { username, password } = req.body; @@ -14,6 +16,12 @@ router.post('/signup', async (req, res) => { const newUser = new User({ username, password }); await newUser.save(); + /* + await User.insertOne({ + username: username, + password: password + }) + */ const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' }); res.json({ message: 'User created successfully', token }); @@ -26,6 +34,13 @@ router.post('/signin', async (req, res) => { const { username, password } = req.body; try { const user = await User.findOne({ username, password }); + /* + const user = await User.findOne({ + username: username, + password: password + }) + */ + if (user) { const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: '1h' }); From 98c4aae302f18d33dbfacaf4ac993554697faf48 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Fri, 5 Dec 2025 23:32:27 +0530 Subject: [PATCH 02/19] fix week-5 --- week-5/README.md | 6 +- week-5/package-lock.json | 6 + week-5/solution/backend/.env.example | 2 +- week-5/solution/backend/db/index.js | 6 +- week-5/solution/backend/package-lock.json | 363 +++++++++++++++++++++ week-5/solution/backend/package.json | 6 +- week-5/solution/frontend/package-lock.json | 6 + week-5/solution/frontend/script.js | 10 +- week-5/solution/package-lock.json | 6 + 9 files changed, 397 insertions(+), 14 deletions(-) create mode 100644 week-5/package-lock.json create mode 100644 week-5/solution/frontend/package-lock.json create mode 100644 week-5/solution/package-lock.json diff --git a/week-5/README.md b/week-5/README.md index b447375dc..4dec5e74d 100644 --- a/week-5/README.md +++ b/week-5/README.md @@ -20,14 +20,16 @@ cp .env.example .env 3. run the server. ``` -npm run dev +C:\Users\tavis\OneDrive\Desktop\cohort\assignments\week-5\solution\backend> npm run dev ``` start building. #### Frontend Setup - go inside week-5/frontend and run: - +``` +C:\Users\tavis\OneDrive\Desktop\cohort\assignments\week-5\solution\frontend> serve . +``` ### Reference UI: ![Image](https://utfs.io/f/A8JZzw0Laf9jdQzX4lrWunt9yxDYPKUZgv60iAroJbcMF5RN) diff --git a/week-5/package-lock.json b/week-5/package-lock.json new file mode 100644 index 000000000..493386486 --- /dev/null +++ b/week-5/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "week-5", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/week-5/solution/backend/.env.example b/week-5/solution/backend/.env.example index 34892a3e7..10b6b8149 100644 --- a/week-5/solution/backend/.env.example +++ b/week-5/solution/backend/.env.example @@ -1,2 +1,2 @@ PORT=3003 -MONGO_URI=mongodb://localhost:27017/todos \ No newline at end of file +MONGO_URI=mongodb+srv://tavishiseth_db_user:Tavishiseth@cluster0.sleqxbs.mongodb.net/ \ No newline at end of file diff --git a/week-5/solution/backend/db/index.js b/week-5/solution/backend/db/index.js index d47b454ce..0c7e3c4dd 100644 --- a/week-5/solution/backend/db/index.js +++ b/week-5/solution/backend/db/index.js @@ -2,13 +2,9 @@ const mongoose = require("mongoose"); require('dotenv').config(); // Connect to MongoDB -// tavishiseth_db_user -// Kushalatolia@123 -// mongodb+srv://tavishiseth_db_user:Kushalatolia@123@cluster0.sleqxbs.mongodb.net/ const connectToDatabase = async () => { try { - // await mongoose.connect(process.env.MONGO_URI); - await mongoose.connect("mongodb+srv://tavishiseth_db_user:Kushalatolia@123@cluster0.sleqxbs.mongodb.net/todo-app-database"); + await mongoose.connect(process.env.MONGO_URI); console.log("Database connected"); } catch (error) { console.error("Database connection failed:", error); diff --git a/week-5/solution/backend/package-lock.json b/week-5/solution/backend/package-lock.json index a59198517..22cae23ff 100644 --- a/week-5/solution/backend/package-lock.json +++ b/week-5/solution/backend/package-lock.json @@ -14,6 +14,9 @@ "express": "^4.21.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.20.1" + }, + "devDependencies": { + "nodemon": "^3.1.11" } }, "node_modules/@mongodb-js/saslprep": { @@ -53,12 +56,46 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -82,6 +119,30 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bson": { "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", @@ -122,6 +183,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -315,6 +408,19 @@ "node": ">= 0.10.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -349,6 +455,21 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -375,6 +496,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -386,6 +520,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -456,6 +600,13 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -470,6 +621,52 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -622,6 +819,19 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/mongodb": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", @@ -764,6 +974,70 @@ "node": ">= 0.6" } }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -807,6 +1081,19 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -820,6 +1107,13 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -865,6 +1159,19 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -995,6 +1302,19 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -1012,6 +1332,32 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1020,6 +1366,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -1044,6 +1400,13 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/week-5/solution/backend/package.json b/week-5/solution/backend/package.json index 8442d75b5..3c0be83d0 100644 --- a/week-5/solution/backend/package.json +++ b/week-5/solution/backend/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node index.js", + "dev": "nodemon index.js" }, "keywords": [], "author": "", @@ -15,5 +16,8 @@ "express": "^4.21.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.20.1" + }, + "devDependencies": { + "nodemon": "^3.1.11" } } diff --git a/week-5/solution/frontend/package-lock.json b/week-5/solution/frontend/package-lock.json new file mode 100644 index 000000000..aba25f735 --- /dev/null +++ b/week-5/solution/frontend/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "frontend", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/week-5/solution/frontend/script.js b/week-5/solution/frontend/script.js index 0ad89d693..8ed574e21 100644 --- a/week-5/solution/frontend/script.js +++ b/week-5/solution/frontend/script.js @@ -11,7 +11,7 @@ document.getElementById('signup-form').addEventListener('submit', async (e) => { const password = document.getElementById('signup-password').value; try { - const response = await fetch('http://localhost:3000/user/signup', { + const response = await fetch('http://localhost:3003/user/signup', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -43,7 +43,7 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => { const password = document.getElementById('signin-password').value; try { - const response = await fetch('http://localhost:3000/user/signin', { + const response = await fetch('http://localhost:3003/user/signin', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -93,7 +93,7 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => { const token = localStorage.getItem('token'); try { - const response = await fetch('http://localhost:3000/todo', { + const response = await fetch('http://localhost:3003/todo', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -120,7 +120,7 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => { async function loadTodos() { const token = localStorage.getItem('token'); try { - const response = await fetch('http://localhost:3000/todo', { + const response = await fetch('http://localhost:3003/todo', { headers: { 'Authorization': `Bearer ${token}`, }, @@ -158,7 +158,7 @@ async function loadTodos() { async function completeTodo(id, completed) { const token = localStorage.getItem('token'); try { - await fetch(`http://localhost:3000/todo/${id}`, { + await fetch(`http://localhost:3003/todo/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/week-5/solution/package-lock.json b/week-5/solution/package-lock.json new file mode 100644 index 000000000..c7fa1c889 --- /dev/null +++ b/week-5/solution/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "solution", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 34f3793cc82efe379b4e9ceaca59c52fb06dafeb Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Sun, 7 Dec 2025 16:24:12 +0530 Subject: [PATCH 03/19] add comments --- week-5/solution/backend/.env.example | 2 +- week-5/solution/backend/middleware/user.js | 6 ++ week-5/solution/backend/routes/todo.js | 9 +++ week-5/solution/backend/routes/user.js | 10 ++- week-5/solution/frontend/index.html | 18 +++++- week-5/solution/frontend/script.js | 73 +++++++++++++++++++++- 6 files changed, 114 insertions(+), 4 deletions(-) diff --git a/week-5/solution/backend/.env.example b/week-5/solution/backend/.env.example index 10b6b8149..34892a3e7 100644 --- a/week-5/solution/backend/.env.example +++ b/week-5/solution/backend/.env.example @@ -1,2 +1,2 @@ PORT=3003 -MONGO_URI=mongodb+srv://tavishiseth_db_user:Tavishiseth@cluster0.sleqxbs.mongodb.net/ \ No newline at end of file +MONGO_URI=mongodb://localhost:27017/todos \ No newline at end of file diff --git a/week-5/solution/backend/middleware/user.js b/week-5/solution/backend/middleware/user.js index 4ed77ad15..404fa1e60 100644 --- a/week-5/solution/backend/middleware/user.js +++ b/week-5/solution/backend/middleware/user.js @@ -2,6 +2,12 @@ const jwt = require('jsonwebtoken'); const SECRET = process.env.SECRET || 'secret000'; const authenticateJwt = (req, res, next) => { + /* + receive req.headers from script.js (submit button of todo) + { + Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage) + } + */ const authHeader = req.headers.authorization; if (authHeader) { const token = authHeader.split(' ')[1]; diff --git a/week-5/solution/backend/routes/todo.js b/week-5/solution/backend/routes/todo.js index cd8671c59..a9ed70fa7 100644 --- a/week-5/solution/backend/routes/todo.js +++ b/week-5/solution/backend/routes/todo.js @@ -3,11 +3,19 @@ const { Todo } = require("../db"); const { authenticateJwt } = require("../middleware/user"); const router = express.Router(); +// authenticateJwt middleware before every todo route router.use(authenticateJwt); router.post("/", async (req, res) => { + /* + receive req.body from script.js (submit button of todo) + { + title: "go to gym" (from todo-input input box) + } + */ const createPayload = req.body; console.log(req.userId); + // req.userId from authenticateJwt middleware if (!createPayload.title) { return res.status(400).json({ @@ -36,6 +44,7 @@ router.post("/", async (req, res) => { }); router.get("/", async (req, res) => { + // req.userId from authenticateJwt middleware try { const todos = await Todo.find({ userId: req.userId }); diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js index 4bc77e7ba..fa721fefe 100644 --- a/week-5/solution/backend/routes/user.js +++ b/week-5/solution/backend/routes/user.js @@ -7,7 +7,15 @@ const router = express.Router(); // app.use(express.json()) router.post('/signup', async (req, res) => { + /* + receive req.body from script.js (submit button of signup) + { + username: username (from signup-username), + password: password (from signup-password) + } + */ const { username, password } = req.body; + // check if username already exists try { const user = await User.findOne({ username }); if (user) { @@ -35,7 +43,7 @@ router.post('/signin', async (req, res) => { try { const user = await User.findOne({ username, password }); /* - const user = await User.findOne({ + const user = await User.create({ username: username, password: password }) diff --git a/week-5/solution/frontend/index.html b/week-5/solution/frontend/index.html index a64cb0435..d568ddd6e 100644 --- a/week-5/solution/frontend/index.html +++ b/week-5/solution/frontend/index.html @@ -20,9 +20,16 @@

Signup

+

Already have an account? Sign In +

@@ -34,9 +41,16 @@

Signin

+

Don't have an account? Sign Up +

@@ -46,7 +60,9 @@

Your Todo List

-
    +
      + +
    diff --git a/week-5/solution/frontend/script.js b/week-5/solution/frontend/script.js index 8ed574e21..3cf98f3c0 100644 --- a/week-5/solution/frontend/script.js +++ b/week-5/solution/frontend/script.js @@ -17,13 +17,28 @@ document.getElementById('signup-form').addEventListener('submit', async (e) => { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }), + /* + send req.body to http://localhost:3003/user/signup + { + username: username (from signup-username), + password: password (from signup-password) + } + */ }); const result = await response.json(); + /* + receive response.json() and response.status() from http://localhost:3003/user/signup + { + "message": "User created successfully/User already exists/Error creating user", + "token": "abcdefghijklmnopqrstuvwxyz" + } + */ isSigningUp = false; if (response.ok) { document.getElementById('response-message').innerText = result.message || 'Signup successful, please sign in'; document.getElementById('signup-container').style.display = 'none'; + // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('signin-container').style.display = 'block'; } else { document.getElementById('response-message').innerText = result.message || 'Signup failed'; @@ -50,11 +65,26 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => { }, body: JSON.stringify({ username, password }), }); + /* + send req.body to http://localhost:3003/user/signup + { + username: username (from signup-username), + password: password (from signup-password) + } + */ const result = await response.json(); + /* + response.json() + { + "message": "Logged in successfully/Invalid username or password/Error signing in", + "token": "abcdefghijklmnopqrstuvwxyz" + } + */ if (response.ok) { localStorage.setItem('token', result.token); document.getElementById('signin-container').style.display = 'none'; + // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('todo-container').style.display = 'block'; document.getElementById('response-message').innerHTML = `Logged in successfully. Logout`; @@ -65,6 +95,7 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => { e.preventDefault(); localStorage.removeItem('token'); // Clear token document.getElementById('todo-container').style.display = 'none'; + // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('signin-container').style.display = 'block'; document.getElementById('response-message').innerText = ''; }); @@ -92,6 +123,7 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => { const token = localStorage.getItem('token'); + // router.post try { const response = await fetch('http://localhost:3003/todo', { method: 'POST', @@ -101,11 +133,31 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => { }, body: JSON.stringify({ title: todoText }), }); + /* + send req.headers to http://localhost:3003/user/todo + { + Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage) + } + send req.body to http://localhost:3003/user/todo + { + title: "go to gym" (from todo-input input box) + } + */ const result = await response.json(); + /* + response.json() + { + msg: "Todo created", + todo: newTodo where newTodo contains { + title: "go to gym", + completed: false, + userId: req.userId, + } + */ isAddingTodo = false; if (response.ok) { - todoInput.value = ''; + todoInput.value = ''; // clear todo input box loadTodos(); } else { console.error(result.msg); @@ -120,12 +172,19 @@ document.getElementById('todo-form').addEventListener('submit', async (e) => { async function loadTodos() { const token = localStorage.getItem('token'); try { + // router.get const response = await fetch('http://localhost:3003/todo', { headers: { 'Authorization': `Bearer ${token}`, }, }); const { todos } = await response.json(); + /* + response.json() + { + todos: todos + } + */ const todoList = document.getElementById('todo-list'); todoList.innerHTML = ''; @@ -166,6 +225,16 @@ async function completeTodo(id, completed) { }, body: JSON.stringify({ completed }), }); + /* + send req.headers to http://localhost:3003/user/todo/{todo._id} + { + Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage) + } + send req.body to http://localhost:3003/user/todo/{todo._id} + { + completed: !todo.completed + } + */ loadTodos(); } catch (error) { console.error('Error completing todo:', error); @@ -176,11 +245,13 @@ async function completeTodo(id, completed) { document.getElementById('show-signin').addEventListener('click', (e) => { e.preventDefault(); document.getElementById('signup-container').style.display = 'none'; + // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('signin-container').style.display = 'block'; }); document.getElementById('show-signup').addEventListener('click', (e) => { e.preventDefault(); document.getElementById('signin-container').style.display = 'none'; + // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('signup-container').style.display = 'block'; }); From 5702e10680583cea5cc5df93882c192c6c33794f Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Sun, 7 Dec 2025 17:06:41 +0530 Subject: [PATCH 04/19] fix logout link --- week-5/solution/frontend/index.html | 2 +- week-5/solution/frontend/script.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/week-5/solution/frontend/index.html b/week-5/solution/frontend/index.html index d568ddd6e..ee3e65775 100644 --- a/week-5/solution/frontend/index.html +++ b/week-5/solution/frontend/index.html @@ -63,7 +63,7 @@

    Your Todo List

    - + diff --git a/week-5/solution/frontend/script.js b/week-5/solution/frontend/script.js index 3cf98f3c0..c19466473 100644 --- a/week-5/solution/frontend/script.js +++ b/week-5/solution/frontend/script.js @@ -87,7 +87,7 @@ document.getElementById('signin-form').addEventListener('submit', async (e) => { // The CSS property display: none; is used to completely hide an HTML element from the document. document.getElementById('todo-container').style.display = 'block'; document.getElementById('response-message').innerHTML = - `Logged in successfully. Logout`; + `Logged in successfully.`; loadTodos(); // Add event listener for the logout link From cc9c6eb81bf6fa72f19525c7e6115f9ff77262be Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Sun, 7 Dec 2025 21:42:02 +0530 Subject: [PATCH 05/19] add zod --- week-5/solution/backend/package-lock.json | 47 +- week-5/solution/backend/package.json | 4 +- week-5/solution/backend/routes/user.js | 47 +- .../frontend/node_modules/.bin/node-gyp-build | 16 + .../node_modules/.bin/node-gyp-build-optional | 16 + .../.bin/node-gyp-build-optional.cmd | 17 + .../.bin/node-gyp-build-optional.ps1 | 28 + .../node_modules/.bin/node-gyp-build-test | 16 + .../node_modules/.bin/node-gyp-build-test.cmd | 17 + .../node_modules/.bin/node-gyp-build-test.ps1 | 28 + .../node_modules/.bin/node-gyp-build.cmd | 17 + .../node_modules/.bin/node-gyp-build.ps1 | 28 + .../frontend/node_modules/.package-lock.json | 41 + .../node_modules/bcrypt/.dockerignore | 6 + .../node_modules/bcrypt/.editorconfig | 19 + .../.github/workflows/build-pack-publish.yml | 110 + .../bcrypt/.github/workflows/ci.yaml | 42 + .../frontend/node_modules/bcrypt/CHANGELOG.md | 184 + .../frontend/node_modules/bcrypt/Dockerfile | 57 + .../node_modules/bcrypt/Dockerfile-alpine | 41 + .../node_modules/bcrypt/ISSUE_TEMPLATE.md | 18 + .../frontend/node_modules/bcrypt/LICENSE | 19 + .../frontend/node_modules/bcrypt/Makefile | 19 + .../frontend/node_modules/bcrypt/README.md | 388 + .../frontend/node_modules/bcrypt/SECURITY.md | 15 + .../frontend/node_modules/bcrypt/bcrypt.js | 242 + .../frontend/node_modules/bcrypt/binding.gyp | 49 + .../frontend/node_modules/bcrypt/build-all.sh | 37 + .../bcrypt/examples/async_compare.js | 28 + .../bcrypt/examples/forever_gen_salt.js | 8 + .../frontend/node_modules/bcrypt/package.json | 62 + .../bcrypt/prebuilds/darwin-arm64/bcrypt.node | Bin 0 -> 88608 bytes .../bcrypt/prebuilds/darwin-x64/bcrypt.node | Bin 0 -> 55488 bytes .../prebuilds/linux-arm/bcrypt.glibc.node | Bin 0 -> 67504 bytes .../prebuilds/linux-arm/bcrypt.musl.node | Bin 0 -> 67420 bytes .../prebuilds/linux-arm64/bcrypt.glibc.node | Bin 0 -> 76640 bytes .../prebuilds/linux-arm64/bcrypt.musl.node | Bin 0 -> 76552 bytes .../prebuilds/linux-x64/bcrypt.glibc.node | Bin 0 -> 84904 bytes .../prebuilds/linux-x64/bcrypt.musl.node | Bin 0 -> 96424 bytes .../bcrypt/prebuilds/win32-arm64/bcrypt.node | Bin 0 -> 182784 bytes .../bcrypt/prebuilds/win32-x64/bcrypt.node | Bin 0 -> 195584 bytes .../frontend/node_modules/bcrypt/promises.js | 45 + .../node_modules/bcrypt/src/bcrypt.cc | 315 + .../node_modules/bcrypt/src/bcrypt_node.cc | 288 + .../node_modules/bcrypt/src/blowfish.cc | 679 ++ .../node_modules/bcrypt/src/node_blf.h | 132 + .../node_modules/bcrypt/test/async.test.js | 209 + .../bcrypt/test/implementation.test.js | 48 + .../node_modules/bcrypt/test/promise.test.js | 168 + .../bcrypt/test/repetitions.test.js | 55 + .../node_modules/bcrypt/test/sync.test.js | 125 + .../node_modules/node-addon-api/LICENSE.md | 9 + .../node_modules/node-addon-api/README.md | 95 + .../node_modules/node-addon-api/common.gypi | 21 + .../node_modules/node-addon-api/except.gypi | 25 + .../node_modules/node-addon-api/index.js | 14 + .../node-addon-api/napi-inl.deprecated.h | 186 + .../node_modules/node-addon-api/napi-inl.h | 7033 +++++++++++++++++ .../node_modules/node-addon-api/napi.h | 3309 ++++++++ .../node-addon-api/node_addon_api.gyp | 42 + .../node_modules/node-addon-api/node_api.gyp | 9 + .../node_modules/node-addon-api/noexcept.gypi | 26 + .../node_modules/node-addon-api/nothing.c | 0 .../node-addon-api/package-support.json | 21 + .../node_modules/node-addon-api/package.json | 480 ++ .../node-addon-api/tools/README.md | 73 + .../node-addon-api/tools/check-napi.js | 99 + .../node-addon-api/tools/clang-format.js | 71 + .../node-addon-api/tools/conversion.js | 301 + .../node_modules/node-gyp-build/LICENSE | 21 + .../node_modules/node-gyp-build/README.md | 58 + .../node_modules/node-gyp-build/SECURITY.md | 5 + .../node_modules/node-gyp-build/bin.js | 84 + .../node_modules/node-gyp-build/build-test.js | 19 + .../node_modules/node-gyp-build/index.js | 6 + .../node-gyp-build/node-gyp-build.js | 207 + .../node_modules/node-gyp-build/optional.js | 7 + .../node_modules/node-gyp-build/package.json | 43 + week-5/solution/frontend/package-lock.json | 42 +- week-5/solution/frontend/package.json | 5 + 80 files changed, 16021 insertions(+), 20 deletions(-) create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd create mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 create mode 100644 week-5/solution/frontend/node_modules/.package-lock.json create mode 100644 week-5/solution/frontend/node_modules/bcrypt/.dockerignore create mode 100644 week-5/solution/frontend/node_modules/bcrypt/.editorconfig create mode 100644 week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml create mode 100644 week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml create mode 100644 week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md create mode 100644 week-5/solution/frontend/node_modules/bcrypt/Dockerfile create mode 100644 week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine create mode 100644 week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md create mode 100644 week-5/solution/frontend/node_modules/bcrypt/LICENSE create mode 100644 week-5/solution/frontend/node_modules/bcrypt/Makefile create mode 100644 week-5/solution/frontend/node_modules/bcrypt/README.md create mode 100644 week-5/solution/frontend/node_modules/bcrypt/SECURITY.md create mode 100644 week-5/solution/frontend/node_modules/bcrypt/bcrypt.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/binding.gyp create mode 100644 week-5/solution/frontend/node_modules/bcrypt/build-all.sh create mode 100644 week-5/solution/frontend/node_modules/bcrypt/examples/async_compare.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/package.json create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.glibc.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.glibc.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.musl.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-arm64/bcrypt.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node create mode 100644 week-5/solution/frontend/node_modules/bcrypt/promises.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc create mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc create mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc create mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h create mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/async.test.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js create mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/README.md create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/common.gypi create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/except.gypi create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/index.js create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi-inl.deprecated.h create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi.h create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/nothing.c create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/package-support.json create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/package.json create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/README.md create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js create mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/LICENSE create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/README.md create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/bin.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/build-test.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/index.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/optional.js create mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/package.json create mode 100644 week-5/solution/frontend/package.json diff --git a/week-5/solution/backend/package-lock.json b/week-5/solution/backend/package-lock.json index 22cae23ff..20c22c522 100644 --- a/week-5/solution/backend/package-lock.json +++ b/week-5/solution/backend/package-lock.json @@ -9,11 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.20.1" + "mongoose": "^8.20.1", + "zod": "^4.1.13" }, "devDependencies": { "nodemon": "^3.1.11" @@ -83,6 +85,20 @@ "dev": true, "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -974,6 +990,26 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/nodemon": { "version": "3.1.11", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", @@ -1454,6 +1490,15 @@ "engines": { "node": ">=18" } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/week-5/solution/backend/package.json b/week-5/solution/backend/package.json index 3c0be83d0..ca7d5459b 100644 --- a/week-5/solution/backend/package.json +++ b/week-5/solution/backend/package.json @@ -11,11 +11,13 @@ "license": "ISC", "description": "", "dependencies": { + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.0", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.20.1" + "mongoose": "^8.20.1", + "zod": "^4.1.13" }, "devDependencies": { "nodemon": "^3.1.11" diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js index fa721fefe..1a7a92912 100644 --- a/week-5/solution/backend/routes/user.js +++ b/week-5/solution/backend/routes/user.js @@ -1,5 +1,7 @@ const express = require('express'); const jwt = require('jsonwebtoken'); +const bcrypt = require('bcrypt'); +const { z } = require('zod'); const { authenticateJwt, SECRET } = require("../middleware/user"); const { User } = require("../db"); const router = express.Router(); @@ -7,6 +9,18 @@ const router = express.Router(); // app.use(express.json()) router.post('/signup', async (req, res) => { + const requiredBody = z.object({ + username: z.email(), + password: z.string().min(3).max(30) + }) + const result = requiredBody.safeParse(req.body) + + if (!result.success) { + res.json({ + message: "Incorrect format: " + result.error + }) + return + } /* receive req.body from script.js (submit button of signup) { @@ -22,10 +36,11 @@ router.post('/signup', async (req, res) => { return res.status(403).json({ message: 'User already exists' }); } - const newUser = new User({ username, password }); + const hashedPassword = await bcrypt.hash(password, 5); + const newUser = new User({ username, password: hashedPassword }); await newUser.save(); - /* - await User.insertOne({ + /* + const user = await User.create({ username: username, password: password }) @@ -41,23 +56,21 @@ router.post('/signup', async (req, res) => { router.post('/signin', async (req, res) => { const { username, password } = req.body; try { - const user = await User.findOne({ username, password }); - /* - const user = await User.create({ - username: username, - password: password - }) - */ - - if (user) { - const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: '1h' }); + const user = await User.findOne({ username }); + if (!user) { + return res.status(403).json({ message: "Invalid username" }); + } - res.json({ message: 'Logged in successfully', token }); - } else { - res.status(403).json({ message: 'Invalid username or password' }); + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return res.status(403).json({ message: "Invalid password" }); } + + const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" }); + res.json({ message: "Logged in successfully", token }); + } catch (error) { - res.status(500).json({ message: 'Error signing in', error }); + res.status(500).json({ message: "Error signing in", error }); } }); diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build b/week-5/solution/frontend/node_modules/.bin/node-gyp-build new file mode 100644 index 000000000..b804ba986 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../node-gyp-build/bin.js" "$@" +else + exec node "$basedir/../node-gyp-build/bin.js" "$@" +fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional new file mode 100644 index 000000000..cb670aab6 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../node-gyp-build/optional.js" "$@" +else + exec node "$basedir/../node-gyp-build/optional.js" "$@" +fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd new file mode 100644 index 000000000..74d85f274 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\optional.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 new file mode 100644 index 000000000..45995c36a --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args + } else { + & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../node-gyp-build/optional.js" $args + } else { + & "node$exe" "$basedir/../node-gyp-build/optional.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test new file mode 100644 index 000000000..bdf6dca40 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test @@ -0,0 +1,16 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../node-gyp-build/build-test.js" "$@" +else + exec node "$basedir/../node-gyp-build/build-test.js" "$@" +fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd new file mode 100644 index 000000000..182a75783 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\build-test.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 new file mode 100644 index 000000000..6cb0b9bda --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args + } else { + & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args + } else { + & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd new file mode 100644 index 000000000..ac854a699 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd @@ -0,0 +1,17 @@ +@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\bin.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 new file mode 100644 index 000000000..c1f9a9aaf --- /dev/null +++ b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 @@ -0,0 +1,28 @@ +#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args + } else { + & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/../node-gyp-build/bin.js" $args + } else { + & "node$exe" "$basedir/../node-gyp-build/bin.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret diff --git a/week-5/solution/frontend/node_modules/.package-lock.json b/week-5/solution/frontend/node_modules/.package-lock.json new file mode 100644 index 000000000..1c862af25 --- /dev/null +++ b/week-5/solution/frontend/node_modules/.package-lock.json @@ -0,0 +1,41 @@ +{ + "name": "frontend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + } + } +} diff --git a/week-5/solution/frontend/node_modules/bcrypt/.dockerignore b/week-5/solution/frontend/node_modules/bcrypt/.dockerignore new file mode 100644 index 000000000..01f4eb7ac --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/.dockerignore @@ -0,0 +1,6 @@ +.git/ +.vscode/ +Dockerfile* +prebuilds/ +node_modules/ +build*/ diff --git a/week-5/solution/frontend/node_modules/bcrypt/.editorconfig b/week-5/solution/frontend/node_modules/bcrypt/.editorconfig new file mode 100644 index 000000000..4e12f93be --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{package.json,*.yml}] +indent_style = space +indent_size = 2 + +[appveyor.yml] +end_of_line = crlf + +[*.md] +trim_trailing_whitespace = false diff --git a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml new file mode 100644 index 000000000..9b14ee121 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml @@ -0,0 +1,110 @@ +name: Prebuildify, package, publish + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + release: + types: [ prereleased, released ] + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # This is unsafe, but we really don't use any other native dependencies + - run: npm ci + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/almalinux-devtoolset11 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/alpine npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-armv7 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-armv7l-musl npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-arm64 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-arm64-musl npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 + - run: find prebuilds + - uses: actions/upload-artifact@v4 + with: + name: prebuild-linux + path: ./prebuilds/ + + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npx prebuildify --napi --strip --arch=x64 --target=node@18.0.0 + - run: npx prebuildify --napi --strip --arch=arm64 --target=node@20.0.0 + - run: dir prebuilds + - uses: actions/upload-artifact@v4 + with: + name: prebuild-windows + path: ./prebuilds/ + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npx prebuildify --napi --strip --arch=arm64 --target=node@18.0.0 + - run: npx prebuildify --napi --strip --arch=x64 --target=node@18.0.0 + - run: find prebuilds + - uses: actions/upload-artifact@v4 + with: + name: prebuild-macos + path: ./prebuilds/ + + pack: + needs: + - build-linux + - build-windows + - build-macos + runs-on: ubuntu-latest + outputs: + PACK_FILE: ${{ steps.pack.outputs.PACK_FILE }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: /tmp/prebuilds/ + - name: Coalesce prebuilds from build matrix + run: | + mkdir prebuilds + for d in /tmp/prebuilds/*; do + cp -Rav $d/* prebuilds/ + done + - run: chmod a+x prebuilds/*/*.node && find prebuilds -executable -type f + - id: pack + name: Prepare NPM package + run: | + echo "PACK_FILE=$(npm pack)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@v4 + with: + name: package-tgz + path: ${{ steps.pack.outputs.PACK_FILE }} + if-no-files-found: 'error' + + test-package: + needs: pack + strategy: + matrix: + node-version: [ 18, 20, 22, 23 ] + os: [ ubuntu-latest, windows-latest, macos-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + registry-url: 'https://registry.npmjs.org' + - uses: actions/download-artifact@v4 + with: + name: package-tgz + - run: npm install ${{ needs.pack.outputs.PACK_FILE }} + - run: node -e "const b = require('bcrypt'); const h = b.hashSync('hello', 10); console.log(h, b.compareSync('hello', h))" diff --git a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml new file mode 100644 index 000000000..77c4e5a9d --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml @@ -0,0 +1,42 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18, 20, 22] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - name: Test + run: npm test + + build-alpine: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18, 20, 22] + container: + image: node:${{ matrix.node-version }}-alpine + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + apk add make g++ python3 + - run: npm ci + - name: Test + run: | + npm test --unsafe-perm diff --git a/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md b/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md new file mode 100644 index 000000000..eab713b41 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md @@ -0,0 +1,184 @@ +# 6.0.0 (2025-02-28) + * Drop support for NodeJS <= 16 + * Remove `node-pre-gyp` in favor of `prebuildify`, prebuilt binaries are now shipped with the package + * Update `node-addon-api` to 8.3.0 + * Update JS code to newer ES syntax + +# 5.1.0 (2022-10-06) + * Update `node-pre-gyp` to 1.0.11 + +# 5.1.0 (2022-10-06) + * Update `node-pre-gyp` to 1.0.10 + * Replace `nodeunit` with `jest` as the testing library + +# 5.0.1 (2021-02-22) + + * Update `node-pre-gyp` to 1.0.0 + +# 5.0.0 (2020-06-02) + + * Fix the bcrypt "wrap-around" bug. It affects passwords with lengths >= 255. + It is uncommon but it's a bug nevertheless. Previous attempts to fix the bug + was unsuccessful. + * Experimental support for z/OS + * Fix a bug related to NUL in password input + * Update `node-pre-gyp` to 0.15.0 + +# 4.0.1 (2020-02-27) + + * Fix compilation errors in Alpine linux + +# 4.0.0 (2020-02-17) + + * Switch to NAPI bcrypt + * Drop support for NodeJS 8 + +# 3.0.8 (2019-12-31) + + * Update `node-pre-gyp` to 0.14 + * Pre-built binaries for NodeJS 13 + +# 3.0.7 (2019-10-18) + + * Update `nan` to 2.14.0 + * Update `node-pre-gyp` to 0.13 + +# 3.0.6 (2019-04-11) + + * Update `nan` to 2.13.2 + +# 3.0.5 (2019-03-19) + + * Update `nan` to 2.13.1 + * NodeJS 12 compatibility + * Remove `node-pre-gyp` from bundled dependencies + +# 3.0.4-napi (2019-03-08) + + * Sync N-API bcrypt with NAN bcrypt + +# 3.0.4 (2019-02-07) + + * Fix GCC, NAN and V8 deprecation warnings + +# 3.0.3 (2018-12-19) + + * Update `nan` to 2.12.1 + +# 3.0.2 (2018-10-18) + + * Update `nan` to 2.11.1 + +# 3.0.1 (2018-09-20) + + * Update `nan` to 2.11.0 + +# 3.0.0 (2018-07-06) + + * Drop support for NodeJS <= 4 + +# 2.0.1 (2018-04-20) + + * Update `node-pre-gyp` to allow downloading prebuilt modules + +# 2.0.0 (2018-04-07) + + * Make `2b` the default bcrypt version + +# 1.1.0-napi (2018-01-21) + + * Initial support for [N-API](https://nodejs.org/api/n-api.html) + +# 1.0.3 (2016-08-23) + + * update to nan v2.6.2 for NodeJS 8 support + * Fix: use npm scripts instead of node-gyp directly. + +# 1.0.2 (2016-12-31) + + * Fix `compare` promise rejection with invalid arguments + +# 1.0.1 (2016-12-07) + + * Fix destructuring imports with promises + +# 1.0.0 (2016-12-04) + + * add Promise support (commit 2488473) + +# 0.8.7 (2016-06-09) + + * update nan to 2.3.5 for improved node v6 support + +# 0.8.6 (2016-04-20) + + * update nan for node v6 support + +# 0.8.5 (2015-08-12) + + * update to nan v2 (adds support for iojs 3) + +# 0.8.4 (2015-07-24) + + * fix deprecation warning for the Encode API + +# 0.8.3 (2015-05-06) + + * update nan to 1.8.4 for iojs 2.x support + +# 0.8.2 (2015-03-28) + + * always use callback for generating random bytes to avoid blocking + +# 0.8.1 (2015-01-18) + * update NaN to 1.5.0 for iojs support + +# 0.8.0 (2014-08-03) + * migrate to NAN for bindings + +# v0.5.0 + * Fix for issue around empty string params throwing Errors. + * Method deprecation. + * Upgrade from libeio/ev to libuv. (shtylman) + ** --- NOTE --- Breaks 0.4.x compatability + * EV_MULTIPLICITY compile flag. + +# v0.4.1 + * Thread safety fix around OpenSSL (GH-32). (bnoordhuis - through node) + * C++ code changes using delete and new instead of malloc and free. (shtylman) + * Compile options for speed, zoom. (shtylman) + * Move much of the type and variable checking to the JS. (shtylman) + +# v0.4.0 + * Added getRounds function that will tell you the number of rounds within a hash/salt + +# v0.3.2 + * Fix api issue with async salt gen first param + +# v0.3.1 + * Compile under node 0.5.x + +# v0.3.0 + * Internal Refactoring + * Remove pthread dependencies and locking + * Fix compiler warnings and a memory bug + +# v0.2.4 + * Use threadsafe functions instead of pthread mutexes + * salt validation to make sure the salt is of the correct size and format + +# v0.2.3 + * cygwin support + +# v0.2.2 + * Remove dependency on libbsd, use libssl instead + +# v0.2.0 + * Added async functionality + * API changes + * hashpw -> encrypt + * all old sync methods now end with _sync + * Removed libbsd(arc4random) dependency...now uses openssl which is more widely spread + +# v0.1.2 + * Security fix. Wasn't reading rounds in properly and was always only using 4 rounds diff --git a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile new file mode 100644 index 000000000..2802bafae --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile @@ -0,0 +1,57 @@ +# Usage: +# +# docker build -t bcryptjs-builder . +# CONTAINER=$(docker create bcryptjs-builder) +# # Then copy the artifact to your host: +# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . +# docker rm "$CONTAINER" +# +# Use --platform to build cross-platform i.e. for ARM: +# +# docker build -t bcryptjs-builder --platform "linux/arm64/v8" . +# CONTAINER=$docker create --platform "linux/arm64/v8" bcryptjs-builder) +# # this copies the prebuilds/linux-arm artifacts +# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . +# docker rm "$CONTAINER" + + +ARG FROM_IMAGE=node:18-bullseye +#ARG FROM_IMAGE=arm32v7/node:16-bullseye +#ARG FROM_IMAGE=arm64v8/node:16-bullseye +FROM ${FROM_IMAGE} + +ENV project bcrypt-js +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL en_US.UTF-8 +ENV LANG ${LC_ALL} + +RUN echo "#log: ${project}: Setup system" \ + && set -x \ + && apt-get update -y \ + && apt-get install -y \ + build-essential \ + python3 \ + && apt-get clean \ + && update-alternatives --install /usr/local/bin/python python /usr/bin/python3 20 \ + && npm i -g prebuildify@5 node-gyp@9 \ + && sync + +ADD . /usr/local/opt/${project} +WORKDIR /usr/local/opt/${project} + +RUN echo "#log: ${project}: Running build" \ + && set -x \ + && npm ci \ + && npm run build + +ARG RUN_TESTS=true +ARG TEST_TIMEOUT_SECONDS= + +RUN if "${RUN_TESTS}"; then \ + echo "#log ${project}: Running tests" \ + && npm test; \ + else \ + echo "#log ${project}: Tests were skipped!"; \ + fi + +CMD /bin/bash -l diff --git a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine new file mode 100644 index 000000000..7570cfe85 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine @@ -0,0 +1,41 @@ +# Usage: +# +# docker build -t bcryptjs-linux-alpine-builder -f Dockerfile-alpine . +# CONTAINER=$(docker create bcryptjs-linux-alpine-builder) +# # Then copy the artifact to your host: +# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . +# docker rm "$CONTAINER" + +ARG FROM_IMAGE=node:18-alpine +FROM ${FROM_IMAGE} + +ENV project bcrypt-js +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL en_US.UTF-8 +ENV LANG ${LC_ALL} + +RUN echo "#log: ${project}: Setup system" \ + && set -x \ + && apk add --update build-base python3 \ + && npm i -g prebuildify@5 node-gyp@9 \ + && sync + +ADD . /usr/local/opt/${project} +WORKDIR /usr/local/opt/${project} + +RUN echo "#log: ${project}: Running build" \ + && set -x \ + && npm ci \ + && npm run build + +ARG RUN_TESTS=true +ARG TEST_TIMEOUT_SECONDS= + +RUN if "${RUN_TESTS}"; then \ + echo "#log ${project}: Running tests" \ + && npm test; \ + else \ + echo "#log ${project}: Tests were skipped!"; \ + fi + +CMD /bin/bash -l diff --git a/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md b/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..b4baa0086 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +Thanks for reporting a new issue with the node bcrypt module! + +To help you resolve your issue faster please make sure you have done the following: + +* Searched existing issues (even closed ones) for your same problem +* Make sure you have installed the required dependencies listed on the readme +* Read your npm error log for lines telling you what failed, usually it is a problem with not having the correct dependencies installed to build the native module + +Once you have done the above and are still confident that the issue is with the module, please describe it below. Some things that really help get your issue resolved faster are: + +* What went wrong? +* What did you expect to happen? +* Which version of nodejs and OS? +* If you find a bug, please write a failing test. + +Thanks! + +P.S. If it doesn't look like you read the above then your issue will likely be closed without further explanation. Sorry, but there are just too many issues opened with no useful information or questions which have been previously addressed. diff --git a/week-5/solution/frontend/node_modules/bcrypt/LICENSE b/week-5/solution/frontend/node_modules/bcrypt/LICENSE new file mode 100644 index 000000000..94e2ba5f9 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Nicholas Campbell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/week-5/solution/frontend/node_modules/bcrypt/Makefile b/week-5/solution/frontend/node_modules/bcrypt/Makefile new file mode 100644 index 000000000..cb2225266 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/Makefile @@ -0,0 +1,19 @@ +TESTS = test/*.js + +all: test + +build: clean compile + +compile: + npm install . + npm run install + +test: build + @./node_modules/.bin/jest \ + $(TESTS) + +clean: + rm -Rf lib/bindings/ + + +.PHONY: clean test build diff --git a/week-5/solution/frontend/node_modules/bcrypt/README.md b/week-5/solution/frontend/node_modules/bcrypt/README.md new file mode 100644 index 000000000..e92310810 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/README.md @@ -0,0 +1,388 @@ +# node.bcrypt.js + +[![ci](https://github.com/kelektiv/node.bcrypt.js/actions/workflows/ci.yaml/badge.svg)](https://github.com/kelektiv/node.bcrypt.js/actions/workflows/ci.yaml) + +[![Build Status](https://ci.appveyor.com/api/projects/status/github/kelektiv/node.bcrypt.js)](https://ci.appveyor.com/project/defunctzombie/node-bcrypt-js-pgo26/branch/master) + +A library to help you hash passwords. + +You can read about [bcrypt in Wikipedia][bcryptwiki] as well as in the following article: +[How To Safely Store A Password][codahale] + +## If You Are Submitting Bugs or Issues + +Please verify that the NodeJS version you are using is a _stable_ version; Unstable versions are currently not supported and issues created while using an unstable version will be closed. + +If you are on a stable version of NodeJS, please provide a sufficient code snippet or log files for installation issues. The code snippet does not require you to include confidential information. However, it must provide enough information so the problem can be replicable, or it may be closed without an explanation. + + +## Version Compatibility + +_Please upgrade to atleast v5.0.0 to avoid security issues mentioned below._ + +| Node Version | Bcrypt Version | +| -------------- | ------------------| +| 0.4 | <= 0.4 | +| 0.6, 0.8, 0.10 | >= 0.5 | +| 0.11 | >= 0.8 | +| 4 | <= 2.1.0 | +| 8 | >= 1.0.3 < 4.0.0 | +| 10, 11 | >= 3 | +| 12 onwards | >= 3.0.6 | + +`node-gyp` only works with stable/released versions of node. Since the `bcrypt` module uses `node-gyp` to build and install, you'll need a stable version of node to use bcrypt. If you do not, you'll likely see an error that starts with: + +``` +gyp ERR! stack Error: "pre" versions of node cannot be installed, use the --nodedir flag instead +``` + +## Security Issues And Concerns + +> Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 *characters*. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis). If a string is provided, it will be encoded using UTF-8. + +As should be the case with any security tool, anyone using this library should scrutinise it. If you find or suspect an issue with the code, please bring it to the maintainers' attention. We will spend some time ensuring that this library is as secure as possible. + +Here is a list of BCrypt-related security issues/concerns that have come up over the years. + +* An [issue with passwords][jtr] was found with a version of the Blowfish algorithm developed for John the Ripper. This is not present in the OpenBSD version and is thus not a problem for this module. HT [zooko][zooko]. +* Versions `< 5.0.0` suffer from bcrypt wrap-around bug and _will truncate passwords >= 255 characters leading to severely weakened passwords_. Please upgrade at earliest. See [this wiki page][wrap-around-bug] for more details. +* Versions `< 5.0.0` _do not handle NUL characters inside passwords properly leading to all subsequent characters being dropped and thus resulting in severely weakened passwords_. Please upgrade at earliest. See [this wiki page][improper-nuls] for more details. + +## Compatibility Note + +This library supports `$2a$` and `$2b$` prefix bcrypt hashes. `$2x$` and `$2y$` hashes are specific to bcrypt implementation developed for John the Ripper. In theory, they should be compatible with `$2b$` prefix. + +Compatibility with hashes generated by other languages is not 100% guaranteed due to difference in character encodings. However, it should not be an issue for most cases. + +### Migrating from v1.0.x + +Hashes generated in earlier version of `bcrypt` remain 100% supported in `v2.x.x` and later versions. In most cases, the migration should be a bump in the `package.json`. + +Hashes generated in `v2.x.x` using the defaults parameters will not work in earlier versions. + +## Dependencies + +* NodeJS +* `node-gyp` + * Please check the dependencies for this tool at: https://github.com/nodejs/node-gyp + * Windows users will need the options for c# and c++ installed with their visual studio instance. + * Python 2.x/3.x +* `OpenSSL` - This is only required to build the `bcrypt` project if you are using versions <= 0.7.7. Otherwise, we're using the builtin node crypto bindings for seed data (which use the same OpenSSL code paths we were, but don't have the external dependency). + +## Install via NPM + +``` +npm install bcrypt +``` +***Note:*** OS X users using Xcode 4.3.1 or above may need to run the following command in their terminal prior to installing if errors occur regarding xcodebuild: ```sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer``` + +_Pre-built binaries for various NodeJS versions are made available on a best-effort basis._ + +Only the current stable and supported LTS releases are actively tested against. + +_There may be an interval between the release of the module and the availabilty of the compiled modules._ + +Currently, we have pre-built binaries that support the following platforms: + +1. Windows x32 and x64 +2. Linux x64 (GlibC and musl) +3. macOS + +If you face an error like this: + +``` +node-pre-gyp ERR! Tried to download(404): https://github.com/kelektiv/node.bcrypt.js/releases/download/v1.0.2/bcrypt_lib-v1.0.2-node-v48-linux-x64.tar.gz +``` + +make sure you have the appropriate dependencies installed and configured for your platform. You can find installation instructions for the dependencies for some common platforms [in this page][depsinstall]. + +## Usage + +### async (recommended) + +```javascript +const bcrypt = require('bcrypt'); +const saltRounds = 10; +const myPlaintextPassword = 's0/\/\P4$$w0rD'; +const someOtherPlaintextPassword = 'not_bacon'; +``` + +#### To hash a password: + +Technique 1 (generate a salt and hash on separate function calls): + +```javascript +bcrypt.genSalt(saltRounds, function(err, salt) { + bcrypt.hash(myPlaintextPassword, salt, function(err, hash) { + // Store hash in your password DB. + }); +}); +``` + +Technique 2 (auto-gen a salt and hash): + +```javascript +bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) { + // Store hash in your password DB. +}); +``` + +Note that both techniques achieve the same end-result. + +#### To check a password: + +```javascript +// Load hash from your password DB. +bcrypt.compare(myPlaintextPassword, hash, function(err, result) { + // result == true +}); +bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) { + // result == false +}); +``` + +[A Note on Timing Attacks](#a-note-on-timing-attacks) + +### with promises + +bcrypt uses whatever `Promise` implementation is available in `global.Promise`. NodeJS >= 0.12 has a native `Promise` implementation built in. However, this should work in any Promises/A+ compliant implementation. + +Async methods that accept a callback, return a `Promise` when callback is not specified if Promise support is available. + +```javascript +bcrypt.hash(myPlaintextPassword, saltRounds).then(function(hash) { + // Store hash in your password DB. +}); +``` +```javascript +// Load hash from your password DB. +bcrypt.compare(myPlaintextPassword, hash).then(function(result) { + // result == true +}); +bcrypt.compare(someOtherPlaintextPassword, hash).then(function(result) { + // result == false +}); +``` + +This is also compatible with `async/await` + +```javascript +async function checkUser(username, password) { + //... fetch user from a db etc. + + const match = await bcrypt.compare(password, user.passwordHash); + + if(match) { + //login + } + + //... +} +``` + +### ESM import +```javascript +import bcrypt from "bcrypt"; + +// later +await bcrypt.compare(password, hash); +``` + +### sync + +```javascript +const bcrypt = require('bcrypt'); +const saltRounds = 10; +const myPlaintextPassword = 's0/\/\P4$$w0rD'; +const someOtherPlaintextPassword = 'not_bacon'; +``` + +#### To hash a password: + +Technique 1 (generate a salt and hash on separate function calls): + +```javascript +const salt = bcrypt.genSaltSync(saltRounds); +const hash = bcrypt.hashSync(myPlaintextPassword, salt); +// Store hash in your password DB. +``` + +Technique 2 (auto-gen a salt and hash): + +```javascript +const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds); +// Store hash in your password DB. +``` + +As with async, both techniques achieve the same end-result. + +#### To check a password: + +```javascript +// Load hash from your password DB. +bcrypt.compareSync(myPlaintextPassword, hash); // true +bcrypt.compareSync(someOtherPlaintextPassword, hash); // false +``` + +[A Note on Timing Attacks](#a-note-on-timing-attacks) + +### Why is async mode recommended over sync mode? +We recommend using async API if you use `bcrypt` on a server. Bcrypt hashing is CPU intensive which will cause the sync APIs to block the event loop and prevent your application from servicing any inbound requests or events. The async version uses a thread pool which does not block the main event loop. + +## API + +`BCrypt.` + + * `genSaltSync(rounds, minor)` + * `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10) + * `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b) + * `genSalt(rounds, minor, cb)` + * `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10) + * `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b) + * `cb` - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. + * `err` - First parameter to the callback detailing any errors. + * `salt` - Second parameter to the callback providing the generated salt. + * `hashSync(data, salt)` + * `data` - [REQUIRED] - the data to be encrypted. + * `salt` - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under **Usage**). + * `hash(data, salt, cb)` + * `data` - [REQUIRED] - the data to be encrypted. + * `salt` - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under **Usage**). + * `cb` - [OPTIONAL] - a callback to be fired once the data has been encrypted. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. + * `err` - First parameter to the callback detailing any errors. + * `encrypted` - Second parameter to the callback providing the encrypted form. + * `compareSync(data, encrypted)` + * `data` - [REQUIRED] - data to compare. + * `encrypted` - [REQUIRED] - data to be compared to. + * `compare(data, encrypted, cb)` + * `data` - [REQUIRED] - data to compare. + * `encrypted` - [REQUIRED] - data to be compared to. + * `cb` - [OPTIONAL] - a callback to be fired once the data has been compared. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. + * `err` - First parameter to the callback detailing any errors. + * `same` - Second parameter to the callback providing whether the data and encrypted forms match [true | false]. + * `getRounds(encrypted)` - return the number of rounds used to encrypt a given hash + * `encrypted` - [REQUIRED] - hash from which the number of rounds used should be extracted. + +## A Note on Rounds + +A note about the cost: when you are hashing your data, the module will go through a series of rounds to give you a secure hash. The value you submit is not just the number of rounds the module will go through to hash your data. The module will use the value you enter and go through `2^rounds` hashing iterations. + +From @garthk, on a 2GHz core you can roughly expect: + + rounds=8 : ~40 hashes/sec + rounds=9 : ~20 hashes/sec + rounds=10: ~10 hashes/sec + rounds=11: ~5 hashes/sec + rounds=12: 2-3 hashes/sec + rounds=13: ~1 sec/hash + rounds=14: ~1.5 sec/hash + rounds=15: ~3 sec/hash + rounds=25: ~1 hour/hash + rounds=31: 2-3 days/hash + + +## A Note on Timing Attacks + +Because it's come up multiple times in this project and other bcrypt projects, it needs to be said. The `bcrypt` library is not susceptible to timing attacks. From codahale/bcrypt-ruby#42: + +> One of the desired properties of a cryptographic hash function is preimage attack resistance, which means there is no shortcut for generating a message which, when hashed, produces a specific digest. + +A great thread on this, in much more detail can be found @ codahale/bcrypt-ruby#43 + +If you're unfamiliar with timing attacks and want to learn more you can find a great writeup @ [A Lesson In Timing Attacks][timingatk] + +However, timing attacks are real. And the comparison function is _not_ time safe. That means that it may exit the function early in the comparison process. Timing attacks happen because of the above. We don't need to be careful that an attacker will learn anything, and our comparison function provides a comparison of hashes. It is a utility to the overall purpose of the library. If you end up using it for something else, we cannot guarantee the security of the comparator. Keep that in mind as you use the library. + +## Hash Info + +The characters that comprise the resultant hash are `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$`. + +Resultant hashes will be 60 characters long and they will include the salt among other parameters, as follows: + +`$[algorithm]$[cost]$[salt][hash]` + +- 2 chars hash algorithm identifier prefix. `"$2a$" or "$2b$"` indicates BCrypt +- Cost-factor (n). Represents the exponent used to determine how many iterations 2^n +- 16-byte (128-bit) salt, base64 encoded to 22 characters +- 24-byte (192-bit) hash, base64 encoded to 31 characters + +Example: +``` +$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa + | | | | + | | | hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa + | | | + | | salt = nOUIs5kJ7naTuTFkBy1veu + | | + | cost-factor => 10 = 2^10 rounds + | + hash-algorithm identifier => 2b = BCrypt +``` + +## Testing + +If you create a pull request, tests better pass :) + +``` +npm install +npm test +``` + +## Credits + +The code for this comes from a few sources: + +* blowfish.cc - OpenBSD +* bcrypt.cc - OpenBSD +* bcrypt::gen_salt - [gen_salt inclusion to bcrypt][bcryptgs] +* bcrypt_node.cc - me + +## Contributors + +* [Antonio Salazar Cardozo][shadowfiend] - Early MacOS X support (when we used libbsd) +* [Ben Glow][pixelglow] - Fixes for thread safety with async calls +* [Van Nguyen][thegoleffect] - Found a timing attack in the comparator +* [NewITFarmer][newitfarmer] - Initial Cygwin support +* [David Trejo][dtrejo] - packaging fixes +* [Alfred Westerveld][alfredwesterveld] - packaging fixes +* [Vincent Côté-Roy][vincentr] - Testing around concurrency issues +* [Lloyd Hilaiel][lloyd] - Documentation fixes +* [Roman Shtylman][shtylman] - Code refactoring, general rot reduction, compile options, better memory management with delete and new, and an upgrade to libuv over eio/ev. +* [Vadim Graboys][vadimg] - Code changes to support 0.5.5+ +* [Ben Noordhuis][bnoordhuis] - Fixed a thread safety issue in nodejs that was perfectly mappable to this module. +* [Nate Rajlich][tootallnate] - Bindings and build process. +* [Sean McArthur][seanmonstar] - Windows Support +* [Fanie Oosthuysen][weareu] - Windows Support +* [Amitosh Swain Mahapatra][recrsn] - $2b$ hash support, ES6 Promise support +* [Nicola Del Gobbo][NickNaso] - Initial implementation with N-API + +## License +Unless stated elsewhere, file headers or otherwise, the license as stated in the LICENSE file. + +[bcryptwiki]: https://en.wikipedia.org/wiki/Bcrypt +[bcryptgs]: http://mail-index.netbsd.org/tech-crypto/2002/05/24/msg000204.html +[codahale]: http://codahale.com/how-to-safely-store-a-password/ +[gh13]: https://github.com/ncb000gt/node.bcrypt.js/issues/13 +[jtr]: http://www.openwall.com/lists/oss-security/2011/06/20/2 +[depsinstall]: https://github.com/kelektiv/node.bcrypt.js/wiki/Installation-Instructions +[timingatk]: https://codahale.com/a-lesson-in-timing-attacks/ +[wrap-around-bug]: https://github.com/kelektiv/node.bcrypt.js/wiki/Security-Issues-and-Concerns#bcrypt-wrap-around-bug-medium-severity +[improper-nuls]: https://github.com/kelektiv/node.bcrypt.js/wiki/Security-Issues-and-Concerns#improper-nul-handling-medium-severity + +[shadowfiend]:https://github.com/Shadowfiend +[thegoleffect]:https://github.com/thegoleffect +[pixelglow]:https://github.com/pixelglow +[dtrejo]:https://github.com/dtrejo +[alfredwesterveld]:https://github.com/alfredwesterveld +[newitfarmer]:https://github.com/newitfarmer +[zooko]:https://twitter.com/zooko +[vincentr]:https://twitter.com/vincentcr +[lloyd]:https://github.com/lloyd +[shtylman]:https://github.com/shtylman +[vadimg]:https://github.com/vadimg +[bnoordhuis]:https://github.com/bnoordhuis +[tootallnate]:https://github.com/tootallnate +[seanmonstar]:https://github.com/seanmonstar +[weareu]:https://github.com/weareu +[recrsn]:https://github.com/recrsn +[NickNaso]: https://github.com/NickNaso diff --git a/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md b/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md new file mode 100644 index 000000000..c132dc869 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +As with any software, `bcrypt` is likely to have bugs. Please report any security vulnerabilities responsibly + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 5.0.x | :white_check_mark: | +| < 5.0 | :x: | + +## Reporting a Vulnerability + +If you are reporting a security vulnerability, please refrain from opening a GitHub issue and instead mail it to +one of the maintainers listed in the README. diff --git a/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js b/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js new file mode 100644 index 000000000..62da52527 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js @@ -0,0 +1,242 @@ +const path = require('path'); +const bindings = require('node-gyp-build')(path.resolve(__dirname)); + +const crypto = require('crypto'); + +const promises = require('./promises'); + +/// generate a salt (sync) +/// @param {Number} [rounds] number of rounds (default 10) +/// @return {String} salt +function genSaltSync(rounds, minor) { + // default 10 rounds + if (!rounds) { + rounds = 10; + } else if (typeof rounds !== 'number') { + throw new Error('rounds must be a number'); + } + + if (!minor) { + minor = 'b'; + } else if (minor !== 'b' && minor !== 'a') { + throw new Error('minor must be either "a" or "b"'); + } + + return bindings.gen_salt_sync(minor, rounds, crypto.randomBytes(16)); +} + +/// generate a salt +/// @param {Number} [rounds] number of rounds (default 10) +/// @param {Function} cb callback(err, salt) +function genSalt(rounds, minor, cb) { + let error; + + // if callback is first argument, then use defaults for others + if (typeof arguments[0] === 'function') { + // have to set callback first otherwise arguments are overridden + cb = arguments[0]; + rounds = 10; + minor = 'b'; + // callback is second argument + } else if (typeof arguments[1] === 'function') { + // have to set callback first otherwise arguments are overridden + cb = arguments[1]; + minor = 'b'; + } + + if (!cb) { + return promises.promise(genSalt, this, [rounds, minor]); + } + + // default 10 rounds + if (!rounds) { + rounds = 10; + } else if (typeof rounds !== 'number') { + // callback error asynchronously + error = new Error('rounds must be a number'); + return process.nextTick(function () { + cb(error); + }); + } + + if (!minor) { + minor = 'b' + } else if (minor !== 'b' && minor !== 'a') { + error = new Error('minor must be either "a" or "b"'); + return process.nextTick(function () { + cb(error); + }); + } + + crypto.randomBytes(16, function (error, randomBytes) { + if (error) { + cb(error); + return; + } + + bindings.gen_salt(minor, rounds, randomBytes, cb); + }); +} + +/// hash data using a salt +/// @param {String|Buffer} data the data to encrypt +/// @param {String} salt the salt to use when hashing +/// @return {String} hash +function hashSync(data, salt) { + if (data == null || salt == null) { + throw new Error('data and salt arguments required'); + } + + if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { + throw new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); + } + + if (typeof salt === 'number') { + salt = module.exports.genSaltSync(salt); + } + + return bindings.encrypt_sync(data, salt); +} + +/// hash data using a salt +/// @param {String|Buffer} data the data to encrypt +/// @param {String} salt the salt to use when hashing +/// @param {Function} cb callback(err, hash) +function hash(data, salt, cb) { + let error; + + if (typeof data === 'function') { + error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); + return process.nextTick(function () { + data(error); + }); + } + + if (typeof salt === 'function') { + error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); + return process.nextTick(function () { + salt(error); + }); + } + + // cb exists but is not a function + // return a rejecting promise + if (cb && typeof cb !== 'function') { + return promises.reject(new Error('cb must be a function or null to return a Promise')); + } + + if (!cb) { + return promises.promise(hash, this, [data, salt]); + } + + if (data == null || salt == null) { + error = new Error('data and salt arguments required'); + return process.nextTick(function () { + cb(error); + }); + } + + if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { + error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); + return process.nextTick(function () { + cb(error); + }); + } + + + if (typeof salt === 'number') { + return module.exports.genSalt(salt, function (err, salt) { + return bindings.encrypt(data, salt, cb); + }); + } + + return bindings.encrypt(data, salt, cb); +} + +/// compare raw data to hash +/// @param {String|Buffer} data the data to hash and compare +/// @param {String} hash expected hash +/// @return {bool} true if hashed data matches hash +function compareSync(data, hash) { + if (data == null || hash == null) { + throw new Error('data and hash arguments required'); + } + + if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { + throw new Error('data must be a string or Buffer and hash must be a string'); + } + + return bindings.compare_sync(data, hash); +} + +/// compare raw data to hash +/// @param {String|Buffer} data the data to hash and compare +/// @param {String} hash expected hash +/// @param {Function} cb callback(err, matched) - matched is true if hashed data matches hash +function compare(data, hash, cb) { + let error; + + if (typeof data === 'function') { + error = new Error('data and hash arguments required'); + return process.nextTick(function () { + data(error); + }); + } + + if (typeof hash === 'function') { + error = new Error('data and hash arguments required'); + return process.nextTick(function () { + hash(error); + }); + } + + // cb exists but is not a function + // return a rejecting promise + if (cb && typeof cb !== 'function') { + return promises.reject(new Error('cb must be a function or null to return a Promise')); + } + + if (!cb) { + return promises.promise(compare, this, [data, hash]); + } + + if (data == null || hash == null) { + error = new Error('data and hash arguments required'); + return process.nextTick(function () { + cb(error); + }); + } + + if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { + error = new Error('data and hash must be strings'); + return process.nextTick(function () { + cb(error); + }); + } + + return bindings.compare(data, hash, cb); +} + +/// @param {String} hash extract rounds from this hash +/// @return {Number} the number of rounds used to encrypt a given hash +function getRounds(hash) { + if (hash == null) { + throw new Error('hash argument required'); + } + + if (typeof hash !== 'string') { + throw new Error('hash must be a string'); + } + + return bindings.get_rounds(hash); +} + +module.exports = { + genSaltSync, + genSalt, + hashSync, + hash, + compareSync, + compare, + getRounds, +} diff --git a/week-5/solution/frontend/node_modules/bcrypt/binding.gyp b/week-5/solution/frontend/node_modules/bcrypt/binding.gyp new file mode 100644 index 000000000..46428be78 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/binding.gyp @@ -0,0 +1,49 @@ +{ + "variables": { + "NODE_VERSION%":" { + const start = Date.now(); + + // genSalt + const salt = await bcrypt.genSalt(10) + console.log('salt: ' + salt); + console.log('salt cb end: ' + (Date.now() - start) + 'ms'); + + // hash + const crypted = await bcrypt.hash('test', salt) + console.log('crypted: ' + crypted); + console.log('crypted cb end: ' + (Date.now() - start) + 'ms'); + console.log('rounds used from hash:', bcrypt.getRounds(crypted)); + + // compare + const res = await bcrypt.compare('test', crypted) + console.log('compared true: ' + res); + console.log('compared true cb end: ' + (Date.now() - start) + 'ms'); + + // compare + const res2 = await bcrypt.compare('bacon', crypted) + console.log('compared false: ' + res2); + console.log('compared false cb end: ' + (Date.now() - start) + 'ms'); + + console.log('end: ' + (Date.now() - start) + 'ms'); +})(); diff --git a/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js b/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js new file mode 100644 index 000000000..3f2ff2f48 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js @@ -0,0 +1,8 @@ +const bcrypt = require('../bcrypt'); + +(function printSalt() { + bcrypt.genSalt(10, (err, salt) => { + console.log('salt: ' + salt); + printSalt(); + }); +})() diff --git a/week-5/solution/frontend/node_modules/bcrypt/package.json b/week-5/solution/frontend/node_modules/bcrypt/package.json new file mode 100644 index 000000000..c849897a4 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/package.json @@ -0,0 +1,62 @@ +{ + "name": "bcrypt", + "description": "A bcrypt library for NodeJS.", + "keywords": [ + "bcrypt", + "password", + "auth", + "authentication", + "encryption", + "crypt", + "crypto" + ], + "main": "./bcrypt", + "version": "6.0.0", + "author": "Nick Campbell (https://github.com/ncb000gt)", + "engines": { + "node": ">= 18" + }, + "repository": { + "type": "git", + "url": "https://github.com/kelektiv/node.bcrypt.js.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/kelektiv/node.bcrypt.js/issues" + }, + "scripts": { + "test": "jest", + "install": "node-gyp-build", + "build": "prebuildify --napi --tag-libc --strip" + }, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "devDependencies": { + "jest": "^29.7.0", + "prebuildify": "^6.0.1" + }, + "contributors": [ + "Antonio Salazar Cardozo (https://github.com/Shadowfiend)", + "Van Nguyen (https://github.com/thegoleffect)", + "David Trejo (https://github.com/dtrejo)", + "Ben Glow (https://github.com/pixelglow)", + "NewITFarmer.com <> (https://github.com/newitfarmer)", + "Alfred Westerveld (https://github.com/alfredwesterveld)", + "Vincent Côté-Roy (https://github.com/vincentcr)", + "Lloyd Hilaiel (https://github.com/lloyd)", + "Roman Shtylman (https://github.com/shtylman)", + "Vadim Graboys (https://github.com/vadimg)", + "Ben Noorduis <> (https://github.com/bnoordhuis)", + "Nate Rajlich (https://github.com/tootallnate)", + "Sean McArthur (https://github.com/seanmonstar)", + "Fanie Oosthuysen (https://github.com/weareu)", + "Amitosh Swain Mahapatra (https://github.com/Agathver)", + "Corbin Crutchley (https://github.com/crutchcorn)", + "Nicola Del Gobbo (https://github.com/NickNaso)" + ], + "binary": { + "module_name": "bcrypt_lib" + } +} diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node new file mode 100644 index 0000000000000000000000000000000000000000..5160c6c388650193378be642f5048e48e201da2a GIT binary patch literal 88608 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 literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node new file mode 100644 index 0000000000000000000000000000000000000000..8de9c5f7903a0ffc9594ebcc23b3e41f0c8d8893 GIT binary patch literal 55488 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

    Iq8W+*_zZY;zo$L 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 literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node new file mode 100644 index 0000000000000000000000000000000000000000..be7ca5f1b1b1c00a12fb204f7a9fe177b2defcf1 GIT binary patch literal 67420 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 X<-0<9?5?cd>hU|e5&@^>X!3sn0TkSK literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node new file mode 100644 index 0000000000000000000000000000000000000000..717cd1457ed5feed0f3e79154ea4544e48ec465c GIT binary patch literal 76552 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 kp9Zu%{dtI{E43eyExHl1STAn8VFGuKi{r~^~ literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node new file mode 100644 index 0000000000000000000000000000000000000000..b58e7dc6b26f4fac20c468ab38f0d334de88f084 GIT binary patch literal 84904 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 boH|cKVf3u_7Yww+G4uLv4 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 z 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 literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node new file mode 100644 index 0000000000000000000000000000000000000000..f7ef025e230112617c80cd625f94a892c2402bce GIT binary patch literal 195584 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 SL0M#=Gzw52F!g`M_WuDtu)iJv literal 0 HcmV?d00001 diff --git a/week-5/solution/frontend/node_modules/bcrypt/promises.js b/week-5/solution/frontend/node_modules/bcrypt/promises.js new file mode 100644 index 000000000..6685cc254 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/promises.js @@ -0,0 +1,45 @@ +let Promise = global.Promise; + +/// encapsulate a method with a node-style callback in a Promise +/// @param {object} 'this' of the encapsulated function +/// @param {function} function to be encapsulated +/// @param {Array-like} args to be passed to the called function +/// @return {Promise} a Promise encapsulating the function +function promise(fn, context, args) { + if (!Array.isArray(args)) { + args = Array.prototype.slice.call(args); + } + + if (typeof fn !== 'function') { + return Promise.reject(new Error('fn must be a function')); + } + + return new Promise((resolve, reject) => { + args.push((err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + + fn.apply(context, args); + }); +} + +/// @param {err} the error to be thrown +function reject(err) { + return Promise.reject(err); +} + +/// changes the promise implementation that bcrypt uses +/// @param {Promise} the implementation to use +function use(promise) { + Promise = promise; +} + +module.exports = { + promise, + reject, + use +} diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc new file mode 100644 index 000000000..bd8c57355 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc @@ -0,0 +1,315 @@ +/* $OpenBSD: bcrypt.c,v 1.31 2014/03/22 23:02:03 tedu Exp $ */ + +/* + * Copyright (c) 1997 Niels Provos + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* This password hashing algorithm was designed by David Mazieres + * and works as follows: + * + * 1. state := InitState () + * 2. state := ExpandKey (state, salt, password) + * 3. REPEAT rounds: + * state := ExpandKey (state, 0, password) + * state := ExpandKey (state, 0, salt) + * 4. ctext := "OrpheanBeholderScryDoubt" + * 5. REPEAT 64: + * ctext := Encrypt_ECB (state, ctext); + * 6. RETURN Concatenate (salt, ctext); + * + */ + +#include +#include +#include +#include + +#include "node_blf.h" + +#ifdef _WIN32 +#define snprintf _snprintf +#endif + +//#if !defined(__APPLE__) && !defined(__MACH__) +//#include "bsd/stdlib.h" +//#endif + +/* This implementation is adaptable to current computing power. + * You can have up to 2^31 rounds which should be enough for some + * time to come. + */ + +static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t); +static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *); + +const static char* error = ":"; + +const static u_int8_t Base64Code[] = +"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +const static u_int8_t index_64[128] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 0, 1, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 255, 255, + 255, 255, 255, 255, 255, 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, + 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255 +}; +#define CHAR64(c) ( (c) > 127 ? 255 : index_64[(c)]) + +static void +decode_base64(u_int8_t *buffer, u_int16_t len, u_int8_t *data) +{ + u_int8_t *bp = buffer; + u_int8_t *p = data; + u_int8_t c1, c2, c3, c4; + while (bp < buffer + len) { + c1 = CHAR64(*p); + c2 = CHAR64(*(p + 1)); + + /* Invalid data */ + if (c1 == 255 || c2 == 255) + break; + + *bp++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (bp >= buffer + len) + break; + + c3 = CHAR64(*(p + 2)); + if (c3 == 255) + break; + + *bp++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2); + if (bp >= buffer + len) + break; + + c4 = CHAR64(*(p + 3)); + if (c4 == 255) + break; + *bp++ = ((c3 & 0x03) << 6) | c4; + + p += 4; + } +} + +void +encode_salt(char *salt, u_int8_t *csalt, char minor, u_int16_t clen, u_int8_t logr) +{ + salt[0] = '$'; + salt[1] = BCRYPT_VERSION; + salt[2] = minor; + salt[3] = '$'; + + // Max rounds are 31 + snprintf(salt + 4, 4, "%2.2u$", logr & 0x001F); + + encode_base64((u_int8_t *) salt + 7, csalt, clen); +} + + +/* Generates a salt for this version of crypt. + Since versions may change. Keeping this here + seems sensible. + from: http://mail-index.netbsd.org/tech-crypto/2002/05/24/msg000204.html +*/ +void +bcrypt_gensalt(char minor, u_int8_t log_rounds, u_int8_t *seed, char *gsalt) +{ + if (log_rounds < 4) + log_rounds = 4; + else if (log_rounds > 31) + log_rounds = 31; + + encode_salt(gsalt, seed, minor, BCRYPT_MAXSALT, log_rounds); +} + +/* We handle $Vers$log2(NumRounds)$salt+passwd$ + i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */ + +void +bcrypt(const char *key, size_t key_len, const char *salt, char *encrypted) +{ + blf_ctx state; + u_int32_t rounds, i, k; + u_int16_t j; + u_int8_t salt_len, logr, minor; + u_int8_t ciphertext[4 * BCRYPT_BLOCKS+1] = "OrpheanBeholderScryDoubt"; + u_int8_t csalt[BCRYPT_MAXSALT]; + u_int32_t cdata[BCRYPT_BLOCKS]; + int n; + + /* Discard "$" identifier */ + salt++; + + if (*salt > BCRYPT_VERSION) { + /* How do I handle errors ? Return ':' */ + strcpy(encrypted, error); + return; + } + + /* Check for minor versions */ + if (salt[1] != '$') { + switch (salt[1]) { + case 'a': /* 'ab' should not yield the same as 'abab' */ + case 'b': /* cap input length at 72 bytes */ + minor = salt[1]; + salt++; + break; + default: + strcpy(encrypted, error); + return; + } + } else + minor = 0; + + /* Discard version + "$" identifier */ + salt += 2; + + if (salt[2] != '$') { + /* Out of sync with passwd entry */ + strcpy(encrypted, error); + return; + } + + /* Computer power doesn't increase linear, 2^x should be fine */ + n = atoi(salt); + if (n > 31 || n < 0) { + strcpy(encrypted, error); + return; + } + logr = (u_int8_t)n; + if ((rounds = (u_int32_t) 1 << logr) < BCRYPT_MINROUNDS) { + strcpy(encrypted, error); + return; + } + + /* Discard num rounds + "$" identifier */ + salt += 3; + + if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) { + strcpy(encrypted, error); + return; + } + + /* We dont want the base64 salt but the raw data */ + decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt); + salt_len = BCRYPT_MAXSALT; + if (minor <= 'a') + key_len = (u_int8_t)(key_len + (minor >= 'a' ? 1 : 0)); + else + { + /* cap key_len at the actual maximum supported + * length here to avoid integer wraparound */ + if (key_len > 72) + key_len = 72; + key_len++; /* include the NUL */ + } + + + /* Setting up S-Boxes and Subkeys */ + Blowfish_initstate(&state); + Blowfish_expandstate(&state, csalt, salt_len, + (u_int8_t *) key, key_len); + for (k = 0; k < rounds; k++) { + Blowfish_expand0state(&state, (u_int8_t *) key, key_len); + Blowfish_expand0state(&state, csalt, salt_len); + } + + /* This can be precomputed later */ + j = 0; + for (i = 0; i < BCRYPT_BLOCKS; i++) + cdata[i] = Blowfish_stream2word(ciphertext, 4 * BCRYPT_BLOCKS, &j); + + /* Now do the encryption */ + for (k = 0; k < 64; k++) + blf_enc(&state, cdata, BCRYPT_BLOCKS / 2); + + for (i = 0; i < BCRYPT_BLOCKS; i++) { + ciphertext[4 * i + 3] = cdata[i] & 0xff; + cdata[i] = cdata[i] >> 8; + ciphertext[4 * i + 2] = cdata[i] & 0xff; + cdata[i] = cdata[i] >> 8; + ciphertext[4 * i + 1] = cdata[i] & 0xff; + cdata[i] = cdata[i] >> 8; + ciphertext[4 * i + 0] = cdata[i] & 0xff; + } + + i = 0; + encrypted[i++] = '$'; + encrypted[i++] = BCRYPT_VERSION; + if (minor) + encrypted[i++] = minor; + encrypted[i++] = '$'; + + snprintf(encrypted + i, 4, "%2.2u$", logr & 0x001F); + + encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT); + encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext, + 4 * BCRYPT_BLOCKS - 1); + memset(&state, 0, sizeof(state)); + memset(ciphertext, 0, sizeof(ciphertext)); + memset(csalt, 0, sizeof(csalt)); + memset(cdata, 0, sizeof(cdata)); +} + +u_int32_t bcrypt_get_rounds(const char * hash) +{ + /* skip past the leading "$" */ + if (!hash || *(hash++) != '$') return 0; + + /* skip past version */ + if (0 == (*hash++)) return 0; + if (*hash && *hash != '$') hash++; + if (*hash++ != '$') return 0; + + return atoi(hash); +} + +static void +encode_base64(u_int8_t *buffer, u_int8_t *data, u_int16_t len) +{ + u_int8_t *bp = buffer; + u_int8_t *p = data; + u_int8_t c1, c2; + while (p < data + len) { + c1 = *p++; + *bp++ = Base64Code[(c1 >> 2)]; + c1 = (c1 & 0x03) << 4; + if (p >= data + len) { + *bp++ = Base64Code[c1]; + break; + } + c2 = *p++; + c1 |= (c2 >> 4) & 0x0f; + *bp++ = Base64Code[c1]; + c1 = (c2 & 0x0f) << 2; + if (p >= data + len) { + *bp++ = Base64Code[c1]; + break; + } + c2 = *p++; + c1 |= (c2 >> 6) & 0x03; + *bp++ = Base64Code[c1]; + *bp++ = Base64Code[c2 & 0x3f]; + } + *bp = '\0'; +} diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc new file mode 100644 index 000000000..2f072a4f9 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc @@ -0,0 +1,288 @@ +#define NAPI_VERSION 3 + +#include + +#include +#include +#include +#include // atoi + +#include "node_blf.h" + +#define NODE_LESS_THAN (!(NODE_VERSION_AT_LEAST(0, 5, 4))) + +namespace { + + bool ValidateSalt(const char* salt) { + + if (!salt || *salt != '$') { + return false; + } + + // discard $ + salt++; + + if (*salt > BCRYPT_VERSION) { + return false; + } + + if (salt[1] != '$') { + switch (salt[1]) { + case 'a': + case 'b': + salt++; + break; + default: + return false; + } + } + + // discard version + $ + salt += 2; + + if (salt[2] != '$') { + return false; + } + + int n = atoi(salt); + if (n > 31 || n < 0) { + return false; + } + + if (((uint8_t)1 << (uint8_t)n) < BCRYPT_MINROUNDS) { + return false; + } + + salt += 3; + if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) { + return false; + } + + return true; + } + + inline char ToCharVersion(const std::string& str) { + return str[0]; + } + + /* SALT GENERATION */ + + class SaltAsyncWorker : public Napi::AsyncWorker { + public: + SaltAsyncWorker(const Napi::Function& callback, const std::string& seed, ssize_t rounds, char minor_ver) + : Napi::AsyncWorker(callback, "bcrypt:SaltAsyncWorker"), seed(seed), rounds(rounds), minor_ver(minor_ver) { + } + + ~SaltAsyncWorker() {} + + void Execute() { + bcrypt_gensalt(minor_ver, rounds, (u_int8_t *)&seed[0], salt); + } + + void OnOK() { + Napi::HandleScope scope(Env()); + Callback().Call({Env().Undefined(), Napi::String::New(Env(), salt)}); + } + + private: + std::string seed; + ssize_t rounds; + char minor_ver; + char salt[_SALT_LEN]; + }; + + Napi::Value GenerateSalt(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() < 4) { + throw Napi::TypeError::New(env, "4 arguments expected"); + } + if (!info[0].IsString()) { + throw Napi::TypeError::New(env, "First argument must be a string"); + } + if (!info[2].IsBuffer() || (info[2].As>()).Length() != 16) { + throw Napi::TypeError::New(env, "Second argument must be a 16 byte Buffer"); + } + + const char minor_ver = ToCharVersion(info[0].As()); + const int32_t rounds = info[1].As(); + Napi::Buffer seed = info[2].As>(); + Napi::Function callback = info[3].As(); + SaltAsyncWorker* saltWorker = new SaltAsyncWorker(callback, std::string(seed.Data(), 16), rounds, minor_ver); + saltWorker->Queue(); + return env.Undefined(); + } + + Napi::Value GenerateSaltSync(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() < 3) { + throw Napi::TypeError::New(env, "3 arguments expected"); + } + if (!info[0].IsString()) { + throw Napi::TypeError::New(env, "First argument must be a string"); + } + if (!info[2].IsBuffer() || (info[2].As>()).Length() != 16) { + throw Napi::TypeError::New(env, "Third argument must be a 16 byte Buffer"); + } + const char minor_ver = ToCharVersion(info[0].As()); + const int32_t rounds = info[1].As(); + Napi::Buffer buffer = info[2].As>(); + u_int8_t* seed = (u_int8_t*) buffer.Data(); + char salt[_SALT_LEN]; + bcrypt_gensalt(minor_ver, rounds, seed, salt); + return Napi::String::New(env, salt, strlen(salt)); + } + + inline std::string BufferToString(const Napi::Buffer &buf) { + return std::string(buf.Data(), buf.Length()); + } + + /* ENCRYPT DATA - USED TO BE HASHPW */ + + class EncryptAsyncWorker : public Napi::AsyncWorker { + public: + EncryptAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& salt) + : Napi::AsyncWorker(callback, "bcrypt:EncryptAsyncWorker"), input(input), salt(salt) { + } + + ~EncryptAsyncWorker() {} + + void Execute() { + if (!(ValidateSalt(salt.c_str()))) { + SetError("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); + } + bcrypt(input.c_str(), input.length(), salt.c_str(), bcrypted); + } + + void OnOK() { + Napi::HandleScope scope(Env()); + Callback().Call({Env().Undefined(),Napi::String::New(Env(), bcrypted)}); + } + private: + std::string input; + std::string salt; + char bcrypted[_PASSWORD_LEN]; + }; + + Napi::Value Encrypt(const Napi::CallbackInfo& info) { + if (info.Length() < 3) { + throw Napi::TypeError::New(info.Env(), "3 arguments expected"); + } + std::string data = info[0].IsBuffer() + ? BufferToString(info[0].As>()) + : info[0].As(); + std::string salt = info[1].As(); + Napi::Function callback = info[2].As(); + EncryptAsyncWorker* encryptWorker = new EncryptAsyncWorker(callback, data, salt); + encryptWorker->Queue(); + return info.Env().Undefined(); + } + + Napi::Value EncryptSync(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() < 2) { + throw Napi::TypeError::New(info.Env(), "2 arguments expected"); + } + std::string data = info[0].IsBuffer() + ? BufferToString(info[0].As>()) + : info[0].As(); + std::string salt = info[1].As(); + if (!(ValidateSalt(salt.c_str()))) { + throw Napi::Error::New(env, "Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); + } + char bcrypted[_PASSWORD_LEN]; + bcrypt(data.c_str(), data.length(), salt.c_str(), bcrypted); + return Napi::String::New(env, bcrypted, strlen(bcrypted)); + } + + /* COMPARATOR */ + inline bool CompareStrings(const char* s1, const char* s2) { + return strcmp(s1, s2) == 0; + } + + class CompareAsyncWorker : public Napi::AsyncWorker { + public: + CompareAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& encrypted) + : Napi::AsyncWorker(callback, "bcrypt:CompareAsyncWorker"), input(input), encrypted(encrypted) { + result = false; + } + + ~CompareAsyncWorker() {} + + void Execute() { + char bcrypted[_PASSWORD_LEN]; + if (ValidateSalt(encrypted.c_str())) { + bcrypt(input.c_str(), input.length(), encrypted.c_str(), bcrypted); + result = CompareStrings(bcrypted, encrypted.c_str()); + } + } + + void OnOK() { + Napi::HandleScope scope(Env()); + Callback().Call({Env().Undefined(), Napi::Boolean::New(Env(), result)}); + } + + private: + std::string input; + std::string encrypted; + bool result; + }; + + Napi::Value Compare(const Napi::CallbackInfo& info) { + if (info.Length() < 3) { + throw Napi::TypeError::New(info.Env(), "3 arguments expected"); + } + std::string input = info[0].IsBuffer() + ? BufferToString(info[0].As>()) + : info[0].As(); + std::string encrypted = info[1].As(); + Napi::Function callback = info[2].As(); + CompareAsyncWorker* compareWorker = new CompareAsyncWorker(callback, input, encrypted); + compareWorker->Queue(); + return info.Env().Undefined(); + } + + Napi::Value CompareSync(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() < 2) { + throw Napi::TypeError::New(info.Env(), "2 arguments expected"); + } + std::string pw = info[0].IsBuffer() + ? BufferToString(info[0].As>()) + : info[0].As(); + std::string hash = info[1].As(); + char bcrypted[_PASSWORD_LEN]; + if (ValidateSalt(hash.c_str())) { + bcrypt(pw.c_str(), pw.length(), hash.c_str(), bcrypted); + return Napi::Boolean::New(env, CompareStrings(bcrypted, hash.c_str())); + } else { + return Napi::Boolean::New(env, false); + } + } + + Napi::Value GetRounds(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() < 1) { + throw Napi::TypeError::New(env, "1 argument expected"); + } + std::string hash = info[0].As(); + u_int32_t rounds; + if (!(rounds = bcrypt_get_rounds(hash.c_str()))) { + throw Napi::Error::New(env, "invalid hash provided"); + } + return Napi::Number::New(env, rounds); + } + +} // anonymous namespace + +Napi::Object init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "gen_salt_sync"), Napi::Function::New(env, GenerateSaltSync)); + exports.Set(Napi::String::New(env, "encrypt_sync"), Napi::Function::New(env, EncryptSync)); + exports.Set(Napi::String::New(env, "compare_sync"), Napi::Function::New(env, CompareSync)); + exports.Set(Napi::String::New(env, "get_rounds"), Napi::Function::New(env, GetRounds)); + exports.Set(Napi::String::New(env, "gen_salt"), Napi::Function::New(env, GenerateSalt)); + exports.Set(Napi::String::New(env, "encrypt"), Napi::Function::New(env, Encrypt)); + exports.Set(Napi::String::New(env, "compare"), Napi::Function::New(env, Compare)); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, init) diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc b/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc new file mode 100644 index 000000000..1fc6cf19e --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc @@ -0,0 +1,679 @@ +/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ +/* + * Blowfish block cipher for OpenBSD + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Implementation advice by David Mazieres . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code is derived from section 14.3 and the given source + * in section V of Applied Cryptography, second edition. + * Blowfish is an unpatented fast block cipher designed by + * Bruce Schneier. + */ + +#include "node_blf.h" + +#undef inline +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* Function for Feistel Networks */ + +#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ + + (s)[0x100 + (((x)>>16)&0xFF)]) \ + ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ + + (s)[0x300 + ( (x) &0xFF)]) + +#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) + +void +Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) +{ + u_int32_t Xl; + u_int32_t Xr; + u_int32_t *s = c->S[0]; + u_int32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[0]; + BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); + BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); + BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); + BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); + BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); + BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); + BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); + BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); + + *xl = Xr ^ p[17]; + *xr = Xl; +} + +void +Blowfish_decipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) +{ + u_int32_t Xl; + u_int32_t Xr; + u_int32_t *s = c->S[0]; + u_int32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[17]; + BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); + BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); + BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); + BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); + BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); + BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); + BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); + BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); + + *xl = Xr ^ p[0]; + *xr = Xl; +} + +void +Blowfish_initstate(blf_ctx *c) +{ + /* P-box and S-box tables initialized with digits of Pi */ + + static const blf_ctx initstate = + { { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} + }, + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } }; + + *c = initstate; +} + +u_int32_t +Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes, + u_int16_t *current) +{ + u_int8_t i; + u_int16_t j; + u_int32_t temp; + + temp = 0x00000000; + j = *current; + + for (i = 0; i < 4; i++, j++) { + if (j >= databytes) + j = 0; + temp = (temp << 8) | data[j]; + } + + *current = j; + return temp; +} + +void +Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes) +{ + u_int16_t i; + u_int16_t j; + u_int16_t k; + u_int32_t temp; + u_int32_t datal; + u_int32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } +} + + +void +Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes, + const u_int8_t *key, u_int16_t keybytes) +{ + u_int16_t i; + u_int16_t j; + u_int16_t k; + u_int32_t temp; + u_int32_t datal; + u_int32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } + +} + +void +blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len) +{ + /* Initialize S-boxes and subkeys with Pi */ + Blowfish_initstate(c); + + /* Transform S-boxes and subkeys with key */ + Blowfish_expand0state(c, k, len); +} + +void +blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks) +{ + u_int32_t *d; + u_int16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_encipher(c, d, d + 1); + d += 2; + } +} + +void +blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks) +{ + u_int32_t *d; + u_int16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_decipher(c, d, d + 1); + d += 2; + } +} + +void +blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) +{ + u_int32_t l, r; + u_int32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) +{ + u_int32_t l, r; + u_int32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len) +{ + u_int32_t l, r; + u_int32_t i, j; + + for (i = 0; i < len; i += 8) { + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + iv = data; + data += 8; + } +} + +void +blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len) +{ + u_int32_t l, r; + u_int8_t *iv; + u_int32_t i, j; + + iv = data + len - 16; + data = data + len - 8; + for (i = len - 8; i >= 8; i -= 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + iv -= 8; + data -= 8; + } + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iva[j]; +} + +#if 0 +void +report(u_int32_t data[], u_int16_t len) +{ + u_int16_t i; + for (i = 0; i < len; i += 2) + printf("Block %0hd: %08lx %08lx.\n", + i / 2, data[i], data[i + 1]); +} +void +main(void) +{ + + blf_ctx c; + char key[] = "AAAAA"; + char key2[] = "abcdefghijklmnopqrstuvwxyz"; + + u_int32_t data[10]; + u_int32_t data2[] = + {0x424c4f57l, 0x46495348l}; + + u_int16_t i; + + /* First test */ + for (i = 0; i < 10; i++) + data[i] = i; + + blf_key(&c, (u_int8_t *) key, 5); + blf_enc(&c, data, 5); + blf_dec(&c, data, 1); + blf_dec(&c, data + 2, 4); + printf("Should read as 0 - 9.\n"); + report(data, 10); + + /* Second test */ + blf_key(&c, (u_int8_t *) key2, strlen(key2)); + blf_enc(&c, data2, 1); + printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); + report(data2, 2); + blf_dec(&c, data2, 1); + report(data2, 2); +} +#endif diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h b/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h new file mode 100644 index 000000000..2d50a39bf --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h @@ -0,0 +1,132 @@ +/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _NODE_BLF_H_ +#define _NODE_BLF_H_ + +#include + +/* Solaris compatibility */ +#ifdef __sun +#define u_int8_t uint8_t +#define u_int16_t uint16_t +#define u_int32_t uint32_t +#define u_int64_t uint64_t +#endif + +#ifdef _WIN32 +#define u_int8_t unsigned __int8 +#define u_int16_t unsigned __int16 +#define u_int32_t unsigned __int32 +#define u_int64_t unsigned __int64 +#endif + +/* Windows ssize_t compatibility */ +#if defined(_WIN32) || defined(_WIN64) +# if defined(_WIN64) + typedef __int64 LONG_PTR; +# else + typedef long LONG_PTR; +# endif + typedef LONG_PTR SSIZE_T; + typedef SSIZE_T ssize_t; +#endif + +/* z/OS compatibility */ +#ifdef __MVS__ +typedef unsigned char u_int8_t; +typedef unsigned short u_int16_t; +typedef unsigned int u_int32_t; +typedef unsigned long long u_int64_t; +#endif + +#define BCRYPT_VERSION '2' +#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */ +#define BCRYPT_BLOCKS 6 /* Ciphertext blocks */ +#define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */ + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ + +#define _PASSWORD_LEN 128 /* max length, not counting NUL */ +#define _SALT_LEN 32 /* max length */ + +/* Blowfish context */ +typedef struct BlowfishContext { + u_int32_t S[4][256]; /* S-Boxes */ + u_int32_t P[BLF_N + 2]; /* Subkeys */ +} blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *); +void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *); +void Blowfish_initstate(blf_ctx *); +void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t); +void Blowfish_expandstate +(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t); + +/* Standard Blowfish */ + +void blf_key(blf_ctx *, const u_int8_t *, u_int16_t); +void blf_enc(blf_ctx *, u_int32_t *, u_int16_t); +void blf_dec(blf_ctx *, u_int32_t *, u_int16_t); + +void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t); +void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t); + +void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); +void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); + +/* Converts u_int8_t to u_int32_t */ +u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *); + +/* bcrypt functions*/ +void bcrypt_gensalt(char, u_int8_t, u_int8_t*, char *); +void bcrypt(const char *, size_t key_len, const char *, char *); +void encode_salt(char *, u_int8_t *, char, u_int16_t, u_int8_t); +u_int32_t bcrypt_get_rounds(const char *); + +#endif diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js new file mode 100644 index 000000000..fb59367a3 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js @@ -0,0 +1,209 @@ +const bcrypt = require('../bcrypt'); + +test('salt_length', done => { + expect.assertions(1); + bcrypt.genSalt(10, function (err, salt) { + expect(salt).toHaveLength(29); + done(); + }); +}) + +test('salt_only_cb', () => { + expect.assertions(1); + expect(() => { + bcrypt.genSalt((err, salt) => { + }); + }).not.toThrow(); +}) + +test('salt_rounds_is_string_number', done => { + expect.assertions(2); + bcrypt.genSalt('10', void 0, function (err, salt) { + expect(err instanceof Error).toBe(true) + expect(err.message).toBe('rounds must be a number') + done(); + }); +}) + +test('salt_rounds_is_string_non_number', done => { + expect.assertions(2); + bcrypt.genSalt('z', function (err, salt) { + expect(err instanceof Error).toBe(true) + expect(err.message).toBe('rounds must be a number') + done(); + }); +}) + +test('salt_minor', done => { + expect.assertions(3); + bcrypt.genSalt(10, 'a', function (err, value) { + expect(value).toHaveLength(29); + const [_, minor, salt] = value.split('$'); + expect(minor).toEqual('2a'); + expect(salt).toEqual('10'); + done(); + }); +}) + +test('salt_minor_b', done => { + expect.assertions(3); + bcrypt.genSalt(10, 'b', function (err, value) { + expect(value).toHaveLength(29); + const [_, minor, salt] = value.split('$'); + expect(minor).toEqual('2b'); + expect(salt).toEqual('10'); + done(); + }); +}) + +test('hash', done => { + expect.assertions(2); + bcrypt.genSalt(10, function (err, salt) { + bcrypt.hash('password', salt, function (err, res) { + expect(res).toBeDefined(); + expect(err).toBeUndefined(); + done(); + }); + }); +}) + +test('hash_rounds', done => { + expect.assertions(1); + bcrypt.hash('bacon', 8, function (err, hash) { + expect(bcrypt.getRounds(hash)).toEqual(8); + done(); + }); +}) + +test('hash_empty_strings', done => { + expect.assertions(1); + bcrypt.genSalt(10, function (err, salt) { + bcrypt.hash('', salt, function (err, res) { + expect(res).toBeDefined(); + done(); + }); + }); +}) + +test('hash_fails_with_empty_salt', done => { + expect.assertions(1); + bcrypt.hash('', '', function (err, res) { + expect(err.message).toBe('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue') + done(); + }); +}) + +test('hash_no_params', done => { + expect.assertions(1); + bcrypt.hash(function (err, hash) { + expect(err.message).toBe('data must be a string or Buffer and salt must either be a salt string or a number of rounds') + done(); + }); +}) + +test('hash_one_param', done => { + expect.assertions(1); + bcrypt.hash('password', function (err, hash) { + expect(err.message).toBe('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); + done(); + }); +}) + +test('hash_salt_validity', done => { + expect.assertions(2); + bcrypt.hash('password', '$2a$10$somesaltyvaluertsetrse', function (err, enc) { + expect(err).toBeUndefined(); + bcrypt.hash('password', 'some$value', function (err, enc) { + expect(err.message).toBe("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); + done(); + }); + }); +}) + +test('verify_salt', done => { + expect.assertions(2); + bcrypt.genSalt(10, function (err, value) { + const [_, version, rounds] = value.split('$'); + expect(version).toEqual('2b'); + expect(rounds).toEqual('10'); + done(); + }); +}) + +test('verify_salt_min_rounds', done => { + expect.assertions(2); + bcrypt.genSalt(1, function (err, value) { + const [_, version, rounds] = value.split('$'); + expect(version).toEqual('2b'); + expect(rounds).toEqual('04'); + done(); + }); +}) + +test('verify_salt_max_rounds', done => { + expect.assertions(2); + bcrypt.genSalt(100, function (err, value) { + const [_, version, rounds] = value.split('$'); + expect(version).toEqual('2b'); + expect(rounds).toEqual('31'); + done(); + }); +}) + +test('hash_compare', done => { + expect.assertions(2); + bcrypt.genSalt(10, function (err, salt) { + bcrypt.hash("test", salt, function (err, hash) { + bcrypt.compare("test", hash, function (err, res) { + expect(hash).toBeDefined(); + bcrypt.compare("blah", hash, function (err, res) { + expect(res).toBe(false); + done(); + }); + }); + }); + }); +}) + +test('hash_compare_empty_strings', done => { + expect.assertions(2); + const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(10)); + + bcrypt.compare("", hash, function (err, res) { + expect(res).toEqual(false) + bcrypt.compare("", "", function (err, res) { + expect(res).toEqual(false); + done(); + }); + }); +}) + +test('hash_compare_invalid_strings', done => { + expect.assertions(2); + const fullString = 'envy1362987212538'; + const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; + const wut = ':'; + bcrypt.compare(fullString, hash, function (err, res) { + expect(res).toBe(true); + bcrypt.compare(fullString, wut, function (err, res) { + expect(res).toBe(false); + done(); + }); + }); +}) + +test('compare_no_params', done => { + expect.assertions(1); + bcrypt.compare(function (err, hash) { + expect(err.message).toBe('data and hash arguments required'); + done(); + }); +}) + +test('hash_compare_one_param', done => { + expect.assertions(1); + bcrypt.compare('password', function (err, hash) { + expect(err.message).toBe('data and hash arguments required'); + done(); + }); +}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js new file mode 100644 index 000000000..647f32a92 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js @@ -0,0 +1,48 @@ +const bcrypt = require('../bcrypt'); + +// some tests were adapted from https://github.com/riverrun/bcrypt_elixir/blob/master/test/base_test.exs +// which are under the BSD LICENSE + +test('openwall', () => { + expect(bcrypt.hashSync("U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"); + expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"); + expect(bcrypt.hashSync("U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO")).toStrictEqual("$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"); + expect(bcrypt.hashSync("", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"); + expect(bcrypt.hashSync("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu")).toStrictEqual("$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"); +}) + +test('openbsd', () => { + expect(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") + expect(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") +}) + +test('long_passwords', () => { + // bcrypt wrap-around bug in $2a$ + expect(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") + expect(bcrypt.hashSync("01XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") + + // tests for $2b$ which fixes wrap-around bugs + expect(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.XxrQqgBi/5Sxuq9soXzDtjIZ7w5pMfK") + expect(bcrypt.hashSync("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.XxrQqgBi/5Sxuq9soXzDtjIZ7w5pMfK") +}) + +test('embedded_nulls', () => { + expect(bcrypt.hashSync("Passw\0rd123", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.VHy/kzL4sCcX3Ib3wN5rNGiRt.TpfxS") + expect(bcrypt.hashSync("Passw\0 you can literally write anything after the NUL character", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.4vJLJQ6nZ/70INTjjSZWQ0iyUek92tu") + expect(bcrypt.hashSync(Buffer.from("Passw\0 you can literally write anything after the NUL character"), "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.4vJLJQ6nZ/70INTjjSZWQ0iyUek92tu") +}) + +test('shorten_salt_to_128_bits', () => { + expect(bcrypt.hashSync("test", "$2a$10$1234567899123456789012")).toStrictEqual("$2a$10$123456789912345678901u.OtL1A1eGK5wmvBKUDYKvuVKI7h2XBu") + expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCh")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCCeUQ7VjYZ2hd4bLYZdhuPpZMUpEUJDw1S") + expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCM")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK") + expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCA")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK") +}) + +test('consistency', () => { + expect(bcrypt.hashSync("ππππππππ", "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeu")).toStrictEqual("$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle") + expect(bcrypt.hashSync("p@5sw0rd", "$2b$12$zQ4CooEXdGqcwi0PHsgc8e")).toStrictEqual("$2b$12$zQ4CooEXdGqcwi0PHsgc8eAf0DLXE/XHoBE8kCSGQ97rXwuClaPam") + expect(bcrypt.hashSync("C'est bon, la vie!", "$2b$12$cbo7LZ.wxgW4yxAA5Vqlv.")).toStrictEqual("$2b$12$cbo7LZ.wxgW4yxAA5Vqlv.KR6QFPt4qCdc9RYJNXxa/rbUOp.1sw.") + expect(bcrypt.hashSync("ἓν οἶδα ὅτι οὐδὲν οἶδα", "$2b$12$LeHKWR2bmrazi/6P22Jpau")).toStrictEqual("$2b$12$LeHKWR2bmrazi/6P22JpauX5my/eKwwKpWqL7L5iEByBnxNc76FRW") + expect(bcrypt.hashSync(Buffer.from("ἓν οἶδα ὅτι οὐδὲν οἶδα"), "$2b$12$LeHKWR2bmrazi/6P22Jpau")).toStrictEqual("$2b$12$LeHKWR2bmrazi/6P22JpauX5my/eKwwKpWqL7L5iEByBnxNc76FRW") +}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js new file mode 100644 index 000000000..010341825 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js @@ -0,0 +1,168 @@ +const bcrypt = require('../bcrypt'); +const promises = require('../promises'); + +test('salt_returns_promise_on_no_args', () => { + // make sure test passes with non-native implementations such as bluebird + // http://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise + expect(typeof bcrypt.genSalt().then).toEqual('function') +}) + +test('salt_returns_promise_on_null_callback', () => { + expect(typeof bcrypt.genSalt(13, null, null).then).toEqual('function') +}) + +test('salt_length', () => { + return expect(bcrypt.genSalt(10)).resolves.toHaveLength(29); +}) + +test('salt_rounds_is_string_number', () => { + return expect(bcrypt.genSalt('10')).rejects.toThrow('rounds must be a number'); +}) + +test('salt_rounds_is_string_non_number', () => { + return expect(bcrypt.genSalt('b')).rejects.toThrow('rounds must be a number'); +}) + +test('hash_returns_promise_on_null_callback', () => { + expect(typeof bcrypt.hash('password', 10, null).then).toStrictEqual('function') +}) + +test('hash', () => { + return expect(bcrypt.genSalt(10) + .then(salt => bcrypt.hash('password', salt))).resolves.toBeDefined() +}) + +test('hash_rounds', () => { + return bcrypt.hash('bacon', 8).then(hash => { + expect(bcrypt.getRounds(hash)).toStrictEqual(8) + }); +}) + +test('hash_empty_strings', () => { + expect.assertions(2); + return Promise.all([ + expect(bcrypt.genSalt(10) + .then(salt => bcrypt.hash('', salt))) + .resolves.toBeDefined(), + expect(bcrypt.hash('', '')).rejects.toThrow(''), + ]); +}) + +test('hash_no_params', () => { + expect.assertions(1); + return expect(bcrypt.hash()).rejects.toThrow('data and salt arguments required'); +}) + +test('hash_one_param', () => { + return expect(bcrypt.hash('password')).rejects.toThrow('data and salt arguments required'); +}) + +test('hash_salt_validity', () => { + expect.assertions(2); + return Promise.all( + [ + expect(bcrypt.hash('password', '$2a$10$somesaltyvaluertsetrse')).resolves.toBeDefined(), + expect(bcrypt.hash('password', 'some$value')).rejects.toThrow("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue") + ]); +}) + +test('verify_salt', () => { + expect.assertions(2); + return bcrypt.genSalt(10).then(result => { + const [_, version, salt] = result.split('$'); + expect(version).toEqual('2b') + expect(salt).toEqual('10') + }); +}) + +test('verify_salt_min_rounds', () => { + expect.assertions(2); + return bcrypt.genSalt(1).then(value => { + const [_, version, rounds] = value.split('$'); + expect(version).toEqual('2b'); + expect(rounds).toEqual('04'); + }); +}) + +test('verify_salt_max_rounds', () => { + expect.assertions(2); + return bcrypt.genSalt(100).then(value => { + const [_, version, rounds] = value.split('$'); + expect(version).toEqual('2b'); + expect(rounds).toEqual('31'); + }); +}) + +test('hash_compare_returns_promise_on_null_callback', () => { + expect(typeof bcrypt.compare('password', 'something', null).then).toStrictEqual('function') +}) + +test('hash_compare', () => { + expect.assertions(3); + return bcrypt.genSalt(10).then(function (salt) { + expect(salt).toHaveLength(29); + return bcrypt.hash("test", salt); + }).then(hash => Promise.all( + [ + expect(bcrypt.compare("test", hash)).resolves.toEqual(true), + expect(bcrypt.compare("blah", hash)).resolves.toEqual(false) + ])); +}) + +test('hash_compare_empty_strings', () => { + expect.assertions(2); + const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(10)); + return Promise.all([ + expect(bcrypt.compare("", hash)).resolves.toEqual(false), + expect(bcrypt.compare("", "")).resolves.toEqual(false) + ]); +}) + +test('hash_compare_invalid_strings', () => { + const fullString = 'envy1362987212538'; + const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; + const wut = ':'; + return Promise.all([ + expect(bcrypt.compare(fullString, hash)).resolves.toEqual(true), + expect(bcrypt.compare(fullString, wut)).resolves.toEqual(false), + ]); +}) + +test('hash_compare_no_params', () => { + expect.assertions(1); + return expect(bcrypt.compare()).rejects.toThrow('data and hash arguments required') +}) + +test('hash_compare_one_param', () => { + expect.assertions(1); + return expect(bcrypt.compare('password')).rejects.toThrow('data and hash arguments required') +}) + +test('change_promise_impl_reject', () => { + + promises.use({ + reject: function () { + return 'mock'; + } + }); + + expect(promises.reject()).toEqual('mock'); + + // need to reset the promise implementation because of require cache + promises.use(global.Promise); +}) + +test('change_promise_impl_promise', () => { + + promises.use({ + reject: function (err) { + expect(err.message).toEqual('fn must be a function'); + return 'mock'; + } + }); + + expect(promises.promise('', '', '')).toEqual('mock'); + + // need to reset the promise implementation because of require cache + promises.use(global.Promise); +}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js new file mode 100644 index 000000000..63ff40777 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js @@ -0,0 +1,55 @@ +const bcrypt = require('../bcrypt'); + +const EXPECTED = 2500; //number of times to iterate these tests.) +const { TEST_TIMEOUT_SECONDS } = process.env; +let timeout = 5e3; // default test timeout + +// it is necessary to increase the test timeout when emulating cross-architecture +// environments (i.e. arm64 from x86-64 host) which have significantly reduced performance: +if ( TEST_TIMEOUT_SECONDS ) + timeout = Number.parseInt(TEST_TIMEOUT_SECONDS, 10) * 1e3; + +jest.setTimeout(timeout); + +test('salt_length', () => { + expect.assertions(EXPECTED); + + return Promise.all(Array.from({length: EXPECTED}, + () => bcrypt.genSalt(10) + .then(salt => expect(salt).toHaveLength(29)))); +}) + +test('test_hash_length', () => { + expect.assertions(EXPECTED); + const SALT = '$2a$04$TnjywYklQbbZjdjBgBoA4e'; + return Promise.all(Array.from({length: EXPECTED}, + () => bcrypt.hash('test', SALT) + .then(hash => expect(hash).toHaveLength(60)))); +}) + +test('test_compare', () => { + expect.assertions(EXPECTED); + const HASH = '$2a$04$TnjywYklQbbZjdjBgBoA4e9G7RJt9blgMgsCvUvus4Iv4TENB5nHy'; + return Promise.all(Array.from({length: EXPECTED}, + () => bcrypt.compare('test', HASH) + .then(match => expect(match).toEqual(true)))); +}) + +test('test_hash_and_compare', () => { + expect.assertions(EXPECTED * 3); + const salt = bcrypt.genSaltSync(4) + + return Promise.all(Array.from({length: EXPECTED}, + () => { + const password = 'secret' + Math.random(); + return bcrypt.hash(password, salt) + .then(hash => { + expect(hash).toHaveLength(60); + const goodCompare = bcrypt.compare(password, hash).then(res => expect(res).toEqual(true)); + const badCompare = bcrypt.compare('bad' + password, hash).then(res => expect(res).toEqual(false)); + + return Promise.all([goodCompare, badCompare]); + }); + })); +}, timeout * 3); + diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js new file mode 100644 index 000000000..2e6809af4 --- /dev/null +++ b/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js @@ -0,0 +1,125 @@ +const bcrypt = require('../bcrypt') + +test('salt_length', () => { + const salt = bcrypt.genSaltSync(13); + expect(salt).toHaveLength(29); + const [_, version, rounds] = salt.split('$'); + expect(version).toStrictEqual('2b') + expect(rounds).toStrictEqual('13') +}) + +test('salt_no_params', () => { + const salt = bcrypt.genSaltSync(); + const [_, version, rounds] = salt.split('$'); + expect(version).toStrictEqual('2b') + expect(rounds).toStrictEqual('10') +}) + +test('salt_rounds_is_string_number', () => { + expect(() => bcrypt.genSaltSync('10')).toThrowError('rounds must be a number'); +}) + +test('salt_rounds_is_NaN', () => { + expect(() => bcrypt.genSaltSync('b')).toThrowError("rounds must be a number"); +}) + +test('salt_minor_a', () => { + const salt = bcrypt.genSaltSync(10, 'a'); + const [_, version, rounds] = salt.split('$'); + expect(version).toStrictEqual('2a') + expect(rounds).toStrictEqual('10') +}) + +test('salt_minor_b', () => { + const salt = bcrypt.genSaltSync(10, 'b'); + const [_, version, rounds] = salt.split('$'); + expect(version).toStrictEqual('2b') + expect(rounds).toStrictEqual('10') +}) + +test('hash', () => { + expect(() => bcrypt.hashSync('password', bcrypt.genSaltSync(10))).not.toThrow() +}) + +test('hash_rounds', () => { + const hash = bcrypt.hashSync('password', 8); + expect(bcrypt.getRounds(hash)).toStrictEqual(8) +}) + +test('hash_empty_string', () => { + expect(() => bcrypt.hashSync('', bcrypt.genSaltSync(10))).not.toThrow(); + expect(() => bcrypt.hashSync('password', '')).toThrowError('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue'); + expect(() => bcrypt.hashSync('', '')).toThrowError('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue'); +}) + +test('hash_pw_no_params', () => { + expect(() => bcrypt.hashSync()).toThrow('data and salt arguments required'); +}) + +test('hash_pw_one_param', () => { + expect(() => bcrypt.hashSync('password')).toThrow('data and salt arguments required'); +}) + +test('hash_pw_not_hash_str', () => { + expect(() => bcrypt.hashSync('password', {})).toThrow("data must be a string or Buffer and salt must either be a salt string or a number of rounds") +}) + +test('hash_salt_validity', () => { + expect(2); + expect(bcrypt.hashSync('password', '$2a$10$somesaltyvaluertsetrse')).toBeDefined() + expect(() => bcrypt.hashSync('password', 'some$value')).toThrow('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue') +}) + +test('verify_salt', () => { + const salt = bcrypt.genSaltSync(10); + const split_salt = salt.split('$'); + expect(split_salt[1]).toStrictEqual('2b') + expect(split_salt[2]).toStrictEqual('10') +}) + +test('verify_salt_min_rounds', () => { + const salt = bcrypt.genSaltSync(1); + const split_salt = salt.split('$'); + expect(split_salt[1]).toStrictEqual('2b') + expect(split_salt[2]).toStrictEqual('04') +}) + +test('verify_salt_max_rounds', () => { + const salt = bcrypt.genSaltSync(100); + const split_salt = salt.split('$'); + expect(split_salt[1]).toStrictEqual('2b') + expect(split_salt[2]).toStrictEqual('31') +}) + +test('hash_compare', () => { + const salt = bcrypt.genSaltSync(10); + expect(29).toStrictEqual(salt.length) + const hash = bcrypt.hashSync("test", salt); + expect(bcrypt.compareSync("test", hash)).toBeDefined() + expect(!(bcrypt.compareSync("blah", hash))).toBeDefined() +}) + +test('hash_compare_empty_strings', () => { + expect(!(bcrypt.compareSync("", "password"))).toBeDefined() + expect(!(bcrypt.compareSync("", ""))).toBeDefined() + expect(!(bcrypt.compareSync("password", ""))).toBeDefined() +}) + +test('hash_compare_invalid_strings', () => { + const fullString = 'envy1362987212538'; + const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; + const wut = ':'; + expect(bcrypt.compareSync(fullString, hash)).toBe(true); + expect(bcrypt.compareSync(fullString, wut)).toBe(false); +}) + +test('getRounds', () => { + const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(9)); + expect(9).toStrictEqual(bcrypt.getRounds(hash)) +}) + +test('getRounds', () => { + const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(9)); + expect(9).toStrictEqual(bcrypt.getRounds(hash)) + expect(() => bcrypt.getRounds('')).toThrow("invalid hash provided"); +}); diff --git a/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md b/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md new file mode 100644 index 000000000..819d91a5b --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2017 [Node.js API collaborators](https://github.com/nodejs/node-addon-api#collaborators) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/week-5/solution/frontend/node_modules/node-addon-api/README.md b/week-5/solution/frontend/node_modules/node-addon-api/README.md new file mode 100644 index 000000000..89a36ef28 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/README.md @@ -0,0 +1,95 @@ +# **node-addon-api module** + +[![codecov](https://codecov.io/gh/nodejs/node-addon-api/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nodejs/node-addon-api/tree/main) + +[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) + +This module contains **header-only C++ wrapper classes** which simplify +the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +provided by Node.js when using C++. It provides a C++ object model +and exception handling semantics with low overhead. + +- [API References](doc/README.md) +- [Badges](#badges) +- [Contributing](#contributing) +- [License](#license) + +## API References + +API references are available in the [doc](doc/README.md) directory. + + +## Current version: 8.5.0 + + +(See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) + +node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. +This allows addons built with it to run with Node.js versions which support the targeted Node-API version. +**However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that +every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. + +The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. + +## Badges + +The use of badges is recommended to indicate the minimum version of Node-API +required for the module. This helps to determine which Node.js major versions are +supported. Addon maintainers can consult the [Node-API support matrix][] to determine +which Node.js versions provide a given Node-API version. The following badges are +available: + +![Node-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v1%20Badge.svg) +![Node-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v2%20Badge.svg) +![Node-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v3%20Badge.svg) +![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) +![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) +![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) +![Node-API v7 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v7%20Badge.svg) +![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg) +![Node-API v9 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v9%20Badge.svg) +![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) + +## Contributing + +We love contributions from the community to **node-addon-api**! +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. + +## Team members + +### Active + +| Name | GitHub Link | +| ------------------- | ----------------------------------------------------- | +| Anna Henningsen | [addaleax](https://github.com/addaleax) | +| Chengzhong Wu | [legendecas](https://github.com/legendecas) | +| Jack Xia | [JckXia](https://github.com/JckXia) | +| Kevin Eady | [KevinEady](https://github.com/KevinEady) | +| Michael Dawson | [mhdawson](https://github.com/mhdawson) | +| Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | +| Vladimir Morozov | [vmoroz](https://github.com/vmoroz) | + +

    + +Emeritus + +### Emeritus + +| Name | GitHub Link | +| ------------------- | ----------------------------------------------------- | +| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | +| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | +| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | +| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | +| Jason Ginchereau | [jasongin](https://github.com/jasongin) | +| Jim Schlight | [jschlight](https://github.com/jschlight) | +| Sampson Gao | [sampsongao](https://github.com/sampsongao) | +| Taylor Woll | [boingoing](https://github.com/boingoing) | + +
    + +## License + +Licensed under [MIT](./LICENSE.md) + +[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#node-api-version-matrix diff --git a/week-5/solution/frontend/node_modules/node-addon-api/common.gypi b/week-5/solution/frontend/node_modules/node-addon-api/common.gypi new file mode 100644 index 000000000..e594f14ff --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/common.gypi @@ -0,0 +1,21 @@ +{ + 'variables': { + 'NAPI_VERSION%': " +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, Getter getter, napi_property_attributes attributes, void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor( + nameValue, getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Name name, Callable cb, napi_property_attributes attributes, void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Function(nameValue, cb, attributes, data); +} + +#endif // !SRC_NAPI_INL_DEPRECATED_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h b/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h new file mode 100644 index 000000000..54651c1b5 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h @@ -0,0 +1,7033 @@ +#ifndef SRC_NAPI_INL_H_ +#define SRC_NAPI_INL_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Node-API C++ Wrapper Classes +// +// Inline header-only implementations for "Node-API" ABI-stable C APIs for +// Node.js. +//////////////////////////////////////////////////////////////////////////////// + +// Note: Do not include this file directly! Include "napi.h" instead. +// This should be a no-op and is intended for better IDE integration. +#include "napi.h" + +#include +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Helpers to handle functions exposed from C++ and internal constants. +namespace details { + +// New napi_status constants not yet available in all supported versions of +// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h. +constexpr int napi_no_external_buffers_allowed = 22; + +template +inline void default_basic_finalizer(node_addon_api_basic_env /*env*/, + void* data, + void* /*hint*/) { + delete static_cast(data); +} + +// Attach a data item to an object and delete it when the object gets +// garbage-collected. +// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes +// available on all supported versions of Node.js. +template < + typename FreeType, + node_addon_api_basic_finalize finalizer = default_basic_finalizer> +inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data, + void* hint = nullptr) { + napi_status status; +#if (NAPI_VERSION < 5) + napi_value symbol, external; + status = napi_create_symbol(env, nullptr, &symbol); + if (status == napi_ok) { + status = napi_create_external(env, data, finalizer, hint, &external); + if (status == napi_ok) { + napi_property_descriptor desc = {nullptr, + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr}; + status = napi_define_properties(env, obj, 1, &desc); + } + } +#else // NAPI_VERSION >= 5 + status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr); +#endif + return status; +} + +// For use in JS to C++ callback wrappers to catch any Napi::Error exceptions +// and rethrow them as JavaScript exceptions before returning from the callback. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline napi_value WrapCallback(napi_env env, Callable callback) { +#else +inline napi_value WrapCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + return callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + return nullptr; + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + return nullptr; + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + return nullptr; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else // NODE_ADDON_API_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + return callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch any Napi::Error +// exceptions and rethrow them as JavaScript exceptions before returning from +// the callback. +template +inline void WrapVoidCallback(Callable callback) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + callback(); +#endif // NAPI_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch _any_ thrown exception +// and rethrow them as JavaScript exceptions before returning from the callback, +// wrapping in an Napi::Error as needed. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline void WrapVoidCallback(napi_env env, Callable callback) { +#else +inline void WrapVoidCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else + // When C++ exceptions are disabled, there is no need to catch and rethrow C++ + // exceptions. JS errors should be thrown with + // `Error::ThrowAsJavaScriptException`. + callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); + } + + Callable callback; + void* data; +}; + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); + } + + Callable callback; + void* data; +}; + +template +napi_value TemplatedVoidCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + Callback(cbInfo); + return nullptr; + }); +} + +template +napi_value TemplatedCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + // MSVC requires to copy 'Callback' function pointer to a local variable + // before invoking it. + auto callback = Callback; + return callback(cbInfo); + }); +} + +template +napi_value TemplatedInstanceCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); + }); +} + +template +napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) + NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*UnwrapCallback)(cbInfo); + return nullptr; + }); +} + +template +struct FinalizeData { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGC, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + + static inline void WrapperGCWithoutData(napi_env env, + void* /*data*/, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env); + delete finalizeData; + }); + } + + static inline void WrapperGC(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + + static inline void WrapperGCWithHint(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + + Finalizer callback; + Hint* hint; +}; + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +template , + typename FinalizerDataType = void> +struct ThreadSafeFinalize { + static inline void Wrapper(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env)); + delete finalizeData; + } + + static inline void FinalizeWrapperWithData(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), finalizeData->data); + delete finalizeData; + } + + static inline void FinalizeWrapperWithContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), static_cast(rawContext)); + delete finalizeData; + } + + static inline void FinalizeFinalizeWrapperWithDataAndContext( + napi_env env, void* rawFinalizeData, void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback( + Env(env), finalizeData->data, static_cast(rawContext)); + delete finalizeData; + } + + FinalizerDataType* data; + Finalizer callback; +}; + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { + details::WrapVoidCallback(env, [&]() { + call(env, + Function(env, jsCallback), + static_cast(context), + static_cast(data)); + }); +} + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, + napi_value jsCallback, + void* /*context*/, + void* /*data*/) { + details::WrapVoidCallback(env, [&]() { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } + }); +} + +#if NAPI_VERSION > 4 + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, std::nullptr_t /*cb*/) { + return nullptr; +} + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, Napi::Function cb) { + return cb; +} + +#else +template +napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { + if (cb.IsEmpty()) { + return TSFN::EmptyFunctionFactory(env); + } + return cb; +} +#endif // NAPI_VERSION > 4 +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +template +struct AccessorCallbackData { + static inline napi_value GetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); + } + + static inline napi_value SetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo); + return nullptr; + }); + } + + Getter getterCallback; + Setter setterCallback; + void* data; +}; + +// Debugging-purpose C++-style variant of sprintf(). +inline std::string StringFormat(const char* format, ...) { + std::string result; + va_list args; + va_start(args, format); + int len = vsnprintf(nullptr, 0, format, args); + result.resize(len); + vsnprintf(&result[0], len + 1, format, args); + va_end(args); + return result; +} + +template +class HasExtendedFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +template +class HasBasicFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +} // namespace details + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +#include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + +//////////////////////////////////////////////////////////////////////////////// +// Module registration +//////////////////////////////////////////////////////////////////////////////// + +// Register an add-on based on an initializer function. +#define NODE_API_MODULE(modname, regfunc) \ + static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, regfunc); \ + } \ + NAPI_MODULE(modname, __napi_##regfunc) + +// Register an add-on based on a subclass of `Addon` with a custom Node.js +// module name. +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_##classname(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_##classname) + +// Register an add-on based on a subclass of `Addon` with the Node.js module +// name given by node-gyp from the `target_name` in binding.gyp. +#define NODE_API_ADDON(classname) \ + NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) + +// Adapt the NAPI_MODULE registration function: +// - Wrap the arguments in NAPI wrappers. +// - Catch any NAPI errors and rethrow as JS exceptions. +inline napi_value RegisterModule(napi_env env, + napi_value exports, + ModuleRegisterCallback registerCallback) { + return details::WrapCallback(env, [&] { + return napi_value( + registerCallback(Napi::Env(env), Napi::Object(env, exports))); + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Maybe class +//////////////////////////////////////////////////////////////////////////////// + +template +bool Maybe::IsNothing() const { + return !_has_value; +} + +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + +//////////////////////////////////////////////////////////////////////////////// +// BasicEnv / Env class +//////////////////////////////////////////////////////////////////////////////// + +inline BasicEnv::BasicEnv(node_addon_api_basic_env env) : _env(env) {} + +inline BasicEnv::operator node_addon_api_basic_env() const { + return _env; +} + +inline Env::Env(napi_env env) : BasicEnv(env) {} + +inline Env::operator napi_env() const { + return const_cast(_env); +} + +inline Object Env::Global() const { + napi_value value; + napi_status status = napi_get_global(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Object()); + return Object(*this, value); +} + +inline Value Env::Undefined() const { + napi_value value; + napi_status status = napi_get_undefined(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline Value Env::Null() const { + napi_value value; + napi_status status = napi_get_null(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline bool Env::IsExceptionPending() const { + bool result; + napi_status status = napi_is_exception_pending(*this, &result); + if (status != napi_ok) + result = false; // Checking for a pending exception shouldn't throw. + return result; +} + +inline Error Env::GetAndClearPendingException() const { + napi_value value; + napi_status status = napi_get_and_clear_last_exception(*this, &value); + if (status != napi_ok) { + // Don't throw another exception when failing to get the exception! + return Error(); + } + return Error(*this, value); +} + +inline MaybeOrValue Env::RunScript(const char* utf8script) const { + String script = String::New(*this, utf8script); + return RunScript(script); +} + +inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { + return RunScript(utf8script.c_str()); +} + +inline MaybeOrValue Env::RunScript(String script) const { + napi_value result; + napi_status status = napi_run_script(*this, script, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + *this, status, Napi::Value(*this, result), Napi::Value); +} + +#if NAPI_VERSION > 2 +template +void BasicEnv::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(); + delete cleanupData; +} + +template +void BasicEnv::CleanupHook::WrapperWithArg(void* data) + NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(static_cast(cleanupData->arg)); + delete cleanupData; +} +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 5 +template fini> +inline void BasicEnv::SetInstanceData(T* data) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, + nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); +} + +template fini> +inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void* hint) { + fini(env, static_cast(data), static_cast(hint)); + }, + hint); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); +} + +template +inline T* BasicEnv::GetInstanceData() const { + void* data = nullptr; + + napi_status status = napi_get_instance_data(_env, &data); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetInstanceData", "invalid arguments"); + + return static_cast(data); +} + +template +void BasicEnv::DefaultFini(Env, T* data) { + delete data; +} + +template +void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) { + delete data; +} +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 8 +inline const char* BasicEnv::GetModuleFileName() const { + const char* result; + napi_status status = node_api_get_module_file_name(_env, &result); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetModuleFileName", "invalid arguments"); + return result; +} +#endif // NAPI_VERSION > 8 +//////////////////////////////////////////////////////////////////////////////// +// Value class +//////////////////////////////////////////////////////////////////////////////// + +inline Value::Value() : _env(nullptr), _value(nullptr) {} + +inline Value::Value(napi_env env, napi_value value) + : _env(env), _value(value) {} + +inline Value::operator napi_value() const { + return _value; +} + +inline bool Value::operator==(const Value& other) const { + return StrictEquals(other); +} + +inline bool Value::operator!=(const Value& other) const { + return !this->operator==(other); +} + +inline bool Value::StrictEquals(const Value& other) const { + bool result; + napi_status status = napi_strict_equals(_env, *this, other, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline Napi::Env Value::Env() const { + return Napi::Env(_env); +} + +inline bool Value::IsEmpty() const { + return _value == nullptr; +} + +inline napi_valuetype Value::Type() const { + if (IsEmpty()) { + return napi_undefined; + } + + napi_valuetype type; + napi_status status = napi_typeof(_env, _value, &type); + NAPI_THROW_IF_FAILED(_env, status, napi_undefined); + return type; +} + +inline bool Value::IsUndefined() const { + return Type() == napi_undefined; +} + +inline bool Value::IsNull() const { + return Type() == napi_null; +} + +inline bool Value::IsBoolean() const { + return Type() == napi_boolean; +} + +inline bool Value::IsNumber() const { + return Type() == napi_number; +} + +#if NAPI_VERSION > 5 +inline bool Value::IsBigInt() const { + return Type() == napi_bigint; +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +inline bool Value::IsDate() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_date(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + +inline bool Value::IsString() const { + return Type() == napi_string; +} + +inline bool Value::IsSymbol() const { + return Type() == napi_symbol; +} + +inline bool Value::IsArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_array(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsArrayBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_arraybuffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsTypedArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_typedarray(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsObject() const { + return Type() == napi_object || IsFunction(); +} + +inline bool Value::IsFunction() const { + return Type() == napi_function; +} + +inline bool Value::IsPromise() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_promise(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsDataView() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_dataview(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_buffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsExternal() const { + return Type() == napi_external; +} + +template +inline T Value::As() const { +#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS + T::CheckCast(_env, _value); +#endif + return T(_env, _value); +} + +template +inline T Value::UnsafeAs() const { + return T(_env, _value); +} + +// static +inline void Value::CheckCast(napi_env /* env */, napi_value value) { + NAPI_CHECK(value != nullptr, "Value::CheckCast", "empty value"); +} + +inline MaybeOrValue Value::ToBoolean() const { + napi_value result; + napi_status status = napi_coerce_to_bool(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); +} + +inline MaybeOrValue Value::ToNumber() const { + napi_value result; + napi_status status = napi_coerce_to_number(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); +} + +inline MaybeOrValue Value::ToString() const { + napi_value result; + napi_status status = napi_coerce_to_string(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); +} + +inline MaybeOrValue Value::ToObject() const { + napi_value result; + napi_status status = napi_coerce_to_object(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Boolean class +//////////////////////////////////////////////////////////////////////////////// + +inline Boolean Boolean::New(napi_env env, bool val) { + napi_value value; + napi_status status = napi_get_boolean(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Boolean()); + return Boolean(env, value); +} + +inline void Boolean::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_boolean, "%d", "Boolean::CheckCast"); +} + +inline Boolean::Boolean() : Napi::Value() {} + +inline Boolean::Boolean(napi_env env, napi_value value) + : Napi::Value(env, value) {} + +inline Boolean::operator bool() const { + return Value(); +} + +inline bool Boolean::Value() const { + bool result; + napi_status status = napi_get_value_bool(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Number class +//////////////////////////////////////////////////////////////////////////////// + +inline Number Number::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_double(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Number()); + return Number(env, value); +} + +inline void Number::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_number, "%d", "Number::CheckCast"); +} + +inline Number::Number() : Value() {} + +inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} + +inline Number::operator int32_t() const { + return Int32Value(); +} + +inline Number::operator uint32_t() const { + return Uint32Value(); +} + +inline Number::operator int64_t() const { + return Int64Value(); +} + +inline Number::operator float() const { + return FloatValue(); +} + +inline Number::operator double() const { + return DoubleValue(); +} + +inline int32_t Number::Int32Value() const { + int32_t result; + napi_status status = napi_get_value_int32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint32_t Number::Uint32Value() const { + uint32_t result; + napi_status status = napi_get_value_uint32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline int64_t Number::Int64Value() const { + int64_t result; + napi_status status = napi_get_value_int64(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline float Number::FloatValue() const { + return static_cast(DoubleValue()); +} + +inline double Number::DoubleValue() const { + double result; + napi_status status = napi_get_value_double(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// BigInt Class +//////////////////////////////////////////////////////////////////////////////// + +inline BigInt BigInt::New(napi_env env, int64_t val) { + napi_value value; + napi_status status = napi_create_bigint_int64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, uint64_t val) { + napi_value value; + napi_status status = napi_create_bigint_uint64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words) { + napi_value value; + napi_status status = + napi_create_bigint_words(env, sign_bit, word_count, words, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline void BigInt::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_bigint, "%d", "BigInt::CheckCast"); +} + +inline BigInt::BigInt() : Value() {} + +inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} + +inline int64_t BigInt::Int64Value(bool* lossless) const { + int64_t result; + napi_status status = + napi_get_value_bigint_int64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint64_t BigInt::Uint64Value(bool* lossless) const { + uint64_t result; + napi_status status = + napi_get_value_bigint_uint64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline size_t BigInt::WordCount() const { + size_t word_count; + napi_status status = + napi_get_value_bigint_words(_env, _value, nullptr, &word_count, nullptr); + NAPI_THROW_IF_FAILED(_env, status, 0); + return word_count; +} + +inline void BigInt::ToWords(int* sign_bit, + size_t* word_count, + uint64_t* words) { + napi_status status = + napi_get_value_bigint_words(_env, _value, sign_bit, word_count, words); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +//////////////////////////////////////////////////////////////////////////////// +// Date Class +//////////////////////////////////////////////////////////////////////////////// + +inline Date Date::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_date(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Date()); + return Date(env, value); +} + +inline void Date::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_date(env, value, &result); + NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); + NAPI_CHECK(result, "Date::CheckCast", "value is not date"); +} + +inline Date::Date() : Value() {} + +inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} + +inline Date::operator double() const { + return ValueOf(); +} + +inline double Date::ValueOf() const { + double result; + napi_status status = napi_get_date_value(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Name class +//////////////////////////////////////////////////////////////////////////////// +inline void Name::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol, got %d.", + type); +} + +inline Name::Name() : Value() {} + +inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// String class +//////////////////////////////////////////////////////////////////////////////// + +inline String String::New(napi_env env, const std::string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const std::u16string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const char* val) { + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_value value; + napi_status status = + napi_create_string_utf8(env, val, std::strlen(val), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val) { + napi_value value; + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_status status = + napi_create_string_utf16(env, val, std::u16string(val).size(), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf8(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf16(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline void String::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_string, "%d", "String::CheckCast"); +} + +inline String::String() : Name() {} + +inline String::String(napi_env env, napi_value value) : Name(env, value) {} + +inline String::operator std::string() const { + return Utf8Value(); +} + +inline String::operator std::u16string() const { + return Utf16Value(); +} + +inline std::string String::Utf8Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, ""); + + std::string value; + value.reserve(length + 1); + value.resize(length); + status = napi_get_value_string_utf8( + _env, _value, &value[0], value.capacity(), nullptr); + NAPI_THROW_IF_FAILED(_env, status, ""); + return value; +} + +inline std::u16string String::Utf16Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + + std::u16string value; + value.reserve(length + 1); + value.resize(length); + status = napi_get_value_string_utf16( + _env, _value, &value[0], value.capacity(), nullptr); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Symbol class +//////////////////////////////////////////////////////////////////////////////// + +inline Symbol Symbol::New(napi_env env, const char* description) { + napi_value descriptionValue = description != nullptr + ? String::New(env, description) + : static_cast(nullptr); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, String description) { + napi_value descriptionValue = description; + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, napi_value description) { + napi_value value; + napi_status status = napi_create_symbol(env, description, &value); + NAPI_THROW_IF_FAILED(env, status, Symbol()); + return Symbol(env, value); +} + +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { + // No need to check if the return value is a symbol or undefined. + // Well known symbols are definite and it is an develop time error + // if the symbol does not exist. +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.UnsafeAs()); + } + return Nothing(); +#else + return Napi::Env(env) + .Global() + .Get("Symbol") + .As() + .Get(name) + .UnsafeAs(); +#endif +} + +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif +} + +inline void Symbol::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_symbol, "%d", "Symbol::CheckCast"); +} + +inline Symbol::Symbol() : Name() {} + +inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// Automagic value creation +//////////////////////////////////////////////////////////////////////////////// + +namespace details { +template +struct vf_number { + static Number From(napi_env env, T value) { + return Number::New(env, static_cast(value)); + } +}; + +template <> +struct vf_number { + static Boolean From(napi_env env, bool value) { + return Boolean::New(env, value); + } +}; + +struct vf_utf8_charp { + static String From(napi_env env, const char* value) { + return String::New(env, value); + } +}; + +struct vf_utf16_charp { + static String From(napi_env env, const char16_t* value) { + return String::New(env, value); + } +}; +struct vf_utf8_string { + static String From(napi_env env, const std::string& value) { + return String::New(env, value); + } +}; + +struct vf_utf16_string { + static String From(napi_env env, const std::u16string& value) { + return String::New(env, value); + } +}; + +template +struct vf_fallback { + static Value From(napi_env env, const T& value) { return Value(env, value); } +}; + +template +struct disjunction : std::false_type {}; +template +struct disjunction : B {}; +template +struct disjunction + : std::conditional>::type {}; + +template +struct can_make_string + : disjunction::type, + typename std::is_convertible::type, + typename std::is_convertible::type, + typename std::is_convertible::type> {}; +} // namespace details + +template +Value Value::From(napi_env env, const T& value) { + using Helper = typename std::conditional< + std::is_integral::value || std::is_floating_point::value, + details::vf_number, + typename std::conditional::value, + String, + details::vf_fallback>::type>::type; + return Helper::From(env, value); +} + +template +String String::From(napi_env env, const T& value) { + struct Dummy {}; + using Helper = typename std::conditional< + std::is_convertible::value, + details::vf_utf8_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf8_string, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_string, + Dummy>::type>::type>::type>::type; + return Helper::From(env, value); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypeTaggable class +//////////////////////////////////////////////////////////////////////////////// + +inline TypeTaggable::TypeTaggable() : Value() {} + +inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value) + : Value(_env, _value) {} + +#if NAPI_VERSION >= 8 + +inline void TypeTaggable::TypeTag(const napi_type_tag* type_tag) const { + napi_status status = napi_type_tag_object(_env, _value, type_tag); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const { + bool result; + napi_status status = + napi_check_object_type_tag(_env, _value, type_tag, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// Object class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object::PropertyLValue::operator Value() const { + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif +} + +template +template +inline Object::PropertyLValue& Object::PropertyLValue::operator=( + ValueType value) { +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif + return *this; +} + +template +inline Value Object::PropertyLValue::AsValue() const { + return Value(*this); +} + +template +inline Object::PropertyLValue::PropertyLValue(Object object, Key key) + : _env(object.Env()), _object(object), _key(key) {} + +inline Object Object::New(napi_env env) { + napi_value value; + napi_status status = napi_create_object(env, &value); + NAPI_THROW_IF_FAILED(env, status, Object()); + return Object(env, value); +} + +inline void Object::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_object || type == napi_function, + "Object::CheckCast", + "Expect napi_object or napi_function, but got %d.", + type); +} + +inline Object::Object() : TypeTaggable() {} + +inline Object::Object(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +inline Object::PropertyLValue Object::operator[]( + const char* utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[]( + const std::string& utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[](uint32_t index) { + return PropertyLValue(*this, index); +} + +inline Object::PropertyLValue Object::operator[](Value index) const { + return PropertyLValue(*this, index); +} + +inline MaybeOrValue Object::operator[](const char* utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[](uint32_t index) const { + return Get(index); +} + +inline MaybeOrValue Object::Has(napi_value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(Value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const char* utf8name) const { + bool result; + napi_status status = napi_has_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const std::string& utf8name) const { + return Has(utf8name.c_str()); +} + +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(Value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { + napi_value key; + napi_status status = + napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); + return HasOwnProperty(key); +} + +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { + return HasOwnProperty(utf8name.c_str()); +} + +inline MaybeOrValue Object::Get(napi_value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(Value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const char* utf8name) const { + napi_value result; + napi_status status = napi_get_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const std::string& utf8name) const { + return Get(utf8name.c_str()); +} + +template +inline MaybeOrValue Object::Set(napi_value key, + const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(Value key, const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) const { + napi_status status = + napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) const { + return Set(utf8name.c_str(), value); +} + +inline MaybeOrValue Object::Delete(napi_value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(Value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(const char* utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Delete(const std::string& utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Has(uint32_t index) const { + bool result; + napi_status status = napi_has_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Get(uint32_t index) const { + napi_value value; + napi_status status = napi_get_element(_env, _value, index, &value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); +} + +template +inline MaybeOrValue Object::Set(uint32_t index, + const ValueType& value) const { + napi_status status = + napi_set_element(_env, _value, index, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Delete(uint32_t index) const { + bool result; + napi_status status = napi_delete_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::GetPropertyNames() const { + napi_value result; + napi_status status = napi_get_property_names(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); +} + +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) const { + napi_status status = napi_define_properties( + _env, + _value, + 1, + reinterpret_cast(&property)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::initializer_list& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.begin())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::vector& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.data())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { + bool result; + napi_status status = napi_instanceof(_env, _value, constructor, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + details::AttachData::Wrapper>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = details:: + AttachData::WrapperWithHint>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +inline Object::const_iterator::const_iterator(const Object* object, + const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::const_iterator Napi::Object::begin() const { + const_iterator it(this, Object::const_iterator::Type::BEGIN); + return it; +} + +inline Object::const_iterator Napi::Object::end() const { + const_iterator it(this, Object::const_iterator::Type::END); + return it; +} + +inline Object::const_iterator& Object::const_iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::const_iterator::operator==( + const const_iterator& other) const { + return _index == other._index; +} + +inline bool Object::const_iterator::operator!=( + const const_iterator& other) const { + return _index != other._index; +} + +inline const std::pair> +Object::const_iterator::operator*() const { + const Value key = _keys[_index]; + const PropertyLValue value = (*_object)[key]; + return {key, value}; +} + +inline Object::iterator::iterator(Object* object, const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::iterator Napi::Object::begin() { + iterator it(this, Object::iterator::Type::BEGIN); + return it; +} + +inline Object::iterator Napi::Object::end() { + iterator it(this, Object::iterator::Type::END); + return it; +} + +inline Object::iterator& Object::iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::iterator::operator==(const iterator& other) const { + return _index == other._index; +} + +inline bool Object::iterator::operator!=(const iterator& other) const { + return _index != other._index; +} + +inline std::pair> +Object::iterator::operator*() { + Value key = _keys[_index]; + PropertyLValue value = (*_object)[key]; + return {key, value}; +} +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 +inline MaybeOrValue Object::Freeze() const { + napi_status status = napi_object_freeze(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Seal() const { + napi_status status = napi_object_seal(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// External class +//////////////////////////////////////////////////////////////////////////////// + +template +inline External External::New(napi_env env, T* data) { + napi_value value; + napi_status status = + napi_create_external(env, data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, External()); + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external(env, + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external( + env, + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +inline void External::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_external, "%d", "External::CheckCast"); +} + +template +inline External::External() : TypeTaggable() {} + +template +inline External::External(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +template +inline T* External::Data() const { + void* data; + napi_status status = napi_get_value_external(_env, _value, &data); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return reinterpret_cast(data); +} + +//////////////////////////////////////////////////////////////////////////////// +// Array class +//////////////////////////////////////////////////////////////////////////////// + +inline Array Array::New(napi_env env) { + napi_value value; + napi_status status = napi_create_array(env, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline Array Array::New(napi_env env, size_t length) { + napi_value value; + napi_status status = napi_create_array_with_length(env, length, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline void Array::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_array(env, value, &result); + NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); + NAPI_CHECK(result, "Array::CheckCast", "value is not array"); +} + +inline Array::Array() : Object() {} + +inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} + +inline uint32_t Array::Length() const { + uint32_t result; + napi_status status = napi_get_array_length(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// ArrayBuffer class +//////////////////////////////////////////////////////////////////////////////// + +inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { + napi_value value; + void* data; + napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength) { + napi_value value; + napi_status status = napi_create_external_arraybuffer( + env, externalData, byteLength, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_arraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "ArrayBuffer::CheckCast", + "napi_is_arraybuffer failed"); + NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); +} + +inline ArrayBuffer::ArrayBuffer() : Object() {} + +inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) + : Object(env, value) {} + +inline void* ArrayBuffer::Data() { + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; +} + +inline size_t ArrayBuffer::ByteLength() { + size_t length; + napi_status status = + napi_get_arraybuffer_info(_env, _value, nullptr, &length); + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; +} + +#if NAPI_VERSION >= 7 +inline bool ArrayBuffer::IsDetached() const { + bool detached; + napi_status status = napi_is_detached_arraybuffer(_env, _value, &detached); + NAPI_THROW_IF_FAILED(_env, status, false); + return detached; +} + +inline void ArrayBuffer::Detach() { + napi_status status = napi_detach_arraybuffer(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION >= 7 + +//////////////////////////////////////////////////////////////////////////////// +// DataView class +//////////////////////////////////////////////////////////////////////////////// +inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); + } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} + +inline void DataView::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_dataview(env, value, &result); + NAPI_CHECK( + status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); + NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); +} + +inline DataView::DataView() : Object() {} + +inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + &_length /* byteLength */, + &_data /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + &arrayBuffer /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +inline size_t DataView::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + &byteOffset /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t DataView::ByteLength() const { + return _length; +} + +inline void* DataView::Data() const { + return _data; +} + +inline float DataView::GetFloat32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline double DataView::GetFloat64(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int8_t DataView::GetInt8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int16_t DataView::GetInt16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int32_t DataView::GetInt32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint8_t DataView::GetUint8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint16_t DataView::GetUint16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint32_t DataView::GetUint32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline void DataView::SetFloat32(size_t byteOffset, float value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetFloat64(size_t byteOffset, double value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt8(size_t byteOffset, int8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt16(size_t byteOffset, int16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt32(size_t byteOffset, int32_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const { + WriteData(byteOffset, value); +} + +template +inline T DataView::ReadData(size_t byteOffset) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW( + RangeError::New(_env, "Offset is outside the bounds of the DataView"), + 0); + } + + return *reinterpret_cast(static_cast(_data) + byteOffset); +} + +template +inline void DataView::WriteData(size_t byteOffset, T value) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW_VOID( + RangeError::New(_env, "Offset is outside the bounds of the DataView")); + } + + *reinterpret_cast(static_cast(_data) + byteOffset) = value; +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArray class +//////////////////////////////////////////////////////////////////////////////// +inline void TypedArray::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_typedarray(env, value, &result); + NAPI_CHECK( + status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); + NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); +} + +inline TypedArray::TypedArray() + : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} + +inline TypedArray::TypedArray(napi_env env, napi_value value) + : Object(env, value), + _type(napi_typedarray_type::napi_int8_array), + _length(0) { + if (value != nullptr) { + napi_status status = + napi_get_typedarray_info(_env, + _value, + &const_cast(this)->_type, + &const_cast(this)->_length, + nullptr, + nullptr, + nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline TypedArray::TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length) + : Object(env, value), _type(type), _length(length) {} + +inline napi_typedarray_type TypedArray::TypedArrayType() const { + return _type; +} + +inline uint8_t TypedArray::ElementSize() const { + switch (_type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: +#if (NAPI_VERSION > 5) + case napi_bigint64_array: + case napi_biguint64_array: +#endif // (NAPI_VERSION > 5) + return 8; + default: + return 0; + } +} + +inline size_t TypedArray::ElementLength() const { + return _length; +} + +inline size_t TypedArray::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t TypedArray::ByteLength() const { + return ElementSize() * ElementLength(); +} + +inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArrayOf class +//////////////////////////////////////////////////////////////////////////////// +template +inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { + TypedArray::CheckCast(env, value); + napi_typedarray_type type; + napi_status status = napi_get_typedarray_info( + env, value, &type, nullptr, nullptr, nullptr, nullptr); + NAPI_CHECK(status == napi_ok, + "TypedArrayOf::CheckCast", + "napi_is_typedarray failed"); + + NAPI_INTERNAL_CHECK( + (type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && std::is_same::value)), + "TypedArrayOf::CheckCast", + "Array type must match the template parameter, (Uint8 arrays may " + "optionally have the \"clamped\" array type.), got %d.", + type); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + napi_typedarray_type type) { + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, elementLength * sizeof(T)); + return New(env, elementLength, arrayBuffer, 0, type); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::ArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} + +template +inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) + : TypedArray(env, value), _data(nullptr) { + napi_status status = napi_ok; + if (value != nullptr) { + void* data = nullptr; + status = napi_get_typedarray_info( + _env, _value, &_type, &_length, &data, nullptr, nullptr); + _data = static_cast(data); + } else { + _type = TypedArrayTypeForPrimitiveType(); + _length = 0; + } + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data) + : TypedArray(env, value, type, length), _data(data) { + if (!(type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && + std::is_same::value))) { + NAPI_THROW_VOID(TypeError::New( + env, + "Array type must match the template parameter. " + "(Uint8 arrays may optionally have the \"clamped\" array type.)")); + } +} + +template +inline T& TypedArrayOf::operator[](size_t index) { + return _data[index]; +} + +template +inline const T& TypedArrayOf::operator[](size_t index) const { + return _data[index]; +} + +template +inline T* TypedArrayOf::Data() { + return _data; +} + +template +inline const T* TypedArrayOf::Data() const { + return _data; +} + +//////////////////////////////////////////////////////////////////////////////// +// Function class +//////////////////////////////////////////////////////////////////////////////// + +template +inline napi_status CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { + napi_status status = + napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); + if (status == napi_ok) { + status = Napi::details::AttachData(env, *result, data); + } + + return status; +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedVoidCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const char* utf8name, + void* data) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + auto callbackData = new CbData{std::move(cb), data}; + + napi_value value; + napi_status status = + CreateFunction(env, utf8name, CbData::Wrapper, callbackData, &value); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, Function()); + } + + return Function(env, value); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data) { + return New(env, cb, utf8name.c_str(), data); +} + +inline void Function::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_function, "%d", "Function::CheckCast"); +} + +inline Function::Function() : Object() {} + +inline Function::Function(napi_env env, napi_value value) + : Object(env, value) {} + +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { + return Call(Env().Undefined(), argc, args); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { + return Call(recv, args.size(), args.begin()); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + return Call(recv, args.size(), args.data()); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + const size_t argc = args.size(); + const size_t stackArgsCount = 6; + napi_value stackArgs[stackArgsCount]; + std::vector heapArgs; + napi_value* argv; + if (argc <= stackArgsCount) { + argv = stackArgs; + } else { + heapArgs.resize(argc); + argv = heapArgs.data(); + } + + for (size_t index = 0; index < argc; index++) { + argv[index] = static_cast(args[index]); + } + + return Call(recv, argc, argv); +} + +inline MaybeOrValue Function::Call(napi_value recv, + size_t argc, + const napi_value* args) const { + napi_value result; + napi_status status = + napi_call_function(_env, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.begin(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.data(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + napi_value result; + napi_status status = + napi_make_callback(_env, context, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { + return New(args.size(), args.begin()); +} + +inline MaybeOrValue Function::New( + const std::vector& args) const { + return New(args.size(), args.data()); +} + +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { + napi_value result; + napi_status status = napi_new_instance(_env, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Promise class +//////////////////////////////////////////////////////////////////////////////// + +inline Promise::Deferred Promise::Deferred::New(napi_env env) { + return Promise::Deferred(env); +} + +inline Promise::Deferred::Deferred(napi_env env) : _env(env) { + napi_status status = napi_create_promise(_env, &_deferred, &_promise); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Promise Promise::Deferred::Promise() const { + return Napi::Promise(_env, _promise); +} + +inline Napi::Env Promise::Deferred::Env() const { + return Napi::Env(_env); +} + +inline void Promise::Deferred::Resolve(napi_value value) const { + napi_status status = napi_resolve_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::Deferred::Reject(napi_value value) const { + napi_status status = napi_reject_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_promise(env, value, &result); + NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); + NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); +} + +inline Promise::Promise() : Object() {} + +inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = thenMethod.Call(*this, {onFulfilled}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled, + napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled, onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = + thenMethod.Call(*this, {onFulfilled, onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Catch(napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value catchMethod; + if (!Get("catch").UnwrapTo(&catchMethod)) { + return Nothing(); + } + MaybeOrValue result = + catchMethod.As().Call(*this, {onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function catchMethod = Get("catch").As(); + MaybeOrValue result = catchMethod.Call(*this, {onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled) const { + return Then(static_cast(onFulfilled)); +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled, + const Function& onRejected) const { + return Then(static_cast(onFulfilled), + static_cast(onRejected)); +} + +inline MaybeOrValue Promise::Catch(const Function& onRejected) const { + return Catch(static_cast(onRejected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Buffer class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Buffer Buffer::New(napi_env env, size_t length) { + napi_value value; + void* data; + napi_status status = + napi_create_buffer(env, length * sizeof(T), &data, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +template +inline Buffer Buffer::New(napi_env env, T* data, size_t length) { + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +template +inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + return Buffer::Copy(env, data, length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGC(env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGCWithHint( + env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif +} + +template +inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { + napi_value value; + napi_status status = + napi_create_buffer_copy(env, length * sizeof(T), data, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +inline void Buffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_buffer(env, value, &result); + NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); + NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); +} + +template +inline Buffer::Buffer() : Uint8Array() {} + +template +inline Buffer::Buffer(napi_env env, napi_value value) + : Uint8Array(env, value) {} + +template +inline size_t Buffer::Length() const { + return ByteLength() / sizeof(T); +} + +template +inline T* Buffer::Data() const { + return reinterpret_cast(const_cast(Uint8Array::Data())); +} + +//////////////////////////////////////////////////////////////////////////////// +// Error class +//////////////////////////////////////////////////////////////////////////////// + +inline Error Error::New(napi_env env) { + napi_status status; + napi_value error = nullptr; + bool is_exception_pending; + napi_extended_error_info last_error_info_copy; + + { + // We must retrieve the last error info before doing anything else because + // doing anything else will replace the last error info. + const napi_extended_error_info* last_error_info; + status = napi_get_last_error_info(env, &last_error_info); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); + + // All fields of the `napi_extended_error_info` structure gets reset in + // subsequent Node-API function calls on the same `env`. This includes a + // call to `napi_is_exception_pending()`. So here it is necessary to make a + // copy of the information as the `error_code` field is used later on. + memcpy(&last_error_info_copy, + last_error_info, + sizeof(napi_extended_error_info)); + } + + status = napi_is_exception_pending(env, &is_exception_pending); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); + + // A pending exception takes precedence over any internal error status. + if (is_exception_pending) { + status = napi_get_and_clear_last_exception(env, &error); + NAPI_FATAL_IF_FAILED( + status, "Error::New", "napi_get_and_clear_last_exception"); + } else { + const char* error_message = last_error_info_copy.error_message != nullptr + ? last_error_info_copy.error_message + : "Error in native callback"; + + napi_value message; + status = napi_create_string_utf8( + env, error_message, std::strlen(error_message), &message); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); + + switch (last_error_info_copy.error_code) { + case napi_object_expected: + case napi_string_expected: + case napi_boolean_expected: + case napi_number_expected: + status = napi_create_type_error(env, nullptr, message, &error); + break; + default: + status = napi_create_error(env, nullptr, message, &error); + break; + } + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); + } + + return Error(env, error); +} + +inline Error Error::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_error); +} + +inline Error Error::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_error); +} + +inline NAPI_NO_RETURN void Error::Fatal(const char* location, + const char* message) { + napi_fatal_error(location, NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH); +} + +inline Error::Error() : ObjectReference() {} + +inline Error::Error(napi_env env, napi_value value) + : ObjectReference(env, nullptr) { + if (value != nullptr) { + // Attempting to create a reference on the error object. + // If it's not a Object/Function/Symbol, this call will return an error + // status. + napi_status status = napi_create_reference(env, value, 1, &_ref); + + if (status != napi_ok) { + napi_value wrappedErrorObj; + + // Create an error object + status = napi_create_object(env, &wrappedErrorObj); + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); + + // property flag that we attach to show the error object is wrapped + napi_property_descriptor wrapObjFlag = { + ERROR_WRAP_VALUE(), // Unique GUID identifier since Symbol isn't a + // viable option + nullptr, + nullptr, + nullptr, + nullptr, + Value::From(env, value), + napi_enumerable, + nullptr}; + + status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + if (status == napi_pending_exception) { + // Test if the pending exception was reported because the environment is + // shutting down. We assume that a status of napi_pending_exception + // coupled with the absence of an actual pending exception means that + // the environment is shutting down. If so, we replace the + // napi_pending_exception status with napi_ok. + bool is_exception_pending = false; + status = napi_is_exception_pending(env, &is_exception_pending); + if (status == napi_ok && !is_exception_pending) { + status = napi_ok; + } else { + status = napi_pending_exception; + } + } +#endif // NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); + + // Create a reference on the newly wrapped object + status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); + } + + // Avoid infinite recursion in the failure case. + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); + } +} + +inline Object Error::Value() const { + if (_ref == nullptr) { + return Object(_env, nullptr); + } + + napi_value refValue; + napi_status status = napi_get_reference_value(_env, _ref, &refValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + napi_valuetype type; + status = napi_typeof(_env, refValue, &type); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + // If refValue isn't a symbol, then we proceed to whether the refValue has the + // wrapped error flag + if (type != napi_symbol) { + // We are checking if the object is wrapped + bool isWrappedObject = false; + + status = napi_has_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &isWrappedObject); + + // Don't care about status + if (isWrappedObject) { + napi_value unwrappedValue; + status = napi_get_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &unwrappedValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + return Object(_env, unwrappedValue); + } + } + + return Object(_env, refValue); +} + +inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {} + +inline Error& Error::operator=(Error&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline Error::Error(const Error& other) : ObjectReference(other) {} + +inline Error& Error::operator=(const Error& other) { + Reset(); + + _env = other.Env(); + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_THROW_IF_FAILED(_env, status, *this); + } + + return *this; +} + +inline const std::string& Error::Message() const NAPI_NOEXCEPT { + if (_message.size() == 0 && _env != nullptr) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + _message = Get("message").As(); + } catch (...) { + // Catch all errors here, to include e.g. a std::bad_alloc from + // the std::string::operator=, because this method may not throw. + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else + _message = Get("message").As(); +#endif +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + } + return _message; +} + +// we created an object on the &_ref +inline void Error::ThrowAsJavaScriptException() const { + HandleScope scope(_env); + if (!IsEmpty()) { +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + bool pendingException = false; + + // check if there is already a pending exception. If so don't try to throw a + // new one as that is not allowed/possible + napi_status status = napi_is_exception_pending(_env, &pendingException); + + if ((status != napi_ok) || + ((status == napi_ok) && (pendingException == false))) { + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + status = napi_throw(_env, Value()); + +#if (NAPI_VERSION >= 10) + napi_status expected_failure_mode = napi_cannot_run_js; +#else + napi_status expected_failure_mode = napi_pending_exception; +#endif + if (status == expected_failure_mode) { + // The environment must be terminating as we checked earlier and there + // was no pending exception. In this case continuing will result + // in a fatal error and there is nothing the author has done incorrectly + // in their code that is worth flagging through a fatal error + return; + } + } else { + status = napi_pending_exception; + } +#else + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + napi_status status = napi_throw(_env, Value()); +#endif + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + if (status != napi_ok) { + throw Error::New(_env); + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS + NAPI_FATAL_IF_FAILED( + status, "Error::ThrowAsJavaScriptException", "napi_throw"); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + } +} + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + +inline const char* Error::what() const NAPI_NOEXCEPT { + return Message().c_str(); +} + +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { + return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; +} + +template +inline TError Error::New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error) { + napi_value str; + napi_status status = napi_create_string_utf8(env, message, length, &str); + NAPI_THROW_IF_FAILED(env, status, TError()); + + napi_value error; + status = create_error(env, nullptr, str, &error); + NAPI_THROW_IF_FAILED(env, status, TError()); + + return TError(env, error); +} + +inline TypeError TypeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_type_error); +} + +inline TypeError TypeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_type_error); +} + +inline TypeError::TypeError() : Error() {} + +inline TypeError::TypeError(napi_env env, napi_value value) + : Error(env, value) {} + +inline RangeError RangeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_range_error); +} + +inline RangeError RangeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_range_error); +} + +inline RangeError::RangeError() : Error() {} + +inline RangeError::RangeError(napi_env env, napi_value value) + : Error(env, value) {} + +#if NAPI_VERSION > 8 +inline SyntaxError SyntaxError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), node_api_create_syntax_error); +} + +inline SyntaxError SyntaxError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), node_api_create_syntax_error); +} + +inline SyntaxError::SyntaxError() : Error() {} + +inline SyntaxError::SyntaxError(napi_env env, napi_value value) + : Error(env, value) {} +#endif // NAPI_VERSION > 8 + +//////////////////////////////////////////////////////////////////////////////// +// Reference class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Reference Reference::New(const T& value, + uint32_t initialRefcount) { + napi_env env = value.Env(); + napi_value val = value; + + if (val == nullptr) { + return Reference(env, nullptr); + } + + napi_ref ref; + napi_status status = napi_create_reference(env, value, initialRefcount, &ref); + NAPI_THROW_IF_FAILED(env, status, Reference()); + + return Reference(env, ref); +} + +template +inline Reference::Reference() + : _env(nullptr), _ref(nullptr), _suppressDestruct(false) {} + +template +inline Reference::Reference(napi_env env, napi_ref ref) + : _env(env), _ref(ref), _suppressDestruct(false) {} + +template +inline Reference::~Reference() { + if (_ref != nullptr) { + if (!_suppressDestruct) { + // TODO(legendecas): napi_delete_reference should be invoked immediately. + // Fix this when https://github.com/nodejs/node/pull/55620 lands. +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + Env().PostFinalizer( + [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); }, + _ref); +#else + napi_delete_reference(_env, _ref); +#endif + } + + _ref = nullptr; + } +} + +template +inline Reference::Reference(Reference&& other) + : _env(other._env), + _ref(other._ref), + _suppressDestruct(other._suppressDestruct) { + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; +} + +template +inline Reference& Reference::operator=(Reference&& other) { + Reset(); + _env = other._env; + _ref = other._ref; + _suppressDestruct = other._suppressDestruct; + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; + return *this; +} + +template +inline Reference::Reference(const Reference& other) + : _env(other._env), _ref(nullptr), _suppressDestruct(false) { + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + // Copying is a limited scenario (currently only used for Error object) and + // always creates a strong reference to the given value even if the incoming + // reference is weak. + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_FATAL_IF_FAILED( + status, "Reference::Reference", "napi_create_reference"); + } +} + +template +inline Reference::operator napi_ref() const { + return _ref; +} + +template +inline bool Reference::operator==(const Reference& other) const { + HandleScope scope(_env); + return this->Value().StrictEquals(other.Value()); +} + +template +inline bool Reference::operator!=(const Reference& other) const { + return !this->operator==(other); +} + +template +inline Napi::Env Reference::Env() const { + return Napi::Env(_env); +} + +template +inline bool Reference::IsEmpty() const { + return _ref == nullptr; +} + +template +inline T Reference::Value() const { + if (_ref == nullptr) { + return T(_env, nullptr); + } + + napi_value value; + napi_status status = napi_get_reference_value(_env, _ref, &value); + NAPI_THROW_IF_FAILED(_env, status, T()); + return T(_env, value); +} + +template +inline uint32_t Reference::Ref() const { + uint32_t result; + napi_status status = napi_reference_ref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline uint32_t Reference::Unref() const { + uint32_t result; + napi_status status = napi_reference_unref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline void Reference::Reset() { + if (_ref != nullptr) { + napi_status status = napi_delete_reference(_env, _ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + _ref = nullptr; + } +} + +template +inline void Reference::Reset(const T& value, uint32_t refcount) { + Reset(); + _env = value.Env(); + + napi_value val = value; + if (val != nullptr) { + napi_status status = napi_create_reference(_env, value, refcount, &_ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Reference::SuppressDestruct() { + _suppressDestruct = true; +} + +template +inline Reference Weak(T value) { + return Reference::New(value, 0); +} + +inline ObjectReference Weak(Object value) { + return Reference::New(value, 0); +} + +inline FunctionReference Weak(Function value) { + return Reference::New(value, 0); +} + +template +inline Reference Persistent(T value) { + return Reference::New(value, 1); +} + +inline ObjectReference Persistent(Object value) { + return Reference::New(value, 1); +} + +inline FunctionReference Persistent(Function value) { + return Reference::New(value, 1); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectReference class +//////////////////////////////////////////////////////////////////////////////// + +inline ObjectReference::ObjectReference() : Reference() {} + +inline ObjectReference::ObjectReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline ObjectReference::ObjectReference(Reference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(ObjectReference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(ObjectReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(const ObjectReference& other) + : Reference(other) {} + +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set( + uint32_t index, const std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(index, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(index, numberValue); +} + +//////////////////////////////////////////////////////////////////////////////// +// FunctionReference class +//////////////////////////////////////////////////////////////////////////////// + +inline FunctionReference::FunctionReference() : Reference() {} + +inline FunctionReference::FunctionReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline FunctionReference::FunctionReference(Reference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline FunctionReference::FunctionReference(FunctionReference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + FunctionReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline MaybeOrValue FunctionReference::operator()( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, size_t argc, const napi_value* args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// CallbackInfo class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) + : _env(env), + _info(info), + _this(nullptr), + _dynamicArgs(nullptr), + _data(nullptr) { + _argc = _staticArgCount; + _argv = _staticArgs; + napi_status status = + napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + if (_argc > _staticArgCount) { + // Use either a fixed-size array (on the stack) or a dynamically-allocated + // array (on the heap) depending on the number of args. + _dynamicArgs = new napi_value[_argc]; + _argv = _dynamicArgs; + + status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline CallbackInfo::~CallbackInfo() { + if (_dynamicArgs != nullptr) { + delete[] _dynamicArgs; + } +} + +inline CallbackInfo::operator napi_callback_info() const { + return _info; +} + +inline Value CallbackInfo::NewTarget() const { + napi_value newTarget; + napi_status status = napi_get_new_target(_env, _info, &newTarget); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, newTarget); +} + +inline bool CallbackInfo::IsConstructCall() const { + return !NewTarget().IsEmpty(); +} + +inline Napi::Env CallbackInfo::Env() const { + return Napi::Env(_env); +} + +inline size_t CallbackInfo::Length() const { + return _argc; +} + +inline const Value CallbackInfo::operator[](size_t index) const { + return index < _argc ? Value(_env, _argv[index]) : Env().Undefined(); +} + +inline Value CallbackInfo::This() const { + if (_this == nullptr) { + return Env().Undefined(); + } + return Object(_env, _this); +} + +inline void* CallbackInfo::Data() const { + return _data; +} + +inline void CallbackInfo::SetData(void* data) { + _data = data; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(env, object, utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor( + env, object, utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, utf8name, data), + attributes, + nullptr}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(env, object, utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({nullptr, + name, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, nullptr, data), + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + value, + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes) { + return Value(utf8name.c_str(), value, attributes); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + napi_value name, napi_value value, napi_property_attributes attributes) { + return PropertyDescriptor( + {nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + Name name, Napi::Value value, napi_property_attributes attributes) { + napi_value nameValue = name; + napi_value valueValue = value; + return PropertyDescriptor::Value(nameValue, valueValue, attributes); +} + +inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) + : _desc(desc) {} + +inline PropertyDescriptor::operator napi_property_descriptor&() { + return _desc; +} + +inline PropertyDescriptor::operator const napi_property_descriptor&() const { + return _desc; +} + +//////////////////////////////////////////////////////////////////////////////// +// InstanceWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline void InstanceWrap::AttachPropData( + napi_env env, napi_value value, const napi_property_descriptor* prop) { + napi_status status; + if (!(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->getter == T::InstanceGetterCallbackWrapper || + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } + } +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + if (instance) (instance->*cb)(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->getterCallback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->setterCallback; + if (instance) (instance->*cb)(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value InstanceWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { + napi_env env = callbackInfo.Env(); + napi_value wrapper = callbackInfo.This(); + napi_status status; + napi_ref ref; + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + NAPI_THROW_IF_FAILED_VOID(env, status); + + Reference* instanceRef = instance; + *instanceRef = Reference(env, ref); +} + +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty() && !_finalized) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty() && _construction_failed) { + napi_remove_wrap(Env(), object, nullptr); + } + } +} + +template +inline T* ObjectWrap::Unwrap(Object wrapper) { + void* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); + return static_cast(unwrapped); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else { + // InstanceWrap::AttachPropData is responsible for attaching the data + // of instance methods and accessors. + T::AttachPropData(env, value, prop); + } + } + + return Function(env, value); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::vector>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline Value ObjectWrap::OnCalledAsFunction( + const Napi::CallbackInfo& callbackInfo) { + NAPI_THROW( + TypeError::New(callbackInfo.Env(), + "Class constructors cannot be invoked without 'new'"), + Napi::Value()); +} + +template +inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} + +template +inline void ObjectWrap::Finalize(BasicEnv /*env*/) {} + +template +inline napi_value ObjectWrap::ConstructorCallbackWrapper( + napi_env env, napi_callback_info info) { + napi_value new_target; + napi_status status = napi_get_new_target(env, info, &new_target); + if (status != napi_ok) return nullptr; + + bool isConstructCall = (new_target != nullptr); + if (!isConstructCall) { + return details::WrapCallback( + env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); + } + + napi_value wrapper = details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + T* instance = new T(callbackInfo); +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + instance->_construction_failed = false; +#else + if (callbackInfo.Env().IsExceptionPending()) { + // We need to clear the exception so that removing the wrap might work. + Error e = callbackInfo.Env().GetAndClearPendingException(); + delete instance; + e.ThrowAsJavaScriptException(); + } else { + instance->_construction_failed = false; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + return callbackInfo.This(); + }); + + return wrapper; +} + +template +inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value ObjectWrap::StaticMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +inline void ObjectWrap::FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* /*hint*/) { + // If the child class does not override _any_ Finalize() method, `env` will be + // unused because of the constexpr guards. Explicitly reference it here to + // bypass compiler warnings. + (void)env; + T* instance = static_cast(data); + + // Prevent ~ObjectWrap from calling napi_remove_wrap. + // The instance->_ref should be deleted with napi_delete_reference in + // ~Reference. + instance->_finalized = true; + + // If class overrides the basic finalizer, execute it. + if constexpr (details::HasBasicFinalizer::value) { +#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + HandleScope scope(env); +#endif + + instance->Finalize(Napi::BasicEnv(env)); + } + + // If class overrides the (extended) finalizer, either schedule it or + // execute it immediately (depending on experimental features enabled). + if constexpr (details::HasExtendedFinalizer::value) { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + // In experimental, attach via node_api_post_finalizer. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + napi_status status = + node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr); + NAPI_FATAL_IF_FAILED(status, + "ObjectWrap::FinalizeCallback", + "node_api_post_finalizer failed"); +#else + // In non-experimental, this `FinalizeCallback` already executes from a + // non-basic environment. Execute the override directly. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + HandleScope scope(env); + PostFinalizeCallback(env, data, static_cast(nullptr)); +#endif + } + // If the instance does _not_ override the (extended) finalizer, delete the + // `T* instance` immediately. + else { + delete instance; + } +} + +template +inline void ObjectWrap::PostFinalizeCallback(napi_env env, + void* data, + void* /*hint*/) { + T* instance = static_cast(data); + instance->Finalize(Napi::Env(env)); + delete instance; +} + +template +template ::StaticSetterCallback method> +inline napi_value ObjectWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + const CallbackInfo cbInfo(env, info); + // MSVC requires to copy 'method' function pointer to a local variable + // before invoking it. + auto m = method; + m(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// HandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) + : _env(env), _scope(scope) {} + +inline HandleScope::HandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline HandleScope::~HandleScope() { + napi_status status = napi_close_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "HandleScope::~HandleScope", "napi_close_handle_scope"); +} + +inline HandleScope::operator napi_handle_scope() const { + return _scope; +} + +inline Napi::Env HandleScope::Env() const { + return Napi::Env(_env); +} + +//////////////////////////////////////////////////////////////////////////////// +// EscapableHandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline EscapableHandleScope::EscapableHandleScope( + napi_env env, napi_escapable_handle_scope scope) + : _env(env), _scope(scope) {} + +inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_escapable_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline EscapableHandleScope::~EscapableHandleScope() { + napi_status status = napi_close_escapable_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "EscapableHandleScope::~EscapableHandleScope", + "napi_close_escapable_handle_scope"); +} + +inline EscapableHandleScope::operator napi_escapable_handle_scope() const { + return _scope; +} + +inline Napi::Env EscapableHandleScope::Env() const { + return Napi::Env(_env); +} + +inline Value EscapableHandleScope::Escape(napi_value escapee) { + napi_value result; + napi_status status = napi_escape_handle(_env, _scope, escapee, &result); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, result); +} + +#if (NAPI_VERSION > 2) +//////////////////////////////////////////////////////////////////////////////// +// CallbackScope class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope) + : _env(env), _scope(scope) {} + +inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) + : _env(env) { + napi_status status = + napi_open_callback_scope(_env, Object::New(env), context, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline CallbackScope::~CallbackScope() { + napi_status status = napi_close_callback_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "CallbackScope::~CallbackScope", "napi_close_callback_scope"); +} + +inline CallbackScope::operator napi_callback_scope() const { + return _scope; +} + +inline Napi::Env CallbackScope::Env() const { + return Napi::Env(_env); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// AsyncContext class +//////////////////////////////////////////////////////////////////////////////// + +inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) + : AsyncContext(env, resource_name, Object::New(env)) {} + +inline AsyncContext::AsyncContext(napi_env env, + const char* resource_name, + const Object& resource) + : _env(env), _context(nullptr) { + napi_value resource_id; + napi_status status = napi_create_string_utf8( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_async_init(_env, resource, resource_id, &_context); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncContext::~AsyncContext() { + if (_context != nullptr) { + napi_async_destroy(_env, _context); + _context = nullptr; + } +} + +inline AsyncContext::AsyncContext(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; +} + +inline AsyncContext& AsyncContext::operator=(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; + return *this; +} + +inline AsyncContext::operator napi_async_context() const { + return _context; +} + +inline Napi::Env AsyncContext::Env() const { + return Napi::Env(_env); +} + +//////////////////////////////////////////////////////////////////////////////// +// AsyncWorker class +//////////////////////////////////////////////////////////////////////////////// + +#if NAPI_HAS_THREADS + +inline AsyncWorker::AsyncWorker(const Function& callback) + : AsyncWorker(callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name) + : AsyncWorker(callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback) + : AsyncWorker(receiver, callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : _env(callback.Env()), + _receiver(Napi::Persistent(receiver)), + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::AsyncWorker(Napi::Env env) : AsyncWorker(env, "generic") {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : _env(env), _receiver(), _callback(), _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::~AsyncWorker() { + if (_work != nullptr) { + napi_delete_async_work(_env, _work); + _work = nullptr; + } +} + +inline void AsyncWorker::Destroy() { + delete this; +} + +inline AsyncWorker::operator napi_async_work() const { + return _work; +} + +inline Napi::Env AsyncWorker::Env() const { + return Napi::Env(_env); +} + +inline void AsyncWorker::Queue() { + napi_status status = napi_queue_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void AsyncWorker::Cancel() { + napi_status status = napi_cancel_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline ObjectReference& AsyncWorker::Receiver() { + return _receiver; +} + +inline FunctionReference& AsyncWorker::Callback() { + return _callback; +} + +inline void AsyncWorker::SuppressDestruct() { + _suppress_destruct = true; +} + +inline void AsyncWorker::OnOK() { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), GetResult(_callback.Env())); + } +} + +inline void AsyncWorker::OnError(const Error& e) { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), + std::initializer_list{e.Value()}); + } +} + +inline void AsyncWorker::SetError(const std::string& error) { + _error = error; +} + +inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { + return {}; +} +// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnExecute(env); +} +// The OnExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + Execute(); + } catch (const std::exception& e) { + SetError(e.what()); + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS + Execute(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnWorkComplete(env, status); +} +inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { + if (status != napi_cancelled) { + HandleScope scope(_env); + details::WrapCallback(env, [&] { + if (_error.size() == 0) { + OnOK(); + } else { + OnError(Error::New(_env, _error)); + } + return nullptr; + }); + } + if (!_suppress_destruct) { + Destroy(); + } +} + +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +//////////////////////////////////////////////////////////////////////////////// +// TypedThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// Starting with NAPI 5, the JavaScript function `func` parameter of +// `napi_create_threadsafe_function` is optional. +#if NAPI_VERSION > 4 +// static, with Callback [missing] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} +#endif + +// static, with Callback [passed] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with: Callback [passed] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = napi_create_threadsafe_function( + env, + details::DefaultCallbackWrapper< + CallbackType, + TypedThreadSafeFunction>(env, + callback), + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction() + : _tsfn() {} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +template +inline TypedThreadSafeFunction:: +operator napi_threadsafe_function() const { + return _tsfn; +} + +template +inline napi_status +TypedThreadSafeFunction::BlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status +TypedThreadSafeFunction::NonBlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline void TypedThreadSafeFunction::Ref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline void TypedThreadSafeFunction::Unref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline napi_status +TypedThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +template +inline napi_status +TypedThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +template +inline napi_status +TypedThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +template +inline ContextType* +TypedThreadSafeFunction::GetContext() const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "TypedThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return static_cast(context); +} + +// static +template +void TypedThreadSafeFunction::CallJsInternal( + napi_env env, napi_value jsCallback, void* context, void* data) { + details::CallJsWrapper( + env, jsCallback, context, data); +} + +#if NAPI_VERSION == 4 +// static +template +Napi::Function +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env env) { + return Napi::Function::New(env, [](const CallbackInfo& cb) {}); +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env env, Napi::Function& callback) { + if (callback.IsEmpty()) { + return EmptyFunctionFactory(env); + } + return callback; +} + +#else +// static +template +std::nullptr_t +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env /*env*/) { + return nullptr; +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env /*env*/, Napi::Function& callback) { + return callback; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New( + env, callback, Object(), resourceName, maxQueueSize, initialThreadCount); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + [](Env, ContextType*) {} /* empty finalizer */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::Wrapper); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeWrapperWithData); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::FinalizeWrapperWithContext); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext); +} + +inline ThreadSafeFunction::ThreadSafeFunction() : _tsfn() {} + +inline ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +inline ThreadSafeFunction::operator napi_threadsafe_function() const { + return _tsfn; +} + +inline napi_status ThreadSafeFunction::BlockingCall() const { + return CallInternal(nullptr, napi_tsfn_blocking); +} + +template <> +inline napi_status ThreadSafeFunction::BlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(DataType* data, + Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking); +} + +inline napi_status ThreadSafeFunction::NonBlockingCall() const { + return CallInternal(nullptr, napi_tsfn_nonblocking); +} + +template <> +inline napi_status ThreadSafeFunction::NonBlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + DataType* data, Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); +} + +inline void ThreadSafeFunction::Ref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline void ThreadSafeFunction::Unref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline napi_status ThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +inline napi_status ThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +inline napi_status ThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() + const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "ThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return ConvertibleContext({context}); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper) { + static_assert(details::can_make_string::value || + std::is_convertible::value, + "Resource name should be convertible to the string type"); + + ThreadSafeFunction tsfn; + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + Value::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + wrapper, + context, + CallJS, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); + } + + return tsfn; +} + +inline napi_status ThreadSafeFunction::CallInternal( + CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const { + napi_status status = + napi_call_threadsafe_function(_tsfn, callbackWrapper, mode); + if (status != napi_ok && callbackWrapper != nullptr) { + delete callbackWrapper; + } + + return status; +} + +// static +inline void ThreadSafeFunction::CallJS(napi_env env, + napi_value jsCallback, + void* /* context */, + void* data) { + if (env == nullptr && jsCallback == nullptr) { + return; + } + + details::WrapVoidCallback(env, [&]() { + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker Base class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(callback.Env(), + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are available on all versions we can remove the dummy Function + // here. + Function callback; + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(env, + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} +#endif + +template +inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release + // duplicated. + _tsfn.Abort(); +} + +template +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress( + Napi::Env /* env */, Napi::Function /* jsCallback */, void* data) { + ThreadSafeData* tsd = static_cast(data); + tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); + delete tsd; +} + +template +inline napi_status AsyncProgressWorkerBase::NonBlockingCall( + DataType* data) { + auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); + auto ret = _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); + if (ret != napi_ok) { + delete tsd; + } + return ret; +} + +template +inline void AsyncProgressWorkerBase::OnWorkComplete( + Napi::Env /* env */, napi_status status) { + _work_completed = true; + _complete_status = status; + _tsfn.Release(); +} + +template +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize( + Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { + if (context->_work_completed) { + context->AsyncWorker::OnWorkComplete(env, context->_complete_status); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) + : AsyncProgressWorker(callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback) + : AsyncProgressWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0), + _signaled(false) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) + : AsyncProgressWorker(env, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(env, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) {} +#endif + +template +inline AsyncProgressWorker::~AsyncProgressWorker() { + { + std::lock_guard lock(this->_mutex); + _asyncdata = nullptr; + _asyncsize = 0; + } +} + +template +inline void AsyncProgressWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressWorker::OnWorkProgress(void*) { + T* data; + size_t size; + bool signaled; + { + std::lock_guard lock(this->_mutex); + data = this->_asyncdata; + size = this->_asyncsize; + signaled = this->_signaled; + this->_asyncdata = nullptr; + this->_asyncsize = 0; + this->_signaled = false; + } + + /** + * The callback of ThreadSafeFunction is not been invoked immediately on the + * callback of uv_async_t (uv io poll), rather the callback of TSFN is + * invoked on the right next uv idle callback. There are chances that during + * the deferring the signal of uv_async_t is been sent again, i.e. potential + * not coalesced two calls of the TSFN callback. + */ + if (data == nullptr && !signaled) { + return; + } + + this->OnProgress(data, size); + delete[] data; +} + +template +inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(this->_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + _signaled = false; + } + this->NonBlockingCall(nullptr); + + delete[] old_data; +} + +template +inline void AsyncProgressWorker::Signal() { + { + std::lock_guard lock(this->_mutex); + _signaled = true; + } + this->NonBlockingCall(static_cast(nullptr)); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Signal() const { + this->_worker->Signal(); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Queue Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name, const Object& resource) + : AsyncProgressQueueWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>( + receiver, + callback, + resource_name, + resource, + /** unlimited queue size */ 0) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) + : AsyncProgressQueueWorker(env, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name, const Object& resource) + : AsyncProgressWorkerBase>( + env, resource_name, resource, /** unlimited queue size */ 0) {} +#endif + +template +inline void AsyncProgressQueueWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressQueueWorker::OnWorkProgress( + std::pair* datapair) { + if (datapair == nullptr) { + return; + } + + T* data = datapair->first; + size_t size = datapair->second; + + this->OnProgress(data, size); + delete datapair; + delete[] data; +} + +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, + size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); +} + +template +inline void AsyncProgressQueueWorker::Signal() const { + this->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, + napi_status status) { + // Draining queued items in TSFN. + AsyncProgressWorkerBase>::OnWorkComplete(env, status); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { + _worker->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +//////////////////////////////////////////////////////////////////////////////// +// Memory Management class +//////////////////////////////////////////////////////////////////////////////// + +inline int64_t MemoryManagement::AdjustExternalMemory(BasicEnv env, + int64_t change_in_bytes) { + int64_t result; + napi_status status = + napi_adjust_external_memory(env, change_in_bytes, &result); + NAPI_FATAL_IF_FAILED(status, + "MemoryManagement::AdjustExternalMemory", + "napi_adjust_external_memory"); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Version Management class +//////////////////////////////////////////////////////////////////////////////// + +inline uint32_t VersionManagement::GetNapiVersion(BasicEnv env) { + uint32_t result; + napi_status status = napi_get_version(env, &result); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNapiVersion", "napi_get_version"); + return result; +} + +inline const napi_node_version* VersionManagement::GetNodeVersion( + BasicEnv env) { + const napi_node_version* result; + napi_status status = napi_get_node_version(env, &result); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNodeVersion", "napi_get_node_version"); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// Addon class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object Addon::Init(Env env, Object exports) { + T* addon = new T(env, exports); + env.SetInstanceData(addon); + return addon->entry_point_; +} + +template +inline T* Addon::Unwrap(Object wrapper) { + return wrapper.Env().GetInstanceData(); +} + +template +inline void Addon::DefineAddon( + Object exports, const std::initializer_list& props) { + DefineProperties(exports, props); + entry_point_ = exports; +} + +template +inline Napi::Object Addon::DefineProperties( + Object object, const std::initializer_list& props) { + const napi_property_descriptor* properties = + reinterpret_cast(props.begin()); + size_t size = props.size(); + napi_status status = + napi_define_properties(object.Env(), object, size, properties); + NAPI_THROW_IF_FAILED(object.Env(), status, object); + for (size_t idx = 0; idx < size; idx++) + T::AttachPropData(object.Env(), object, &properties[idx]); + return object; +} +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 2 +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook, Arg* arg) { + return CleanupHook(*this, hook, arg); +} + +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook) { + return CleanupHook(*this, hook); +} + +template +Env::CleanupHook::CleanupHook() { + data = nullptr; +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, Hook hook) + : wrapper(Env::CleanupHook::Wrapper) { + data = new CleanupData{std::move(hook), nullptr}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, + Hook hook, + Arg* arg) + : wrapper(Env::CleanupHook::WrapperWithArg) { + data = new CleanupData{std::move(hook), arg}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +bool Env::CleanupHook::Remove(BasicEnv env) { + napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); + delete data; + data = nullptr; + return status == napi_ok; +} + +template +bool Env::CleanupHook::IsEmpty() const { + return data == nullptr; +} +#endif // NAPI_VERSION > 2 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { + using T = void*; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithoutData, + static_cast(nullptr), + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGC, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithHint, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +#endif // SRC_NAPI_INL_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/napi.h b/week-5/solution/frontend/node_modules/node-addon-api/napi.h new file mode 100644 index 000000000..ba0e13416 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/napi.h @@ -0,0 +1,3309 @@ +#ifndef SRC_NAPI_H_ +#define SRC_NAPI_H_ + +#ifndef NAPI_HAS_THREADS +#if !defined(__wasm__) || (defined(__EMSCRIPTEN_PTHREADS__) || \ + (defined(__wasi__) && defined(_REENTRANT))) +#define NAPI_HAS_THREADS 1 +#else +#define NAPI_HAS_THREADS 0 +#endif +#endif + +#include +#include +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known +// good version) +#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 +#define NAPI_HAS_CONSTEXPR 1 +#endif + +// VS2013 does not support char16_t literal strings, so we'll work around it +// using wchar_t strings and casting them. This is safe as long as the character +// sizes are the same. +#if defined(_MSC_VER) && _MSC_VER <= 1800 +static_assert(sizeof(char16_t) == sizeof(wchar_t), + "Size mismatch between char16_t and wchar_t"); +#define NAPI_WIDE_TEXT(x) reinterpret_cast(L##x) +#else +#define NAPI_WIDE_TEXT(x) u##x +#endif + +// Backwards-compatibility to handle the rename of this macro definition, in +// case they are used within userland code. +#ifdef NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#endif +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS +#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ + !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#define NAPI_DISABLE_CPP_EXCEPTIONS +#endif + +// If C++ exceptions are not explicitly enabled or disabled, enable them +// if exceptions were enabled in the compiler settings. +#if !defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) +#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) +#define NODE_ADDON_API_CPP_EXCEPTIONS +#else +#error Exception support not detected. \ + Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. +#endif +#endif + +// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE +// should not be set +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NODE_ADDON_API_CPP_EXCEPTIONS is defined. +#endif + +#ifdef _NOEXCEPT +#define NAPI_NOEXCEPT _NOEXCEPT +#else +#define NAPI_NOEXCEPT noexcept +#endif + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + +// When C++ exceptions are enabled, Errors are thrown directly. There is no need +// to return anything after the throw statements. The variadic parameter is an +// optional return value that is ignored. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#else // NODE_ADDON_API_CPP_EXCEPTIONS + +// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, +// which are pending until the callback returns to JS. The variadic parameter +// is an optional return value; usually it is an empty result. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0) + +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ + } while (0) + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ + } + +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif + +#define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; +#define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; + +#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ + NAPI_DISALLOW_ASSIGN(CLASS) \ + NAPI_DISALLOW_COPY(CLASS) + +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ + } while (0) + +// Internal check helper. Be careful that the formatted message length should be +// max 255 size and null terminated. +#define NAPI_INTERNAL_CHECK(expr, location, ...) \ + do { \ + if (!(expr)) { \ + std::string msg = Napi::details::StringFormat(__VA_ARGS__); \ + Napi::Error::Fatal(location, msg.c_str()); \ + } \ + } while (0) + +#define NAPI_INTERNAL_CHECK_EQ(actual, expected, value_format, location) \ + do { \ + auto actual_value = (actual); \ + NAPI_INTERNAL_CHECK(actual_value == (expected), \ + location, \ + "Expected " #actual " to be equal to " #expected \ + ", but got " value_format ".", \ + actual_value); \ + } while (0) + +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + +//////////////////////////////////////////////////////////////////////////////// +/// Node-API C++ Wrapper Classes +/// +/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a +/// C++ object model and C++ exception-handling semantics with low overhead. +/// The wrappers are all header-only so that they do not affect the ABI. +//////////////////////////////////////////////////////////////////////////////// +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +// NAPI_CPP_CUSTOM_NAMESPACE can be #define'd per-addon to avoid symbol +// conflicts between different instances of node-addon-api + +// First dummy definition of the namespace to make sure that Napi::(name) still +// refers to the right things inside this file. +namespace NAPI_CPP_CUSTOM_NAMESPACE {} +using namespace NAPI_CPP_CUSTOM_NAMESPACE; + +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Forward declarations +class Env; +class Value; +class Boolean; +class Number; +#if NAPI_VERSION > 5 +class BigInt; +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) +class Date; +#endif +class String; +class Object; +class Array; +class ArrayBuffer; +class Function; +class Error; +class PropertyDescriptor; +class CallbackInfo; +class TypedArray; +template +class TypedArrayOf; + +using Int8Array = + TypedArrayOf; ///< Typed-array of signed 8-bit integers +using Uint8Array = + TypedArrayOf; ///< Typed-array of unsigned 8-bit integers +using Int16Array = + TypedArrayOf; ///< Typed-array of signed 16-bit integers +using Uint16Array = + TypedArrayOf; ///< Typed-array of unsigned 16-bit integers +using Int32Array = + TypedArrayOf; ///< Typed-array of signed 32-bit integers +using Uint32Array = + TypedArrayOf; ///< Typed-array of unsigned 32-bit integers +using Float32Array = + TypedArrayOf; ///< Typed-array of 32-bit floating-point values +using Float64Array = + TypedArrayOf; ///< Typed-array of 64-bit floating-point values +#if NAPI_VERSION > 5 +using BigInt64Array = + TypedArrayOf; ///< Typed array of signed 64-bit integers +using BigUint64Array = + TypedArrayOf; ///< Typed array of unsigned 64-bit integers +#endif // NAPI_VERSION > 5 + +/// Defines the signature of a Node-API C++ module's registration callback +/// (init) function. +using ModuleRegisterCallback = Object (*)(Env env, Object exports); + +class MemoryManagement; + +/// A simple Maybe type, representing an object which may or may not have a +/// value. +/// +/// If an API method returns a Maybe<>, the API method can potentially fail +/// either because an exception is thrown, or because an exception is pending, +/// e.g. because a previous API call threw an exception that hasn't been +/// caught yet. In that case, a "Nothing" value is returned. +template +class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); +}; + +template +inline Maybe Nothing(); + +template +inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +template +using MaybeOrValue = Maybe; +#else +template +using MaybeOrValue = T; +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +using node_addon_api_basic_env = node_api_nogc_env; +using node_addon_api_basic_finalize = node_api_nogc_finalize; +#else +using node_addon_api_basic_env = napi_env; +using node_addon_api_basic_finalize = napi_finalize; +#endif + +/// Environment for Node-API values and operations. +/// +/// All Node-API values and operations must be associated with an environment. +/// An environment instance is always provided to callback functions; that +/// environment must then be used for any creation of Node-API values or other +/// Node-API operations within the callback. (Many methods infer the +/// environment from the `this` instance that the method is called on.) +/// +/// Multiple environments may co-exist in a single process or a thread. +/// +/// In the V8 JavaScript engine, a Node-API environment approximately +/// corresponds to an Isolate. +class BasicEnv { + private: + node_addon_api_basic_env _env; +#if NAPI_VERSION > 5 + template + static void DefaultFini(Env, T* data); + template + static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); +#endif // NAPI_VERSION > 5 + public: + BasicEnv(node_addon_api_basic_env env); + + operator node_addon_api_basic_env() const; + + // Without these operator overloads, the error: + // + // Use of overloaded operator '==' is ambiguous (with operand types + // 'Napi::Env' and 'Napi::Env') + // + // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr + bool operator==(const BasicEnv& other) const { + return _env == other._env; + }; + bool operator==(std::nullptr_t /*other*/) const { + return _env == nullptr; + }; + +#if NAPI_VERSION > 2 + template + class CleanupHook; + + template + CleanupHook AddCleanupHook(Hook hook); + + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 5 + template + T* GetInstanceData() const; + + template + using Finalizer = void (*)(Env, T*); + template fini = BasicEnv::DefaultFini> + void SetInstanceData(T* data) const; + + template + using FinalizerWithHint = void (*)(Env, DataType*, HintType*); + template fini = + BasicEnv::DefaultFiniWithHint> + void SetInstanceData(DataType* data, HintType* hint) const; +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 2 + template + class CleanupHook { + public: + CleanupHook(); + CleanupHook(BasicEnv env, Hook hook, Arg* arg); + CleanupHook(BasicEnv env, Hook hook); + bool Remove(BasicEnv env); + bool IsEmpty() const; + + private: + static inline void Wrapper(void* data) NAPI_NOEXCEPT; + static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; + + void (*wrapper)(void* arg); + struct CleanupData { + Hook hook; + Arg* arg; + } * data; + }; +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 8 + const char* GetModuleFileName() const; +#endif // NAPI_VERSION > 8 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template + inline void PostFinalizer(FinalizerType finalizeCallback) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + + friend class Env; +}; + +class Env : public BasicEnv { + public: + Env(napi_env env); + + operator napi_env() const; + + Object Global() const; + Value Undefined() const; + Value Null() const; + + bool IsExceptionPending() const; + Error GetAndClearPendingException() const; + + MaybeOrValue RunScript(const char* utf8script) const; + MaybeOrValue RunScript(const std::string& utf8script) const; + MaybeOrValue RunScript(String script) const; +}; + +/// A JavaScript value of unknown type. +/// +/// For type-specific operations, convert to one of the Value subclasses using a +/// `To*` or `As()` method. The `To*` methods do type coercion; the `As()` +/// method does not. +/// +/// Napi::Value value = ... +/// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid +/// arg..."); Napi::String str = value.As(); // Cast to a +/// string value +/// +/// Napi::Value anotherValue = ... +/// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value +class Value { + public: + Value(); ///< Creates a new _empty_ Value instance. + Value(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Creates a JS value from a C++ primitive. + /// + /// `value` may be any of: + /// - bool + /// - Any integer type + /// - Any floating point type + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + /// - napi::Value + /// - napi_value + template + static Value From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + /// Converts to a Node-API value primitive. + /// + /// If the instance is _empty_, this returns `nullptr`. + operator napi_value() const; + + /// Tests if this value strictly equals another value. + bool operator==(const Value& other) const; + + /// Tests if this value does not strictly equal another value. + bool operator!=(const Value& other) const; + + /// Tests if this value strictly equals another value. + bool StrictEquals(const Value& other) const; + + /// Gets the environment the value is associated with. + Napi::Env Env() const; + + /// Checks if the value is empty (uninitialized). + /// + /// An empty value is invalid, and most attempts to perform an operation on an + /// empty value will result in an exception. Note an empty value is distinct + /// from JavaScript `null` or `undefined`, which are valid values. + /// + /// When C++ exceptions are disabled at compile time, a method with a `Value` + /// return type may return an empty value to indicate a pending exception. So + /// when not using C++ exceptions, callers should check whether the value is + /// empty before attempting to use it. + bool IsEmpty() const; + + napi_valuetype Type() const; ///< Gets the type of the value. + + bool IsUndefined() + const; ///< Tests if a value is an undefined JavaScript value. + bool IsNull() const; ///< Tests if a value is a null JavaScript value. + bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. + bool IsNumber() const; ///< Tests if a value is a JavaScript number. +#if NAPI_VERSION > 5 + bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) + bool IsDate() const; ///< Tests if a value is a JavaScript date. +#endif + bool IsString() const; ///< Tests if a value is a JavaScript string. + bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. + bool IsArray() const; ///< Tests if a value is a JavaScript array. + bool IsArrayBuffer() + const; ///< Tests if a value is a JavaScript array buffer. + bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. + bool IsObject() const; ///< Tests if a value is a JavaScript object. + bool IsFunction() const; ///< Tests if a value is a JavaScript function. + bool IsPromise() const; ///< Tests if a value is a JavaScript promise. + bool IsDataView() const; ///< Tests if a value is a JavaScript data view. + bool IsBuffer() const; ///< Tests if a value is a Node buffer. + bool IsExternal() const; ///< Tests if a value is a pointer to external data. + + /// Casts to another type of `Napi::Value`, when the actual type is known or + /// assumed. + /// + /// This conversion does NOT coerce the type. Calling any methods + /// inappropriate for the actual value type will throw `Napi::Error`. + /// + /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method + /// asserts that the actual type is the expected type. + template + T As() const; + + // Unsafe Value::As(), should be avoided. + template + T UnsafeAs() const; + + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. + + protected: + /// !cond INTERNAL + napi_env _env; + napi_value _value; + /// !endcond +}; + +/// A JavaScript boolean value. +class Boolean : public Value { + public: + static Boolean New(napi_env env, ///< Node-API environment + bool value ///< Boolean value + ); + + static void CheckCast(napi_env env, napi_value value); + + Boolean(); ///< Creates a new _empty_ Boolean instance. + Boolean(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator bool() const; ///< Converts a Boolean value to a boolean primitive. + bool Value() const; ///< Converts a Boolean value to a boolean primitive. +}; + +/// A JavaScript number value. +class Number : public Value { + public: + static Number New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Number(); ///< Creates a new _empty_ Number instance. + Number(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator int32_t() + const; ///< Converts a Number value to a 32-bit signed integer value. + operator uint32_t() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + operator int64_t() + const; ///< Converts a Number value to a 64-bit signed integer value. + operator float() + const; ///< Converts a Number value to a 32-bit floating-point value. + operator double() + const; ///< Converts a Number value to a 64-bit floating-point value. + + int32_t Int32Value() + const; ///< Converts a Number value to a 32-bit signed integer value. + uint32_t Uint32Value() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + int64_t Int64Value() + const; ///< Converts a Number value to a 64-bit signed integer value. + float FloatValue() + const; ///< Converts a Number value to a 32-bit floating-point value. + double DoubleValue() + const; ///< Converts a Number value to a 64-bit floating-point value. +}; + +#if NAPI_VERSION > 5 +/// A JavaScript bigint value. +class BigInt : public Value { + public: + static BigInt New(napi_env env, ///< Node-API environment + int64_t value ///< Number value + ); + static BigInt New(napi_env env, ///< Node-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New(napi_env env, ///< Node-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + static void CheckCast(napi_env env, napi_value value); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + int64_t Int64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store + ///< the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is + /// negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); +}; +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +/// A JavaScript date value. +class Date : public Value { + public: + /// Creates a new Date value from a double primitive. + static Date New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + operator double() const; ///< Converts a Date value to double primitive + + double ValueOf() const; ///< Converts a Date value to a double primitive. +}; +#endif + +/// A JavaScript string or symbol value (that can be used as a property name). +class Name : public Value { + public: + static void CheckCast(napi_env env, napi_value value); + + Name(); ///< Creates a new _empty_ Name instance. + Name(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +/// A JavaScript string value. +class String : public Name { + public: + /// Creates a new String value from a UTF-8 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::string& value ///< UTF-8 encoded C++ string + ); + + /// Creates a new String value from a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::u16string& value ///< UTF-16 encoded C++ string + ); + + /// Creates a new String value from a UTF-8 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char* value ///< UTF-8 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-16 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value ///< UTF-16 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-8 encoded C string with specified + /// length. + static String New(napi_env env, ///< Node-API environment + const char* value, ///< UTF-8 encoded C string (not + ///< necessarily null-terminated) + size_t length ///< length of the string in bytes + ); + + /// Creates a new String value from a UTF-16 encoded C string with specified + /// length. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value, ///< UTF-16 encoded C string (not necessarily + ///< null-terminated) + size_t length ///< Length of the string in 2-byte code units + ); + + /// Creates a new String based on the original object's type. + /// + /// `value` may be any of: + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + template + static String From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + String(); ///< Creates a new _empty_ String instance. + String(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator std::string() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + operator std::u16string() + const; ///< Converts a String value to a UTF-16 encoded C++ string. + std::string Utf8Value() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + std::u16string Utf16Value() + const; ///< Converts a String value to a UTF-16 encoded C++ string. +}; + +/// A JavaScript symbol value. +class Symbol : public Name { + public: + /// Creates a new Symbol value with an optional description. + static Symbol New( + napi_env env, ///< Node-API environment + const char* description = + nullptr ///< Optional UTF-8 encoded null-terminated C string + /// describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + const std::string& + description ///< UTF-8 encoded C++ string describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New(napi_env env, ///< Node-API environment + String description ///< String value describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + napi_value description ///< String value describing the symbol + ); + + /// Get a public Symbol (e.g. Symbol.iterator). + static MaybeOrValue WellKnown(napi_env, const std::string& name); + + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static MaybeOrValue For(napi_env env, const std::string& description); + + // Create a symbol in the global registry, C style string (null terminated) + static MaybeOrValue For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static MaybeOrValue For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static MaybeOrValue For(napi_env env, napi_value description); + + static void CheckCast(napi_env env, napi_value value); + + Symbol(); ///< Creates a new _empty_ Symbol instance. + Symbol(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +class TypeTaggable : public Value { + public: +#if NAPI_VERSION >= 8 + void TypeTag(const napi_type_tag* type_tag) const; + bool CheckTypeTag(const napi_type_tag* type_tag) const; +#endif // NAPI_VERSION >= 8 + protected: + TypeTaggable(); + TypeTaggable(napi_env env, napi_value value); +}; + +/// A JavaScript object value. +class Object : public TypeTaggable { + public: + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { + public: + /// Converts an L-value to a value. + operator Value() const; + + /// Assigns a value to the property. The type of value can be + /// anything supported by `Object::Set`. + template + PropertyLValue& operator=(ValueType value); + + /// Converts an L-value to a value. For convenience. + Value AsValue() const; + + private: + PropertyLValue() = delete; + PropertyLValue(Object object, Key key); + napi_env _env; + napi_value _object; + Key _key; + + friend class Napi::Object; + }; + + /// Creates a new Object value. + static Object New(napi_env env ///< Node-API environment + ); + + static void CheckCast(napi_env env, napi_value value); + + Object(); ///< Creates a new _empty_ Object instance. + Object(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Gets or sets a named property. + PropertyLValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ); + + /// Gets or sets a named property. + PropertyLValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[]( + uint32_t index /// Property / element index + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue operator[](uint32_t index ///< Property / element index + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(Value key ///< Property key + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(Value key ///< Property key + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets a property. + MaybeOrValue Get(napi_value key ///< Property key primitive + ) const; + + /// Gets a property. + MaybeOrValue Get(Value key ///< Property key + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Sets a property. + template + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive + ) const; + + /// Sets a property. + template + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value + ) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const char* utf8name, ///< UTF-8 encoded null-terminated property name + const ValueType& value) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(napi_value key ///< Property key primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(Value key ///< Property key + ) const; + + /// Delete property. + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Delete property. + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether an indexed property is present. + MaybeOrValue Has(uint32_t index ///< Property / element index + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue Get(uint32_t index ///< Property / element index + ) const; + + /// Sets an indexed property or array element. + template + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive + ) const; + + /// Deletes an indexed property or array element. + MaybeOrValue Delete(uint32_t index ///< Property / element index + ) const; + + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names + + /// Defines a property on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( + const PropertyDescriptor& + property ///< Descriptor for the property to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::initializer_list& properties + ///< List of descriptors for the properties to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::vector& properties + ///< Vector of descriptors for the properties to be defined + ) const; + + /// Checks if an object is an instance created by a constructor function. + /// + /// This is equivalent to the JavaScript `instanceof` operator. + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function + ) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + class const_iterator; + + inline const_iterator begin() const; + + inline const_iterator end() const; + + class iterator; + + inline iterator begin(); + + inline iterator end(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze() const; + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal() const; +#endif // NAPI_VERSION >= 8 +}; + +template +class External : public TypeTaggable { + public: + static External New(napi_env env, T* data); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static External New(napi_env env, T* data, Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static External New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static void CheckCast(napi_env env, napi_value value); + + External(); + External(napi_env env, napi_value value); + + T* Data() const; +}; + +class Array : public Object { + public: + static Array New(napi_env env); + static Array New(napi_env env, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Array(); + Array(napi_env env, napi_value value); + + uint32_t Length() const; +}; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +class Object::const_iterator { + private: + enum class Type { BEGIN, END }; + + inline const_iterator(const Object* object, const Type type); + + public: + inline const_iterator& operator++(); + + inline bool operator==(const const_iterator& other) const; + + inline bool operator!=(const const_iterator& other) const; + + inline const std::pair> operator*() + const; + + private: + const Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; + +class Object::iterator { + private: + enum class Type { BEGIN, END }; + + inline iterator(Object* object, const Type type); + + public: + inline iterator& operator++(); + + inline bool operator==(const iterator& other) const; + + inline bool operator!=(const iterator& other) const; + + inline std::pair> operator*(); + + private: + Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +/// A JavaScript array buffer value. +class ArrayBuffer : public Object { + public: + /// Creates a new ArrayBuffer instance over a new automatically-allocated + /// buffer. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + size_t byteLength ///< Length of the buffer to be allocated, in bytes + ); + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength ///< Length of the external buffer to be used by the + ///< array, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env env, + /// void* externalData)` + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback, ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData, Hint* hint)` + Hint* finalizeHint ///< Hint (second parameter) to be passed to the + ///< finalize callback + ); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static void CheckCast(napi_env env, napi_value value); + + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. + ArrayBuffer(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + void* Data(); ///< Gets a pointer to the data buffer. + size_t ByteLength(); ///< Gets the length of the array buffer in bytes. + +#if NAPI_VERSION >= 7 + bool IsDetached() const; + void Detach(); +#endif // NAPI_VERSION >= 7 +}; + +/// A JavaScript typed-array value with unknown array type. +/// +/// For type-specific operations, cast to a `TypedArrayOf` instance using the +/// `As()` method: +/// +/// Napi::TypedArray array = ... +/// if (t.TypedArrayType() == napi_int32_array) { +/// Napi::Int32Array int32Array = t.As(); +/// } +class TypedArray : public Object { + public: + static void CheckCast(napi_env env, napi_value value); + + TypedArray(); ///< Creates a new _empty_ TypedArray instance. + TypedArray(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + napi_typedarray_type TypedArrayType() + const; ///< Gets the type of this typed-array. + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + uint8_t ElementSize() + const; ///< Gets the size in bytes of one element in the array. + size_t ElementLength() const; ///< Gets the number of elements in the array. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + protected: + /// !cond INTERNAL + napi_typedarray_type _type; + size_t _length; + + TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length); + + template + static +#if defined(NAPI_HAS_CONSTEXPR) + constexpr +#endif + napi_typedarray_type + TypedArrayTypeForPrimitiveType() { + return std::is_same::value ? napi_int8_array + : std::is_same::value ? napi_uint8_array + : std::is_same::value ? napi_int16_array + : std::is_same::value ? napi_uint16_array + : std::is_same::value ? napi_int32_array + : std::is_same::value ? napi_uint32_array + : std::is_same::value ? napi_float32_array + : std::is_same::value ? napi_float64_array +#if NAPI_VERSION > 5 + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array +#endif // NAPI_VERSION > 5 + : napi_int8_array; + } + /// !endcond +}; + +/// A JavaScript typed-array value with known array type. +/// +/// Note while it is possible to create and access Uint8 "clamped" arrays using +/// this class, the _clamping_ behavior is only applied in JavaScript. +template +class TypedArrayOf : public TypedArray { + public: + /// Creates a new TypedArray instance over a new automatically-allocated array + /// buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + /// Creates a new TypedArray instance over a provided array buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + static void CheckCast(napi_env env, napi_value value); + + TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. + TypedArrayOf(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + T& operator[](size_t index); ///< Gets or sets an element in the array. + const T& operator[](size_t index) const; ///< Gets an element in the array. + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + T* Data(); + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + const T* Data() const; + + private: + T* _data; + + TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data); +}; + +/// The DataView provides a low-level interface for reading/writing multiple +/// number types in an ArrayBuffer irrespective of the platform's endianness. +class DataView : public Object { + public: + static DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); + + static void CheckCast(napi_env env, napi_value value); + + DataView(); ///< Creates a new _empty_ DataView instance. + DataView(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + void* Data() const; + + float GetFloat32(size_t byteOffset) const; + double GetFloat64(size_t byteOffset) const; + int8_t GetInt8(size_t byteOffset) const; + int16_t GetInt16(size_t byteOffset) const; + int32_t GetInt32(size_t byteOffset) const; + uint8_t GetUint8(size_t byteOffset) const; + uint16_t GetUint16(size_t byteOffset) const; + uint32_t GetUint32(size_t byteOffset) const; + + void SetFloat32(size_t byteOffset, float value) const; + void SetFloat64(size_t byteOffset, double value) const; + void SetInt8(size_t byteOffset, int8_t value) const; + void SetInt16(size_t byteOffset, int16_t value) const; + void SetInt32(size_t byteOffset, int32_t value) const; + void SetUint8(size_t byteOffset, uint8_t value) const; + void SetUint16(size_t byteOffset, uint16_t value) const; + void SetUint32(size_t byteOffset, uint32_t value) const; + + private: + template + T ReadData(size_t byteOffset) const; + + template + void WriteData(size_t byteOffset, T value) const; + + void* _data{}; + size_t _length{}; +}; + +class Function : public Object { + public: + using VoidCallback = void (*)(const CallbackInfo& info); + using Callback = Value (*)(const CallbackInfo& info); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const char* utf8name = nullptr, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data = nullptr); + + static void CheckCast(napi_env env, napi_value value); + + Function(); + Function(napi_env env, napi_value value); + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call(const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call(napi_value recv, + const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; +}; + +class Promise : public Object { + public: + class Deferred { + public: + static Deferred New(napi_env env); + Deferred(napi_env env); + + Napi::Promise Promise() const; + Napi::Env Env() const; + + void Resolve(napi_value value) const; + void Reject(napi_value value) const; + + private: + napi_env _env; + napi_deferred _deferred; + napi_value _promise; + }; + + static void CheckCast(napi_env env, napi_value value); + + Promise(); + Promise(napi_env env, napi_value value); + + MaybeOrValue Then(napi_value onFulfilled) const; + MaybeOrValue Then(napi_value onFulfilled, + napi_value onRejected) const; + MaybeOrValue Catch(napi_value onRejected) const; + + MaybeOrValue Then(const Function& onFulfilled) const; + MaybeOrValue Then(const Function& onFulfilled, + const Function& onRejected) const; + MaybeOrValue Catch(const Function& onRejected) const; +}; + +template +class Buffer : public Uint8Array { + public: + static Buffer New(napi_env env, size_t length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + static Buffer New(napi_env env, T* data, size_t length); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static Buffer NewOrCopy(napi_env env, T* data, size_t length); + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static Buffer Copy(napi_env env, const T* data, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Buffer(); + Buffer(napi_env env, napi_value value); + size_t Length() const; + T* Data() const; + + private: +}; + +/// Holds a counted reference to a value; initially a weak reference unless +/// otherwise specified, may be changed to/from a strong reference by adjusting +/// the refcount. +/// +/// The referenced value is not immediately destroyed when the reference count +/// is zero; it is merely then eligible for garbage-collection if there are no +/// other references to the value. +template +class Reference { + public: + static Reference New(const T& value, uint32_t initialRefcount = 0); + + Reference(); + Reference(napi_env env, napi_ref ref); + ~Reference(); + + // A reference can be moved but cannot be copied. + Reference(Reference&& other); + Reference& operator=(Reference&& other); + NAPI_DISALLOW_ASSIGN(Reference) + + operator napi_ref() const; + bool operator==(const Reference& other) const; + bool operator!=(const Reference& other) const; + + Napi::Env Env() const; + bool IsEmpty() const; + + // Note when getting the value of a Reference it is usually correct to do so + // within a HandleScope so that the value handle gets cleaned up efficiently. + T Value() const; + + uint32_t Ref() const; + uint32_t Unref() const; + void Reset(); + void Reset(const T& value, uint32_t refcount = 0); + + // Call this on a reference that is declared as static data, to prevent its + // destructor from running at program shutdown time, which would attempt to + // reset the reference when the environment is no longer valid. Avoid using + // this if at all possible. If you do need to use static data, MAKE SURE to + // warn your users that your addon is NOT threadsafe. + void SuppressDestruct(); + + protected: + Reference(const Reference&); + + /// !cond INTERNAL + napi_env _env; + napi_ref _ref; + /// !endcond + + private: + bool _suppressDestruct; +}; + +class ObjectReference : public Reference { + public: + ObjectReference(); + ObjectReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + ObjectReference(Reference&& other); + ObjectReference& operator=(Reference&& other); + ObjectReference(ObjectReference&& other); + ObjectReference& operator=(ObjectReference&& other); + NAPI_DISALLOW_ASSIGN(ObjectReference) + + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value) const; + MaybeOrValue Set(const char* utf8name, Napi::Value value) const; + MaybeOrValue Set(const char* utf8name, const char* utf8value) const; + MaybeOrValue Set(const char* utf8name, bool boolValue) const; + MaybeOrValue Set(const char* utf8name, double numberValue) const; + MaybeOrValue Set(const std::string& utf8name, napi_value value) const; + MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; + MaybeOrValue Set(const std::string& utf8name, + std::string& utf8value) const; + MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; + MaybeOrValue Set(const std::string& utf8name, double numberValue) const; + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value) const; + MaybeOrValue Set(uint32_t index, const Napi::Value value) const; + MaybeOrValue Set(uint32_t index, const char* utf8value) const; + MaybeOrValue Set(uint32_t index, const std::string& utf8value) const; + MaybeOrValue Set(uint32_t index, bool boolValue) const; + MaybeOrValue Set(uint32_t index, double numberValue) const; + + protected: + ObjectReference(const ObjectReference&); +}; + +class FunctionReference : public Reference { + public: + FunctionReference(); + FunctionReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + FunctionReference(Reference&& other); + FunctionReference& operator=(Reference&& other); + FunctionReference(FunctionReference&& other); + FunctionReference& operator=(FunctionReference&& other); + NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; +}; + +// Shortcuts to creating a new reference with inferred type and refcount = 0. +template +Reference Weak(T value); +ObjectReference Weak(Object value); +FunctionReference Weak(Function value); + +// Shortcuts to creating a new reference with inferred type and refcount = 1. +template +Reference Persistent(T value); +ObjectReference Persistent(Object value); +FunctionReference Persistent(Function value); + +/// A persistent reference to a JavaScript error object. Use of this class +/// depends somewhat on whether C++ exceptions are enabled at compile time. +/// +/// ### Handling Errors With C++ Exceptions +/// +/// If C++ exceptions are enabled, then the `Error` class extends +/// `std::exception` and enables integrated error-handling for C++ exceptions +/// and JavaScript exceptions. +/// +/// If a Node-API call fails without executing any JavaScript code (for +/// example due to an invalid argument), then the Node-API wrapper +/// automatically converts and throws the error as a C++ exception of type +/// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API +/// throws a JavaScript exception, then the Node-API wrapper automatically +/// converts and throws it as a C++ exception of type `Napi::Error`. +/// +/// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ +/// callback, then the Node-API wrapper automatically converts and throws it +/// as a JavaScript exception. Therefore, catching a C++ exception of type +/// `Napi::Error` prevents a JavaScript exception from being thrown. +/// +/// #### Example 1A - Throwing a C++ exception: +/// +/// Napi::Env env = ... +/// throw Napi::Error::New(env, "Example exception"); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 2A - Propagating a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 3A - Handling a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result; +/// try { +/// result = jsFunctionThatThrows({ arg1, arg2 }); +/// } catch (const Napi::Error& e) { +/// cerr << "Caught JavaScript exception: " + e.what(); +/// } +/// +/// Since the exception was caught here, it will not be propagated as a +/// JavaScript exception. +/// +/// ### Handling Errors Without C++ Exceptions +/// +/// If C++ exceptions are disabled (by defining +/// `NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS`) then this class does not extend +/// `std::exception`, and APIs in the `Napi` namespace do not throw C++ +/// exceptions when they fail. Instead, they raise _pending_ JavaScript +/// exceptions and return _empty_ `Value`s. Calling code should check +/// `Value::IsEmpty()` before attempting to use a returned value, and may use +/// methods on the `Env` class to check for, get, and clear a pending JavaScript +/// exception. If the pending exception is not cleared, it will be thrown when +/// the native callback returns to JavaScript. +/// +/// #### Example 1B - Throwing a JS exception +/// +/// Napi::Env env = ... +/// Napi::Error::New(env, "Example +/// exception").ThrowAsJavaScriptException(); return; +/// +/// After throwing a JS exception, the code should generally return +/// immediately from the native callback, after performing any necessary +/// cleanup. +/// +/// #### Example 2B - Propagating a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) return; +/// +/// An empty value result from a Node-API call indicates an error occurred, +/// and a JavaScript exception is pending. To let the exception propagate, the +/// code should generally return immediately from the native callback, after +/// performing any necessary cleanup. +/// +/// #### Example 3B - Handling a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) { +/// Napi::Error e = env.GetAndClearPendingException(); +/// cerr << "Caught JavaScript exception: " + e.Message(); +/// } +/// +/// Since the exception was cleared here, it will not be propagated as a +/// JavaScript exception after the native callback returns. +class Error : public ObjectReference +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + , + public std::exception +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +{ + public: + static Error New(napi_env env); + static Error New(napi_env env, const char* message); + static Error New(napi_env env, const std::string& message); + + static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + + Error(); + Error(napi_env env, napi_value value); + + // An error can be moved or copied. + Error(Error&& other); + Error& operator=(Error&& other); + Error(const Error&); + Error& operator=(const Error&); + + const std::string& Message() const NAPI_NOEXCEPT; + void ThrowAsJavaScriptException() const; + + Object Value() const; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + const char* what() const NAPI_NOEXCEPT override; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + + protected: + /// !cond INTERNAL + using create_error_fn = napi_status (*)(napi_env envb, + napi_value code, + napi_value msg, + napi_value* result); + + template + static TError New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error); + /// !endcond + + private: + static inline const char* ERROR_WRAP_VALUE() NAPI_NOEXCEPT; + mutable std::string _message; +}; + +class TypeError : public Error { + public: + static TypeError New(napi_env env, const char* message); + static TypeError New(napi_env env, const std::string& message); + + TypeError(); + TypeError(napi_env env, napi_value value); +}; + +class RangeError : public Error { + public: + static RangeError New(napi_env env, const char* message); + static RangeError New(napi_env env, const std::string& message); + + RangeError(); + RangeError(napi_env env, napi_value value); +}; + +#if NAPI_VERSION > 8 +class SyntaxError : public Error { + public: + static SyntaxError New(napi_env env, const char* message); + static SyntaxError New(napi_env env, const std::string& message); + + SyntaxError(); + SyntaxError(napi_env env, napi_value value); +}; +#endif // NAPI_VERSION > 8 + +class CallbackInfo { + public: + CallbackInfo(napi_env env, napi_callback_info info); + ~CallbackInfo(); + + // Disallow copying to prevent multiple free of _dynamicArgs + NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) + + Napi::Env Env() const; + Value NewTarget() const; + bool IsConstructCall() const; + size_t Length() const; + const Value operator[](size_t index) const; + Value This() const; + void* Data() const; + void SetData(void* data); + explicit operator napi_callback_info() const; + + private: + const size_t _staticArgCount = 6; + napi_env _env; + napi_callback_info _info; + napi_value _this; + size_t _argc; + napi_value* _argv; + napi_value _staticArgs[6]{}; + napi_value* _dynamicArgs; + void* _data; +}; + +class PropertyDescriptor { + public: + using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); + using SetterCallback = void (*)(const Napi::CallbackInfo& info); + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + napi_value name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + Name name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + PropertyDescriptor(napi_property_descriptor desc); + + operator napi_property_descriptor&(); + operator const napi_property_descriptor&() const; + + private: + napi_property_descriptor _desc; +}; + +/// Property descriptor for use with `ObjectWrap::DefineClass()`. +/// +/// This is different from the standalone `PropertyDescriptor` because it is +/// specific to each `ObjectWrap` subclass. This prevents using descriptors +/// from a different class when defining a new class (preventing the callbacks +/// from having incorrect `this` pointers). +template +class ClassPropertyDescriptor { + public: + ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} + + operator napi_property_descriptor&() { return _desc; } + operator const napi_property_descriptor&() const { return _desc; } + + private: + napi_property_descriptor _desc; +}; + +template +struct MethodCallbackData { + TCallback callback; + void* data; +}; + +template +struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; +}; + +template +class InstanceWrap { + public: + using InstanceVoidMethodCallback = void (T::*)(const CallbackInfo& info); + using InstanceMethodCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + using InstanceVoidMethodCallbackData = + MethodCallbackData; + using InstanceMethodCallbackData = + MethodCallbackData; + using InstanceAccessorCallbackData = + AccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, + napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct SetterTag {}; + + template + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return nullptr; + } +}; + +/// Base class to be extended by C++ classes exposed to JavaScript; each C++ +/// class instance gets "wrapped" by a JavaScript object that is managed by this +/// class. +/// +/// At initialization time, the `DefineClass()` method must be used to +/// hook up the accessor and method callbacks. It takes a list of +/// property descriptors, which can be constructed via the various +/// static methods on the base class. +/// +/// #### Example: +/// +/// class Example: public Napi::ObjectWrap { +/// public: +/// static void Initialize(Napi::Env& env, Napi::Object& target) { +/// Napi::Function constructor = DefineClass(env, "Example", { +/// InstanceAccessor<&Example::GetSomething, +/// &Example::SetSomething>("value"), +/// InstanceMethod<&Example::DoSomething>("doSomething"), +/// }); +/// target.Set("Example", constructor); +/// } +/// +/// Example(const Napi::CallbackInfo& info); // Constructor +/// Napi::Value GetSomething(const Napi::CallbackInfo& info); +/// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& +/// value); Napi::Value DoSomething(const Napi::CallbackInfo& info); +/// } +template +class ObjectWrap : public InstanceWrap, public Reference { + public: + ObjectWrap(const CallbackInfo& callbackInfo); + virtual ~ObjectWrap(); + + static T* Unwrap(Object wrapper); + + // Methods exposed to JavaScript must conform to one of these callback + // signatures. + using StaticVoidMethodCallback = void (*)(const CallbackInfo& info); + using StaticMethodCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticGetterCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticSetterCallback = void (*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static Function DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list& properties, + void* data = nullptr); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const std::vector& properties, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); + virtual void Finalize(Napi::Env env); + virtual void Finalize(BasicEnv env); + + private: + using This = ObjectWrap; + + static napi_value ConstructorCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticSetterCallbackWrapper(napi_env env, + napi_callback_info info); + static void FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* hint); + + static void PostFinalizeCallback(napi_env env, void* data, void* hint); + + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); + + using StaticVoidMethodCallbackData = + MethodCallbackData; + using StaticMethodCallbackData = MethodCallbackData; + + using StaticAccessorCallbackData = + AccessorCallbackData; + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct StaticSetterTag {}; + + template + static napi_callback WrapStaticSetter(StaticSetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapStaticSetter(StaticSetterTag) + NAPI_NOEXCEPT { + return nullptr; + } + + bool _construction_failed = true; + bool _finalized = false; +}; + +class HandleScope { + public: + HandleScope(napi_env env, napi_handle_scope scope); + explicit HandleScope(Napi::Env env); + ~HandleScope(); + + // Disallow copying to prevent double close of napi_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(HandleScope) + + operator napi_handle_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_handle_scope _scope; +}; + +class EscapableHandleScope { + public: + EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); + explicit EscapableHandleScope(Napi::Env env); + ~EscapableHandleScope(); + + // Disallow copying to prevent double close of napi_escapable_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) + + operator napi_escapable_handle_scope() const; + + Napi::Env Env() const; + Value Escape(napi_value escapee); + + private: + napi_env _env; + napi_escapable_handle_scope _scope; +}; + +#if (NAPI_VERSION > 2) +class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); + + // Disallow copying to prevent double close of napi_callback_scope + NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) + + operator napi_callback_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_callback_scope _scope; +}; +#endif + +class AsyncContext { + public: + explicit AsyncContext(napi_env env, const char* resource_name); + explicit AsyncContext(napi_env env, + const char* resource_name, + const Object& resource); + virtual ~AsyncContext(); + + AsyncContext(AsyncContext&& other); + AsyncContext& operator=(AsyncContext&& other); + NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) + + operator napi_async_context() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _context; +}; + +#if NAPI_HAS_THREADS +class AsyncWorker { + public: + virtual ~AsyncWorker(); + + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) + + operator napi_async_work() const; + + Napi::Env Env() const; + + void Queue(); + void Cancel(); + void SuppressDestruct(); + + ObjectReference& Receiver(); + FunctionReference& Callback(); + + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, napi_status status); + + protected: + explicit AsyncWorker(const Function& callback); + explicit AsyncWorker(const Function& callback, const char* resource_name); + explicit AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncWorker(const Object& receiver, const Function& callback); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + + virtual void Execute() = 0; + virtual void OnOK(); + virtual void OnError(const Error& e); + virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); + + void SetError(const std::string& error); + + private: + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); + + napi_env _env; + napi_async_work _work; + ObjectReference _receiver; + FunctionReference _callback; + std::string _error; + bool _suppress_destruct; +}; +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +class ThreadSafeFunction { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + ThreadSafeFunction(); + ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data, Callback callback) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + struct ConvertibleContext { + template + operator T*() { + return static_cast(context); + } + void* context; + }; + + // This API may be called from any thread. + ConvertibleContext GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + napi_status CallInternal(CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const; + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + napi_threadsafe_function _tsfn; +}; + +// A TypedThreadSafeFunction by default has no context (nullptr) and can +// accept any type (void) to its CallJs. +template +class TypedThreadSafeFunction { + public: + // This API may only be called from the main thread. + // Helper function that returns nullptr if running Node-API 5+, otherwise a + // non-empty, no-op Function. This provides the ability to specify at + // compile-time a callback parameter to `New` that safely does no action + // when targeting _any_ Node-API version. +#if NAPI_VERSION > 4 + static std::nullptr_t EmptyFunctionFactory(Napi::Env env); +#else + static Napi::Function EmptyFunctionFactory(Napi::Env env); +#endif + static Napi::Function FunctionOrEmpty(Napi::Env env, + Napi::Function& callback); + +#if NAPI_VERSION > 4 + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); +#endif + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + TypedThreadSafeFunction(); + TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall(DataType* data = nullptr) const; + + // This API may be called from any thread. + napi_status NonBlockingCall(DataType* data = nullptr) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + // This API may be called from any thread. + ContextType* GetContext() const; + + private: + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + static void CallJsInternal(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + protected: + napi_threadsafe_function _tsfn; +}; +template +class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { + return _asyncprogressworker; + }; + DataType* data() { return _data; }; + + private: + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; + }; + void OnWorkComplete(Napi::Env env, napi_status status) override; + + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); +#endif + + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); + + napi_status NonBlockingCall(DataType* data); + + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize( + Napi::Env env, void* data, AsyncProgressWorkerBase* context); +}; + +template +class AsyncProgressWorker : public AsyncProgressWorkerBase { + public: + virtual ~AsyncProgressWorker(); + + class ExecutionProgress { + friend class AsyncProgressWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; + }; + + void OnWorkProgress(void*) override; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal(); + void SendProgress_(const T* data, size_t count); + + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + bool _signaled; +}; + +template +class AsyncProgressQueueWorker + : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker(){}; + + class ExecutionProgress { + friend class AsyncProgressQueueWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) + : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; + }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); +}; +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +// Memory management. +class MemoryManagement { + public: + static int64_t AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes); +}; + +// Version management +class VersionManagement { + public: + static uint32_t GetNapiVersion(BasicEnv env); + static const napi_node_version* GetNodeVersion(BasicEnv env); +}; + +#if NAPI_VERSION > 5 +template +class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + using AddonProp = ClassPropertyDescriptor; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; +}; +#endif // NAPI_VERSION > 5 + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +// Inline implementations of all the above class methods are included here. +#include "napi-inl.h" + +#endif // SRC_NAPI_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp b/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp new file mode 100644 index 000000000..8c099262a --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp @@ -0,0 +1,42 @@ +{ + 'targets': [ + { + 'target_name': 'node_addon_api', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['noexcept.gypi'], + } + }, + { + 'target_name': 'node_addon_api_except', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + } + }, + { + 'target_name': 'node_addon_api_except_all', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + 'defines': [ 'NODE_ADDON_API_CPP_EXCEPTIONS_ALL' ] + } + }, + { + 'target_name': 'node_addon_api_maybe', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['noexcept.gypi'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + } + }, + ] +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp b/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp new file mode 100644 index 000000000..4ff0ae7df --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'nothing', + 'type': 'static_library', + 'sources': [ 'nothing.c' ] + } + ] +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi b/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi new file mode 100644 index 000000000..83df4ddf0 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi @@ -0,0 +1,26 @@ +{ + 'defines': [ 'NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS' ], + 'cflags': [ '-fno-exceptions' ], + 'cflags_cc': [ '-fno-exceptions' ], + 'conditions': [ + ["OS=='win'", { + # _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi + #"defines": [ + # "_HAS_EXCEPTIONS=0" + #], + "msvs_settings": { + "VCCLCompilerTool": { + 'ExceptionHandling': 0, + 'EnablePREfast': 'true', + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', + }, + }], + ], +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/nothing.c b/week-5/solution/frontend/node_modules/node-addon-api/nothing.c new file mode 100644 index 000000000..e69de29bb diff --git a/week-5/solution/frontend/node_modules/node-addon-api/package-support.json b/week-5/solution/frontend/node_modules/node-addon-api/package-support.json new file mode 100644 index 000000000..10d3607ac --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/package-support.json @@ -0,0 +1,21 @@ +{ + "versions": [ + { + "version": "*", + "target": { + "node": "active" + }, + "response": { + "type": "time-permitting", + "paid": false, + "contact": { + "name": "node-addon-api team", + "url": "https://github.com/nodejs/node-addon-api/issues" + } + }, + "backing": [ { "project": "https://github.com/nodejs" }, + { "foundation": "https://openjsf.org/" } + ] + } + ] +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/package.json b/week-5/solution/frontend/node_modules/node-addon-api/package.json new file mode 100644 index 000000000..f04d661fa --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/package.json @@ -0,0 +1,480 @@ +{ + "bugs": { + "url": "https://github.com/nodejs/node-addon-api/issues" + }, + "contributors": [ + { + "name": "Abhishek Kumar Singh", + "url": "https://github.com/abhi11210646" + }, + { + "name": "Alba Mendez", + "url": "https://github.com/jmendeth" + }, + { + "name": "Alexander Floh", + "url": "https://github.com/alexanderfloh" + }, + { + "name": "Ammar Faizi", + "url": "https://github.com/ammarfaizi2" + }, + { + "name": "András Timár, Dr", + "url": "https://github.com/timarandras" + }, + { + "name": "Andrew Petersen", + "url": "https://github.com/kirbysayshi" + }, + { + "name": "Anisha Rohra", + "url": "https://github.com/anisha-rohra" + }, + { + "name": "Anna Henningsen", + "url": "https://github.com/addaleax" + }, + { + "name": "Arnaud Botella", + "url": "https://github.com/BotellaA" + }, + { + "name": "Arunesh Chandra", + "url": "https://github.com/aruneshchandra" + }, + { + "name": "Azlan Mukhtar", + "url": "https://github.com/azlan" + }, + { + "name": "Ben Berman", + "url": "https://github.com/rivertam" + }, + { + "name": "Benjamin Byholm", + "url": "https://github.com/kkoopa" + }, + { + "name": "Bill Gallafent", + "url": "https://github.com/gallafent" + }, + { + "name": "blagoev", + "url": "https://github.com/blagoev" + }, + { + "name": "Bruce A. MacNaughton", + "url": "https://github.com/bmacnaughton" + }, + { + "name": "Cory Mickelson", + "url": "https://github.com/corymickelson" + }, + { + "name": "Daniel Bevenius", + "url": "https://github.com/danbev" + }, + { + "name": "Dante Calderón", + "url": "https://github.com/dantehemerson" + }, + { + "name": "Darshan Sen", + "url": "https://github.com/RaisinTen" + }, + { + "name": "David Halls", + "url": "https://github.com/davedoesdev" + }, + { + "name": "Deepak Rajamohan", + "url": "https://github.com/deepakrkris" + }, + { + "name": "Dmitry Ashkadov", + "url": "https://github.com/dmitryash" + }, + { + "name": "Dongjin Na", + "url": "https://github.com/nadongguri" + }, + { + "name": "Doni Rubiagatra", + "url": "https://github.com/rubiagatra" + }, + { + "name": "Eric Bickle", + "url": "https://github.com/ebickle" + }, + { + "name": "extremeheat", + "url": "https://github.com/extremeheat" + }, + { + "name": "Feng Yu", + "url": "https://github.com/F3n67u" + }, + { + "name": "Ferdinand Holzer", + "url": "https://github.com/fholzer" + }, + { + "name": "Gabriel Schulhof", + "url": "https://github.com/gabrielschulhof" + }, + { + "name": "Guenter Sandner", + "url": "https://github.com/gms1" + }, + { + "name": "Gus Caplan", + "url": "https://github.com/devsnek" + }, + { + "name": "Helio Frota", + "url": "https://github.com/helio-frota" + }, + { + "name": "Hitesh Kanwathirtha", + "url": "https://github.com/digitalinfinity" + }, + { + "name": "ikokostya", + "url": "https://github.com/ikokostya" + }, + { + "name": "Jack Xia", + "url": "https://github.com/JckXia" + }, + { + "name": "Jake Barnes", + "url": "https://github.com/DuBistKomisch" + }, + { + "name": "Jake Yoon", + "url": "https://github.com/yjaeseok" + }, + { + "name": "Jason Ginchereau", + "url": "https://github.com/jasongin" + }, + { + "name": "Jenny", + "url": "https://github.com/egg-bread" + }, + { + "name": "Jeroen Janssen", + "url": "https://github.com/japj" + }, + { + "name": "Jim Schlight", + "url": "https://github.com/jschlight" + }, + { + "name": "Jinho Bang", + "url": "https://github.com/romandev" + }, + { + "name": "José Expósito", + "url": "https://github.com/JoseExposito" + }, + { + "name": "joshgarde", + "url": "https://github.com/joshgarde" + }, + { + "name": "Julian Mesa", + "url": "https://github.com/julianmesa-gitkraken" + }, + { + "name": "Kasumi Hanazuki", + "url": "https://github.com/hanazuki" + }, + { + "name": "Kelvin", + "url": "https://github.com/kelvinhammond" + }, + { + "name": "Kevin Eady", + "url": "https://github.com/KevinEady" + }, + { + "name": "Kévin VOYER", + "url": "https://github.com/kecsou" + }, + { + "name": "kidneysolo", + "url": "https://github.com/kidneysolo" + }, + { + "name": "Koki Nishihara", + "url": "https://github.com/Nishikoh" + }, + { + "name": "Konstantin Tarkus", + "url": "https://github.com/koistya" + }, + { + "name": "Kyle Farnung", + "url": "https://github.com/kfarnung" + }, + { + "name": "Kyle Kovacs", + "url": "https://github.com/nullromo" + }, + { + "name": "legendecas", + "url": "https://github.com/legendecas" + }, + { + "name": "LongYinan", + "url": "https://github.com/Brooooooklyn" + }, + { + "name": "Lovell Fuller", + "url": "https://github.com/lovell" + }, + { + "name": "Luciano Martorella", + "url": "https://github.com/lmartorella" + }, + { + "name": "mastergberry", + "url": "https://github.com/mastergberry" + }, + { + "name": "Mathias Küsel", + "url": "https://github.com/mathiask88" + }, + { + "name": "Mathias Stearn", + "url": "https://github.com/RedBeard0531" + }, + { + "name": "Matteo Collina", + "url": "https://github.com/mcollina" + }, + { + "name": "Michael Dawson", + "url": "https://github.com/mhdawson" + }, + { + "name": "Michael Price", + "url": "https://github.com/mikepricedev" + }, + { + "name": "Michele Campus", + "url": "https://github.com/kYroL01" + }, + { + "name": "Mikhail Cheshkov", + "url": "https://github.com/mcheshkov" + }, + { + "name": "nempoBu4", + "url": "https://github.com/nempoBu4" + }, + { + "name": "Nicola Del Gobbo", + "url": "https://github.com/NickNaso" + }, + { + "name": "Nick Soggin", + "url": "https://github.com/iSkore" + }, + { + "name": "Nikolai Vavilov", + "url": "https://github.com/seishun" + }, + { + "name": "Nurbol Alpysbayev", + "url": "https://github.com/anurbol" + }, + { + "name": "pacop", + "url": "https://github.com/pacop" + }, + { + "name": "Peter Šándor", + "url": "https://github.com/petersandor" + }, + { + "name": "Philipp Renoth", + "url": "https://github.com/DaAitch" + }, + { + "name": "rgerd", + "url": "https://github.com/rgerd" + }, + { + "name": "Richard Lau", + "url": "https://github.com/richardlau" + }, + { + "name": "Rolf Timmermans", + "url": "https://github.com/rolftimmermans" + }, + { + "name": "Ross Weir", + "url": "https://github.com/ross-weir" + }, + { + "name": "Ryuichi Okumura", + "url": "https://github.com/okuryu" + }, + { + "name": "Saint Gabriel", + "url": "https://github.com/chineduG" + }, + { + "name": "Sampson Gao", + "url": "https://github.com/sampsongao" + }, + { + "name": "Sam Roberts", + "url": "https://github.com/sam-github" + }, + { + "name": "strager", + "url": "https://github.com/strager" + }, + { + "name": "Taylor Woll", + "url": "https://github.com/boingoing" + }, + { + "name": "Thomas Gentilhomme", + "url": "https://github.com/fraxken" + }, + { + "name": "Tim Rach", + "url": "https://github.com/timrach" + }, + { + "name": "Tobias Nießen", + "url": "https://github.com/tniessen" + }, + { + "name": "todoroff", + "url": "https://github.com/todoroff" + }, + { + "name": "Toyo Li", + "url": "https://github.com/toyobayashi" + }, + { + "name": "Tux3", + "url": "https://github.com/tux3" + }, + { + "name": "Vlad Velmisov", + "url": "https://github.com/Velmisov" + }, + { + "name": "Vladimir Morozov", + "url": "https://github.com/vmoroz" + }, + { + "name": "WenheLI", + "url": "https://github.com/WenheLI" + }, + { + "name": "Xuguang Mei", + "url": "https://github.com/meixg" + }, + { + "name": "Yohei Kishimoto", + "url": "https://github.com/morokosi" + }, + { + "name": "Yulong Wang", + "url": "https://github.com/fs-eire" + }, + { + "name": "Ziqiu Zhao", + "url": "https://github.com/ZzqiZQute" + }, + { + "name": "Feng Yu", + "url": "https://github.com/F3n67u" + }, + { + "name": "wanlu wang", + "url": "https://github.com/wanlu" + }, + { + "name": "Caleb Hearon", + "url": "https://github.com/chearon" + }, + { + "name": "Marx", + "url": "https://github.com/MarxJiao" + }, + { + "name": "Ömer AKGÜL", + "url": "https://github.com/tuhalf" + } + ], + "description": "Node.js API (Node-API)", + "devDependencies": { + "benchmark": "^2.1.4", + "bindings": "^1.5.0", + "clang-format": "^1.4.0", + "eslint": "^9.13.0", + "fs-extra": "^11.1.1", + "neostandard": "^0.12.0", + "pre-commit": "^1.2.2", + "semver": "^7.6.0" + }, + "directories": {}, + "gypfile": false, + "homepage": "https://github.com/nodejs/node-addon-api", + "keywords": [ + "n-api", + "napi", + "addon", + "native", + "bindings", + "c", + "c++", + "nan", + "node-addon-api" + ], + "license": "MIT", + "main": "index.js", + "name": "node-addon-api", + "readme": "README.md", + "repository": { + "type": "git", + "url": "git://github.com/nodejs/node-addon-api.git" + }, + "files": [ + "*.{c,h,gyp,gypi}", + "package-support.json", + "tools/" + ], + "scripts": { + "prebenchmark": "node-gyp rebuild -C benchmark", + "benchmark": "node benchmark", + "create-coverage": "npm test --coverage", + "report-coverage-html": "rm -rf coverage-html && mkdir coverage-html && gcovr -e test --merge-mode-functions merge-use-line-max --html-nested ./coverage-html/index.html test", + "report-coverage-xml": "rm -rf coverage-xml && mkdir coverage-xml && gcovr -e test --merge-mode-functions merge-use-line-max --xml -o ./coverage-xml/coverage-cxx.xml test", + "pretest": "node-gyp rebuild -C test", + "test": "node test", + "test:debug": "node-gyp rebuild -C test --debug && NODE_API_BUILD_CONFIG=Debug node ./test/index.js", + "predev": "node-gyp rebuild -C test --debug", + "dev": "node test", + "predev:incremental": "node-gyp configure build -C test --debug", + "dev:incremental": "node test", + "doc": "doxygen doc/Doxyfile", + "lint": "eslint && node tools/clang-format", + "lint:fix": "eslint --fix && node tools/clang-format --fix" + }, + "pre-commit": "lint", + "version": "8.5.0", + "support": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md b/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md new file mode 100644 index 000000000..6b80e94f5 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md @@ -0,0 +1,73 @@ +# Tools + +## clang-format + +The clang-format checking tools is designed to check changed lines of code compared to given git-refs. + +## Migration Script + +The migration tool is designed to reduce repetitive work in the migration process. However, the script is not aiming to convert every thing for you. There are usually some small fixes and major reconstruction required. + +### How To Use + +To run the conversion script, first make sure you have the latest `node-addon-api` in your `node_modules` directory. +``` +npm install node-addon-api +``` + +Then run the script passing your project directory +``` +node ./node_modules/node-addon-api/tools/conversion.js ./ +``` + +After finish, recompile and debug things that are missed by the script. + + +### Quick Fixes +Here is the list of things that can be fixed easily. + 1. Change your methods' return value to void if it doesn't return value to JavaScript. + 2. Use `.` to access attribute or to invoke member function in Napi::Object instead of `->`. + 3. `Napi::New(env, value);` to `Napi::[Type]::New(env, value); + + +### Major Reconstructions +The implementation of `Napi::ObjectWrap` is significantly different from NAN's. `Napi::ObjectWrap` takes a pointer to the wrapped object and creates a reference to the wrapped object inside ObjectWrap constructor. `Napi::ObjectWrap` also associates wrapped object's instance methods to Javascript module instead of static methods like NAN. + +So if you use Nan::ObjectWrap in your module, you will need to execute the following steps. + + 1. Convert your [ClassName]::New function to a constructor function that takes a `Napi::CallbackInfo`. Declare it as +``` +[ClassName](const Napi::CallbackInfo& info); +``` +and define it as +``` +[ClassName]::[ClassName](const Napi::CallbackInfo& info) : Napi::ObjectWrap<[ClassName]>(info){ + ... +} +``` +This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instantiated and `Napi::ObjectWrap` can use the `this` pointer to create a reference to the wrapped object. + + 2. Move your original constructor code into the new constructor. Delete your original constructor. + 3. In your class initialization function, associate native methods in the following way. +``` +Napi::FunctionReference constructor; + +void [ClassName]::Init(Napi::Env env, Napi::Object exports, Napi::Object module) { + Napi::HandleScope scope(env); + Napi::Function ctor = DefineClass(env, "Canvas", { + InstanceMethod<&[ClassName]::Func1>("Func1"), + InstanceMethod<&[ClassName]::Func2>("Func2"), + InstanceAccessor<&[ClassName]::ValueGetter>("Value"), + StaticMethod<&[ClassName]::StaticMethod>("MethodName"), + InstanceValue("Value", Napi::[Type]::New(env, value)), + }); + + constructor = Napi::Persistent(ctor); + constructor .SuppressDestruct(); + exports.Set("[ClassName]", ctor); +} +``` + 4. In function where you need to Unwrap the ObjectWrap in NAN like `[ClassName]* native = Nan::ObjectWrap::Unwrap<[ClassName]>(info.This());`, use `this` pointer directly as the unwrapped object as each ObjectWrap instance is associated with a unique object instance. + + +If you still find issues after following this guide, please leave us an issue describing your problem and we will try to resolve it. diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js new file mode 100644 index 000000000..9199af334 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js @@ -0,0 +1,99 @@ +'use strict'; +// Descend into a directory structure and, for each file matching *.node, output +// based on the imports found in the file whether it's an N-API module or not. + +const fs = require('fs'); +const path = require('path'); + +// Read the output of the command, break it into lines, and use the reducer to +// decide whether the file is an N-API module or not. +function checkFile (file, command, argv, reducer) { + const child = require('child_process').spawn(command, argv, { + stdio: ['inherit', 'pipe', 'inherit'] + }); + let leftover = ''; + let isNapi; + child.stdout.on('data', (chunk) => { + if (isNapi === undefined) { + chunk = (leftover + chunk.toString()).split(/[\r\n]+/); + leftover = chunk.pop(); + isNapi = chunk.reduce(reducer, isNapi); + if (isNapi !== undefined) { + child.kill(); + } + } + }); + child.on('close', (code, signal) => { + if ((code === null && signal !== null) || (code !== 0)) { + console.log( + command + ' exited with code: ' + code + ' and signal: ' + signal); + } else { + // Green if it's a N-API module, red otherwise. + console.log( + '\x1b[' + (isNapi ? '42' : '41') + 'm' + + (isNapi ? ' N-API' : 'Not N-API') + + '\x1b[0m: ' + file); + } + }); +} + +// Use nm -a to list symbols. +function checkFileUNIX (file) { + checkFile(file, 'nm', ['-a', file], (soFar, line) => { + if (soFar === undefined) { + line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/); + if (line[2] === 'U') { + if (/^napi/.test(line[3])) { + soFar = true; + } + } + } + return soFar; + }); +} + +// Use dumpbin /imports to list symbols. +function checkFileWin32 (file) { + checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => { + if (soFar === undefined) { + line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/); + if (line && /^napi/.test(line[line.length - 1])) { + soFar = true; + } + } + return soFar; + }); +} + +// Descend into a directory structure and pass each file ending in '.node' to +// one of the above checks, depending on the OS. +function recurse (top) { + fs.readdir(top, (error, items) => { + if (error) { + throw new Error('error reading directory ' + top + ': ' + error); + } + items.forEach((item) => { + item = path.join(top, item); + fs.stat(item, ((item) => (error, stats) => { + if (error) { + throw new Error('error about ' + item + ': ' + error); + } + if (stats.isDirectory()) { + recurse(item); + } else if (/[.]node$/.test(item) && + // Explicitly ignore files called 'nothing.node' because they are + // artefacts of node-addon-api having identified a version of + // Node.js that ships with a correct implementation of N-API. + path.basename(item) !== 'nothing.node') { + process.platform === 'win32' + ? checkFileWin32(item) + : checkFileUNIX(item); + } + })(item)); + }); + }); +} + +// Start with the directory given on the command line or the current directory +// if nothing was given. +recurse(process.argv.length > 3 ? process.argv[2] : '.'); diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js new file mode 100644 index 000000000..e4bb4f52e --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +const spawn = require('child_process').spawnSync; +const path = require('path'); + +const filesToCheck = ['*.h', '*.cc']; +const FORMAT_START = process.env.FORMAT_START || 'main'; + +function main (args) { + let fix = false; + while (args.length > 0) { + switch (args[0]) { + case '-f': + case '--fix': + fix = true; + break; + default: + } + args.shift(); + } + + const clangFormatPath = path.dirname(require.resolve('clang-format')); + const binary = process.platform === 'win32' + ? 'node_modules\\.bin\\clang-format.cmd' + : 'node_modules/.bin/clang-format'; + const options = ['--binary=' + binary, '--style=file']; + if (fix) { + options.push(FORMAT_START); + } else { + options.push('--diff', FORMAT_START); + } + + const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); + const result = spawn( + 'python', + [gitClangFormatPath, ...options, '--', ...filesToCheck], + { encoding: 'utf-8' } + ); + + if (result.stderr) { + console.error('Error running git-clang-format:', result.stderr); + return 2; + } + + const clangFormatOutput = result.stdout.trim(); + // Bail fast if in fix mode. + if (fix) { + console.log(clangFormatOutput); + return 0; + } + // Detect if there is any complains from clang-format + if ( + clangFormatOutput !== '' && + clangFormatOutput !== 'no modified files to format' && + clangFormatOutput !== 'clang-format did not modify any files' + ) { + console.error(clangFormatOutput); + const fixCmd = 'npm run lint:fix'; + console.error(` + ERROR: please run "${fixCmd}" to format changes in your commit + Note that when running the command locally, please keep your local + main branch and working branch up to date with nodejs/node-addon-api + to exclude un-related complains. + Or you can run "env FORMAT_START=upstream/main ${fixCmd}".`); + return 1; + } +} + +if (require.main === module) { + process.exitCode = main(process.argv.slice(2)); +} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js new file mode 100644 index 000000000..e92a03a26 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js @@ -0,0 +1,301 @@ +#! /usr/bin/env node + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const args = process.argv.slice(2); +const dir = args[0]; +if (!dir) { + console.log('Usage: node ' + path.basename(__filename) + ' '); + process.exit(1); +} + +const NodeApiVersion = require('../').version; + +const disable = args[1]; +let ConfigFileOperations; +if (disable !== '--disable' && dir !== '--disable') { + ConfigFileOperations = { + 'package.json': [ + [/([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'], + [/[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, ''] + ], + 'binding.gyp': [ + [/([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \'\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], + [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);\s+(\w+)\.Reset\((\1)\);\s+\1->SetClassName\((Nan::String::New|Nan::New<(v8::)*String>)\("(.+?)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$6", {'], + [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], + [/Nan::New\(([\w\d:]+)\)->GetFunction\(\)/g, 'Napi::Function::New(env, $1)'], + [/Nan::New\(([\w\d:]+)\)->GetFunction()/g, 'Napi::Function::New(env, $1);'], + [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], + [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], + + // FunctionTemplate to FunctionReference + [/Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'], + [/Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'], + [/v8::Local/g, 'Napi::FunctionReference'], + [/Local/g, 'Napi::FunctionReference'], + [/v8::FunctionTemplate/g, 'Napi::FunctionReference'], + [/FunctionTemplate/g, 'Napi::FunctionReference'], + + [/([ ]*)Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, '$1InstanceMethod("$2", &$3),'], + [/([ ]*)(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm, + '});\n\n' + + '$1constructor = Napi::Persistent($3);\n' + + '$1constructor.SuppressDestruct();\n' + + '$1target.Set("$2", $3);'], + + // TODO: Other attribute combinations + [/static_cast\(ReadOnly\s*\|\s*DontDelete\)/gm, + 'static_cast(napi_enumerable | napi_configurable)'], + + [/([\w\d:<>]+?)::Cast\((.+?)\)/g, '$2.As<$1>()'], + + [/\*Nan::Utf8String\(([^)]+)\)/g, '$1->As().Utf8Value().c_str()'], + [/Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As()'], + [/Nan::Utf8String/g, 'std::string'], + + [/v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], + [/String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], + [/\.length\(\)/g, '.Length()'], + + [/Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,'], + + [/class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>'], + [/(\w+)\(([^)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3'], + + // HandleOKCallback to OnOK + [/HandleOKCallback/g, 'OnOK'], + // HandleErrorCallback to OnError + [/HandleErrorCallback/g, 'OnError'], + + // ex. .As() to .As() + [/\.As\(\)/g, '.As()'], + [/\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As()'], + + // ex. Nan::New(info[0]) to Napi::Number::New(info[0]) + [/Nan::New<(v8::)*Integer>\((.+?)\)/g, 'Napi::Number::New(env, $2)'], + [/Nan::New\(([0-9.]+)\)/g, 'Napi::Number::New(env, $1)'], + [/Nan::New<(v8::)*String>\("(.+?)"\)/g, 'Napi::String::New(env, "$2")'], + [/Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")'], + [/Nan::New<(v8::)*(.+?)>\(\)/g, 'Napi::$2::New(env)'], + [/Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)'], + [/Nan::New<(v8::)*(.+?)>\(/g, 'Napi::$2::New(env, '], + [/Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, '], + [/Nan::NewBuffer\(/g, 'Napi::Buffer::New(env, '], + // TODO: Properly handle this + [/Nan::New\(/g, 'Napi::New(env, '], + + [/\.IsInt32\(\)/g, '.IsNumber()'], + [/->IsInt32\(\)/g, '.IsNumber()'], + + [/(.+?)->BooleanValue\(\)/g, '$1.As().Value()'], + [/(.+?)->Int32Value\(\)/g, '$1.As().Int32Value()'], + [/(.+?)->Uint32Value\(\)/g, '$1.As().Uint32Value()'], + [/(.+?)->IntegerValue\(\)/g, '$1.As().Int64Value()'], + [/(.+?)->NumberValue\(\)/g, '$1.As().DoubleValue()'], + + // ex. Nan::To(info[0]) to info[0].Value() + [/Nan::To\((.+?)\)/g, '$2.To()'], + [/Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To()'], + // ex. Nan::To(info[0]) to info[0].As().Value() + [/Nan::To\((.+?)\)/g, '$1.As().Value()'], + // ex. Nan::To(info[0]) to info[0].As().Int32Value() + [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], + // ex. Nan::To(info[0]) to info[0].As().Int32Value() + [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], + // ex. Nan::To(info[0]) to info[0].As().Uint32Value() + [/Nan::To\((.+?)\)/g, '$1.As().Uint32Value()'], + // ex. Nan::To(info[0]) to info[0].As().Int64Value() + [/Nan::To\((.+?)\)/g, '$1.As().Int64Value()'], + // ex. Nan::To(info[0]) to info[0].As().FloatValue() + [/Nan::To\((.+?)\)/g, '$1.As().FloatValue()'], + // ex. Nan::To(info[0]) to info[0].As().DoubleValue() + [/Nan::To\((.+?)\)/g, '$1.As().DoubleValue()'], + + [/Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())'], + + [/Nan::Has\(([^,]+),\s*/gm, '($1).Has('], + [/\.Has\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Has($1)'], + [/\.Has\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Has($1)'], + + [/Nan::Get\(([^,]+),\s*/gm, '($1).Get('], + [/\.Get\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Get($1)'], + [/\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)'], + + [/Nan::Set\(([^,]+),\s*/gm, '($1).Set('], + [/\.Set\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\s*,/gm, '.Set($1,'], + [/\.Set\([\s|\\]*Nan::New\(([^)]+)\)\s*,/gm, '.Set($1,'], + + // ex. node::Buffer::HasInstance(info[0]) to info[0].IsBuffer() + [/node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()'], + // ex. node::Buffer::Length(info[0]) to info[0].Length() + [/node::Buffer::Length\((.+?)\)/g, '$1.As>().Length()'], + // ex. node::Buffer::Data(info[0]) to info[0].Data() + [/node::Buffer::Data\((.+?)\)/g, '$1.As>().Data()'], + [/Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, '], + + // Nan::AsyncQueueWorker(worker) + [/Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();'], + [/Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()'], + + // Nan::ThrowError(error) to Napi::Error::New(env, error).ThrowAsJavaScriptException() + [/([ ]*)return Nan::Throw(\w*?)Error\((.+?)\);/g, '$1Napi::$2Error::New(env, $3).ThrowAsJavaScriptException();\n$1return env.Null();'], + [/Nan::Throw(\w*?)Error\((.+?)\);\n(\s*)return;/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n$3return env.Null();'], + [/Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n'], + // Nan::RangeError(error) to Napi::RangeError::New(env, error) + [/Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)'], + + [/Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)'], + + [/Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);'], + [/Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope'], + [/Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty('], + [/\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", '], + // [ /Nan::GetPropertyNames\(([^,]+)\)/, '$1->GetPropertyNames()' ], + [/Nan::Equals\(([^,]+),/g, '$1.StrictEquals('], + + [/(.+)->Set\(/g, '$1.Set('], + + [/Nan::Callback/g, 'Napi::FunctionReference'], + + [/Nan::Persistent/g, 'Napi::ObjectReference'], + [/Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target'], + + [/(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;'], + [/Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();'], + + [/Nan::NAN_METHOD_RETURN_TYPE/g, 'void'], + [/NAN_INLINE/g, 'inline'], + + [/Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&'], + [/NAN_METHOD\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/static\s*NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/static\s*NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], + [/NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], + [/void Init\((v8::)*Local<(v8::)*Object> exports\)/g, 'Napi::Object Init(Napi::Env env, Napi::Object exports)'], + [/NAN_MODULE_INIT\(([\w\d:]+?)\);/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports);'], + [/NAN_MODULE_INIT\(([\w\d:]+?)\)/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports)'], + + [/::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)'], + [/constructor_template/g, 'constructor'], + + [/Nan::FunctionCallbackInfo<(v8::)?Value>[ ]*& [ ]*info\)[ ]*{\n*([ ]*)/gm, 'Napi::CallbackInfo& info) {\n$2Napi::Env env = info.Env();\n$2'], + [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&\s*info\);/g, 'Napi::CallbackInfo& info);'], + [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&/g, 'Napi::CallbackInfo&'], + + [/Buffer::HasInstance\(([^)]+)\)/g, '$1.IsBuffer()'], + + [/info\[(\d+)\]->/g, 'info[$1].'], + [/info\[([\w\d]+)\]->/g, 'info[$1].'], + [/info\.This\(\)->/g, 'info.This().'], + [/->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()'], + [/info.GetReturnValue\(\).SetUndefined\(\)/g, 'return env.Undefined()'], + [/info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;'], + + // ex. Local to Napi::Value + [/v8::Local/g, 'Napi::$1'], + [/Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'], + + // Declare an env in helper functions that take a Napi::Value + [/(\w+)\(Napi::Value (\w+)(,\s*[^()]+)?\)\s*{\n*([ ]*)/gm, '$1(Napi::Value $2$3) {\n$4Napi::Env env = $2.Env();\n$4'], + + // delete #include and/or + [/#include +(<|")(?:node|nan).h("|>)/g, '#include $1napi.h$2\n#include $1uv.h$2'], + // NODE_MODULE to NODE_API_MODULE + [/NODE_MODULE/g, 'NODE_API_MODULE'], + [/Nan::/g, 'Napi::'], + [/nan.h/g, 'napi.h'], + + // delete .FromJust() + [/\.FromJust\(\)/g, ''], + // delete .ToLocalCheck() + [/\.ToLocalChecked\(\)/g, ''], + [/^.*->SetInternalFieldCount\(.*$/gm, ''], + + // replace using node; and/or using v8; to using Napi; + [/using (node|v8);/g, 'using Napi;'], + [/using namespace (node|Nan|v8);/g, 'using namespace Napi;'], + // delete using v8::Local; + [/using v8::Local;\n/g, ''], + // replace using v8::XXX; with using Napi::XXX + [/using v8::([A-Za-z]+);/g, 'using Napi::$1;'] + +]; + +const paths = listFiles(dir); +paths.forEach(function (dirEntry) { + const filename = dirEntry.split('\\').pop().split('/').pop(); + + // Check whether the file is a source file or a config file + // then execute function accordingly + const sourcePattern = /.+\.h|.+\.cc|.+\.cpp/; + if (sourcePattern.test(filename)) { + convertFile(dirEntry, SourceFileOperations); + } else if (ConfigFileOperations[filename] != null) { + convertFile(dirEntry, ConfigFileOperations[filename]); + } +}); + +function listFiles (dir, filelist) { + const files = fs.readdirSync(dir); + filelist = filelist || []; + files.forEach(function (file) { + if (file === 'node_modules') { + return; + } + + if (fs.statSync(path.join(dir, file)).isDirectory()) { + filelist = listFiles(path.join(dir, file), filelist); + } else { + filelist.push(path.join(dir, file)); + } + }); + return filelist; +} + +function convert (content, operations) { + for (let i = 0; i < operations.length; i++) { + const operation = operations[i]; + content = content.replace(operation[0], operation[1]); + } + return content; +} + +function convertFile (fileName, operations) { + fs.readFile(fileName, 'utf-8', function (err, file) { + if (err) throw err; + + file = convert(file, operations); + + fs.writeFile(fileName, file, function (err) { + if (err) throw err; + }); + }); +} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE b/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE new file mode 100644 index 000000000..56fce0895 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Mathias Buus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/README.md b/week-5/solution/frontend/node_modules/node-gyp-build/README.md new file mode 100644 index 000000000..f712ca686 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/README.md @@ -0,0 +1,58 @@ +# node-gyp-build + +> Build tool and bindings loader for [`node-gyp`][node-gyp] that supports prebuilds. + +``` +npm install node-gyp-build +``` + +[![Test](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml/badge.svg)](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml) + +Use together with [`prebuildify`][prebuildify] to easily support prebuilds for your native modules. + +## Usage + +> **Note.** Prebuild names have changed in [`prebuildify@3`][prebuildify] and `node-gyp-build@4`. Please see the documentation below. + +`node-gyp-build` works similar to [`node-gyp build`][node-gyp] except that it will check if a build or prebuild is present before rebuilding your project. + +It's main intended use is as an npm install script and bindings loader for native modules that bundle prebuilds using [`prebuildify`][prebuildify]. + +First add `node-gyp-build` as an install script to your native project + +``` js +{ + ... + "scripts": { + "install": "node-gyp-build" + } +} +``` + +Then in your `index.js`, instead of using the [`bindings`](https://www.npmjs.com/package/bindings) module use `node-gyp-build` to load your binding. + +``` js +var binding = require('node-gyp-build')(__dirname) +``` + +If you do these two things and bundle prebuilds with [`prebuildify`][prebuildify] your native module will work for most platforms +without having to compile on install time AND will work in both node and electron without the need to recompile between usage. + +Users can override `node-gyp-build` and force compiling by doing `npm install --build-from-source`. + +Prebuilds will be attempted loaded from `MODULE_PATH/prebuilds/...` and then next `EXEC_PATH/prebuilds/...` (the latter allowing use with `zeit/pkg`) + +## Supported prebuild names + +If so desired you can bundle more specific flavors, for example `musl` builds to support Alpine, or targeting a numbered ARM architecture version. + +These prebuilds can be bundled in addition to generic prebuilds; `node-gyp-build` will try to find the most specific flavor first. Prebuild filenames are composed of _tags_. The runtime tag takes precedence, as does an `abi` tag over `napi`. For more details on tags, please see [`prebuildify`][prebuildify]. + +Values for the `libc` and `armv` tags are auto-detected but can be overridden through the `LIBC` and `ARM_VERSION` environment variables, respectively. + +## License + +MIT + +[prebuildify]: https://github.com/prebuild/prebuildify +[node-gyp]: https://www.npmjs.com/package/node-gyp diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md b/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md new file mode 100644 index 000000000..da9c516dd --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md @@ -0,0 +1,5 @@ +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/bin.js b/week-5/solution/frontend/node_modules/node-gyp-build/bin.js new file mode 100644 index 000000000..c778e0ab9 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/bin.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +var proc = require('child_process') +var os = require('os') +var path = require('path') + +if (!buildFromSource()) { + proc.exec('node-gyp-build-test', function (err, stdout, stderr) { + if (err) { + if (verbose()) console.error(stderr) + preinstall() + } + }) +} else { + preinstall() +} + +function build () { + var win32 = os.platform() === 'win32' + var shell = win32 + var args = [win32 ? 'node-gyp.cmd' : 'node-gyp', 'rebuild'] + + try { + var pkg = require('node-gyp/package.json') + args = [ + process.execPath, + path.join(require.resolve('node-gyp/package.json'), '..', typeof pkg.bin === 'string' ? pkg.bin : pkg.bin['node-gyp']), + 'rebuild' + ] + shell = false + } catch (_) {} + + proc.spawn(args[0], args.slice(1), { stdio: 'inherit', shell, windowsHide: true }).on('exit', function (code) { + if (code || !process.argv[3]) process.exit(code) + exec(process.argv[3]).on('exit', function (code) { + process.exit(code) + }) + }) +} + +function preinstall () { + if (!process.argv[2]) return build() + exec(process.argv[2]).on('exit', function (code) { + if (code) process.exit(code) + build() + }) +} + +function exec (cmd) { + if (process.platform !== 'win32') { + var shell = os.platform() === 'android' ? 'sh' : true + return proc.spawn(cmd, [], { + shell, + stdio: 'inherit' + }) + } + + return proc.spawn(cmd, [], { + windowsVerbatimArguments: true, + stdio: 'inherit', + shell: true, + windowsHide: true + }) +} + +function buildFromSource () { + return hasFlag('--build-from-source') || process.env.npm_config_build_from_source === 'true' +} + +function verbose () { + return hasFlag('--verbose') || process.env.npm_config_loglevel === 'verbose' +} + +// TODO (next major): remove in favor of env.npm_config_* which works since npm +// 0.1.8 while npm_config_argv will stop working in npm 7. See npm/rfcs#90 +function hasFlag (flag) { + if (!process.env.npm_config_argv) return false + + try { + return JSON.parse(process.env.npm_config_argv).original.indexOf(flag) !== -1 + } catch (_) { + return false + } +} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js b/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js new file mode 100644 index 000000000..b6622a5c2 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = 'test' + +var path = require('path') +var test = null + +try { + var pkg = require(path.join(process.cwd(), 'package.json')) + if (pkg.name && process.env[pkg.name.toUpperCase().replace(/-/g, '_')]) { + process.exit(0) + } + test = pkg.prebuild.test +} catch (err) { + // do nothing +} + +if (test) require(path.join(process.cwd(), test)) +else require('./')() diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/index.js b/week-5/solution/frontend/node_modules/node-gyp-build/index.js new file mode 100644 index 000000000..07eb14ffd --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/index.js @@ -0,0 +1,6 @@ +const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line +if (typeof runtimeRequire.addon === 'function') { // if the platform supports native resolving prefer that + module.exports = runtimeRequire.addon.bind(runtimeRequire) +} else { // else use the runtime version here + module.exports = require('./node-gyp-build.js') +} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js b/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js new file mode 100644 index 000000000..76b96e107 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js @@ -0,0 +1,207 @@ +var fs = require('fs') +var path = require('path') +var os = require('os') + +// Workaround to fix webpack's build warnings: 'the request of a dependency is an expression' +var runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line + +var vars = (process.config && process.config.variables) || {} +var prebuildsOnly = !!process.env.PREBUILDS_ONLY +var abi = process.versions.modules // TODO: support old node where this is undef +var runtime = isElectron() ? 'electron' : (isNwjs() ? 'node-webkit' : 'node') + +var arch = process.env.npm_config_arch || os.arch() +var platform = process.env.npm_config_platform || os.platform() +var libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc') +var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || '' +var uv = (process.versions.uv || '').split('.')[0] + +module.exports = load + +function load (dir) { + return runtimeRequire(load.resolve(dir)) +} + +load.resolve = load.path = function (dir) { + dir = path.resolve(dir || '.') + + try { + var name = runtimeRequire(path.join(dir, 'package.json')).name.toUpperCase().replace(/-/g, '_') + if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD'] + } catch (err) {} + + if (!prebuildsOnly) { + var release = getFirst(path.join(dir, 'build/Release'), matchBuild) + if (release) return release + + var debug = getFirst(path.join(dir, 'build/Debug'), matchBuild) + if (debug) return debug + } + + var prebuild = resolve(dir) + if (prebuild) return prebuild + + var nearby = resolve(path.dirname(process.execPath)) + if (nearby) return nearby + + var target = [ + 'platform=' + platform, + 'arch=' + arch, + 'runtime=' + runtime, + 'abi=' + abi, + 'uv=' + uv, + armv ? 'armv=' + armv : '', + 'libc=' + libc, + 'node=' + process.versions.node, + process.versions.electron ? 'electron=' + process.versions.electron : '', + typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line + ].filter(Boolean).join(' ') + + throw new Error('No native build was found for ' + target + '\n loaded from: ' + dir + '\n') + + function resolve (dir) { + // Find matching "prebuilds/-" directory + var tuples = readdirSync(path.join(dir, 'prebuilds')).map(parseTuple) + var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0] + if (!tuple) return + + // Find most specific flavor first + var prebuilds = path.join(dir, 'prebuilds', tuple.name) + var parsed = readdirSync(prebuilds).map(parseTags) + var candidates = parsed.filter(matchTags(runtime, abi)) + var winner = candidates.sort(compareTags(runtime))[0] + if (winner) return path.join(prebuilds, winner.file) + } +} + +function readdirSync (dir) { + try { + return fs.readdirSync(dir) + } catch (err) { + return [] + } +} + +function getFirst (dir, filter) { + var files = readdirSync(dir).filter(filter) + return files[0] && path.join(dir, files[0]) +} + +function matchBuild (name) { + return /\.node$/.test(name) +} + +function parseTuple (name) { + // Example: darwin-x64+arm64 + var arr = name.split('-') + if (arr.length !== 2) return + + var platform = arr[0] + var architectures = arr[1].split('+') + + if (!platform) return + if (!architectures.length) return + if (!architectures.every(Boolean)) return + + return { name, platform, architectures } +} + +function matchTuple (platform, arch) { + return function (tuple) { + if (tuple == null) return false + if (tuple.platform !== platform) return false + return tuple.architectures.includes(arch) + } +} + +function compareTuples (a, b) { + // Prefer single-arch prebuilds over multi-arch + return a.architectures.length - b.architectures.length +} + +function parseTags (file) { + var arr = file.split('.') + var extension = arr.pop() + var tags = { file: file, specificity: 0 } + + if (extension !== 'node') return + + for (var i = 0; i < arr.length; i++) { + var tag = arr[i] + + if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') { + tags.runtime = tag + } else if (tag === 'napi') { + tags.napi = true + } else if (tag.slice(0, 3) === 'abi') { + tags.abi = tag.slice(3) + } else if (tag.slice(0, 2) === 'uv') { + tags.uv = tag.slice(2) + } else if (tag.slice(0, 4) === 'armv') { + tags.armv = tag.slice(4) + } else if (tag === 'glibc' || tag === 'musl') { + tags.libc = tag + } else { + continue + } + + tags.specificity++ + } + + return tags +} + +function matchTags (runtime, abi) { + return function (tags) { + if (tags == null) return false + if (tags.runtime && tags.runtime !== runtime && !runtimeAgnostic(tags)) return false + if (tags.abi && tags.abi !== abi && !tags.napi) return false + if (tags.uv && tags.uv !== uv) return false + if (tags.armv && tags.armv !== armv) return false + if (tags.libc && tags.libc !== libc) return false + + return true + } +} + +function runtimeAgnostic (tags) { + return tags.runtime === 'node' && tags.napi +} + +function compareTags (runtime) { + // Precedence: non-agnostic runtime, abi over napi, then by specificity. + return function (a, b) { + if (a.runtime !== b.runtime) { + return a.runtime === runtime ? -1 : 1 + } else if (a.abi !== b.abi) { + return a.abi ? -1 : 1 + } else if (a.specificity !== b.specificity) { + return a.specificity > b.specificity ? -1 : 1 + } else { + return 0 + } + } +} + +function isNwjs () { + return !!(process.versions && process.versions.nw) +} + +function isElectron () { + if (process.versions && process.versions.electron) return true + if (process.env.ELECTRON_RUN_AS_NODE) return true + return typeof window !== 'undefined' && window.process && window.process.type === 'renderer' +} + +function isAlpine (platform) { + return platform === 'linux' && fs.existsSync('/etc/alpine-release') +} + +// Exposed for unit tests +// TODO: move to lib +load.parseTags = parseTags +load.matchTags = matchTags +load.compareTags = compareTags +load.parseTuple = parseTuple +load.matchTuple = matchTuple +load.compareTuples = compareTuples diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/optional.js b/week-5/solution/frontend/node_modules/node-gyp-build/optional.js new file mode 100644 index 000000000..8daa04a6f --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/optional.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +/* +I am only useful as an install script to make node-gyp not compile for purely optional native deps +*/ + +process.exit(0) diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/package.json b/week-5/solution/frontend/node_modules/node-gyp-build/package.json new file mode 100644 index 000000000..6f6f28b50 --- /dev/null +++ b/week-5/solution/frontend/node_modules/node-gyp-build/package.json @@ -0,0 +1,43 @@ +{ + "name": "node-gyp-build", + "version": "4.8.4", + "description": "Build tool and bindings loader for node-gyp that supports prebuilds", + "main": "index.js", + "imports": { + "fs": { + "bare": "builtin:fs", + "default": "fs" + }, + "path": { + "bare": "builtin:path", + "default": "path" + }, + "os": { + "bare": "builtin:os", + "default": "os" + } + }, + "devDependencies": { + "array-shuffle": "^1.0.1", + "standard": "^14.0.0", + "tape": "^5.0.0" + }, + "scripts": { + "test": "standard && node test" + }, + "bin": { + "node-gyp-build": "./bin.js", + "node-gyp-build-optional": "./optional.js", + "node-gyp-build-test": "./build-test.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/prebuild/node-gyp-build.git" + }, + "author": "Mathias Buus (@mafintosh)", + "license": "MIT", + "bugs": { + "url": "https://github.com/prebuild/node-gyp-build/issues" + }, + "homepage": "https://github.com/prebuild/node-gyp-build" +} diff --git a/week-5/solution/frontend/package-lock.json b/week-5/solution/frontend/package-lock.json index aba25f735..65e317057 100644 --- a/week-5/solution/frontend/package-lock.json +++ b/week-5/solution/frontend/package-lock.json @@ -2,5 +2,45 @@ "name": "frontend", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "dependencies": { + "bcrypt": "^6.0.0" + } + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + } + } } diff --git a/week-5/solution/frontend/package.json b/week-5/solution/frontend/package.json new file mode 100644 index 000000000..8da26fb6d --- /dev/null +++ b/week-5/solution/frontend/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "bcrypt": "^6.0.0" + } +} From 9239bbf4b4a294cd3d28b3a539b974bab38b6177 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 14:28:57 +0530 Subject: [PATCH 06/19] add coursera-app --- week-5/solution/backend/routes/user.js | 2 +- week-7/server/.env.example | 3 +- week-7/server/db/db.js | 51 +++++ week-7/server/package-lock.json | 293 ++++++++++++++++--------- week-7/server/package.json | 2 +- week-7/server/routes/admin.js | 28 +++ week-7/server/routes/user.js | 29 +++ week-7/server/server.js | 75 +------ 8 files changed, 304 insertions(+), 179 deletions(-) create mode 100644 week-7/server/db/db.js create mode 100644 week-7/server/routes/admin.js create mode 100644 week-7/server/routes/user.js diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js index 1a7a92912..b0d68df52 100644 --- a/week-5/solution/backend/routes/user.js +++ b/week-5/solution/backend/routes/user.js @@ -17,7 +17,7 @@ router.post('/signup', async (req, res) => { if (!result.success) { res.json({ - message: "Incorrect format: " + result.error + message: "Incorrect format: " + result.error.issues[0].message }) return } diff --git a/week-7/server/.env.example b/week-7/server/.env.example index 6c797aa93..b8ef1a275 100644 --- a/week-7/server/.env.example +++ b/week-7/server/.env.example @@ -1,2 +1,3 @@ JWT_SECRET="" -PORT= \ No newline at end of file +PORT= +MONGO_URI=mongodb://localhost:27017/coursera-app \ No newline at end of file diff --git a/week-7/server/db/db.js b/week-7/server/db/db.js new file mode 100644 index 000000000..7aa301b87 --- /dev/null +++ b/week-7/server/db/db.js @@ -0,0 +1,51 @@ +// Define mongoose schemas + +const mongoose = require('mongoose'); +const ObjectId = mongoose.Types.ObjectId + +const userSchema = new mongoose.Schema({ + // userSchema here + email: { type: String, unique: true}, + password: String, + firstName: String, + lastName: String +}); + +const adminSchema = new mongoose.Schema({ +// adminSchema here + email: { type: String, unique: true}, + password: String, + firstName: String, + lastName: String +}); + +const courseSchema = new mongoose.Schema({ +// courseSchema here + title: String, + description: String, + price: Number, + imageUrl: String, + creatorId: ObjectId +}); + +const purchaseSchema = new mongoose.Schema({ + userId: ObjectId, + courseId: ObjectId +}); + +// Define mongoose models +const User = mongoose.model('User', userSchema); +const Admin = mongoose.model('Admin', adminSchema); +const Course = mongoose.model('Course', courseSchema); +const Purchase = mongoose.model('Purchase', purchaseSchema); + +const authMiddleware = (req, res, next) => { +// authMiddleware logic here +}; + +module.exports = { + User, + Admin, + Course, + Purchase +} \ No newline at end of file diff --git a/week-7/server/package-lock.json b/week-7/server/package-lock.json index 5f1a5b874..83ad2b058 100644 --- a/week-7/server/package-lock.json +++ b/week-7/server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.21.0", + "express": "^4.22.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.6.2" } @@ -107,17 +107,27 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -148,9 +158,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -184,23 +194,6 @@ "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -232,6 +225,20 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -257,13 +264,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -277,6 +281,18 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -293,45 +309,64 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/finalhandler": { @@ -380,16 +415,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -398,34 +438,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -435,9 +464,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -601,6 +630,15 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -820,9 +858,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -853,9 +891,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/proxy-addr": { @@ -1011,39 +1049,76 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" diff --git a/week-7/server/package.json b/week-7/server/package.json index ed08190c4..5139f9dc3 100644 --- a/week-7/server/package.json +++ b/week-7/server/package.json @@ -12,7 +12,7 @@ "dependencies": { "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.21.0", + "express": "^4.22.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.6.2" } diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js new file mode 100644 index 000000000..b787b33d3 --- /dev/null +++ b/week-7/server/routes/admin.js @@ -0,0 +1,28 @@ +// Admin routes + +const { Router } = require("express"); +const adminRouter = Router(); + +adminRouter.post('/signup', (req, res) => { + // logic to sign up admin +}); + +adminRouter.post('/login', (req, res) => { + // logic to log in admin +}); + +adminRouter.post('/courses', (req, res) => { + // logic to create a course +}); + +adminRouter.put('/courses/:courseId', (req, res) => { + // logic to edit a course +}); + +adminRouter.get('/courses', (req, res) => { + // logic to get all courses +}); + +module.exports = { + adminRouter: adminRouter +} \ No newline at end of file diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js new file mode 100644 index 000000000..e0cf58da7 --- /dev/null +++ b/week-7/server/routes/user.js @@ -0,0 +1,29 @@ +// User routes + +const { Router } = require("express"); +const userRouter = Router(); + +userRouter.post('/signup', (req, res) => { + // logic to sign up user +}); + +userRouter.post('/login', (req, res) => { + // logic to log in user +}); + +userRouter.get('/courses', (req, res) => { + // logic to list all courses + res.json("hello") +}); + +userRouter.post('/courses/:courseId', (req, res) => { + // logic to purchase a course +}); + +userRouter.get('/purchasedCourses', (req, res) => { + // logic to view purchased courses +}); + +module.exports = { + userRouter: userRouter +} \ No newline at end of file diff --git a/week-7/server/server.js b/week-7/server/server.js index de3d9f638..4c0c54fb2 100644 --- a/week-7/server/server.js +++ b/week-7/server/server.js @@ -3,7 +3,11 @@ const express = require('express'); const jwt = require('jsonwebtoken'); const mongoose = require('mongoose'); const dotenv = require("dotenv"); +const { userRouter } = require('./routes/user'); +const { adminRouter } = require('./routes/admin'); +const { connectToDatabase } = require("./db/db"); dotenv.config(); +// Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env const app = express(); app.use(express.json()); @@ -11,74 +15,11 @@ app.use(express.json()); const secret = process.env.JWT_SECRERT; // This should be in an environment variable in a real application const port = process.env.PORT; -// Define mongoose schemas -const userSchema = new mongoose.Schema({ - // userSchema here -}); - -const adminSchema = new mongoose.Schema({ -// adminSchema here -}); - -const courseSchema = new mongoose.Schema({ -// courseSchema here -}); - -// Define mongoose models -const User = mongoose.model('User', userSchema); -const Admin = mongoose.model('Admin', adminSchema); -const Course = mongoose.model('Course', courseSchema); - -const authMiddleware = (req, res, next) => { -// authMiddleware logic here -}; - -// Connect to MongoDB -mongoose.connect(''); - - -// Admin routes -app.post('/admin/signup', (req, res) => { - // logic to sign up admin -}); - -app.post('/admin/login', (req, res) => { - // logic to log in admin -}); - -app.post('/admin/courses', (req, res) => { - // logic to create a course -}); - -app.put('/admin/courses/:courseId', (req, res) => { - // logic to edit a course -}); - -app.get('/admin/courses', (req, res) => { - // logic to get all courses -}); - -// User routes -app.post('/users/signup', (req, res) => { - // logic to sign up user -}); - -app.post('/users/login', (req, res) => { - // logic to log in user -}); - -app.get('/users/courses', (req, res) => { - // logic to list all courses -}); - -app.post('/users/courses/:courseId', (req, res) => { - // logic to purchase a course -}); - -app.get('/users/purchasedCourses', (req, res) => { - // logic to view purchased courses -}); +app.use("/users", userRouter) +app.use("/admin", adminRouter) app.listen(port, () => { + // Connect to MongoDB + mongoose.connect(process.env.MONGO_URI); console.log('Server is listening on port 3000'); }); \ No newline at end of file From 6f8715b205f7a5a38fb38ae82244db13fc447887 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 14:33:05 +0530 Subject: [PATCH 07/19] delete node_modules --- .../frontend/node_modules/.bin/node-gyp-build | 16 - .../node_modules/.bin/node-gyp-build-optional | 16 - .../.bin/node-gyp-build-optional.cmd | 17 - .../.bin/node-gyp-build-optional.ps1 | 28 - .../node_modules/.bin/node-gyp-build-test | 16 - .../node_modules/.bin/node-gyp-build-test.cmd | 17 - .../node_modules/.bin/node-gyp-build-test.ps1 | 28 - .../node_modules/.bin/node-gyp-build.cmd | 17 - .../node_modules/.bin/node-gyp-build.ps1 | 28 - .../frontend/node_modules/.package-lock.json | 41 - .../node_modules/bcrypt/.dockerignore | 6 - .../node_modules/bcrypt/.editorconfig | 19 - .../.github/workflows/build-pack-publish.yml | 110 - .../bcrypt/.github/workflows/ci.yaml | 42 - .../frontend/node_modules/bcrypt/CHANGELOG.md | 184 - .../frontend/node_modules/bcrypt/Dockerfile | 57 - .../node_modules/bcrypt/Dockerfile-alpine | 41 - .../node_modules/bcrypt/ISSUE_TEMPLATE.md | 18 - .../frontend/node_modules/bcrypt/LICENSE | 19 - .../frontend/node_modules/bcrypt/Makefile | 19 - .../frontend/node_modules/bcrypt/README.md | 388 - .../frontend/node_modules/bcrypt/SECURITY.md | 15 - .../frontend/node_modules/bcrypt/bcrypt.js | 242 - .../frontend/node_modules/bcrypt/binding.gyp | 49 - .../frontend/node_modules/bcrypt/build-all.sh | 37 - .../bcrypt/examples/async_compare.js | 28 - .../bcrypt/examples/forever_gen_salt.js | 8 - .../frontend/node_modules/bcrypt/package.json | 62 - .../bcrypt/prebuilds/darwin-arm64/bcrypt.node | Bin 88608 -> 0 bytes .../bcrypt/prebuilds/darwin-x64/bcrypt.node | Bin 55488 -> 0 bytes .../prebuilds/linux-arm/bcrypt.glibc.node | Bin 67504 -> 0 bytes .../prebuilds/linux-arm/bcrypt.musl.node | Bin 67420 -> 0 bytes .../prebuilds/linux-arm64/bcrypt.glibc.node | Bin 76640 -> 0 bytes .../prebuilds/linux-arm64/bcrypt.musl.node | Bin 76552 -> 0 bytes .../prebuilds/linux-x64/bcrypt.glibc.node | Bin 84904 -> 0 bytes .../prebuilds/linux-x64/bcrypt.musl.node | Bin 96424 -> 0 bytes .../bcrypt/prebuilds/win32-arm64/bcrypt.node | Bin 182784 -> 0 bytes .../bcrypt/prebuilds/win32-x64/bcrypt.node | Bin 195584 -> 0 bytes .../frontend/node_modules/bcrypt/promises.js | 45 - .../node_modules/bcrypt/src/bcrypt.cc | 315 - .../node_modules/bcrypt/src/bcrypt_node.cc | 288 - .../node_modules/bcrypt/src/blowfish.cc | 679 -- .../node_modules/bcrypt/src/node_blf.h | 132 - .../node_modules/bcrypt/test/async.test.js | 209 - .../bcrypt/test/implementation.test.js | 48 - .../node_modules/bcrypt/test/promise.test.js | 168 - .../bcrypt/test/repetitions.test.js | 55 - .../node_modules/bcrypt/test/sync.test.js | 125 - .../node_modules/node-addon-api/LICENSE.md | 9 - .../node_modules/node-addon-api/README.md | 95 - .../node_modules/node-addon-api/common.gypi | 21 - .../node_modules/node-addon-api/except.gypi | 25 - .../node_modules/node-addon-api/index.js | 14 - .../node-addon-api/napi-inl.deprecated.h | 186 - .../node_modules/node-addon-api/napi-inl.h | 7033 ----------------- .../node_modules/node-addon-api/napi.h | 3309 -------- .../node-addon-api/node_addon_api.gyp | 42 - .../node_modules/node-addon-api/node_api.gyp | 9 - .../node_modules/node-addon-api/noexcept.gypi | 26 - .../node_modules/node-addon-api/nothing.c | 0 .../node-addon-api/package-support.json | 21 - .../node_modules/node-addon-api/package.json | 480 -- .../node-addon-api/tools/README.md | 73 - .../node-addon-api/tools/check-napi.js | 99 - .../node-addon-api/tools/clang-format.js | 71 - .../node-addon-api/tools/conversion.js | 301 - .../node_modules/node-gyp-build/LICENSE | 21 - .../node_modules/node-gyp-build/README.md | 58 - .../node_modules/node-gyp-build/SECURITY.md | 5 - .../node_modules/node-gyp-build/bin.js | 84 - .../node_modules/node-gyp-build/build-test.js | 19 - .../node_modules/node-gyp-build/index.js | 6 - .../node-gyp-build/node-gyp-build.js | 207 - .../node_modules/node-gyp-build/optional.js | 7 - .../node_modules/node-gyp-build/package.json | 43 - 75 files changed, 15896 deletions(-) delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd delete mode 100644 week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 delete mode 100644 week-5/solution/frontend/node_modules/.package-lock.json delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/.dockerignore delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/.editorconfig delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/Dockerfile delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/LICENSE delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/Makefile delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/README.md delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/SECURITY.md delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/bcrypt.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/binding.gyp delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/build-all.sh delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/examples/async_compare.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/package.json delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.glibc.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.glibc.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.musl.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-arm64/bcrypt.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/promises.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/async.test.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js delete mode 100644 week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/README.md delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/common.gypi delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/except.gypi delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/index.js delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi-inl.deprecated.h delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/napi.h delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/nothing.c delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/package-support.json delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/package.json delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/README.md delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js delete mode 100644 week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/LICENSE delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/README.md delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/bin.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/build-test.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/index.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/optional.js delete mode 100644 week-5/solution/frontend/node_modules/node-gyp-build/package.json diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build b/week-5/solution/frontend/node_modules/.bin/node-gyp-build deleted file mode 100644 index b804ba986..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") - -case `uname` in - *CYGWIN*|*MINGW*|*MSYS*) - if command -v cygpath > /dev/null 2>&1; then - basedir=`cygpath -w "$basedir"` - fi - ;; -esac - -if [ -x "$basedir/node" ]; then - exec "$basedir/node" "$basedir/../node-gyp-build/bin.js" "$@" -else - exec node "$basedir/../node-gyp-build/bin.js" "$@" -fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional deleted file mode 100644 index cb670aab6..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") - -case `uname` in - *CYGWIN*|*MINGW*|*MSYS*) - if command -v cygpath > /dev/null 2>&1; then - basedir=`cygpath -w "$basedir"` - fi - ;; -esac - -if [ -x "$basedir/node" ]; then - exec "$basedir/node" "$basedir/../node-gyp-build/optional.js" "$@" -else - exec node "$basedir/../node-gyp-build/optional.js" "$@" -fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd deleted file mode 100644 index 74d85f274..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@ECHO off -GOTO start -:find_dp0 -SET dp0=%~dp0 -EXIT /b -:start -SETLOCAL -CALL :find_dp0 - -IF EXIST "%dp0%\node.exe" ( - SET "_prog=%dp0%\node.exe" -) ELSE ( - SET "_prog=node" - SET PATHEXT=%PATHEXT:;.JS;=;% -) - -endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\optional.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 deleted file mode 100644 index 45995c36a..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-optional.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env pwsh -$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent - -$exe="" -if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { - # Fix case when both the Windows and Linux builds of Node - # are installed in the same directory - $exe=".exe" -} -$ret=0 -if (Test-Path "$basedir/node$exe") { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args - } else { - & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args - } - $ret=$LASTEXITCODE -} else { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "node$exe" "$basedir/../node-gyp-build/optional.js" $args - } else { - & "node$exe" "$basedir/../node-gyp-build/optional.js" $args - } - $ret=$LASTEXITCODE -} -exit $ret diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test deleted file mode 100644 index bdf6dca40..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") - -case `uname` in - *CYGWIN*|*MINGW*|*MSYS*) - if command -v cygpath > /dev/null 2>&1; then - basedir=`cygpath -w "$basedir"` - fi - ;; -esac - -if [ -x "$basedir/node" ]; then - exec "$basedir/node" "$basedir/../node-gyp-build/build-test.js" "$@" -else - exec node "$basedir/../node-gyp-build/build-test.js" "$@" -fi diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd deleted file mode 100644 index 182a75783..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@ECHO off -GOTO start -:find_dp0 -SET dp0=%~dp0 -EXIT /b -:start -SETLOCAL -CALL :find_dp0 - -IF EXIST "%dp0%\node.exe" ( - SET "_prog=%dp0%\node.exe" -) ELSE ( - SET "_prog=node" - SET PATHEXT=%PATHEXT:;.JS;=;% -) - -endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\build-test.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 deleted file mode 100644 index 6cb0b9bda..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build-test.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env pwsh -$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent - -$exe="" -if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { - # Fix case when both the Windows and Linux builds of Node - # are installed in the same directory - $exe=".exe" -} -$ret=0 -if (Test-Path "$basedir/node$exe") { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args - } else { - & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args - } - $ret=$LASTEXITCODE -} else { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args - } else { - & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args - } - $ret=$LASTEXITCODE -} -exit $ret diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd deleted file mode 100644 index ac854a699..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@ECHO off -GOTO start -:find_dp0 -SET dp0=%~dp0 -EXIT /b -:start -SETLOCAL -CALL :find_dp0 - -IF EXIST "%dp0%\node.exe" ( - SET "_prog=%dp0%\node.exe" -) ELSE ( - SET "_prog=node" - SET PATHEXT=%PATHEXT:;.JS;=;% -) - -endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\bin.js" %* diff --git a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 b/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 deleted file mode 100644 index c1f9a9aaf..000000000 --- a/week-5/solution/frontend/node_modules/.bin/node-gyp-build.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env pwsh -$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent - -$exe="" -if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { - # Fix case when both the Windows and Linux builds of Node - # are installed in the same directory - $exe=".exe" -} -$ret=0 -if (Test-Path "$basedir/node$exe") { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args - } else { - & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args - } - $ret=$LASTEXITCODE -} else { - # Support pipeline input - if ($MyInvocation.ExpectingInput) { - $input | & "node$exe" "$basedir/../node-gyp-build/bin.js" $args - } else { - & "node$exe" "$basedir/../node-gyp-build/bin.js" $args - } - $ret=$LASTEXITCODE -} -exit $ret diff --git a/week-5/solution/frontend/node_modules/.package-lock.json b/week-5/solution/frontend/node_modules/.package-lock.json deleted file mode 100644 index 1c862af25..000000000 --- a/week-5/solution/frontend/node_modules/.package-lock.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "frontend", - "lockfileVersion": 3, - "requires": true, - "packages": { - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - } - } -} diff --git a/week-5/solution/frontend/node_modules/bcrypt/.dockerignore b/week-5/solution/frontend/node_modules/bcrypt/.dockerignore deleted file mode 100644 index 01f4eb7ac..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -.git/ -.vscode/ -Dockerfile* -prebuilds/ -node_modules/ -build*/ diff --git a/week-5/solution/frontend/node_modules/bcrypt/.editorconfig b/week-5/solution/frontend/node_modules/bcrypt/.editorconfig deleted file mode 100644 index 4e12f93be..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[{package.json,*.yml}] -indent_style = space -indent_size = 2 - -[appveyor.yml] -end_of_line = crlf - -[*.md] -trim_trailing_whitespace = false diff --git a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml deleted file mode 100644 index 9b14ee121..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/build-pack-publish.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Prebuildify, package, publish - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - release: - types: [ prereleased, released ] - -jobs: - build-linux: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - # This is unsafe, but we really don't use any other native dependencies - - run: npm ci - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/almalinux-devtoolset11 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/alpine npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-armv7 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-armv7l-musl npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-arm64 npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: docker run -u $(id -u):$(id -g) -v `pwd`:/input -w /input ghcr.io/prebuild/linux-arm64-musl npx prebuildify --napi --tag-libc --strip --target=node@18.0.0 - - run: find prebuilds - - uses: actions/upload-artifact@v4 - with: - name: prebuild-linux - path: ./prebuilds/ - - build-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - - run: npm ci - - run: npx prebuildify --napi --strip --arch=x64 --target=node@18.0.0 - - run: npx prebuildify --napi --strip --arch=arm64 --target=node@20.0.0 - - run: dir prebuilds - - uses: actions/upload-artifact@v4 - with: - name: prebuild-windows - path: ./prebuilds/ - - build-macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - registry-url: 'https://registry.npmjs.org' - - run: npm ci - - run: npx prebuildify --napi --strip --arch=arm64 --target=node@18.0.0 - - run: npx prebuildify --napi --strip --arch=x64 --target=node@18.0.0 - - run: find prebuilds - - uses: actions/upload-artifact@v4 - with: - name: prebuild-macos - path: ./prebuilds/ - - pack: - needs: - - build-linux - - build-windows - - build-macos - runs-on: ubuntu-latest - outputs: - PACK_FILE: ${{ steps.pack.outputs.PACK_FILE }} - steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 - with: - path: /tmp/prebuilds/ - - name: Coalesce prebuilds from build matrix - run: | - mkdir prebuilds - for d in /tmp/prebuilds/*; do - cp -Rav $d/* prebuilds/ - done - - run: chmod a+x prebuilds/*/*.node && find prebuilds -executable -type f - - id: pack - name: Prepare NPM package - run: | - echo "PACK_FILE=$(npm pack)" >> "$GITHUB_OUTPUT" - - uses: actions/upload-artifact@v4 - with: - name: package-tgz - path: ${{ steps.pack.outputs.PACK_FILE }} - if-no-files-found: 'error' - - test-package: - needs: pack - strategy: - matrix: - node-version: [ 18, 20, 22, 23 ] - os: [ ubuntu-latest, windows-latest, macos-latest ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: 'https://registry.npmjs.org' - - uses: actions/download-artifact@v4 - with: - name: package-tgz - - run: npm install ${{ needs.pack.outputs.PACK_FILE }} - - run: node -e "const b = require('bcrypt'); const h = b.hashSync('hello', 10); console.log(h, b.compareSync('hello', h))" diff --git a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml b/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml deleted file mode 100644 index 77c4e5a9d..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/.github/workflows/ci.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: ci - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18, 20, 22] - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - name: Test - run: npm test - - build-alpine: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18, 20, 22] - container: - image: node:${{ matrix.node-version }}-alpine - steps: - - uses: actions/checkout@v4 - - name: Install dependencies - run: | - apk add make g++ python3 - - run: npm ci - - name: Test - run: | - npm test --unsafe-perm diff --git a/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md b/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md deleted file mode 100644 index eab713b41..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/CHANGELOG.md +++ /dev/null @@ -1,184 +0,0 @@ -# 6.0.0 (2025-02-28) - * Drop support for NodeJS <= 16 - * Remove `node-pre-gyp` in favor of `prebuildify`, prebuilt binaries are now shipped with the package - * Update `node-addon-api` to 8.3.0 - * Update JS code to newer ES syntax - -# 5.1.0 (2022-10-06) - * Update `node-pre-gyp` to 1.0.11 - -# 5.1.0 (2022-10-06) - * Update `node-pre-gyp` to 1.0.10 - * Replace `nodeunit` with `jest` as the testing library - -# 5.0.1 (2021-02-22) - - * Update `node-pre-gyp` to 1.0.0 - -# 5.0.0 (2020-06-02) - - * Fix the bcrypt "wrap-around" bug. It affects passwords with lengths >= 255. - It is uncommon but it's a bug nevertheless. Previous attempts to fix the bug - was unsuccessful. - * Experimental support for z/OS - * Fix a bug related to NUL in password input - * Update `node-pre-gyp` to 0.15.0 - -# 4.0.1 (2020-02-27) - - * Fix compilation errors in Alpine linux - -# 4.0.0 (2020-02-17) - - * Switch to NAPI bcrypt - * Drop support for NodeJS 8 - -# 3.0.8 (2019-12-31) - - * Update `node-pre-gyp` to 0.14 - * Pre-built binaries for NodeJS 13 - -# 3.0.7 (2019-10-18) - - * Update `nan` to 2.14.0 - * Update `node-pre-gyp` to 0.13 - -# 3.0.6 (2019-04-11) - - * Update `nan` to 2.13.2 - -# 3.0.5 (2019-03-19) - - * Update `nan` to 2.13.1 - * NodeJS 12 compatibility - * Remove `node-pre-gyp` from bundled dependencies - -# 3.0.4-napi (2019-03-08) - - * Sync N-API bcrypt with NAN bcrypt - -# 3.0.4 (2019-02-07) - - * Fix GCC, NAN and V8 deprecation warnings - -# 3.0.3 (2018-12-19) - - * Update `nan` to 2.12.1 - -# 3.0.2 (2018-10-18) - - * Update `nan` to 2.11.1 - -# 3.0.1 (2018-09-20) - - * Update `nan` to 2.11.0 - -# 3.0.0 (2018-07-06) - - * Drop support for NodeJS <= 4 - -# 2.0.1 (2018-04-20) - - * Update `node-pre-gyp` to allow downloading prebuilt modules - -# 2.0.0 (2018-04-07) - - * Make `2b` the default bcrypt version - -# 1.1.0-napi (2018-01-21) - - * Initial support for [N-API](https://nodejs.org/api/n-api.html) - -# 1.0.3 (2016-08-23) - - * update to nan v2.6.2 for NodeJS 8 support - * Fix: use npm scripts instead of node-gyp directly. - -# 1.0.2 (2016-12-31) - - * Fix `compare` promise rejection with invalid arguments - -# 1.0.1 (2016-12-07) - - * Fix destructuring imports with promises - -# 1.0.0 (2016-12-04) - - * add Promise support (commit 2488473) - -# 0.8.7 (2016-06-09) - - * update nan to 2.3.5 for improved node v6 support - -# 0.8.6 (2016-04-20) - - * update nan for node v6 support - -# 0.8.5 (2015-08-12) - - * update to nan v2 (adds support for iojs 3) - -# 0.8.4 (2015-07-24) - - * fix deprecation warning for the Encode API - -# 0.8.3 (2015-05-06) - - * update nan to 1.8.4 for iojs 2.x support - -# 0.8.2 (2015-03-28) - - * always use callback for generating random bytes to avoid blocking - -# 0.8.1 (2015-01-18) - * update NaN to 1.5.0 for iojs support - -# 0.8.0 (2014-08-03) - * migrate to NAN for bindings - -# v0.5.0 - * Fix for issue around empty string params throwing Errors. - * Method deprecation. - * Upgrade from libeio/ev to libuv. (shtylman) - ** --- NOTE --- Breaks 0.4.x compatability - * EV_MULTIPLICITY compile flag. - -# v0.4.1 - * Thread safety fix around OpenSSL (GH-32). (bnoordhuis - through node) - * C++ code changes using delete and new instead of malloc and free. (shtylman) - * Compile options for speed, zoom. (shtylman) - * Move much of the type and variable checking to the JS. (shtylman) - -# v0.4.0 - * Added getRounds function that will tell you the number of rounds within a hash/salt - -# v0.3.2 - * Fix api issue with async salt gen first param - -# v0.3.1 - * Compile under node 0.5.x - -# v0.3.0 - * Internal Refactoring - * Remove pthread dependencies and locking - * Fix compiler warnings and a memory bug - -# v0.2.4 - * Use threadsafe functions instead of pthread mutexes - * salt validation to make sure the salt is of the correct size and format - -# v0.2.3 - * cygwin support - -# v0.2.2 - * Remove dependency on libbsd, use libssl instead - -# v0.2.0 - * Added async functionality - * API changes - * hashpw -> encrypt - * all old sync methods now end with _sync - * Removed libbsd(arc4random) dependency...now uses openssl which is more widely spread - -# v0.1.2 - * Security fix. Wasn't reading rounds in properly and was always only using 4 rounds diff --git a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile deleted file mode 100644 index 2802bafae..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -# Usage: -# -# docker build -t bcryptjs-builder . -# CONTAINER=$(docker create bcryptjs-builder) -# # Then copy the artifact to your host: -# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . -# docker rm "$CONTAINER" -# -# Use --platform to build cross-platform i.e. for ARM: -# -# docker build -t bcryptjs-builder --platform "linux/arm64/v8" . -# CONTAINER=$docker create --platform "linux/arm64/v8" bcryptjs-builder) -# # this copies the prebuilds/linux-arm artifacts -# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . -# docker rm "$CONTAINER" - - -ARG FROM_IMAGE=node:18-bullseye -#ARG FROM_IMAGE=arm32v7/node:16-bullseye -#ARG FROM_IMAGE=arm64v8/node:16-bullseye -FROM ${FROM_IMAGE} - -ENV project bcrypt-js -ENV DEBIAN_FRONTEND noninteractive -ENV LC_ALL en_US.UTF-8 -ENV LANG ${LC_ALL} - -RUN echo "#log: ${project}: Setup system" \ - && set -x \ - && apt-get update -y \ - && apt-get install -y \ - build-essential \ - python3 \ - && apt-get clean \ - && update-alternatives --install /usr/local/bin/python python /usr/bin/python3 20 \ - && npm i -g prebuildify@5 node-gyp@9 \ - && sync - -ADD . /usr/local/opt/${project} -WORKDIR /usr/local/opt/${project} - -RUN echo "#log: ${project}: Running build" \ - && set -x \ - && npm ci \ - && npm run build - -ARG RUN_TESTS=true -ARG TEST_TIMEOUT_SECONDS= - -RUN if "${RUN_TESTS}"; then \ - echo "#log ${project}: Running tests" \ - && npm test; \ - else \ - echo "#log ${project}: Tests were skipped!"; \ - fi - -CMD /bin/bash -l diff --git a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine b/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine deleted file mode 100644 index 7570cfe85..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/Dockerfile-alpine +++ /dev/null @@ -1,41 +0,0 @@ -# Usage: -# -# docker build -t bcryptjs-linux-alpine-builder -f Dockerfile-alpine . -# CONTAINER=$(docker create bcryptjs-linux-alpine-builder) -# # Then copy the artifact to your host: -# docker cp "$CONTAINER:/usr/local/opt/bcrypt-js/prebuilds" . -# docker rm "$CONTAINER" - -ARG FROM_IMAGE=node:18-alpine -FROM ${FROM_IMAGE} - -ENV project bcrypt-js -ENV DEBIAN_FRONTEND noninteractive -ENV LC_ALL en_US.UTF-8 -ENV LANG ${LC_ALL} - -RUN echo "#log: ${project}: Setup system" \ - && set -x \ - && apk add --update build-base python3 \ - && npm i -g prebuildify@5 node-gyp@9 \ - && sync - -ADD . /usr/local/opt/${project} -WORKDIR /usr/local/opt/${project} - -RUN echo "#log: ${project}: Running build" \ - && set -x \ - && npm ci \ - && npm run build - -ARG RUN_TESTS=true -ARG TEST_TIMEOUT_SECONDS= - -RUN if "${RUN_TESTS}"; then \ - echo "#log ${project}: Running tests" \ - && npm test; \ - else \ - echo "#log ${project}: Tests were skipped!"; \ - fi - -CMD /bin/bash -l diff --git a/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md b/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md deleted file mode 100644 index b4baa0086..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,18 +0,0 @@ -Thanks for reporting a new issue with the node bcrypt module! - -To help you resolve your issue faster please make sure you have done the following: - -* Searched existing issues (even closed ones) for your same problem -* Make sure you have installed the required dependencies listed on the readme -* Read your npm error log for lines telling you what failed, usually it is a problem with not having the correct dependencies installed to build the native module - -Once you have done the above and are still confident that the issue is with the module, please describe it below. Some things that really help get your issue resolved faster are: - -* What went wrong? -* What did you expect to happen? -* Which version of nodejs and OS? -* If you find a bug, please write a failing test. - -Thanks! - -P.S. If it doesn't look like you read the above then your issue will likely be closed without further explanation. Sorry, but there are just too many issues opened with no useful information or questions which have been previously addressed. diff --git a/week-5/solution/frontend/node_modules/bcrypt/LICENSE b/week-5/solution/frontend/node_modules/bcrypt/LICENSE deleted file mode 100644 index 94e2ba5f9..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010 Nicholas Campbell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/week-5/solution/frontend/node_modules/bcrypt/Makefile b/week-5/solution/frontend/node_modules/bcrypt/Makefile deleted file mode 100644 index cb2225266..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -TESTS = test/*.js - -all: test - -build: clean compile - -compile: - npm install . - npm run install - -test: build - @./node_modules/.bin/jest \ - $(TESTS) - -clean: - rm -Rf lib/bindings/ - - -.PHONY: clean test build diff --git a/week-5/solution/frontend/node_modules/bcrypt/README.md b/week-5/solution/frontend/node_modules/bcrypt/README.md deleted file mode 100644 index e92310810..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/README.md +++ /dev/null @@ -1,388 +0,0 @@ -# node.bcrypt.js - -[![ci](https://github.com/kelektiv/node.bcrypt.js/actions/workflows/ci.yaml/badge.svg)](https://github.com/kelektiv/node.bcrypt.js/actions/workflows/ci.yaml) - -[![Build Status](https://ci.appveyor.com/api/projects/status/github/kelektiv/node.bcrypt.js)](https://ci.appveyor.com/project/defunctzombie/node-bcrypt-js-pgo26/branch/master) - -A library to help you hash passwords. - -You can read about [bcrypt in Wikipedia][bcryptwiki] as well as in the following article: -[How To Safely Store A Password][codahale] - -## If You Are Submitting Bugs or Issues - -Please verify that the NodeJS version you are using is a _stable_ version; Unstable versions are currently not supported and issues created while using an unstable version will be closed. - -If you are on a stable version of NodeJS, please provide a sufficient code snippet or log files for installation issues. The code snippet does not require you to include confidential information. However, it must provide enough information so the problem can be replicable, or it may be closed without an explanation. - - -## Version Compatibility - -_Please upgrade to atleast v5.0.0 to avoid security issues mentioned below._ - -| Node Version | Bcrypt Version | -| -------------- | ------------------| -| 0.4 | <= 0.4 | -| 0.6, 0.8, 0.10 | >= 0.5 | -| 0.11 | >= 0.8 | -| 4 | <= 2.1.0 | -| 8 | >= 1.0.3 < 4.0.0 | -| 10, 11 | >= 3 | -| 12 onwards | >= 3.0.6 | - -`node-gyp` only works with stable/released versions of node. Since the `bcrypt` module uses `node-gyp` to build and install, you'll need a stable version of node to use bcrypt. If you do not, you'll likely see an error that starts with: - -``` -gyp ERR! stack Error: "pre" versions of node cannot be installed, use the --nodedir flag instead -``` - -## Security Issues And Concerns - -> Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 *characters*. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis). If a string is provided, it will be encoded using UTF-8. - -As should be the case with any security tool, anyone using this library should scrutinise it. If you find or suspect an issue with the code, please bring it to the maintainers' attention. We will spend some time ensuring that this library is as secure as possible. - -Here is a list of BCrypt-related security issues/concerns that have come up over the years. - -* An [issue with passwords][jtr] was found with a version of the Blowfish algorithm developed for John the Ripper. This is not present in the OpenBSD version and is thus not a problem for this module. HT [zooko][zooko]. -* Versions `< 5.0.0` suffer from bcrypt wrap-around bug and _will truncate passwords >= 255 characters leading to severely weakened passwords_. Please upgrade at earliest. See [this wiki page][wrap-around-bug] for more details. -* Versions `< 5.0.0` _do not handle NUL characters inside passwords properly leading to all subsequent characters being dropped and thus resulting in severely weakened passwords_. Please upgrade at earliest. See [this wiki page][improper-nuls] for more details. - -## Compatibility Note - -This library supports `$2a$` and `$2b$` prefix bcrypt hashes. `$2x$` and `$2y$` hashes are specific to bcrypt implementation developed for John the Ripper. In theory, they should be compatible with `$2b$` prefix. - -Compatibility with hashes generated by other languages is not 100% guaranteed due to difference in character encodings. However, it should not be an issue for most cases. - -### Migrating from v1.0.x - -Hashes generated in earlier version of `bcrypt` remain 100% supported in `v2.x.x` and later versions. In most cases, the migration should be a bump in the `package.json`. - -Hashes generated in `v2.x.x` using the defaults parameters will not work in earlier versions. - -## Dependencies - -* NodeJS -* `node-gyp` - * Please check the dependencies for this tool at: https://github.com/nodejs/node-gyp - * Windows users will need the options for c# and c++ installed with their visual studio instance. - * Python 2.x/3.x -* `OpenSSL` - This is only required to build the `bcrypt` project if you are using versions <= 0.7.7. Otherwise, we're using the builtin node crypto bindings for seed data (which use the same OpenSSL code paths we were, but don't have the external dependency). - -## Install via NPM - -``` -npm install bcrypt -``` -***Note:*** OS X users using Xcode 4.3.1 or above may need to run the following command in their terminal prior to installing if errors occur regarding xcodebuild: ```sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer``` - -_Pre-built binaries for various NodeJS versions are made available on a best-effort basis._ - -Only the current stable and supported LTS releases are actively tested against. - -_There may be an interval between the release of the module and the availabilty of the compiled modules._ - -Currently, we have pre-built binaries that support the following platforms: - -1. Windows x32 and x64 -2. Linux x64 (GlibC and musl) -3. macOS - -If you face an error like this: - -``` -node-pre-gyp ERR! Tried to download(404): https://github.com/kelektiv/node.bcrypt.js/releases/download/v1.0.2/bcrypt_lib-v1.0.2-node-v48-linux-x64.tar.gz -``` - -make sure you have the appropriate dependencies installed and configured for your platform. You can find installation instructions for the dependencies for some common platforms [in this page][depsinstall]. - -## Usage - -### async (recommended) - -```javascript -const bcrypt = require('bcrypt'); -const saltRounds = 10; -const myPlaintextPassword = 's0/\/\P4$$w0rD'; -const someOtherPlaintextPassword = 'not_bacon'; -``` - -#### To hash a password: - -Technique 1 (generate a salt and hash on separate function calls): - -```javascript -bcrypt.genSalt(saltRounds, function(err, salt) { - bcrypt.hash(myPlaintextPassword, salt, function(err, hash) { - // Store hash in your password DB. - }); -}); -``` - -Technique 2 (auto-gen a salt and hash): - -```javascript -bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) { - // Store hash in your password DB. -}); -``` - -Note that both techniques achieve the same end-result. - -#### To check a password: - -```javascript -// Load hash from your password DB. -bcrypt.compare(myPlaintextPassword, hash, function(err, result) { - // result == true -}); -bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) { - // result == false -}); -``` - -[A Note on Timing Attacks](#a-note-on-timing-attacks) - -### with promises - -bcrypt uses whatever `Promise` implementation is available in `global.Promise`. NodeJS >= 0.12 has a native `Promise` implementation built in. However, this should work in any Promises/A+ compliant implementation. - -Async methods that accept a callback, return a `Promise` when callback is not specified if Promise support is available. - -```javascript -bcrypt.hash(myPlaintextPassword, saltRounds).then(function(hash) { - // Store hash in your password DB. -}); -``` -```javascript -// Load hash from your password DB. -bcrypt.compare(myPlaintextPassword, hash).then(function(result) { - // result == true -}); -bcrypt.compare(someOtherPlaintextPassword, hash).then(function(result) { - // result == false -}); -``` - -This is also compatible with `async/await` - -```javascript -async function checkUser(username, password) { - //... fetch user from a db etc. - - const match = await bcrypt.compare(password, user.passwordHash); - - if(match) { - //login - } - - //... -} -``` - -### ESM import -```javascript -import bcrypt from "bcrypt"; - -// later -await bcrypt.compare(password, hash); -``` - -### sync - -```javascript -const bcrypt = require('bcrypt'); -const saltRounds = 10; -const myPlaintextPassword = 's0/\/\P4$$w0rD'; -const someOtherPlaintextPassword = 'not_bacon'; -``` - -#### To hash a password: - -Technique 1 (generate a salt and hash on separate function calls): - -```javascript -const salt = bcrypt.genSaltSync(saltRounds); -const hash = bcrypt.hashSync(myPlaintextPassword, salt); -// Store hash in your password DB. -``` - -Technique 2 (auto-gen a salt and hash): - -```javascript -const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds); -// Store hash in your password DB. -``` - -As with async, both techniques achieve the same end-result. - -#### To check a password: - -```javascript -// Load hash from your password DB. -bcrypt.compareSync(myPlaintextPassword, hash); // true -bcrypt.compareSync(someOtherPlaintextPassword, hash); // false -``` - -[A Note on Timing Attacks](#a-note-on-timing-attacks) - -### Why is async mode recommended over sync mode? -We recommend using async API if you use `bcrypt` on a server. Bcrypt hashing is CPU intensive which will cause the sync APIs to block the event loop and prevent your application from servicing any inbound requests or events. The async version uses a thread pool which does not block the main event loop. - -## API - -`BCrypt.` - - * `genSaltSync(rounds, minor)` - * `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10) - * `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b) - * `genSalt(rounds, minor, cb)` - * `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10) - * `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b) - * `cb` - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. - * `err` - First parameter to the callback detailing any errors. - * `salt` - Second parameter to the callback providing the generated salt. - * `hashSync(data, salt)` - * `data` - [REQUIRED] - the data to be encrypted. - * `salt` - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under **Usage**). - * `hash(data, salt, cb)` - * `data` - [REQUIRED] - the data to be encrypted. - * `salt` - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under **Usage**). - * `cb` - [OPTIONAL] - a callback to be fired once the data has been encrypted. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. - * `err` - First parameter to the callback detailing any errors. - * `encrypted` - Second parameter to the callback providing the encrypted form. - * `compareSync(data, encrypted)` - * `data` - [REQUIRED] - data to compare. - * `encrypted` - [REQUIRED] - data to be compared to. - * `compare(data, encrypted, cb)` - * `data` - [REQUIRED] - data to compare. - * `encrypted` - [REQUIRED] - data to be compared to. - * `cb` - [OPTIONAL] - a callback to be fired once the data has been compared. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available. - * `err` - First parameter to the callback detailing any errors. - * `same` - Second parameter to the callback providing whether the data and encrypted forms match [true | false]. - * `getRounds(encrypted)` - return the number of rounds used to encrypt a given hash - * `encrypted` - [REQUIRED] - hash from which the number of rounds used should be extracted. - -## A Note on Rounds - -A note about the cost: when you are hashing your data, the module will go through a series of rounds to give you a secure hash. The value you submit is not just the number of rounds the module will go through to hash your data. The module will use the value you enter and go through `2^rounds` hashing iterations. - -From @garthk, on a 2GHz core you can roughly expect: - - rounds=8 : ~40 hashes/sec - rounds=9 : ~20 hashes/sec - rounds=10: ~10 hashes/sec - rounds=11: ~5 hashes/sec - rounds=12: 2-3 hashes/sec - rounds=13: ~1 sec/hash - rounds=14: ~1.5 sec/hash - rounds=15: ~3 sec/hash - rounds=25: ~1 hour/hash - rounds=31: 2-3 days/hash - - -## A Note on Timing Attacks - -Because it's come up multiple times in this project and other bcrypt projects, it needs to be said. The `bcrypt` library is not susceptible to timing attacks. From codahale/bcrypt-ruby#42: - -> One of the desired properties of a cryptographic hash function is preimage attack resistance, which means there is no shortcut for generating a message which, when hashed, produces a specific digest. - -A great thread on this, in much more detail can be found @ codahale/bcrypt-ruby#43 - -If you're unfamiliar with timing attacks and want to learn more you can find a great writeup @ [A Lesson In Timing Attacks][timingatk] - -However, timing attacks are real. And the comparison function is _not_ time safe. That means that it may exit the function early in the comparison process. Timing attacks happen because of the above. We don't need to be careful that an attacker will learn anything, and our comparison function provides a comparison of hashes. It is a utility to the overall purpose of the library. If you end up using it for something else, we cannot guarantee the security of the comparator. Keep that in mind as you use the library. - -## Hash Info - -The characters that comprise the resultant hash are `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$`. - -Resultant hashes will be 60 characters long and they will include the salt among other parameters, as follows: - -`$[algorithm]$[cost]$[salt][hash]` - -- 2 chars hash algorithm identifier prefix. `"$2a$" or "$2b$"` indicates BCrypt -- Cost-factor (n). Represents the exponent used to determine how many iterations 2^n -- 16-byte (128-bit) salt, base64 encoded to 22 characters -- 24-byte (192-bit) hash, base64 encoded to 31 characters - -Example: -``` -$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa - | | | | - | | | hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa - | | | - | | salt = nOUIs5kJ7naTuTFkBy1veu - | | - | cost-factor => 10 = 2^10 rounds - | - hash-algorithm identifier => 2b = BCrypt -``` - -## Testing - -If you create a pull request, tests better pass :) - -``` -npm install -npm test -``` - -## Credits - -The code for this comes from a few sources: - -* blowfish.cc - OpenBSD -* bcrypt.cc - OpenBSD -* bcrypt::gen_salt - [gen_salt inclusion to bcrypt][bcryptgs] -* bcrypt_node.cc - me - -## Contributors - -* [Antonio Salazar Cardozo][shadowfiend] - Early MacOS X support (when we used libbsd) -* [Ben Glow][pixelglow] - Fixes for thread safety with async calls -* [Van Nguyen][thegoleffect] - Found a timing attack in the comparator -* [NewITFarmer][newitfarmer] - Initial Cygwin support -* [David Trejo][dtrejo] - packaging fixes -* [Alfred Westerveld][alfredwesterveld] - packaging fixes -* [Vincent Côté-Roy][vincentr] - Testing around concurrency issues -* [Lloyd Hilaiel][lloyd] - Documentation fixes -* [Roman Shtylman][shtylman] - Code refactoring, general rot reduction, compile options, better memory management with delete and new, and an upgrade to libuv over eio/ev. -* [Vadim Graboys][vadimg] - Code changes to support 0.5.5+ -* [Ben Noordhuis][bnoordhuis] - Fixed a thread safety issue in nodejs that was perfectly mappable to this module. -* [Nate Rajlich][tootallnate] - Bindings and build process. -* [Sean McArthur][seanmonstar] - Windows Support -* [Fanie Oosthuysen][weareu] - Windows Support -* [Amitosh Swain Mahapatra][recrsn] - $2b$ hash support, ES6 Promise support -* [Nicola Del Gobbo][NickNaso] - Initial implementation with N-API - -## License -Unless stated elsewhere, file headers or otherwise, the license as stated in the LICENSE file. - -[bcryptwiki]: https://en.wikipedia.org/wiki/Bcrypt -[bcryptgs]: http://mail-index.netbsd.org/tech-crypto/2002/05/24/msg000204.html -[codahale]: http://codahale.com/how-to-safely-store-a-password/ -[gh13]: https://github.com/ncb000gt/node.bcrypt.js/issues/13 -[jtr]: http://www.openwall.com/lists/oss-security/2011/06/20/2 -[depsinstall]: https://github.com/kelektiv/node.bcrypt.js/wiki/Installation-Instructions -[timingatk]: https://codahale.com/a-lesson-in-timing-attacks/ -[wrap-around-bug]: https://github.com/kelektiv/node.bcrypt.js/wiki/Security-Issues-and-Concerns#bcrypt-wrap-around-bug-medium-severity -[improper-nuls]: https://github.com/kelektiv/node.bcrypt.js/wiki/Security-Issues-and-Concerns#improper-nul-handling-medium-severity - -[shadowfiend]:https://github.com/Shadowfiend -[thegoleffect]:https://github.com/thegoleffect -[pixelglow]:https://github.com/pixelglow -[dtrejo]:https://github.com/dtrejo -[alfredwesterveld]:https://github.com/alfredwesterveld -[newitfarmer]:https://github.com/newitfarmer -[zooko]:https://twitter.com/zooko -[vincentr]:https://twitter.com/vincentcr -[lloyd]:https://github.com/lloyd -[shtylman]:https://github.com/shtylman -[vadimg]:https://github.com/vadimg -[bnoordhuis]:https://github.com/bnoordhuis -[tootallnate]:https://github.com/tootallnate -[seanmonstar]:https://github.com/seanmonstar -[weareu]:https://github.com/weareu -[recrsn]:https://github.com/recrsn -[NickNaso]: https://github.com/NickNaso diff --git a/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md b/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md deleted file mode 100644 index c132dc869..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/SECURITY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Security Policy - -As with any software, `bcrypt` is likely to have bugs. Please report any security vulnerabilities responsibly - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 5.0.x | :white_check_mark: | -| < 5.0 | :x: | - -## Reporting a Vulnerability - -If you are reporting a security vulnerability, please refrain from opening a GitHub issue and instead mail it to -one of the maintainers listed in the README. diff --git a/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js b/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js deleted file mode 100644 index 62da52527..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/bcrypt.js +++ /dev/null @@ -1,242 +0,0 @@ -const path = require('path'); -const bindings = require('node-gyp-build')(path.resolve(__dirname)); - -const crypto = require('crypto'); - -const promises = require('./promises'); - -/// generate a salt (sync) -/// @param {Number} [rounds] number of rounds (default 10) -/// @return {String} salt -function genSaltSync(rounds, minor) { - // default 10 rounds - if (!rounds) { - rounds = 10; - } else if (typeof rounds !== 'number') { - throw new Error('rounds must be a number'); - } - - if (!minor) { - minor = 'b'; - } else if (minor !== 'b' && minor !== 'a') { - throw new Error('minor must be either "a" or "b"'); - } - - return bindings.gen_salt_sync(minor, rounds, crypto.randomBytes(16)); -} - -/// generate a salt -/// @param {Number} [rounds] number of rounds (default 10) -/// @param {Function} cb callback(err, salt) -function genSalt(rounds, minor, cb) { - let error; - - // if callback is first argument, then use defaults for others - if (typeof arguments[0] === 'function') { - // have to set callback first otherwise arguments are overridden - cb = arguments[0]; - rounds = 10; - minor = 'b'; - // callback is second argument - } else if (typeof arguments[1] === 'function') { - // have to set callback first otherwise arguments are overridden - cb = arguments[1]; - minor = 'b'; - } - - if (!cb) { - return promises.promise(genSalt, this, [rounds, minor]); - } - - // default 10 rounds - if (!rounds) { - rounds = 10; - } else if (typeof rounds !== 'number') { - // callback error asynchronously - error = new Error('rounds must be a number'); - return process.nextTick(function () { - cb(error); - }); - } - - if (!minor) { - minor = 'b' - } else if (minor !== 'b' && minor !== 'a') { - error = new Error('minor must be either "a" or "b"'); - return process.nextTick(function () { - cb(error); - }); - } - - crypto.randomBytes(16, function (error, randomBytes) { - if (error) { - cb(error); - return; - } - - bindings.gen_salt(minor, rounds, randomBytes, cb); - }); -} - -/// hash data using a salt -/// @param {String|Buffer} data the data to encrypt -/// @param {String} salt the salt to use when hashing -/// @return {String} hash -function hashSync(data, salt) { - if (data == null || salt == null) { - throw new Error('data and salt arguments required'); - } - - if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { - throw new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); - } - - if (typeof salt === 'number') { - salt = module.exports.genSaltSync(salt); - } - - return bindings.encrypt_sync(data, salt); -} - -/// hash data using a salt -/// @param {String|Buffer} data the data to encrypt -/// @param {String} salt the salt to use when hashing -/// @param {Function} cb callback(err, hash) -function hash(data, salt, cb) { - let error; - - if (typeof data === 'function') { - error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); - return process.nextTick(function () { - data(error); - }); - } - - if (typeof salt === 'function') { - error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); - return process.nextTick(function () { - salt(error); - }); - } - - // cb exists but is not a function - // return a rejecting promise - if (cb && typeof cb !== 'function') { - return promises.reject(new Error('cb must be a function or null to return a Promise')); - } - - if (!cb) { - return promises.promise(hash, this, [data, salt]); - } - - if (data == null || salt == null) { - error = new Error('data and salt arguments required'); - return process.nextTick(function () { - cb(error); - }); - } - - if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { - error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); - return process.nextTick(function () { - cb(error); - }); - } - - - if (typeof salt === 'number') { - return module.exports.genSalt(salt, function (err, salt) { - return bindings.encrypt(data, salt, cb); - }); - } - - return bindings.encrypt(data, salt, cb); -} - -/// compare raw data to hash -/// @param {String|Buffer} data the data to hash and compare -/// @param {String} hash expected hash -/// @return {bool} true if hashed data matches hash -function compareSync(data, hash) { - if (data == null || hash == null) { - throw new Error('data and hash arguments required'); - } - - if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { - throw new Error('data must be a string or Buffer and hash must be a string'); - } - - return bindings.compare_sync(data, hash); -} - -/// compare raw data to hash -/// @param {String|Buffer} data the data to hash and compare -/// @param {String} hash expected hash -/// @param {Function} cb callback(err, matched) - matched is true if hashed data matches hash -function compare(data, hash, cb) { - let error; - - if (typeof data === 'function') { - error = new Error('data and hash arguments required'); - return process.nextTick(function () { - data(error); - }); - } - - if (typeof hash === 'function') { - error = new Error('data and hash arguments required'); - return process.nextTick(function () { - hash(error); - }); - } - - // cb exists but is not a function - // return a rejecting promise - if (cb && typeof cb !== 'function') { - return promises.reject(new Error('cb must be a function or null to return a Promise')); - } - - if (!cb) { - return promises.promise(compare, this, [data, hash]); - } - - if (data == null || hash == null) { - error = new Error('data and hash arguments required'); - return process.nextTick(function () { - cb(error); - }); - } - - if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { - error = new Error('data and hash must be strings'); - return process.nextTick(function () { - cb(error); - }); - } - - return bindings.compare(data, hash, cb); -} - -/// @param {String} hash extract rounds from this hash -/// @return {Number} the number of rounds used to encrypt a given hash -function getRounds(hash) { - if (hash == null) { - throw new Error('hash argument required'); - } - - if (typeof hash !== 'string') { - throw new Error('hash must be a string'); - } - - return bindings.get_rounds(hash); -} - -module.exports = { - genSaltSync, - genSalt, - hashSync, - hash, - compareSync, - compare, - getRounds, -} diff --git a/week-5/solution/frontend/node_modules/bcrypt/binding.gyp b/week-5/solution/frontend/node_modules/bcrypt/binding.gyp deleted file mode 100644 index 46428be78..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/binding.gyp +++ /dev/null @@ -1,49 +0,0 @@ -{ - "variables": { - "NODE_VERSION%":" { - const start = Date.now(); - - // genSalt - const salt = await bcrypt.genSalt(10) - console.log('salt: ' + salt); - console.log('salt cb end: ' + (Date.now() - start) + 'ms'); - - // hash - const crypted = await bcrypt.hash('test', salt) - console.log('crypted: ' + crypted); - console.log('crypted cb end: ' + (Date.now() - start) + 'ms'); - console.log('rounds used from hash:', bcrypt.getRounds(crypted)); - - // compare - const res = await bcrypt.compare('test', crypted) - console.log('compared true: ' + res); - console.log('compared true cb end: ' + (Date.now() - start) + 'ms'); - - // compare - const res2 = await bcrypt.compare('bacon', crypted) - console.log('compared false: ' + res2); - console.log('compared false cb end: ' + (Date.now() - start) + 'ms'); - - console.log('end: ' + (Date.now() - start) + 'ms'); -})(); diff --git a/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js b/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js deleted file mode 100644 index 3f2ff2f48..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/examples/forever_gen_salt.js +++ /dev/null @@ -1,8 +0,0 @@ -const bcrypt = require('../bcrypt'); - -(function printSalt() { - bcrypt.genSalt(10, (err, salt) => { - console.log('salt: ' + salt); - printSalt(); - }); -})() diff --git a/week-5/solution/frontend/node_modules/bcrypt/package.json b/week-5/solution/frontend/node_modules/bcrypt/package.json deleted file mode 100644 index c849897a4..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "bcrypt", - "description": "A bcrypt library for NodeJS.", - "keywords": [ - "bcrypt", - "password", - "auth", - "authentication", - "encryption", - "crypt", - "crypto" - ], - "main": "./bcrypt", - "version": "6.0.0", - "author": "Nick Campbell (https://github.com/ncb000gt)", - "engines": { - "node": ">= 18" - }, - "repository": { - "type": "git", - "url": "https://github.com/kelektiv/node.bcrypt.js.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/kelektiv/node.bcrypt.js/issues" - }, - "scripts": { - "test": "jest", - "install": "node-gyp-build", - "build": "prebuildify --napi --tag-libc --strip" - }, - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "devDependencies": { - "jest": "^29.7.0", - "prebuildify": "^6.0.1" - }, - "contributors": [ - "Antonio Salazar Cardozo (https://github.com/Shadowfiend)", - "Van Nguyen (https://github.com/thegoleffect)", - "David Trejo (https://github.com/dtrejo)", - "Ben Glow (https://github.com/pixelglow)", - "NewITFarmer.com <> (https://github.com/newitfarmer)", - "Alfred Westerveld (https://github.com/alfredwesterveld)", - "Vincent Côté-Roy (https://github.com/vincentcr)", - "Lloyd Hilaiel (https://github.com/lloyd)", - "Roman Shtylman (https://github.com/shtylman)", - "Vadim Graboys (https://github.com/vadimg)", - "Ben Noorduis <> (https://github.com/bnoordhuis)", - "Nate Rajlich (https://github.com/tootallnate)", - "Sean McArthur (https://github.com/seanmonstar)", - "Fanie Oosthuysen (https://github.com/weareu)", - "Amitosh Swain Mahapatra (https://github.com/Agathver)", - "Corbin Crutchley (https://github.com/crutchcorn)", - "Nicola Del Gobbo (https://github.com/NickNaso)" - ], - "binary": { - "module_name": "bcrypt_lib" - } -} diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-arm64/bcrypt.node deleted file mode 100644 index 5160c6c388650193378be642f5048e48e201da2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88608 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 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/darwin-x64/bcrypt.node deleted file mode 100644 index 8de9c5f7903a0ffc9594ebcc23b3e41f0c8d8893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55488 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

    Iq8W+*_zZY;zo$L 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 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm/bcrypt.musl.node deleted file mode 100644 index be7ca5f1b1b1c00a12fb204f7a9fe177b2defcf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67420 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 X<-0<9?5?cd>hU|e5&@^>X!3sn0TkSK diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-arm64/bcrypt.musl.node deleted file mode 100644 index 717cd1457ed5feed0f3e79154ea4544e48ec465c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76552 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 kp9Zu%{dtI{E43eyExHl1STAn8VFGuKi{r~^~ diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/linux-x64/bcrypt.glibc.node deleted file mode 100644 index b58e7dc6b26f4fac20c468ab38f0d334de88f084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84904 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 boH|cKVf3u_7Yww+G4uLv4 z@B5$k{rvfS!r6PTwVt)s^E~Uht!Jz*uG)X;cG@ol$%^5eHJ`9$%uCAZ%C$vcaeezf@ZPu*JFcx&-B?^{^> ziBEpyqgM_cn)9{*^wOPA9J}l{TegOO3vT+>maW_`xapf)uCl+Ix0LXE_2SK2rrY0d zZmG1t-`e8X@~vAg<@d7RY~A`ze*g2s-`-Mcf46LTyZ!x(t^RMTyzFZv^dJAiUo#Qx3c37? znR=@Fjl!RE@BJrT-;SC8MVohhCuYjkI4^Fx)KOQ=)CC~)*c8Xj+XfqRYE{hKW*-dv zU(<(UCg~5|uW-yU6B+6?-{AQGO|HE2qo2K#=im8tFl5a=m`7N>*qHh&Z~w@WJC_)9 z-yWU<)BJ?rf9E&yFKJBjO23-feh~$obUcjTzxQz!R;6C&mACm7?Hrk_89^yHi2kEq z^2(3hcIT3%w|vytuE4RX{P>)Dombv|$L-4~vbY0>wqEBs^^#ZK{?S`LNk!pOI5jT6 zyU(dt!_)s?{ky?2$MRyv%Znw`MJDfPwK0o2Rxs^bw=4IDKZ(cQ#`flqxaFba&jOPsC08 zD{-g&WIWdXYCPV4h`%5A_CDAZFKF+M7ji$c{cwC#`$FdRe&%%}^ZFq3x|w2G1M}a8gNyeVr&+#7Vov@DW6u!^zmhr_8lK= z(ouY??AYLB+R|{@#C=7v{G)52d2g*xo1)_Que;)T?Qnjf;GJIenB{{{GltWvc3J#} zw9~KUk3(}iHW)MAm~PaEldJxY8M7ny=HljB?&ELfHE-|joz@kPSE}rjXL|EK9Ki36 zmsEx@kYCeHUiZ$k+n40@w~yKOU#9)_L3_tEjd-TFcXT+WvtTv(u=WRIaTv#bI}RVt zKLO`m0i5%+Kc%q*?f24tZ_vJ7&(;3&jP@m_X~7TAH=c=^b8{*Onj4d`xYwBSOtRy< z@uma$X6GNE)rvKaS#-nalj;0evSZTn@gs)UH7^+ctsmUp>*TN43?1`Eux3AR;H9Po z8e?(r8_qA!Y(F3#n0P4Bkq2+xP&4D#dBbWB<~e3Z-XN|&yy#$2jyZnp>P-*j#g=?e z^}3v*r}y{%(}BDX&8vDNGB?4TpMxy8_rfhgfyi^;=kGx;pdHwG{%3R$C&-D!@6*0 zJ$+_ywWWog_g@>6la=%;z){_#WQFS_<|Y0jZ{Y+dD?j(96+096+;lMU*~XnKoJ4M7 zrn|7haq|;5+535ki_M4%XNbLTq6V?jpIx!@J|{PK z0x(tpL$JA@Hvt$cfDzp1UOTP2qJ`%Tjyo?mfRUR4BR2y^ZUhExb5}S+=lywOsQDi- zhT8uDW4Pvjz!+x#4;aIo{{dro&wtApZeeY%57zg3)|Fl3!TMgGvA)+w*0$~|X#7hN|yxw9hM%)ieWHt)J!MLUn2 z?wyte441z+e}bLE-=?Hl`T2>P#@*NO(HZlAF%KA@&ocR+f0tQ!3$Tv@dvO3$V?Po% z9cq_|v~8g+edSj~+FncB7vs5=vx2sD%=u7(@p=ZE4(t@~;WNDt9>z8~5>IxxZoDOD zZ&{0*o%9lp>F61ZEX~gASnitqTIRUA&a7Pbdha5}ec;RVdo-S?d@=4;3NIRm<`kWW z%UImVST1KQN0>)ZFqU%UBl5o^v#sWl*>-|$n;U7HbJjf8Fpr}Xk`*t-hqdo@;w`zK zE#KMDc_ni(UJloL6DEHcb9lzdTDg@uEHhc&%h2Vj*L$bQ-ihjw2K0|vGP%!=I!!<>bXL8>^|7MQ&&+`*~>xM3-Q72FE`vloA}CS^XGl8D{dD3V&o+YUmBS;<;sy}#w;iIXbsPX z%}!dlJF>wf4(3tuXXO}PsCUFn#rUT4VBE#PU`Q~p@$xDLmlemWW3 zFUAh8JVO0h&d{ST(WaL5@Yolem3t}EZ_m}6#<|Xn+%eE{5OXLhbB?4*Hs3s6KfAtH=2#l#>Z41j*o2@p1Q$@%C^C`l2iGPkGneLsAQ<*-ePCW zih0P<>l*L!fa9^`26YEv;hfc-rEdGzrD zzmji~ZR&p&>!%0V_Hul9``*u9wX+AA*7I-tA`gbsPw!l08gd#q2mTUR&(r3GDaMmb zi^6FP;0TwGJHuP{ezwBGIXj$K!%Oj@m74-Q45UxaiwAJf4K9R-Dvh7H_u&DY z{Ly%bzyN?1hX|$p;N{+F;s@yp$^HCc{#q(pVph7~U`XSNJ{=}rbD`07 z;NV4gA=F_nF3juGeScE>8~@DqH~*RK7yp^czw^9g-33@p+hvW zWz;_&)PE{`M*Z)G*Q$T>4IN&1FZja)`26<453f~!G4R8C!4K*`6+RRE@LKhk0YAJK z{Ja1@zkTq-Yt{ca@WXq-59&V^J`?=#n)+UFy*{gHLEWIH1xI40s(Fy9sspE=LjQav z$5b_Ny?&n6vH5>nQ{E!k89N^L_KYwix{#M^kUcN3uF8iSFVr!&#A05kXKoFyrDHzE z^~Zzzm$|>xNmf-N%NL_7Qg7BAtZ2Mt2DV#OD(mip(jBuYzlQQnX6Vt1{Mqg8L++){ ztuqqYrb>F^FHN#)J$A$_XZX>1V`sPjLt*m%e`xsljCqY8n~{?+RoO#Lm78z8J#Y3d z%0U-2()ZlP#u*99+@Z$H=Gk(dRWvTQFkN(J5&idI|D3CX3-`HBVN1Svg0X9k!mo6x z=BVrF9CiOzb6ootbKL!3oTJ80z~!8I>N#QR0q5%STr@qOO(pvc+pXnoZ}dKRrYxC0 zJ5Jee|4k?^ZJA|qkH)giBH11D);L}{^r^7)$vx_vt~prJuzUt#?kXqy?t@F2dpY#I zT=a$hC3$u2iJ|xUb+SyA=H5*C8tHoAR*bD{cSk1gccGP~mkSMtm?{EGmcH1gRq}x< zDbJzI41t#6Gy2T|Cb96oXY!FwtTL${!`z)AUf#3jeTrEg!4_?Zne>uU;}w?}@3yr~ z?|SkgVma8wCsrj>vbWVwBFn~lw!53N;6HP`IKTNJJwLJV$Eh>9CLO{R%z9u6CNb5@ zL%`Zde9$2lCYa@;eb{Gmlj#!+clX)TqKR>`y+4c#V`xbm{|v`1xDA&P>z-+jugCVO zAD2u!;HnUKJxe8&f`GsTHP5(c1wVJux!HxKjX3?7;e(?Pm1ud?D+EMq?-mMQ(4#p^5J6qsjuRMnI_t4&bP&yX09%E zQmc_;ImCu#f4j855xn%)nB#G5PS+g6h8}&i)-{Wc3`>4*mg62x@q84|+4wo|#3-}z zGaqtNZLwsP3;$rJrUh$tj!8EVBV0`V9(Z%P?!ng)Xeb)Yg$9j^5jIxNkgS>YL34a> ztm(k0N$Vf##HUD}?tSD%Vvg00yH9lgaG}|_jP{4PUi~h={ZiVWHP>*Cjv?k;(60D+ z4LEykSuJ?10iV^x&3|HfW{q&aPPU)f2#gKRu3?_;|N3IHF)U+C=&Z6YQuZp#2UzhTqRl_Y!j#Eku*rst&@Y73G{{W5G?YIjgT2RM=J@6j%@6g^XDEMj@PWYFw3y@UvoI#j zNgs}kB?*ik_7~jK^$#HP=Pw?KEe3rm2ARC_x$QH`6K;!fik`M@OXlEi;v0$IkIzmY zVh)EH53zT<7B#mdxIP5EyO~!PdSER$)35e?PO?W4uF-P&5RU8V*X+vnh~sax&(;j; zdj?PV&#JTgo?R0@bNcJe3ZHEr+*j`%JUe*s>8B6!PwOv~D?doHX4W}Ri7xnDj5B8G zBb-5}bcnamcRUMk5!@HYgLN-FFkkkz&ccJ{Z*dV%rZxX0*J$x?HDf~kI%aLu?h#`Ky} zlOpw?u#smy^tGOQ;a_t7iBqhpzPQ>RE7PHWd)HRu{Vj0_#_F#v?#D`p14Cu^XOxYw z>u!{7*OF0|8!7vIMp+hRf_HDm^E0d=(ed=EfA;k^am3Sc<9(I!ik7SctGl-kv-VAU zD?Bf{HZi`sIP)Mz1+x z9e7u`T^6hx)e~-Q`;~LjtzSr5*vXi06O@B9tx4${*>PHz4Z)hcjWsFzNOqF&e+%f>MSoy|Go{S9pYdcOuYn+Gv2`k%@*&yPvZ>V5}!Dq_!B*H>heZKKS>^4HM@ z(_QH8rp`cktr1?4F(F=sOX0SmG?@mE>Eb$T=sxNd(fCkXPG@_~4`rS*PzSoVA;eT(j`;9GiNb&aXoRb#w^ zzKrns09_#ZHKFsg-U1o1-9<*I-Jw8+i)QuI*~M=lBeuJe5!^2;^>wS|4eD5)fj2q> z8DSze^(Po>zdb$h+JBmP?_w;SYmGN8f&<+@0uHn$bbVz;**{Wd`7N^c_fSVTGyFTt zFB_JQu??>@}?MiqSRu#NOJ?#lud{M_f$Bys9=jo1*_H`ya*MncwhK-_rmY}y??2Lr3w=I1# z`)igDK!15d-0Sl$9&`P6d^8JtB(MinTRI!)V{MOk%*$c!uZ`FK(%JrvZ8H`f%Q`%? zHkmpMt~J(T=-7jd5G-V$^vre_*eXj>CZDUDIdttd-oFqZEDzSU?%xy8jb|I2HMzc= zFB#(941K~jKgekFj-ZXz&A|9xM)_pQwQnZ)-_9tTV9U_ylzk(k>}{0!>*$BRo(=ru zG&Y>p(~n;Dc>)Kn3GJnotRr@TFZLR~fyL1fGp!SPt$|*J&`awt zO0&{3AD5ef)0u0gO~p2me_?U?{yFI>_<<$0{j#T?I?t9SQtyYV zpRRqtwmUi7(x8~S_8wj;#wO|}W@vc~nwQJQf|oS5fq1EuK2=vXU@K#5!|yDozmS*m z;{Ci-{6(LaiY+gVX=nX-p8;p-vSj*ff7y>&*)hRSrOZ`r%YwedPv!CIR0+0#DLa>+ z*pC}s!aR!i7;j31hPr=vtS<-3Gx$k-7q+=9qs=#iHfmp#QMQS);xgl{A@(MIQ=L&6 zb^eAr@*h+-JfrN(w#;wmW|TEkb|HQ%&fquknfOhzNVKr{Lk3xXTL!-o^Af))U(oU# zbZ-rKPO+Nv@!T5m9P&nV5YPQ2;48_T^YN64$fsf(hd7s~)c1fq1wKQbYNBt+r*irp zkf#Qp%TtF=zBNyY)}`x`sj_wbGO8SYDi*x+@f7nA4gVfm!#~!p)cqyUNxCJIrzS&B ztv%K8GTP(^ZB+K9jItcd6F$GJ%P5Q6GM``W&M13xjL$D-qViVFj@R=|q2iq`Hkk6? z%8jw{N!dY`H_){v%eTYovBN?;$j&2Tdl}{-KJ05J*?qot5gGjl#&;q9y$w3a{s`rz z`gN;Edf+4_UVIf^oq~+~>{35AyBA(K3GcoV&uVWVemT)}&YXeVbKX7X`q8nE{rx$- zgq@-NV)^e~fqqPim$0`+L*t@aQ?)P7&j*s*|3pz}7*xmX*oRa;jKo_jd>WQx|@bOKdxf7?pnG#HF;x)==NJ zy~1z%u^Qtk=R<2k`ci97Hgov}_ZHEFh7>JAYXBuf^*_Hy+Pccaa(>+d-;S* zun~5_*A_P)^>HJd*tqZHe!r>yGT(1{2k_-Hi58;Cl1qFWv>{d<#?-glm^yxwg>wmg zV5i7Vjl|vkI)M)}P~5$hF}e5=C5mM`f>mVm&NKb-9k2NI!C8KzV$_Ta9lN6e{@x6) zFJ(OL#r~Ki%QYtPwq$hFCXinh`r`6^>hY(!qbjxHA3fR@#0fLR;NG%f5xyb4Z`2U_O>U%t127WggvIjkkiePY#)< zu}l77AEbNAjCW+KV`YA1E*s=C*tt+HI@r0u=hkOhM$Cujn#;SHi^jX0exrLIU5Cj( z@W;AezP|1|g0aGe#ASbE<6FvmB36+hG3O|2c+s@t>eQ=@JM;&qL&sOZ)nl>3 zqb_?6Cs-$&-tEW!Pk>wD`y@28_9MLewR*GhvE@@{oWUOMe>cF7_iG>HG5q?5EVC#T z>|-?GX5FpWmeW4V_4NWYLZ1`Y2M678 zx4k^u=Vcooz;=+08yC&@urJ58_JitcOiH}(4)ys}yhgd_h(_qX)2sUT%Y67T3!hkW zrR{?~39T26Cv&~@oS?6D{c)D<;9cuM{I>XVKYt8aut9N&Cgk1bQt-#Tt(?RUlFjwC zVZOYx_0V^+IaJ;Xe>HFMuQR)UPtG-dzFIdtQNX@yLy$Lj3-=9!{IX7H>5>f=A|?_!Ts@FllDM45aO)q6Rk z-Y2Q2_k~o}lTp?XDeKNCTS8eVM?(G(ZQV6vJb#TkmGEU+{L&ZCgjW>LBpJ@?DvcEu>C z6BjSaH^>;GFazt9zObD4)1&Ys@QM7qdD!`F1m{c<|iRteZ``FlKGA zzWU%folX5`!B@0RIsKJWKY5v%HsMCDwqII&k?!lQ;K+Z9Cg} z1!-;~(Nc^)pw0nTbK+MyqR14+WC~sEImNYImEV*5HRRp#C1ZVhv|-Jjq<@vc8)wlv(#F!7`t`#M?;ltiADBg-<@9go zG4yQw$X<9${EX|Tk3lY2+LSfTybhep2ADF-q~&+bm_Sa)>YUWXw>Ql+SxpD7p*%0% zIWs|S2m0qg@wFyhQI?b99kiK6Nt-Wspd8s?@jkzQecE+0#$TI#Yqo5V;*}d*>Z#lW zvuce1G&VSCp2|@YH6&hVIKH_ z$3poiUXy$r!uV~Ur`d1$f^V-RN7@*j)mbH$PkcYLp6BJGWxtGWx4!6}EN{#4^T$8r z^F2-6NMnnY3{xI6eqP+$71}S1U-w7YLn^_h)%8-=LU~}@=>8C8g7cFQ&gP+gaQ-O+ z&c6nAJHoovL0xe2jc8r&pP;VfQZd)FqkVG!Q_7Uf_$}brG4t$Q8FhXXX+JHajG*eq z;#%W97`A5~mq+Vx{~hX-Km*P3(u}fyh?HHDQTF!%?4}6pkr{Qq64X(=GsO9jj5_yG z$IhSrvop&6GHCztNc%Urj?zW@ej&|HW|Vy@Xn$j*{qHjB+(I4M4B;4_%_#o}&i9zlTlpU2;g8MWx^=DbJ{Q5G?q4qmY(K8=$S6BOnfi3t>(a0Gxm{q% zmOHV~&jWdFRV=k$I9-=z^FX?;GU>0-m+Yu!>>llFoFpz+giPO4hP{u@bzT=GA#ow|#u4~z$ZC;MqVdepX0B-07$4dDZu>BL}sY}TuAW^E>P zb$2dy{6%Jmt%HqpDBfS*MR^Z<6>Z~<_Px^Tqf7Q)q>lpnkWCTx(UnUdjMKs|G1E>E zd+Q5d_*V;!;fmtMc3n$IkNW!=5qqFc0uA=xZ zzR1!n-Ho0*Q5H*etux+d@ylM+IC<7Jg*?l^CQ?3|u8H^Pov20C=&+lIn2mjO#y0As zbM}S%Z5r#-+2RkI$mgSg2Po@<8KJA}-wVy93!VK6yd&8KeL7BH2lZl`Y&;)b%b;tV zIR?BZoqqy*NH%Bh1Y!^w?Rr@YmcGlNZ$?|?J@(K>ac9{@6@o$EM;ONuZ2DJ-vs}n; zUVqIO795Gk?m80htdbAyqKD-_%Wv)~$nuWlW|3dddjZ36-^ILl(U%=t^>AwobnS_G-|=QUGBfHKINNpJu%uR_uB=hjQlL%NGGYz z+cWBX%eE)al(OQCvR1XPHQw5=J!2gmt;79;YR_8HJn}NiHblx28D;AQdmVAq2yBy4 zrzxnjG*ag^uA_MUjQW6&)&H@KvfG083nJ}b$f)yiwGU|4l~Go2VS`&>|0bjCgOr7O zT)$S4NA$S#xpcYy_30hutp#VnD7Gj)vz_x=D)W*Zi}4lP@D&>epPe(T^(2`-mtXmb z>-p6>O9Xymg8TAcRj1@9>?g0G@Seoip1BuWo!F{kuzQKSDSish7D-2IpZr(kr(2#O zN1*IxvvJe%=`)54b`ZmzOMESi%~oK~OE0S3zoNg@?pMTUwRQ?^+pA|N4^eT@*H}yb zJGuV43G>hUV!z~{k7m5TXaD*L@t?;Ze|+D*C!c)sm;4i+qIp`IUy{vZ(9e-PE&6Zx z0Q&$nvA+0R{XBS&IAvGdTrC`Ix;K{EFbF@1T-w)j%*MG1d?fywh>fi;jmd}EpIDRU zO{z|A%TJnZld7xvb+(n4pfif`SG>-<+zsZgqH?EdO^vUA%YiSuQFyJOzJT3e2N3+5Ni2lMBDc79{Ma8P@0+z(nF zv$<;Myv(?tExSYjMAo$S2{-wwIk4CUV^W<-~Xaaa!t>VXVhCsJ?TT0y_iw9JW_Tzqih*v z(YT-TtzCG=T{GHKyh;0fZSY|mG!J>96?%&oJ^(MAV2wRi6RUdc`kYkToJgG@HuzrZ zNDn=By-O~(Q+1lSBOOk6E|dK{vVC??9?gF~D?clEZ{;_?m;IXE_ufk0cL@kr>ci*N@4GP6dhwT6=Sh&3BN!r`U~)z3C1%Qw2}4 zC-MK^0HXj{ifyQ^Y+2c(PFaw-IZ znoMpJKjqU)Sger?XS>5elCDl)@4;`-0Yp$m|RE~H~Ru~$@RzHRmj+(i(6V8hbpfCtku1 zvG^9fW8l`WH>!QKACuZ2?;h@L3gnI(tW_tT?d@fqb|Qmc8sy6i&9@Ud{bF!$`yd7t z=4009nQ0rDt9=e%i>DQ%E)3>Z%$}QYw}br+_sbFbtP9q^rB6OI%&Kz#DN?@GFTb90 zt#Q_^cl>t)ze6Q@Tm~H^*9Bq2 zq=0haBo^3^hIMczv}Lecb$=Ocv_1uIQwF>u$_#jScpi@FZ!+qPQlG(izMN4uTw!Lxpsn@xdop(c-zhAR>ioaizhPDl? zF~zjCem;Oa(|W5N=kK*R$k%RT9#Ne(?q8EqERkL2|=dyJRat`6I~jy6sBWU_0^si%JH z7(;bD<|SFj6Ul`Y-KQe6^TPQDHcoXd^FXdh-&t9Ke5*q?SJ%e9_ah&Yl&^(XlH4x} zWb(;SCNFYQ^G7$$ykU6L%=xvs+omF$>ps)JZj&qgbzAS6jf-y_mRejJubPT%u8XYg zWU#hxv~@h7X@G38xT8A^9pzMa>oil6WgS>b`YCa;A?Pd z@jCL7g4~TI?C*oW!f&iqv~oM!g?} z^_bt+!+GJ$gyZ^ZMt$Ddv9gMokbTemI`FNWe(~Ete$-#1FS=r8`XB4h9F7}rD|V>r zFNyd+OXQPLw&Nz|#d%FVqc@Ff*d41i~X_;@WNGJ19>#PGiTW>3vc|0cGTQfFMh4|?<-erB)qj)!vj5NUU#u+t6aANS zbYj#O8Nh$(tN-WtFP|XqP(1QS_%Ho)tNr8sm+uq*{8Rjw2W3;gh5z!`w$2~rzx0p2 zEbuD>8~j54OEY5^Zme%~e*fhx*yr_M;OEePAs0L9zwFmuxo>0p?)e! zykvy;$w0Qa=qjz%sQ>cgxGxt%|K$hVKZ88Aa%f=x#m>7S|@1kdl0}brE_`0{4SdV-cwF!L}wHr&j zNUX{7w2^(h44WUHNq&^|J=n`zI9$FaQHU({lU)Ew6S zm;cUA@pv=sgI}DJTKpbt@r*W$>Bssm@K9zu*}-+RX~pO0^Ihn-jxkhkb-d)(EYAsI z$5k1=i=792m1J)%wEsJaTUq=6aLik{)ufV?FN9B$+<$O@c+w5;akkChle4W#eha=z zpB=u~x5FoAV~788NNVxnSXGt$7JQYyIuH7FDr}vC)Z(qgESOK|w+R1ZM7IpTr4Rpu zjrCuJf5m<3z<+g_1atFOjC;(kI# z`L`*rX6%~J_lRw@V87FTL!W;H-+mgK(!!K~#QopXM)s!Qjn07gAZ5vQ#=8qRlKHAL zEThg}tIuFOgEPv$sP@Y^pC!`%j6bHnIo(Sg%}MKFaircW8TIZA>V;$dT}JsWluP!V z6;CHNp_olJxR%d>Y_l<&W8~OO;k^#YxN^=3@niqw=EFNZ>8sy!W9{d;HVJFVd7#Tyz zTV3=o-n!EAmd;m+c~8AC@H@_upZm$bo(7XI6cwg?( zn9cdw;4CT7`_uNE0_mv3&~gj09@#9`4nt1a^1onT3tv!k4bLV}c@yQp-yhpGi*d5= zMIW0&*qhIVy*2{7i5T^TVAoR)>|?QAllx#dhp@XZ3|sjM7lNIn+%>G>&4c=2uMA<@*omz)bb7J<$C-xq{!D7W}tQqc#yHiW(TT-b-u z-61UvdAJuEch}GW-%Bp)gME1jd)I|wck!Dw+DbG`Q6^-j&3&!3w+ zV)jj55Ao(KpVoW|WPIwDU5`@+0lOlu?!wX}2z;EFNihcShNp?4N}A{8UESNn7UQ z^VW>Amnf6mx98Hwou{-`+!FG1@hQ94>AY*sU@YU^y*PjDJ(08K^`I9fJ9>Z6={wsz zW|HYG*oR>}&BoEl18|*~ec!MP`NW|#8&`97h~C-EjB}}7cs5G{9iX!x1w*pA9@+gf z+A40Tc0H^kyKh*`d3cP+e@`ipi`b-L9*J@u1Vj4_+uz68hRDR*;K$YEfb<~4q;t?g zedl-=IwSX8C^zKJzTA+4_P<-8+>j~flC#PU`3!nTw7HXW3#B9e%C=Q* zNWlejL&CEOTO+w4OR@1pFB`uiKPEa?=>a=eo@?DH*JUd2IBMR~cXQF3)1Xs0M>oiS z*(*H^PL=zzop++ub|`u^%y~HiJ;I!qTbS2c;?cex@?eHbP^c;yO}vh z8-Q7t{egYY1n*|<9rOYFZe}6xX7XN`*J{io1&mwoW)=qTW>$a;@z*rYPtrI(6&u}h zD6l(Hz?;f=B**f>PdFaQ=+leL#$CXCjB&|E9u6GVU7^n27?e@>57bq?DRGmQPnKR4 z{SKA?uIX=y7xw$DL7q%-&d=XGCx5m7oFDOm&ic`~TUl3Dmh%p!&iPS}#4c>Mde*4u zYxO?ob!^5SDDi#2zB7Jy*Z8@!Yk*Nton5STC#-XxGk$De#D+VA_b^T5efB#UXaAWp zevXu@_^L;C+Vh zUYpf_z7jG|xKA&d|5yz_MG%x zFY&i%Q_Q=D7N?vMxf{59O3ytbvWwXLZeR&7-)GLk(JrplZ&Pg6)4Ja~z26@0tn}^S z<;Zu*glB4;s%J_ErH~ODbPjLl7US(@k6;Y3>|LesH+!jjOZ|6g?N}V>i+!!}X`P3- z0vFyJ&z?(T*_p~i*b5K$(Ubc+Xx15vd%L60xv$~5FaP?_E&MHTLp#{;-jCk@)3?#8 znFH%;L)>|{Z|D7Xwta{AIq3Z?>+UP?Iq#zDOiOGhAE)*He#v%ssGom)k~JJHFIJud z=QYIioebI2m(EE)iw@G6x7KC};B>*;J@~wbml>TQd7!s9X7dVruQeU&OS#0oThT-8 z$M$ZG`{z?~Hl*;Ta~^u=SMu~A`4qCBzQY_d&mVM{tK>&7XDe9WguXrHv4xR##% zhb@lx*u8^N-&ktAw(CvGf7jLKs%&?caF*t7_Po$XTj0IzyPa*q)%H@%oV(E5X{!ehV_6P&3Igexm0NN*=bdr#Ifm z^?v)_H}_uy*S2hJ+$)ThalbL3Ll?X$x^b9s;ptW1v$Cgau#-A8JeGQPbUgKZ5obcf z8(o9r+YSxS+V}}5%4FX^Jta-EF?7qE%0hmU7Tlgdetx5OsweXN#JbKp)0+^=v{kVdX?7|Ue{dZ{WiQ_dX@L@;q}_9 zynhX^w_N4@D!kr(mG@M5edsFh7vXhmqPH)+E}iJ@4Xt6|1N7|5khj~*Z2$VjA*|vaIWH8_1nhb-R_cHn=9CWZGq05?(lvl z-#N73tvsjS$h=B8=XF*VF(7Oz&Z}72#du~7F)Qo&Eyl(h!h9;Rp`>fZ@M|tI3pq<0 zdn(qBtlp^kE;isujyB{6ZTv_6GB8}!uvJlbpgCWe$myH{|LNa5y#rkuV~nStZc^Kx}EzZFm$hT7prak z(d{oIo43ne}|<%#!pxP1GhJTbIGxC`|2g3KKCkp5lN`96I_>27@w zY^Bim5Kcna$mx~8gD%3G^##yxI+HtuN8j_+_h*?q`RkQCz-5R>_DWXng2sm_TfiTO z-fUbAPSJ6dw{ndhwz#;5>jG$uUm~Bd@_E*R#`#z0j5F#J`gUGG*UCpnn-19sk38d~ zUM2t9>J)IJ@!t}Re*t{6caWK{b2vns!Zo>G4|=H?TsL6934aOdD1U++nM(O~1K}Nd zRX$u`I>^PE8m;>tct0%TjE}R*vY1;~_RJu7Jtz6OYWh}t9}lD3-+-5OCcF6hAz*1- z%wtEi&OqaNCT3RtWL)j%b_MXlv6n^0!(PwSKXsmZ&mUiD!DGx>^pTw$eg7fk1;m$? zU;X^#e*;!|ssv$3c%TFjt+$k8TIgBiox9tJd+!4E|Cap)1ZiSM*@5c`g|%I;D zD#-8AxT5m;kLfo*?AIB3;eL7Fy02gAX7(H6Bka@tyg5}0>?m&R_!dH!K*xl2b(TU? zsj=s{V{ca$($1dYVZWPE*#04M$z_{bd@StONj@F?wb+I2sc#Uw*fq{B-a+G@Z**B% zlUeVHuwFI!cGOEo>YcFT^I;-OHZ-sf3I~~+ik!luyNGe<9X&g4{3_YRvit12dB`yP z96!uHgO?I_Cl8i8?$3L|vY}^}b*(el{gLuSP+rD&s<0C))~JkM)nS~foA~GC{j(TL zn`3Tro%Ih%{~d{!x3g9^BuXSl_q3ge;7B&<OVoco^Bj*$N44edZ&bntny};NEpse^2+B z@S@{fj4bO!p7g{=v?s8?)mP4%YSclQ(ybg=d0KEZX9Qqg|zVVqMb^ej~JYX zSa~9z-9Cu9YhSl3r}}fmYS0s-0zEMXoiy?sTfq8@$kOLIt4ee1Kfi2GpRUft&+oxa zD>${hvN_v(0G!IUnvFb7qC?+#9=RHhU3i+qoCgoUe^GiiNgq=en__$U%IvBq>G!M1 zm-+zLqHzp3)U&uhLfPvVz_%|S{54~3jiqylb(zWrd_(D(z3gK@N*_(|=$+VQ;ru?q z{91k6fOR&=Yvf*aG!Ml`RnG+vyk~fS;!>01yH%FAUF!DTQ@3I1psJ9@(RJQR8FPxr zyB}MBFg3Sj0lw3QT!(YNoa9}zO3GJs>x{6O$&Tf_%3Bic%?s)r-l=yID@u6IH>vL` zy24$#i+Kz&x%=uWl6Ub<$`*GCdnrSccU5q%PVUe|OB*t+h;KP1;g6i5$&O9cl}ERt zTN8<7M}?Dt6gA^+62(#2-;Ow#~}fRkD%lfCZeZ zWotYHjr98wctYdLgr#<7bN}~OWg~=gHOePF(%Wl>d-^ThJlreiTT@f`lTN7te+^$q zp4v;yAh56YNA2v@oF5qHJLImo&7Fn$4G|_WbgFT$>-%vxY}2e{#JXf@g>+0Iv1m7nW_4Z0pagH zix>Lg`|CLq{y^Uc`)LSw*ZJ)w!3|@<#Kzh+#wUH7W4p7)wKhi?xrJx@L9&Z}VAm{s z*uJ^Uq#q`Z^NrkiO1AIAEm^5IY2y+{-dyHrAB%5fTEFZpe)%qa%{sHG*tBC@>re6hMR?_acs0|O?&~*V7b~`E@q?{<7i)}b?D2y3e*;(OAM1-` zuDL!q%DJ}V_j%(m^Hxk;xeKF^3BsXlM#buu_Q5V}-v#V?#v~pU4_sn%3w)U4e^AkUXGn5$1DRL%i>ARfxt7MCdFL5WiB^~hn zM(}$Cd^YD4>RZ1D6T2!}_7V%6=D00wg94l)gWGca>mgisVvEMWZ#g+s-ON{ZW(pXx z5pzwlBL^7lL3JQ&7tDo*L+(i)ye~1NQcz*i?+ zGc}i(Hs6&U@gj5JyS5{GkhMec=~IcJEfwITVpnC$l}>KU#c_do;BK7miW4|qA~JibM_;tKrM zeZU;dwOB+7S#gwSFM?xT-^cZ)Y%}R{=e}_-^KEf*`|@Ww1uNY7 z#O_b`+Ok*R&F1_~<6a>S#2z=_DF5;}VzC{^_awK;=lm|;U9rCGvi>tLdIMc|S@3-T zUyk^)>HGhweTmpp20PixsWrpAeBP~p@#D$#@mjMjpZDvJ*E;;x@EhZ|cH5!RS*h72 ziskTLU~PO`UJ1EybCc;~oPE}FtKD}3 zcD?s~wOztnh?kH*z7;vg-tytXANHLgo?a18y-_Bf^KBTd-G+}P?RQZ>!9mLEM#o=Um&C)ecNEFx#1qmEc{IsTjC(#7Cs8GH7nwdF0=9zqWM z^#6jtvrk>Ec);)R57#r6x?yGveXZ!G{tw~#hN2p~e^7ro&wH)T?n6w~vl8$q8*Ct+ zt0ULj{r%ks-Nw)CETx|ME2iH~^ry0Q^rP~RmcqBxkzD%7C|@3xP_Iq8E6_oIOPi+$ znS9~t8SK*9Q@x93L1)qN-TdlZ?Q!4{2fy&e1=CyV0({hhgSr48hxndgGq|jamrs}d zlwzMqb&vA*`vv7K-;r%ld->6I%=tUC(RC@;*Mr0F@VChswnF~rt>E%I@`t%^;y$#s z4r3P_VSHNa4(ofbXaj7@`|z+Anl6SmYk;e<>~+eo7QeD@kT1G$&S!^cWpTwAMW@?? zxeN9)F>JbY?;?MV?QVOc?A&iShcdBtdu3gWS3@~R{9m1v_@~t|V{H3LzP+Ju;AZ+3p}k>}e~a5g=Jo*Vu*f(^mp~8k zgmgy9+pwScdv5Nz@ykqoLI2y`X@38$$h87w>DxK~P`(O#$d6>|@~GUL3w?C{q4FA< z%dk`9*n$Oon}KoURhwo6~k2RsJ}_ zYrzqogwyCVhqK%=pREb%)sYL)J0W%#G38Tjxh)HNhMNxCcBZuz+q-Ft@8_Qn15cLI zER|kngO0=rZaR z(|;6J6u;`bl{)`I{}-zF;9J1ygs#|y$J||m_-?p+GzqW#^gz&8G^Q7wr@rUhMB6AHE(*R4 z;EyZxhkg4?V{6vf@KXZ5KQI}3*|AgyW5Flw8(%PP=JH+i>>~ai=__OG%!hCFlDkg~ zQ@kpjtM_!AJkCYtegb2W%!syO&w7VsmD+Xsd^Do%bEe}Jp2>z&>@`|nI8$BrsjYo? zJvg!Boh`XvP^q$TA1G4>82(v2MC=ild@WOA_rkZ!?@@bbyx+XnSehYQ7K!Es(A@UJ zJQd4Uz1Cp74UAWOB|j2=$1gZH4w%_*0e#d5e8IeZzIcDoKYnh*^*+oC$&DW32?^v( z&c(!B9$&tby|+ngfK|$~F5*5A0eigP=Q?}-{V@^kc^=?hW^DWp!7C?za|GFI&}Xve zqp{J7ixfM+4|}{~=h=2vpM6q!7@I&bmVX9ja{yEDDu63H1D;;-UdD;u@Zm>ns3<>c z9?0Rd<`6Bfn23#&XP%nH-pO^Q`JsBg2a|(+TtUucIksRAc5rT?#@n>uT6Fc$d+s^7 zjpy4M8+Tr@>x!05&QL%8Y-1g{Hr4^HZH%D&y~MC&Pg)#Aao99M`$2PpZxUF#o)zEl z@u1vj#jX_d(Y^`%x}XPr4G&E{6?ZFdf0O;J&KWJ@8OC>j@31VW%QZW8Qoe#R)pa|| zaRvQyx4N^cE(d#qSkH3SR}Xq|Z{pN+>GVeI;0APV0poq#nK8YMXU($hC99qS7p$>B z|89@^9<96+`T&2*F@D^Pncj)*RLp$=fAukwmaiaKF8L+9C|kn2RwTOY*fOfAL-Odg zIjp@=$d3}fQ-Ez$<M&9a>_nqc|rcpkgW~OVvVX9_MR(Z`bMZ^c z;o$;&6@4d3{wO(B42a9wvjYEPJa`z_j*U|FZrU&=PjtPIP3y~#ADsP7 zP0osXV*)-F_RRCYQT}P1cMqNR7kH+%dxH5%m%VQ1r<@Sse{}m!_LkD|L6zg7NgnuQ z%~z5;Gik-!*so#z%FoE*x|um8E;D0f*H`Co4^8d))CsOjlO1y}?f2p9^lY5`aPvrl zoCY@!f4RPc>AQRuewhz{iTr{9zN2fwFYSllfZP@Qx+1gV zIO|LCfi}Jw^b*$&*WIH{y1U4vU)0*;z2l;b47#MSJ&XBipSq6Rdp%da4ZLmr=MOak3L}V*NG(~TNCddGHfw(yqxPJ;C~eOAM@^u zhE)(VsoFK6<+E94>d7glLq4P8+vT}#OCIo!UEMU(1!m7-)A3v8W_b($Q1Ws-@$7$# zJR7;UVW;+85Ay6Y?@8|D%(V8k@Jd&0+^gPmb<0>{8p>DKzRzRqr9N?{caePRXJTVk zRKMA~=;Rdo0dJJ&KL)MJ@UdTGAL4a*<0NA~wT^fiv`VZrBlZsU;{(g!_Xg3+vbi@bd>owttrIICKs+SBGT{eEU;vdeHA*586v!7BY_>`kTYJ zUx>%N!}R|g^Ekv#NrrT9y(ZN&m{|OKfQJwE7;B9@pwW`Ms$Z+U zHP!$zq-lrAk@xe%d|9xTaSrhLDObFJaViEU zUPSLr<@;&VUYI~GH9Ywod42tSsrq`axUl_g*g?{VTCcQu1R3S+`M2H&-=N*O=XU+9 zFV21uam(TSmB258^E`d?Zk+c$#j}W6Iwd*0n-TN$J=ov#S4jNoD8J(hP1>QI^j=XA z2ape45{=sr7^nKajSWaYNM>qn9?5kkOP-!We(HO$TB|3Ksp)uj6g92G_klH@WXcZgV@91O}4iDq@M=mMZ^n>(Vt%ew&F~4`BUspvAlX@-V5OId33{b z@p0gCBx`&WI%7;9F1K-38{e;)wpa1*t=XwI=&;wyglPS-mEFrYmtv3Oy${+RD?%o; z1TukhG$j*a@UCQn^v#KDlO3nf7q5*s>31L(UMVAf&OVveePhPDpUZQtd9C-#{HBgE zTJID1UQB%KiXp_CwAN?E6Dz)B*F9?-nK6&Q#~H8MO`Y$@^d5(IUSn_R8OG;!UfoiT zohO^<-t);A5Ll+Wr)HQNHRv&38iQ%x3WNe0)qL zIgM3^;Irqb_sY$@%fWmP!lQyqA6>gNZj|HVlC&Kll{sy5zD zvu(^{s(isb;Hu9C<67Iip|@yyBlnHuq-#t)X9iDiGABIFl_Vn@sQUMeQ`~vC5O!W3M2`HnG2R4BdN6J~}d)?6{WSh5YSB9xac< z>s(9cBw1^Vogph^Pc)22*UrRl#g1q|f6X1PyvM}RV#>5H+Qsu8=Hw#dS*I(O>Hacs zH^f}Md@$>bI5cZ@Ikx_yBj6OjaO%^n#pT3Q8|agKp%vPji;>$H?gy`9-79{=c^Qj( zXhZB}#XPQ&vz4;{R8Mv%dqNwp1=mH4RpVQRtY1yLkF?JH+|OC7_2>ZQ8AWZ>S2<@- z@!uZFV*lI*tDmsL#%PTn*7!?(+Gj&&U5EN9I?t`FRn61tD|k(FNeuRFvd!4|7H>n% zN_5DghBD2wV8s#U$#dOd-c)7diL$$k;pRXh0(||&2=nu)_J4j!?^i| zs9&dj=@(e5&(r2P?C62oy&GK;^45jy<@<=QXnlt9(+U{+GX6@@1B#(W^+6c7`yjUb z`Qr{gf3^kh_l56F50po90({tdCHXdgM?R0>M&l7GPb|p~^Nu&T)O9oSog?35RX)ZW z*k`9#J!T?EfdK|yw(V=7bl@0HV4%-25 z`!a}@ zBX?+R?D zj$&UsU#Lla|CcotJN~0K^$7mT!$qd!$LNWQ05{6b)*kSs{kWPR#nltgI6CIZKq|KZsE_~!%IW(~*( zV)+ZkX?=mqCs}i{DXYML)E^y%KRPw?O#Z0+%x&n&y4CO>uF3M8f@|x$K4hC-oSUQO;!<~ zv-7=>KHRG_)HCva^u1oa`zJqTAiWpLps?N{)@Wwkv+3{aNMy(UQEPHM#agsi^9Z_* z^=JM27|)zn)-328MlRsTeA#VS3(Y^Tu{OBYbMwz*9S?B+xa5uIuKgj_!UgQfFi*(u z-|k%juM@c8STu%$EHl3$r=Z35z1;6x{i2^M>36>R?IPz+{SIWiihtUqx5rked&oO? z*OYtMTK2t+7xm6Y6Fy)Bq2WRiqgMaNk^zmV2KSzGz>ZPV?2{Fcr zrgNtDk2H4o{nvCLgQs!Ld0m4%#YU6|TtN9rY@8*;VC#ujyLI^2x?a%s$h-p6k2S^^SXOk}tkI-7K z=DpfO*kGbLe4;Z%&e}ui!&~G%Cc(Qg0`ETqc%uS%!mFR78MCrv3-I+$le?Zc+^FP? z%EpEn>-nwVH|ZqoJm&FUP0v=olZUQ9l8Y_}Z|+@}j1bN;-$Cl*|6JK-0snLENs;3m zRo{o8z3^PuNe&l0PLCP zd44PIbLRJ4+_V6mU#=W_*=uSS+6(>rZV|hHJfRS8qSJ%$tlr(>9ifH)%6z+^XUHob z=UP7LT6~T=m-kKAo0S#dNciRZELE!a9Das-mFZC2io8$yZaU!b$Gi@_o%ZXAMI0Gs zb~MXIbI9QU-U+@hU&)&2Atz7r9-k(?`cboTdj6Nj>9-!dto}!{lC`<0U}WbEl`sD6 z_}hO&e-3ev#+rD31>d2cb*Wj|K%K_jX8sW1Hr^ZapR-o%^A9`oh1hk$4IyXd$oQrO_tD3-OPu+uoy77k;g}pH^_zf)K9sk995J~{h>U#etSTbb z+DV8uANxF*&kE)vIklYmgwIznm$~2|7r3jP*oxhZtNq^Cc;W3jCpUK! zW7SxWzJ1fU$H?h<5&l~4&CGy+FB5N{C5v+pluJR9Ls{{z%L86SDA+C|HKUA(I;z2fZihe8{~mlx6Zt$mpx zS+RV2vg2yTy7&s-wIZ%a93!v%gY7%{u3%m{HiU7Co^GgQAC+&aCi1furohcyVs?$g zOnQ;iRAu+>`tFle)8P3@j5XROflfRU&)G-({{BkdeTuZdOSBxgEP2=afptj$>nmJ` zW9WmIeGa^i5h1*D%90)O^);@MT5VJhk6eZ58sF^*^BMu-ZgH7o~@qjR6SN| zjuTUXPYT-Cz_W^Rzl1L={iFA%KgG3ebMfGXF8tFl58THb+eL{`ud@e>eXh{tIO5 z|F`AejsFGyI|a{$^6xMIH}l^QImiFMTmHG1lh2JVaFdRgNXka!JE-L~>59|UI?tdb z7a5*gtaAr)+k5!SA;z>A-@o|ZlAmkBmYIv6m>}L%;hGT@yG%a%ugWdv+`qfy3uUvo zwyoR#2W*%I{0X;?yk_M=^IZ0Cg8tXje}ewqpnsSCUHVV3M@EeAsrO!F3N!}wuW?*X zjBlG`7DUI=sn~+v31ckfcYo(=Ij8Fn*4@JxK6S}Lcj&y_@+|k-YTYwN_hM5}^qg7w z33Qx0a%e91`O;4b#$7>w3ENjf=OT}8#FtAjW^|dS=Nhx(dQU#_#1m2{f6Vx=%NYNC z8Dlrmu@B*U&5TjwE4e#sVWS=6kn@i5rSpvO&!Fq;7o_W(wFm#Z==v6Tc^yB#kEV6l zMV0Q}hwA>){aSZCr~O3>ijh?gdN@BX7#+R_|XZ!8{hP*z(9a&;J8V)6^Y+u;vde#>Ezqxz=_^7KZ z|Ns4-B=DXDko<=DBLPGwgTD|E(ruebs5X$+E`hdccb7q`oe*m`0t)ye0cty#+A@N* z4cq01wL6Kms}(HSZkGhx-6+)xSi5$&O8{*L(jQV#i7n>)eBJlFlXpU}?&tCO{PUg1 zV;=LK`@TQVJ?GqW&pG#8>+H7BVV|?Dg;wH!XiIzf9&8>fhP8#>%57_^>t8zjtk*Ay zZ;s6op7`OHLl*L;)mh=(D>;-ivdE&6#W{l(Cf;CJHiBl+(gmG!^go79BRi8A(%L(d zHznN$`%I_MUVv?2S(Lmr#Ep-W55BTLNFKo`c;?WYN@Uzl&eK*>C;QX=PVQG)=h@D_ zaD8e{_J!7*=P4sTA^f^5-LD7GuY}k`);t%RQ~I#o*h&3y$;k9)8tFN&HmrKQzj(It z%qP#{!**j8d4djOa){B-|LpPc)_8h+lC^8#S@bQD&0eyXaRyJ6GPdk+i?4kbkJwWU zoI7%3fOWfb_0n*>=msw&j|(?q;DuywKW^ON%@??lp0AgjG;X}iUNH(D7++(b9pv-A zIP^pD+C3wuz21r)kv7>swrk8QxLKASVG=y+t~xtaf3GqtWs52$^|!+Qh7wR`rgvx7{R>sRji z0b{H1bTR6q_9C&XtBnmvFiLj1`;;U)EQk8l?sNF`41cF`{{4-&pZO01m*%{E>f5r9 z7~4&3S!Rdmd)ox?Lop&Cvd4*%M`CiYED4_Or<=DVJ;#C}ctMfehD1X&le+m}s_ zbj&Y0Jv+EIt$(Sv8&ixwg%3Hp$^GGpO$Rri)0tmiKSy(#O}+IGMJkR{wtQ@MOF8e$ z>sQRFr;W-<=tGK$mvBH^jL~=2mE725 zt3x`iyZANxL(*B3bL;tEA2jQ8qCSXE4)w5SH~UIGhj^xZf_1!4^WcY3=sbL}-$%QD zn{JmlBL(&U2km~Ga;c`s_nVGzzH~>RO|YpUt#mE($y!$WAbLUlK7kI6v6t?Gk1#)_ z@RP35z#x>|dAH~svp4L+OvrB}zoR>u`;r_IIxCEr(^-u(8d z=z1<8y`DLM5AJi&J4mJqp%aOrBMGzbTXu!j(T=`A^#<*Wu1;lS*Va$YDi;nu_@;q)*m)xRjpoSbX#9|IR(MxW=WvqJO;qP2pw9sxdZ;5f1Hl~?bSK2c-gzjURu zNR!T?fV3~8Kfq_OpLnJH$PY%B0L@srd<*!Bk9oWy_*!R08oy@ z8nv~bXZ&isJTdZ2QQb?hrLf*rLLUyiqCV5QAvCqSHJM>u6C#gn0!jKKJgj^wJ&yg1 zOXK+!|LK3_F}raR(I*wxY$qLU%a@AE}GcS)#iP2KY_u zE}2t&IrG~m;=eVOzUcV|^c|Da&+ADqO+Vj5`UUE*q4^68&3olP!FpK8yL#S*3bPi% zi;1;0aY&x;U<(#pCZ*dvA|Dn0s}7xmaW1uPh?Q{vRGMBdqixY>>OypTN8s<=C$^0G z59L{_4}~Q!HXkTPZ(M^OGKrj3GjY_yIPE60n(MkJTNOqRjy)yDf7M@;Wt)Eef_IZC zTMvwWi@X&A2k`S+rn#v}_k9|D(%hgkuL}V~;wfeqqDQP{jiIes)SvidEHtNbiQO1- zn(Hpkty$ybaBlIu=DPCScu6gCo$}PzhvwW2zQ&M+7iV2`2ezWTpdF}{9&!%*x+-wa zp^efE&O6KQ0DsXsaa&uY4L`YF(epCar)7*wYmoXBqztXlTLT`m^w#rR%s4k3L7&{ulk!S@bwQn}VP0|ChsaR?_$64sZP@egu6_YR&)A*1Ef=C{{sp-7@k!_^Kn1 zH5`Uk4~b1&Txx9ynHv) zuJSFJj1Lxhr327>wq>3a4=i`75t}os|gN|M&2TGiQq= z;|f=`PQafM(_X+=jky?pC$z`w&al1_GtN4h{?&i)tMvE#C6=D4$Dbcr&Xj+9gm+#( zfBR&&ytch`rFnV1U+j7oJjTMVJ@C$JAK7RiO>4F0%JhS0cmJXN(4d`ZK$gMQ;+K!g zq1|KbK|}a0z;|}u%szJsd@ITxCpOvYn@NnlW_;d^Jq;ch0^TuXzRma93njP36$_4- zXYdQjU+vyq;01PYpJc-o$n%;j$uxeyF}j)Vr*}Th^~KRzqcukO_9nbjHg3mu*Miv9 z@O~RP?$?ou=adfLEVT=lC0P$c@I=XE>XQTi6z{G4mTl~~l@}tDz>ik&Ui~+=qLo>> z_Pvo!vKNg>kFSPuu`8jC6o_D@cXizL?*vlS7LT`Up;M4$*gG+ zTm$F|FXpQltH(zZV>E*^g6vZbU208XZd~|SvsSzf~UKT_SNxrk1-6XUT*N76Rt z82h2~m^TkEgb!ix;aqJ9M&Mh8p(ki0j&FzPMCIZE7mtS1%t+%KsDBt;L$`g~@R8}E zf3h*lz8q&=+K`ECHm`k-C!fV8H_M+Jy%IPFujkp=Q)AVgt9f2+#=Oq>61aV3oa(3K z(0bsc{%5ild43munci>L{E4~a;cPx|^w_VD_VD>b&(R)td>%je%e+ftl+TLCuUI45 z7hhK{`J>R@P*TTyYp$hJ0oF6wR7iLCl681&B1&HMeLFs);%Dc5z>SYPkO!WoJbM4v zyo>ty*zj|xL;LOWF~h^)q1~dF7D4r)Jf`=IgmoaR} z@pgUsBAs=??=!48KP!+6+UZM;(R&cH44SV&t_0pQV*%*hF34d$D{znpwAU7GN0`4y zfN!Z?+*>sLkyf3teQ>t5xs7^^uEQ?UIiWuDAH9g9|L8@MymzR(=1l*rLxJMnCCD*q zqdVZ_#K7Nk?VmMIBi-RyuuprOd~;W1KjTxJqW))f=To4)pZi?<=h_wJz^Q+!UGZ7; ze;d=jdMW9g)4qB(_tqQS@Zm-yWm<>+tQ=Cr0*BdKWn3H<@=Wa9KqC z#GTyvi$K#K`#0IUznvX}XV!nD7SNG{Yz5ilQLT1>>8TRGqSMYzW zkpG8QrVAWM1`1kA(b;NWw&+a%PVb%84z=UG{|)#)0~pAkrL=z59KAb2n%29wILpGm zk+WU-iKm`F`~K1X^nTW*!*icAlCRkoa7X^Q+;0$G18?{*#{3a@ljg~>td4!mg>-Wd z!#9)J))0TQ5ICWHoX2es;Y;M_d+ad}SbSS3zX868oIl`G^dR-chyk@c!}ZD1Ij_*8 z-hN_Y`y2SG%wB3GR$XK*WKXs06mooNe@0^*7@d}H+p1NoBojb8#!oNB7)-kO-|AJX z(5K}knYTFUp067IAH_8prB}(lo0lHud<{Mm+&!}DkJkHhcJCO9?Og~q4t`TGh-^l4L^D$l3wI6iG};nVhM zbexBn1NOip`n3I1dVE*%`*zx=Z3|iY4X06Q8a>2TM_={UGI4w($JgnXLjTKrqcXSJXHMC$ zfJcTHUnSOK5{EO z(!|D;&Pg#vem@I-g5Nv)X%oL*vb^AIp%eCz-D2E9rWg!3othiMlk7;q|F+`b08dRsv$QwMB!_yNApUXL;T1!R(~9|!T4}4&M;zx6}4K z8@b1^Calau?`As<-SFTD-x|{NzZsd|Nk4be=Wfn6SO(flKdZg%w4cmH{^l$G z-py|pU*TMmI=A!H-lK-JD!$0G9TscFACSrZJkzD)m84~nNAOhoTj}%_!0)@jGGSSr zti7FI<@+_h8~HxO7a6A$nWb|VU(m~k|K53^0QjHL{xmXGnHA~yF#9~o&yM!O$eB_8 z54Zmgt!L2YdkQ*^@2IVaaSzrca5E*|!d+iK=w=@demTq^GOKvwlRWn&&%+N1dM$6P zGUrHyXW}a%_=@z=$QgCu_yN)5zof@Y*_W&(;}e1f_|YgFGihDp6DOqeOSjVvy~R1p zVslPMzm}byu;H^Ntt*|jJv;GdkjxPtwtC0)TmNN-I3&~6H;%&%|julz0dmXg)PZGX<5IY_BIAn7^ zs)jx&eF<>z?s71Di~C8huo~MKYfT?8l-$Z1QNbJ~@wwPD*Q#hkrfPdM(3WI9NphyJ zhH*T)oaaX~%6B|!CAP1(65Bp%>8?3*_ovRJ#?gWN8NCWRTB9=(yam@@(zKU^&omxJ z7FS+iGgD{h{AYaC2l&r0cnqvd;ShB=>_rV6KJWUQnCF$|?5sI!r8>mFOnRgCb#AOU z!FT}A44hqjCEt_wq*Y&WzXsMzY#()LJgvJMIs|X)s`#$rS^ZB*4uA%T zKOC12iMww2&WCWO*yHbty}>!rcH}(m*AJxl6@Qi=h4yFo^Y46_v{%>*D=t~AsB?~d zB({SWvL&2mu5Lt5Dr63mq66f}B>9=3ov}Y2(z%X`&G-wf=9#mm;bR4Eyw|hxa$dyC zOWcUWL}0o5&)6l`Ta6c=KW~j<%Baq*e=IeA2jtm5k^X!Gn@xkvA3r=KBb@6@ z^>8`RvHtv!Vqe}Yp2FGeYW8~CA2`9it(g*D*M|a4)oFeaQ@UZ@VyY1*w z&dk#{=+4CdnFddL>&sPb&?5hf+PO1S@^pJX`^z|O$5~4q_E#E@!}yx{Kb^kZJg%rk zb*er$j!{AT1j_E=E}%)kN9X8F8SsZOy%buI(_#AfL{^{VgTr&_59_AjwHvuqauo*H znLY54-*dN-()GUAJQsukXo1e#F+T^UvHrWIBtB|ZO6mBGGJa1y)R?clLwUn<)5tXrSiQ)%tb z)!I+G^fX!z)DJ5d=!?Ecz(MUiIpp zGk?9a-m_kJz4q=?SN-|iYb2UrUW^Qa&N=Zv>1XqvaSb2K1;g5$z<-mMz7EfuJ1j5v zk(?#F&H?7yAL(oH8H9tU%MP&CX0-22%foBNxOAKiO-SBoMke0krh)UsVD;?@2kRSo zeU}ezH}jMO?KikMoW~iag13=rj*%X_Ji`6o&hzjEd|ZR=`Rs#Z$iv>A7`b|X=~(Nj z6#JJqdEW;tPlIQhCtLfqw_M6zC{8`t345cgm)bX@+v{^Kf4)_6&avr?1vylGP1VB@sE1`P2heX1Egulal$A3@cp zdL8OSZXSGQzcP>>G<0S5zVEfJNpF27!!j~n68`)oG7>Rb+G^lmk0S3T9}T!NEqr?$ zJea$e4L&Kpcz;?y8coBOc{4sk4)kL7k@Pv0-hVh{RvY_8(YiVNtNGKr;9B&|;A`;m zfU#3~a+@co>7Di<#5;V%JhS%zCXbXEp6riFYgtNDc|X2+^~@TfjKN3Wxq0;`ADEi8 z1RT`wCTx@XT~AC<{gxB+R=+p;Vh8!uWr~iNN+P;hQ|9e^P`|n@d$6@QuP2cym;giX|etYAK(?6@KFkxGniQTMo=NhM% z|BU=Ue0Nq2hliJsbz$@HIrhU=Q`un7_K>JGPnjZQZMQ)7p ztGIvub;v6(SY13J@=iT-&mDNX_+N1aYo{mg?E+6#w;#8(=bC3(u1|fTHD?*?{-jK6 zKRTsd>~T#U7g9%!&MqUXeVaNWJWEex(HH6XHSTE6x%7D+|F+tr@S#p@kumfS|65z+ z_kg8j49^y6>bMp;5WcDPdpOV6{12fQ_G+Mfq6x`Ve9qo*%xyUde-!w`1rb znu!}SoQ5>+lyGNiU|`>A+V$xl^j`Kk$%xp-D*BJ|&A^_A-kEo^_-^!UcIpRlN3q+j z`{3*F&xyb~bu=uh*U;ZMH0O3A@9jbdEM~U@3YLkJmUO*oI4q7eSkg9OAJAiESPi>=#T-l-0*}U&cI_MlWb|SgRi({S#Fa z6Ui*ysf#_0-!b@G{FE`5+L1rTWlzbCw~oWd`}o*#EgzSD$Qs)aWskR%`kzCtO=XN} zSK9OBZJ6{xD`!}GkIC2VW7zYq#eRi+sMsvygkR`qk8%HJ$kgLB9M9j-`aHT?>0ia4 zoHqgsq-PQx2PspQh@bPGNXbI~EP0uAma%Tc_m@2xb!;@g>S6X?9X|rr%J-N*--2|$ zW-FmQ2G@POd?b3BDHva4b*wmoFFAEa@o`_VSGu{8{j5Ab^<~=i`WB>L;#(H?E~{L> z&yMQlzuU`CAA|JoT=`vkclnLA8{IPVa?435|1 zUqj{3Us7L;`XmEH@q1tov-54%eLXX_C!Ncy*^4^JYDTUhW)J>Z{AXSk5#O$(2HIaj ze$JZoHPesZQs4XO$MA9Orrd_fk`wV==Uc}2=g>x!I)5=joquUu-%gK9_2mPng^Wuw zObB@Oc>D?(LwlSkW6?YLRcj1CJV$$H?sj#t_zfg_&@HFXgC@o6rev0ea>BpvI zqAyfhR=I&6F$z=2m(n}M!Q)`nxI}gc-p1bl=z3y#9Yr^=NqbT1BTYOon>abgc&0pe zixnqA*ShcOu~`LI+pCo?cz=fZE!}VTEeB_hKB~B2cH;2U^o=^cO`79iy1RAviT1T%y`iQr{J?GVr0XmbVhe$Ib-EM#$Lgf zI3}A@#5J@kB7J+j=gT8~|LQ$YkMwnT&$EcP=siy*E}QpU9_f41d;U35I^eTiZ^73lxp(07%+jr8Gd>%Hc3xD9P^d_(P%&Ct4Rl%o9s zc@4T(`d^~`o00D}LHoax1R!z&5RWr!F1_aHAd-K^BA|_rFSQ( zTd*ih!{RfHU2R>?*oX5P7hm1AANXti*y-oTpJETA_14Yj@hjZPyyW9pF?a z`#82-wZS&Q?2AUcvwd(pX?qg&M$>pm*+sWgzk_}<_IEy?>)~>Qf8XiC!K|0{tf$y% z2iMGrH66j^T-lVDX+A%${SkVTbMae0pVqh?))2utxccVPG3u6{VPHKTR`)gMsQaw( zdi{O)TI?e4-QRDa%lu#Ucl%}UyT4yLN8P5s1NVlyb^yhYR~+6l?8x86w?KAD!Kcu` zr_kVG0rJVytj$Suf1du*g;_!GHejz9MZ)jwG@O=`?-abZlXd?`)H#yQ+v1~ktOdD= zN#NA^UFfIGy6Ex`v)0TVTx;C*Fi?*El{I*fKd7GJ^I#eMFzcr3FT75A^fwe+4}NFt zj9Lp!TAJ25>|kWxW&Gw_XUn%Gs{0FLb2JUO(q*?P)he<1lYzhG-CJow4xqIBC2 z>Ksiz%cDNq^QQqWN2m9NJystH?n*POoc9Hj9k4Ew{ zbYY9H`u;um813A)wIqIpl`A?}+!^YXuCobS7ctGl-N>e|e-uAg`t09FB|BZeF0CEO z%Ut`+o#-4Q?EAau<8$DBsBgu=(leze4zuS;Q3v-n;FDMocI`Dq?WxhoUhCa^A$}xz zlzpn&eM)?WnC61Vl&^gDc8 z63er##0umOcvYRwH1!D%_(LY~gHXKRF>mxgGI=!dRgi;p#xPdPd*~yke6;r({t4#2 zWFlfbj{#?6j}Q7a#~ye6nnRCT6{c=vPcLSIgZ<=~8+Y3D(|IBC5W3@?0s42??m2)h zvhi}@sxe>}=3Jirfbuv~_qIBUtMVay0A9Bm|9bt_62~5&6DzXjaIe>bz=y2-NyNek za3=VC#&i_;%cjC1LgP1(#Fi>u^>KV6$I_PUIMXI7)?c>auVddVHMU^ssl_KNX+wN> zUaxy558JGXJ0m$Gc;e6;^B=!j_4ftx5fe9TQ?KUsvMa64%d2O$B(cxPUx9hc*Ze*a z*wmWDe?w!-28U|w;(f%4Xs=J^`iav#ipqGNnJV|4fx_(ux=`?s!?PQ(hozA(~plzcanN9Wp) z7jJEat{RX1Vry$Wz}a&Iz2NCoD_44?LwiHL^4b4W zMrK$#re82l-E{@sEfOEX*Vp)z>JFF~`UF2;5Ac<0n1_RVw_;>P zCj+?@IBhF>xK(}mJopeKeu?Ix9(W9%^Rf0FCM|&O#=@rgm1)onW&41I%C7}KlHer$ ztUxJ~P+cGATx)2w@lmLV?TA!x7&pQC0RPBu|MTzrAGsO5q0U9Bc4Hg(3&(a(HV&tXFmOWB$Q!Qyp2vQ6^I;g zx@NWTDjZ{S8fxMfUWPcoETn_wB zys5OyYK-X2F85DlV`F~+zj}=w-k}&4wTlyWV*_xE;bT{GZ)l4{3@GQ`to-1;H94_t z&Ry9-eyxf)d_LUfoWHo-GWi{R?*{V+-2CIx`Nwe%DHzO|pXJoX@|2dH1AHo)a@;ds z4s)h{X&!C{2W;kO3UefSybn2KBs@`^f9+Y!9_0Ss1)sb1)?3|m{cL@w?k#H@73tvq z!SFW>PG+?i!FM<(J~IRiI3Ln*SU7;ZBYfa21iZLh_b#;*ohV(Ubm^A$PCUh79V?)Z zN$M2;LwC5SoArkO127i8M@(MYVIE@0EE|jszQEw7Z0^Ufm2+-opYT$&*$nQz9=0}r z9iFc}z~;T7mVV(iXN2ER*;gnlTy@vE{PsE@o*FwSdspFS_jN9QVq5p&XHk0{|GRzX zce;J&cZ8dp=${`yDU+&Ji)r7rK)I_KfQ58x}YfU{3_*Co)Jc&&8QbHJG#d}^YMZ4WTeJ`_EU zVy`UV-h=6!Us>}!G*n7Fb@a`PQuvIenCt!{rE|Eur2S(0B0jIST$!q{eE~9t$Gb1# zy=(-p;V*WSn7jUb-$XVMZyd@O&(_y?a&4ILa+i(H^MsqMoL=s?YTrQGUiyWsTOq$3 zzb@c7V`_&V(3jOsy4w~!7alLgZgiP#)#Wh8_-f)y&zsf~gZE^w{Xk;g>iW}{!&`K| z^7E7*3vOdO0=GG%i(WMKW#rTgOJ_KgISegUua2Fb$C(@8H%m0$a}F9Gm8S6ldELbE za`&dj)`Og(HFn_#t$fEWOv_ORTZ@pP)>HSP$@t;V-da-5{Ctah<)_IX|Ed}CYd+y0p~}O+YTD}vl1CK zMSN;xtGuz)fsV-d2wnq^iPL7vI{gE1pt#h^G<@z(;H31}pZk9x7?{1li1!-#{dorE z|6}kY?+z%*`zhAhm$B)+4nB+b_<5#ee%Tka#(FaUYTz$>;osBOl3{TCoSodC|&)mPz4sN$EIn#7-A^87s!Ogz=kJ-e-NI{PuC0|i4 zF`d6?Hxf%@Q8JS=#j8v1Q2+K65W@t%xMzahD0rtBH+OPzHpc33eTyXP5Py)p%6Hk{ zTtl8co9)JC{5bY}!)}Z~pI#Z|S28>kTBaX3t1nff23WEOwyra0i%r0m-gWd+bpNWi7kxTu$%aggP zGkS08_I%<#PW!>XFs{FYUwiWENIp?R)m6s|BnRvNtUGptbOeoB=#M#ab=d zO*X(b;QL8*gz&Zw;pVmUVfxQD)le=@qm%0Fi` z)`MGzfX5e0BJXJI!bx|oi`q{hhj?SGk(`9w8sXQ-&+PNuczT0(=0evyXL8=Li}{`P zR{u_w70kW|?f(Y-I~H|B%;p2`T~tNwI+vuqf!ezadsQd6p|u3QFl*MSelrHyJmIzY zH@LdZ(iHIqxToV>eg3hp&x??yYxusG%zdXh|5OmhN37W5b2i?%Jl8#s>)zum`x^n- zch2S;XRVLJZwq+_XI6hIjRTC^$jevxa9|`k#2JDEMeSGd|6Fp2;(BZTBkB2H!u)r$ z4rQ#(WxW(XDQ@@nId7qxV2x|Ewr#!XYWh^eoF~zNev$)SG6bmDb*yY5dEZcJj${2I249>#2BcJlYL_jwlFiH|a{ zn5lG&^a9~pnnpbQfu{1tk?yb8;6b|XRn#3#-&LahxW-pHJ%A0#T_;AObobQi`{Qypp0{Id-sC zuyiKvE0L_Fm`3v3`+T027*Czx-A-ij4(p!CrVsMJJ}*+CI|O3L@6-A+avOMVqRz-S ztJX{*&X>w9=IqB_)&#}xTg*A}-pLQNekCwrpF{btBDZ}7+5Bf&C3*dJpm8nxrg68F z%t&R73vb2dpCNgG7<@;V_ZaQg*wbp;Y-_d7t0HHw{t5JyV{NG$W9QUvJyY5^EwHU_ z+}heT)$p6gGprkWGpxR8*0wtN8usRJ7gx~Q&$^x{U)``9eRe|zaL|~##m6+K84t8- zefU29O~MQJ?q&DC)`4}*%h32VgL0wvNzE>j`1Gx=JR$sYx^feXnYV?^m(%>kx=E}H z^}sD;IXOl~I&)qlzWcM1j8!t6_Q9L!hwLtnb$^{dA8Y&~$J&xdS~mKvT<4QT%z>+G z)LO0ewwAKO#iaZunz!W1her8r_Lt&^b9t|^-o>wYt>3rK@F$<$MfMubgm%crD>(w5 ze+8s(t0V|_Aijvw5QO1@)Y>p%-!(1S7a0X z=}o=v#yJ0p<|XD@Q~p4 zUOE}s(eC8?e#gIe)Yr}Uy|L5AfuqPh8oR}LmrmwOa~Uhz)S3?+Y8>pDi-mi?;@9O* z?%LD?Z&^k>4#^iRug*wd0cswPx+&a??Li< z`Szys(Z(+Im92R=Z)fa>JFtLR=U@lDnW zwZ(ly4;@?Kg(Mt~NcF>M|pXWE6 zYpeTY2khZ2lDB@HJZpY;n>9>wz4*l&&stBs@vO}^@WdN`Wt}l?TS^-q!2aqcfo zp`WdR#%ZUcfW5S1eX*`G7-{_a-tv|iPsK~X$8a%x%JgA-jkzDNuxnGvE_Cur(>w}J zv!S6ij7js@$$4erdFJ>}ztaO<$65ctmuupj)vtNPZNH4YV=O(N^~`TMbK1Rb@##<@ z{w3rO9d>Oenp4f?O6Ky+-QDNZ4Y>0-l3v%HQ}wmj?Q0!zE8dtxe~W~3oRwWozhn1Z zw<)zIc3ORzIjLw%ock*cer2K82fyM+BIx!eUL8Mzz7Je(<@Y4=EIg|*%KND8#@~qW zD;`zGZ)lS{R>_u0#=-g06Xo!?nCAXr_uV$&fS+&P!$sJFSijc}v{iET>+k{5aqvvmsUooT9pV(Y*5q{#ly8QHniI#ge^EmW*>)+{ZD*nkfuDhlSjCr`p;UweCJC9_kuKzM3=2N4Xk$7Q)kn%nWsfB zO@Z+z<|1p9QKzYEg!2#H-tK;Qpw39??zR+b>iNj_iiu)DC*t4z$aJC&*k79e4W(=K zrKoGNxo7WXo(u7hjNY^hp9GiZ-0R>QEuBVotqa2sWBZ5q2;DSuH zS-&IEw%S))4s8W#!zwG^!u+`O4y_ON&#(V*@l^e4ru|O(p*o7Gqlh|IQa&I5&m_Di z`ghN4kspZqsX36ms6JZgr93#EOO76~U*bFIex!1`@El0b6;X;7BT?`5`CR*{XZtrXVXg z_t;wuY_LJI=9pMt`Gs3$BY4tz(6wO;CTaWilk4PX01UOBe)JC`*srNW`8(70>xYOD z5+}{Ej686_%qzGx{2sVc%kI4y8fEUy`m6svkGMSftaf9@-DJY6QDHHvZpXn{5ML^JY@ z#*rd!O%Gq`?PP!9w^jP_C~a0yx5~tk`Dkx-6ZV`Lq-7cWHu1>v(XVQM>9XVr}q8MkIr!U=BGmQGn=&0)Fu7O@Og6eIR)W^z+%`u zZS>~p+o$>;d6lo`X)1M{Yn~RR`z>35V18S=-|uIh&ZAy$p1i(#^X2uiJw0FCxBb7E zuaVYu_e_`eN7{d%M$X*M_&gao#Itn3chM(g|31l@cY!~|n?2Cq6lm+mueF%{hsOEI z*S}n+b>&U;g#EQv#pA3`vZ+SNKM8!7{*pD~z;f|?c*?0yRCE8bVxAPVYs}K09?*J- z9ZY94x8di1>eJP2#OK=QoSC~x=S5TGm7Fwz{M(R^-a&@n!x=k#*A|Ix{B=sMS02gv zUY-)ta_*0uUM3$5&dwpL&kSRWI1bM*w{!ND+xaasXh%K}U944;Y0K5O7PW7{Cq(+m zA262xrcRwDRU1*-_)q>f1qw|+*eB)bOv*vhfJ-<;o_9FY{1$14$kV60=%MRB1&a2) z&7N^D@R?_9(t%-izrem?`cjiIai2}Ujsj!(rW6C$DEw@7!!@TRr<*zh6I&EZ_igrV z(Cq={r0?lref(!%AJxCd`R~&BkKa?a$)rB;uTFCZ{xL<^2sl&X`GXFTpTP&ut|VUF zW^8<6{S zKE-->lyNDB-(lYA?8;BE;dLtR33v7he&TsnMnTKJlWvh7`%a`y`++6sZG@-I%&qof z&D1-MdZX}FjW-Ic&~>2UX|^xY}ev;gJT!k<41jrO?v zwgZ1ndG+Udem5~b@nUC8K}&QpXC22Ew5Sc;n{gfQW7vo%uzu;+(H#QBl1a;v2R>}- z$q(;FrX6Wss(tfv2EY503+=WyKf^ue=;ivRFgF?8JM)Zmb2%o)Z4WfBvptK7ts9JO z20V0r#Q7NXQ?gUUBu8IjVy4a(ElUpgFJM<{L37ZDBfI=D_YQdZW65uwF{5uUBvyeJ z3n0H;e!t()n0<98({|z3vEgh-XShkzV-_wwCSS*zo>cPybTSMk?TgVd|OL z105Y@J(gSuk0?l9V$J~XD`u`m1O5G3Z9PX7_h3|8e+=BFd=Ly_63c&{@{`EPs_xbvo)_5=&}uKRrB$7T@&J(W={eH3zE zLHl;#!1G3DJMm$Fr|c(2=6kz;R+~9{g5MEv5&dfJZG5`Q#um3=%X}lFt0EzKc$e4) zrjBpQ55_Goc$+qWb==54&2?G_)HVW&I}fzK)@%P9WmlVdnn-NMoWy)^yN@{-s^c8= z39=o_vdX-D4SR+3nP#njvXyFXdzM;>Yc!YGZqAxZ`kd4p(dUuoGEZ|!o`Ja>p3gs* z!`_*>oGU+%eCVv8U4HRV+7fTjURW}&&Qkl&wr9UP(z*Ge@oQxVm;Wa^$uK<9$iFqz zD_ObeKD*76TjR)K4*FZk$uY^N$emukfA}MQN!W-SvRM*4m)Jgw(a_7UrYi7ztFlMAcB_xIZ0ySjequaVyeVl9*^UN>u>^k|Cz zYcD}phhD8{$z_R7(#tsivYoYeGBEqMN!I3{yZY4v{!3n{YrELmJO-F4y%7A_bYf_I z&MldV=UD67s3%1|*>+|kx2o&A)2r5gw-nonWV;}+mOd`TyQ@goIe{+H-=Z$nd7tXk zdPyIp$GC^_(yv+mH2VCc&V}i0P6R&HfNUFsud7UXdTgxceco7gPVlSL(;SF&WC8=h zZus43r5ndJTF7#x+r~9U;q|32k8A9t?wItBFQ9Au&vEeKc&qxaH0!x)e5KRRKOx4g z_uN%&&JYjkkp|+og|+_|p5=m5&rwHZZcay2wl%$rc0#~6#DD7E=sY1h;Vz+Z*7U=} z-?h1PrhY*0L&{3mZ(%DHkI{OUdNhrr?A!e~Dj$Epj>_UQ*k4!gB0d_u8T$mdpt%n- z_uarfM`s4Bt<%fEou)jyubw>k$(#<6uc^rD_*h{?F{KtU>WEYmd*Beu;AIUes8e`^aqw^GN z8*moAbC!K3Wv&d<|9O04eE<5wG1p&pVZrssp2d!M;RV+_q~(syyxz&h57*9Yoc^UJ zub&=XGH!Z!(zxl@TK_r)+;rfRy`PCxaK^FX_-BYe%i62*AIpjmvW0n|+z!ptylHb9 z(2q6QV;bryS9^J6PA|IeTj;NRYhHVHOd_Lc@0Sp3zW%m3!l&O8pJ4Fa`RR9`H~xv|QRg^(T7Gs*W(D(`kk9yh z=9zOCvyf4AH`)QgDM))c_v4Fx3%Ww+DCb^l!EM#kT3TqYf%aNx@3-71IEFGgS%Gup zJKy9RV`thu;Lak*EYmhVw*&&pJPY9Do5~?Vr+q6`IMD{0JR^XJPS3hqK%42O5?zzsy73b@a!&mOICh z&qX7fSchwktlpeg$vc%r7Ec7Og>EQ+TbcIG?tjVLG4nrPXE)GQ7KAs_uG-o<%BAI< z>`lEryYkBh_DR;QGTJcoO`WqDuLV!^{zI1={rvD!@`mUu_D^%pHvd}AaL}(HGONkU znJJycvf+2BN4;~q8Er|*Xm79cyyooaCf1+1?2(W~cOhr!{3>=4-RHM4c-S)gsWG&r zah$n3(gvIp!`Ev6Jb3#_(Xf2^HsJf?@6R@19G{QO!rHOvx=3F!@Z3h98u1O(|0HWv zlVnry$~(_|>S@{sKBk^w=b2w;t$La=o368%ll=Cvz)kh7r@oW4p|p|onq#FO#oyTD zNA5XBmoFZK2gdf^Rsn2Hn?c&7?I#OZLpQMRQ$K=T=P8Y|;yH=(L>J_H~ZY%+cP*0Pal3W*XYzY7TX00XB79o(T@%HCAXB<-?h7vIMqGG zi^blQyS2z_lug2c=k9?PdhpYzkv{)1?uGcUwNSh^Lfk>|WWCp3iM?)z)`uqSkWC*z zr<%<#>x9+_Y$XMzym&Qzm7ks2hhTs6)*G!o4^Bpg!gqgVx-Qv+s@UHjJ0D(rK6gZM zE+88ooXszIS-~BN4KdoD25*{&|A=^$c#n96`0@krp!wX{_W(R-9&1w&{v3oqYaZZf zxy!0UEv08N8t0L(oS575$ft2f;m@a$;gv7Sn8cGDp1T>7{HQcHPHt^SY_#!l&67Wh ze;l7-9IW>n%e(RulsoVMWq&IE3@+S1`MXy>f?D{$s`yqz@X;va2Nrat~YveZo)X!ArioMQb>4OMHIy=U07v zsB!s9cySGFd?3+9z+;eA!_qZB|0{RD z)<*n#ggOP%DCBsQCEj3M@= zZS}nq)82){oRy=_|=}`H1N8V5_OC z6Fy!;I}UXzW-t7D(MiS;dL`X9^(4Lbxx_K$T@CL{+vFErXExWY*@hp6XjuJS!t*5S z!o0(2c;t{Cv~p@WJJt9AbshWi){;^yyFtFlHPB=aeOPAtmfcXp*dAbPIaYRUQ+jO2 z8Qc7+`%2iDo3Tw{zS`6IS<4n>o3Ug!Y@@I9sXv>ti)pI{xCsW*BmD;X``7tjYOMzk zfC({N@WawPbh)qzoAG8hB&nm_tAp_>#?gGxD1J>F)=b$+^e8AJAMIGjMu8U`-e`7bUg{F=G zgC$F^GP*i~a?7SNQ*GPZuS)2h%C?YRMtY5CVSj}6$?Vw%>eYM@PjnY>dPsQl!>43N zDd_Fuo?_WxI+=?J@*QA}LJ^XX-P+Xk0=iRp^E%a6rnU+U&XuuFBfp+llDGAj@?UPg zKGgCZ>hzbX9#*Cq{}{D%vGG5a-1Xl6W$LqHMXA4Eqwa0M(bT_sQ17U*o3ypa5v&WM z2hrzGnd5)J4|%|@;q&K6NiM6Geuy((%uAVgr>nzyGSGN+A|}|by*e>>yp@P#m^jmq zq{!Pt`@+F(z)&%TYuBlX!Dh`$At^WxVste>Ix(c`pA`d*=TqhyDNf;D7Z2owiv^2J$Z$ z_Wzc_|E~N#{2Z1m3-`~p_M7@>Uv`_fGi=Ue1Pok_jxr+?K9CiDCu0<`G)K3W!OI6= zy?wen<}Suu2CUI3UC5XXtl=5QW<>gagYDrRSaulAMNS$% z+Ea=bC_RsTm4U_2byj4syjNDZLjO7ci+!qLE_Rh_>?>8s(Sv@pf{#0X>6hIxoAClK z;SYM|@L4!yr*UW!IK;S`8P{)+nFevFfPTAoi2UjT7l#JqDRURV(3o2;{KTHID?+Z;9**RI-vbObqgOi2CB^5Y_ylSa}x!o&oBn+KG9?9 z9=iV+9xq678j)|z{wN>bxZ+44!5;P{*;hNo5AlVI!n>1eglCnnG_5N=-2^|fk!d{q z9!XZye9-reiWA}a;rQvRM!LFJ-8$WTqhvohtIw*(`zT*&;ix{ft2#%jfB5*c25Eg$ zOxPZ9T6XUw`%KE3_>iAV&#T}NM^@Q|oG-ieVdOIXM&LQ(zn$=ar->~a!_O~8*%);s z`L*ba@cNVKdX`s{XR>VB1;$pV_u@P61*_qBD|~GV*u7EOlzk*loBp;wkZx-;ZE0No zx$xU52J7{7%!7AZd>NZ2b-v-itn{|d3KCP%|)q0{Al1( zI>9pcaF>2Cedqfk_Y7X?4eo!&eqbD9?`GbduB#H(B;tkn|D%_bURLidd%C&j#5|K% z^XsRlql%FQ4>-Kk)pNvvYl^sD{{bHwPCokp{gL+@dR>jnP z4c}+xVyn4=`>HsHP{v%aFK+DQ8C`DUHfVzVa$`jQInP)HZhh}}u55=8yo<5tNq74v z?2qTNsmH;gdCWn1d39efH;8C}ZVnyRVmsf+)=ri12s^p<5A=(1?J8!i^mI=D*xUce zuUS7Q0#|&I>-NxJ@rEXR|C;Gv>{HePcwS=+T!xQ@9|Be=zuV8Jt~F{6jaa$nx{4DTc55DbDWTIL4)e9aeVBt(gCYpM%B#v+F7Hj}N%(wc` zi}eW>UEsz%U{OrWy&oWtUB`2ryIh>{=oR#y@->V*czLz_W&0e)Zs0K8g+uZ@3p-S> zx05l@e{=7R?n_X=LaZrX-~aq3vJrjr_LanuD^~qB^_SD8P5Tyd%pb6|e*S!ye+$Nf zh0yptM&6i`XGSw7cPv%xH;T+ytPbrtytaQu+k)Rme>13mO8V)k+@&YQ&t&go z_?m}@%;QF@eAI}1@mzI~E}R-7Pl)f#PLIJaXPiyDOgP%3JhTIzVaz(&VHc0AFTqEv{NbmKj z%b_mr=v|nU{7bszs03>~YsqxcO+EM$O4p_JRA&dl@4OK8Hk1E0`X`$9^D>K%J1%ek z;@&-`eGBqSJ3(_<7a|=Cn3rzgn_pFJ^n1?gk96GmVyNY5bZt$6qTVv_SbY+XJ-}}_ z@{51`jP0!PAEwXG4IBS*cziGT&;krL0LvtNDS2sTTlWHd(DR1w!CqAHu7WeJ#;P=sk!od2$UatG|-l!EyZ(m!0n^U zc_yCW5!yJsp1j;^^NeJ(xzfdO=JnC)@-ps7_QwMs3P)E~(wY;ibMZhrNDl|r!qcV z`|)kaFdqVz+wO?;MUYWGpl9SaJ0Kyc$v0hfyJjw z?V{dh<}nJbsQjhmi>>>+({Y}o$R$dfOxp5w?)i}wz+>A(k-oo$-o=y7J&$MmkE`!( z_Yq?X-$pZUU1h`-!iE5jB^gUn@sHsf(&uYDowV&h^Ymrb&si7IyYwBRU5(HG?#1-G zOE}{rm^`2U-_385@f#jMy>a;heF=Wmm$AP~?;a5E3he(kJ%81G{-MfJrbEAsa|Z3H zuf@RanH`zKU{`EnIz@q{e zt|R%#3m?3Fg9Trso;RGeR}RF$wf;UU@q;J&ACWxDRu=hWaBpDoG5F-$e~X@$`|2_F z9B?6aN0y=AIQ@K^@rxc-ru*f$RllG9eBm$kXCSVg%cq1RD_OII<59|bIR3YKPUHAZ zdQRi`r}fNyMS$%ho|Eqb$LEu8IF4US+Hf52hEI%y<1Sqe?n6f*r)eJ|8=Bg8(l)&) zu-t?V4ZYj0X6S7PJf$0+W+N9hR|gX8k=?kWyzAl}I`y4`wc7OahL5Fu`V-&z743Tt zLNnX=7Fdx!?G?19tBzc7Am4IrZNF!~NX%L4E@*$0F~zx4Fa;f(z7nHwD|Wo7ks-Mc zhxdyAiY#(qct3`ZTlljHnNjr=K+AE~`KWx1(!L-m;1bP42I1?a9aS3Vmvn~7OZy+C zm5|2QOM6vm7m>!-OM6*qlS$+2rTt84mypKSOM6~v*O127OZ$n^K0q2@FRfE)A0~~j zm-a)YRg=coOM6CX*OA88OM6Ob%&lysUfOp_8xFGpx*IZYz|G}>FB!N6fE!=0o>tX8 zin{rFX%8zcn>4;&+D4^aK^k8#twm{9lg8Ie`x0sA?w4l=a_LWayfYh{FKdMGN&D$- z;M6ws1uLjWd*~vYqq@x0CMqX>ZzVCmycO7& zfp<6h-Zk%t^nH^5&EJ8R^xGckTfpz8byi6%e`Yz#D@E@*6 zxj8MP$Un1&I6ugyJA>Sr7(_-5^D7(bc3^Kg*s1vrAjI8j{iC49dI zKe$5tm(VRYmLrEswwi>T>DfhG8-{%63&UZ~l7ctk6_k_RdJf~+a9`7=P8A%gh=U zM(0;LlVuSHXQHuX5Np!dyfubQ+L9h#;}AbaM%rC!PiPQdjiQfvnts$Qb$z+!X}{|8 zJ%+!A7t?pyVDAOyf10K^dLKcbITw9qHS;51!G9k(3+VFS=|k>I^wxLwQjN!GGaugX z=>uBLUevv(Otw;$x7JQiw9=OFt3Pe)bosQmj~ZN8({>>A!WrRjGgmRr5tRNMpXMKI zExC$uU#qb6UFb^S1J6;9XkWUw*dG_qK`zNt z>`%$E=kp5>GG|2JLatW5DzEzy6?abML)h21a+kUCAEv#rrc7qz5}wahCQe;mnTY6v zvWC9ihdoTTqoWy_;JVfRH0!ePy1Ubj)ATyJ@or+X4nG4h2Do>!&$*tyZ3izG;|C61 zRMd15gOUAmWoM|jn|^q8EyVY#hOcyAr+jT3qz=vHx!PeI2TuH2K0Nv18I)~?{}&S1 zU?pwc$s91}?GEW7&IiPR!6agY37!slx`|y^%^6Jfjku|c4pHWaeR}pf_M)#r+uYY_ z&OX9pW_A;kKyxYnU1+0II&)}_;*xHo9QIKYkJRA6Pu%-9=?oN;_Vq}SJ(gfQh5 zcWfE`7>xHAG%@v-@Ln>=c;W$_Yi_hIdbVBJE+psITxiXl#xFL6nL&IWVtlzfZUOzB zH7yffgsw}vABX2sOZzCNU3LR+4PGa?!zy)2luspY*)?}#VyGu z9e6EB@vL%7kfB^@aKMl_NT7;{O+;DI?(f2p7(J7Nf)@rd@LxX%x{?UcG?vlRFjS_Gj~ira|~`w zT@~9M3t9`vo4AB$2b_FjeCJJ>_So77!Gr&}_{Sy5vKLBTx18|5^V@_@2zp)=8|#({ zsm#0r)NKgFZXY)oSail#G}#}DDyxXUirXFebJtI9r@?ek8IM8 zqt|rp$qT`^c5pdZSw^7HA( z_4c^1>OM-DYt!?w%=t+x_jdPwf!{won6>2(^rMab99Iok6{-XWXi#rre8FGg^LTpVxBUswunw0A8)99`)}S z{y8503HO8p%=?0WI@y2C80bfJ^ny$BeGM??W%OHcEL|1eoy~jsUlBj2LH<-h=;#_sDWc>PqX(8P>*sZj%r3W&3qT(TPMa zI*k5UXMT?0|EZYjuc5co`RLSNMLK5D4!*#7rT4nJO5O3K^;!8JBM*Ly6$SRK|Cl6J zO8*qABOm+@0N<_H!-G$4Dv^(*kqy?P_mWN%UGrSZ#otkkWNX*f7bAa+GWV$|+_gTc z^BUzxA8q`;1C1pKtz#x0H*4Sq@=69M7cP+QxG|*%;_Yo9U2;nN-NAT!LE`ACPIyCO zA@#CPTT}^+S^5Rnkw+F);uCY2|M2sTfx{&U+0JE4KlB=VzIE0tU{LpN25Y9?ca44VU=`^QwR)gd?`qn&U9JqM0u#9QMpMjooLot6!xCyO2~ znPWRSi>YIL=Vvc+&^MeSZe}pB>LTuStSwz#ddJ%t8%oOiqX#SDg~vW)btFeUT#{Ot zU0!-{eB+cN{EUdpbbQO`j`{TM#N3M#;rp!eHvGg5jV!zWscoD5LhEJV@;JW7C|`c#?YpnY?Y!w2{sULq z<7(TAuCKj{6;}Vk&pRIH3%d$sLeW4^poGr!N`VIV9c}Cw@F8*NZ zH+W0jYS}DKeVX-|a>t=#jk)s1&~A8sF6TTNv{%#nlZ-3=_tp*lf6C~}2MoRm=kuq=ON49h zg|qo-SWEUcxF~rB{&K0o8yDY1SM#AMt)=Q?{}4R<+yEZVUmedE-er4uM_nG?{Wgtv z!mI8tHP>B1UFvgr?&gyBH6D1PH=Yg9faC?y-v5<;-~0ahouuCr?FqGWnz9xKn(jMj zcq2TZ4!JeOZ`8w?ITCK+7weUoE#$VNj zTDrrG?XKAOcI|(o;=RD7Q-)(h+k1Acw=~XiZlXq;;wq_U?!8^m^6$a+=-tVr+p<bZbeN|NEwIF4+T4Z^-*WdJ&tev+xb?dL*SF!Pw+HxHtP6W6^B6Ljbuc{ zr=GpG!)6VVzq)BB$8G1IX=l5y9WF#MZTy=0UtaZv-7oT9^}kp(v*pEASJL-uJ6>eo ze~&#v^>o7D+_fa9Vae(KMfg;{bkp1!i5tP`iuJ__>=YfE*LC-eFsHQfHO@f!=k#vo z^naK~Z%%LFSu`Wu2?3`X;Iu1HTwC~?{#kF0=FIUYBIU`yvL=YOg7hnNZ^lArBf2h#SK7TbQ@U?rGoB78x1W+a?X- z`ebB-k#IfBwhFwydH9arqu_K#ajnyo+#>of9EtwH8|2=O_e1}y4E;|SO8>ylqyN+R z)rtNCZ>Q-WIFCgC9$$S7d^9kD{ulb`AO1cv{pSzR|AstZf~+w>|CBNGPg*{>qcz|w z)Gs;1YfrQSUoU8aM&r=`N$6ksh}U80|A_P`qJQd&d-OjHSKc5d$3h>j{PGAoK;*GI z)3k3v`zOZ^&ac+Bk!gQ$EH4<^&mW|H${5<`cPQ;|GPGY0?Q2XLPc5{6KlsD^L-+X& zhgru(_kw}w{v@<3SgYq577N3yKjf?Pp0Yq`ZV2_8M@DJ@aW!a=V{FoZRr2qYre2sG=KZtD_U+}b0z(D zX?`3wA;UX3Yvj@V+7W18^LVqL-n}`b?oGrb@Y8z}b9jV#^XR?a%pp7jToJt|)AatZ zNADS&?}4Y_uQx%oG@Rz0(M~P+Wb{LRnr~0j{5JS2wiUC7(40y38kyz?aRPa20Kavv zM>5ZF{MH%Zli<7dc!u`rr-$p}UvIKb-#|C z){@gC8y#lt>zQjOt|m@%X$ajMV+~37hpz8!aOmgg-Cp*MeQ+%t-u~d)j!xp7YrAP5 z-i|JyKr)12fc!U$wiaj~?sQtYYFq7UpBrQk*pIz{_&Ob_TJAa7K5xyj;z&o?HzRAR zpz&0|$q9o0IyWRAJnG+%4y@OizM-{zzBrt7U3pJ-WQ}5!_uvooCUG<;D!yn&mC^G`-%+c$*Vr-8og{934F8`i zBWM1SePOsBTY8(X5rad%`0_EaEz4paS-LD#5`3O-{VV;Tv$tOO=}&(;U`NQ252a^E zaLUy z@?2nGR!|wrxnl`?wbYqC^xbaA;Q6z9e-VCQ8JX?n1y)}9l8IN>$I9kRx@XkFrPvIY zVh<$NGx}Mp;xP8SX7sH*Zx(%e=c)DIZy%~;92&RhXCyw%So%WPL2A&Yh1~n;9x|{2 z9|rKioWpW`jg3EQK4*rYncJ{$uA~m;>!qjp?gEb$!&m1|(V;AgS636`edXyIU^sz( z$sf=7hshri{-^gA?{)98dx3B$On2ZeRbC9XIM9O zQLcwM@cM-=4PUFXV@xot5&dBXe8K2joW2eGpE%0&HHuB<`?P6?T%Wdo`Zl)j`z*zK z=@5+1LqYzkFfzz{-vPlxZ7P@O#2d)Ou{hKsmlTJHx-NA5S zXCt%cLYv^pNAL?;&DY2&esXJuuFNXU7$^sn2qn4_wFm$sQ(p zW=%VwyI7pax&tc%PK#`N5$15kUSe8v-saO)+v`3>|D{h-KGKveg7MiB-2R;LA zWjoRs2G4;E-t#axo}url2KB%n^)27<< z>`47X?5KV}HW&A0C*GPJXJRHio=ekYV1%o&g0f$1`Fa522-Z;l9{G$)&%(ukN=kftRJY3!g?87s% z2Jsj_vW6YV{PmoZRNWfiZ18v%c-+0_uG0nJUEAb>Eg|qYmSJ5JDl~fcr?fY|(B%P* zhk?Ni=ui8HZP1N;^Q%aU61!NuEyAzXTi~5%#`3y*eowqXZHNxk2DtZ7sL(PoTq3@? zn$9ykPiG$U46YHE%ANl~T=VmP(eGjUF(qB@a$jBIX=TIccn5ubFFKy5Inelt0aC{J z9qJg9uDi%r_qPrintsSn({8^;wzJ5_@~KBxfX?#NU8Nu2|GoAtVcIIX9lk$0)n zTc_{mnY@YrkGwYltFl`Ahu7X4WHXpsK}9mzfWsC=F%>bV4XAAp6&2H_!>k~R$e?JN zIGjSMj;N*CAZqm@W?AV8&S}7Gz@buXuu&W;)CMdoFqQxBe%8G*eZTK}zw7!x zTn}si*1d*%-D|qn@T@fje*Q|IxC7_W4#P|5Qa+0Bqp&tqq8}~iYW@zrpS~Secm=S( zr??n|dX4$BV>m~p(Kn;Z6OC~52mBcge)#V86T0WA#6Lh>tVVh9H~3w^-q85tdOx%~ zz85ZGkBS9yl=q6@{4TxxrfOr&hAqeS-kKX)r#nY;WEc-`g&(T#YjKZ+C)TGiSAHxu zqb__xh9%q!=iFdDYoD*7%VCPA4&PMQ8LW->1&L$B@q&+Ib>Ok=_oi2uz&;%Br>H|d zS0j!;I;RW1d-a2!(%H@f)=xR!&gBV)&NANZU7qmjTt#6kS+(O^~|AIX_jhF`_yFi4|R$SJmrXf^^B;D`APA6W7?vC>+ z)AfFbY0PmLVI>&6FX&-XIaq?cAB-2}p#1ZP-)kd*Q7go8m8{@4y`wT?s2c zd7y{4;)lWw{Glnn0kRnnKaTAPbl!bv&-S4$Lz6GhtgJ;lzaMW3>`AA*3_p>e({FEw zy1M1-W}Ui;`zU;aiL$RNKacv2Jqm}H<9j}8r{-jw3#WTo&tHLDP#%j=25*9V=E44Z ztQm)Z)}Co-gZ9$8axmPih3StqzqKW6x5R)S%;^uWzTC8r!X^F1D0IdCDAU5Da4LQ8 zPCT9mUpCgm7vQ%Qzp3sxrhCwKv@@&Z87O^)b$ET0*u4hrJ7iqaA9A3(Sb}lhVPALa z`r?3te(o7_!f|$NuzN;uc;D_g8w6|7*dwBywTLt$Z}6=t+wi$E3k* z>NIyCC6>&X;wSGVI&&I)t*W~kFnPR(v`3e5Z?jo zgX!Cv2JHL$&EOXN19)43KAZNTkHOjK{=z5I$3=AXDc$I|5a~(RTN}GzPoQlT_8349 zY2FKnyBjf%rEvu95u`C3>D@hXb`P!dZ^HcHIj8-ArKUgM3m8G=h4uo1_i5;})LuZ+ zd4ELzNNL!Ba=R1z3N!R_Kj1XfMem!$Ufk`5Z5K>J zj~vgd#yPgynG87NHkj_7eimmxVJ~{{qgam~u8#|A@Sr;;jX1U?=&cD-4o^euXGlw@3MY7d^eA8#nd;UO$e(Ib%FNQ)gsIW z)Ctm+elxhBZn&ZiXotOmZozJ-+jS`S-(im7Ta+uD-dDepFeb_!FU%()er?zLzURS)wrYpK9k! z4k3T!zWPVnuPe(Z`6=al5n_?XBD6zDqjq))rNwy+r{vl4-f;W4rF;X{A=5Fh;Jd=5 zgvwqX=3<1gLKCFYFuii_ND z%Kk`>H=WN8UCU>O3SD8P=aum^#=`k#ol0noM{@);SD@wzpm&9R0QVO99{(bZ;~;a) zh0)wK?PJpHBcwgr)ZhzW(%4i~{tDB=vW44Dg?ek{LWBc(X!9wK-=|RhQuT?>XHjz< zXC10 zeEIkdabs|1;WFbB^zDA#l7a@B3wN*`>k491yDk7G|Z&0Qy; z9i#n1NjSSRrVqYd<1$6%#JG4)gU9~C#W6@q>>?;^TYgRZvZovfTKIzb_mqV|3 zA9MFHXz!kf--jT(weVMtvmz%nTFFm+Ohiyg%)txcwD%=ugs6fY;IQYD;DfV-(Z7#0sd&5 z_=~65xC`-TBK}zLg7JS%#^SjR9+WrG>B0AuXy{&JFb4(wZ7BI2i?xe{AlIEX>^Y3W z`btC!&i%%C33p19PE*shGwC#4%j2;Z0rmf-Qta8K__6Lcr8CN2vA#Kd(~!P7kd2le z6UG<_gTkk967Wqgg%v-T&Q0lB9(6{PM68l=jpDGYI^mqZw+0vYMYy&Okvq$gmKdbP z26>cYKF;4&dDK1TP^56|#Ne(BsjY`@~JbcB)Vg>QjC z%hXvLy|Aw+^Rw>@iggWP8!%6_9Azc2y@;o9t+H z;m;antyDV1vnVHHqx2HBmuS z#_0|L@8Agdr}hbF9S*VOS}Tk6#tFWyu;pi3I`FexQxA zd5Xo(VS?wW5!lm6VN;mUw}wzSMKIm5pNGQw1o=euS8*}Q(gc*@t;AoH1 z(f$>o{YxJY6Zgp^qhHtPTKnf+z7{y$q5Vte_OEEW7*5|D()|SBVIRe-#raLu@FE&p z0@L|f^xZev3$)Fee6GrNnfmZroS{SVk^MI2ZS7|dHTNB|e`|Z3wFL4UXu+E0Ryk%N zc~E~m#2Q#k{TlY)HagC~lKw<(Ht8GG&(V21e&8(``-~ep;7kpa>5D5@*8G+-s2B@! zN9jBOa+8d5n1Q-XbG&2v%KO;5Vth{R8m)V{qiv+`xp$xpo`WBZO)9(L?9KGwWnDXC zK);Fc%_-2ReWQ9|COKg(+t*<9Z0JDstDAQJDa99sIWk)B)?~(IsuuOP7GnU+1qD-@ zF-9wn{13|0pY^{Mmg8G6Y3%pkl_S18u*>mSn{u=+&h=;^$HjCG;64w?5oc^V%Mo?@ za5tP0=qSs;`~0W1B};$TZkZ%c`i_BQ8FYiP{OPH`uA6^V9zQ`ClaV*sPI9ENs#YG6 zhpm;zh0tNB-KH{0Z9ko-LFIBg`t?SXnSCgi4ThUbe#dXETw=Vl6>czB6~6{&FK%Rc z(s&Ku6>0L6vh=-hKx-Gf9yUOR_CBM0#niJwHrn*e>4~QCCd>ZTY)PJ0bYA(yARF<&@h;??)H}zdJ>3a#qH}PDg_nuG(eS>%o z!g+hwm2K3QT8u?cp`Rf6QQ0oO!Tf0b+@a?6qP^f<&AE`ixD{v3>(GA655)JrcYyTDHA%z)F{Z_2$RBv?mx7(u<1^6k{$NF z(>fmQwXC7`XzFJ;8yS7b8noj(@f`-$KWf(>)7tb5ZT+!2P_94PaE1HOZRbpMR z(@yN^s9Vy~O&#Xr9MhzDA)P{w1JM>?u7$#Z-A3r9n0xUFz6W8VzY4AbFE^sM`yr2- z4hZd=5<1%i=R{)6ZYb~ESq$ld@nv76GXpZFu`$V+(w_lYD;bcy9pzwx97z6;gHMuy zFJy2IGC2=9&{*JHKiy9E`!X7$dl{b?LVe9OLq{WYEA07LV1xW0z2Y{E)^@6eC5+M; ziTFvzv|fkx{f+W265L^RO^)p?=tEH$htOWx)qasnbkME7h#Xo>c|vs-b0||!LQiQz zz5N~If$bPq{|f!HvA4AV_caCo4PlN6GG~%LbQ$gPbWhRoSCn&2M?&2!MVXooH~8ju zn*UIFZ_V@$q9g4${q?x$+k~)pV4a4>Q5Mq8xUIj0dQhwC1oDOUw#j^>b6~n4-?|3x z!kDl>?CbhO?DW9>w2g>|{0O(rs92x2VKFEN^hK_dC zutUbTu=9sqC)m+i8QK9&cWmm{tV^ERKz*C!i~6-v%vU+4r8Wq48+z2Ke%M=fCGj}s zZH88XH-8`S1m0A=qq0&)wUz7@;0;%`Z(cq zoUw#&+is;h#BmPprds4t8P2bf>1i2mYjII|5MtKT#%n*-xLab^BabeeLLT`avHomD z8w91_r?!2=KW!Lyi-7PF9q!)f4W4^pZN>%dK@fiHo0@8pP(D|}t$zc)Z-akl_v7Gx zHQYv#`)ktuLb$hHZF;rZ+G?xhsj;sv%w>;$AZWy{`E$2&!EU@b~79H zgFeajKYCkEk3n!h1Z{v3-=kr#jJ6(VTvA(e)J@!j^R~-7BFtj9!l|LRW%m$>i|QDS zxr|4$j@P#n_qgLuV&mR<$Ahuwc8V^!$HC?J4hrKBOGxZ1(K_9+TRp%l!Z0@E9XH}z z9OKmvQ@r7hWMbUkr=%lzFq!7|2#tyJABr@X7H3=`8a6}TW7LI~e-qNt@s8>K)3ze|*iDZ{w%q**8Wj0^+DanuUaGBL{pt=Ig&bpt*~<{j(8~@Hc~; z!~Mv`Lw_26IcxOLM;BOz?JOHj{t6C%F-EIr+W92S^@aA3&=}7f{ig*o#6BIZ|D-$V zamK=R`a=}vY*BVG=hpIEH~apnL0;2n?kgSb(D$f+^zGuem^)T$`_%tqZW;YQ&T-J@ z`F(`OdK{#u?>@(c2f_lU%Z^WbO+f~1QGxJAxRVtn^{_14JJ}(>eOvT4b zn6IF9AL64xJ0sJ(d@S=BOYMhf2qS-_JJLBeiD(lCe2zU}xbvF)?4z~?{g5^njk^BJ zwC1~^n&m9dH!GrYHGOO)wL@6X7~XWI<=JMSA8qYyGaAosK$}5p3?wsud>gh6b^aXU zb6jhn{i9`&H?=pv;9Roj4`FWC-11zqUv9TnK1cT2xny*A$o1!v+2!xB|BvEz)U}4v zU97zJO!<+H@}l}wl#BJWCP3-5&|LQ^S_iOq;#!^2$!d!StpPSA-=Gsgcm$kTlu@zNE%s5rae zx8kL<1CKuVEts#4_}yNUX|$^^WNKVsfefo47wnmVOkLy|6gqbtmx8`X=W#2oUE=%( zmYs(t*92E%jyq&c@_`QKfwOwBN7w6+5i(m1o~eCkLOLnU?zCUIcEi*@I%7=T(wnAr z&~@olhx}}9|1yoOZ?Zxrh?BlEAs%ir%e&uc|1#}qyeW~s>GaJpV!VUzw*AhFuAPuR z+yRAi41xQCmkXjxr{%CKLKw>Kd(pYmR@m8K_k-~1MD}k2lY6?WTQiq>Ov74LC**&y zQP&|@;7kUUZv&8ylgM|9hvGe~>(L^PBf48UQ5Y(Y_mM}f#ZkRXZ;#^;&XW-xJ+wHC zF20b7aeL>DqF?l}MP=1x?m&iOx7bMC=6e|FcouQiIi8D+@fKUp;%$Z zCkYpl4k76zE2Fg;FDGo4xF5nx(EP8{+@nmCd+5B%k?%D&olz?nN{%;y$M)dSi+S`H zH|JQWjAY!m-o3G4z0p)y;~|z}3{$ivx})_K;*H{>d(`RoR{TaDw%`l-Y{$r-nv>)n z{*jO6RF5fcS?{jD-?pxgJiEdAIE}OQ$7nAJ<^iu-aDD^6_vv(cx!8$yGTLj1KEZ(X z^LE&JguBJjrqlXSZ{z{V7~g(up!9;rL+3Cjx>xvytr6BR%vIFnF6rLM1b2Id33j%y z>6#to5!ngY#f3ep+4*bxp7-K@v^8R8Ske6v$2;qmt^fhUgHVuAua10h8L{eDv3}zhxeNPeU7N!ktV_7?;*Z>37#h8SuL7uJ2bkY6Z$u zI@OI4;%eP0(s@M5S+wP6FfW0%=-^X9H1DJ<=>?tr@;Nz zw9RV)DqZUABGpfkoV2^WAP?fVGvrD-3$;r$e?#B3@5KHo+RsdOmVhYI&4w-Kea#kK zB=i;$W`*0+2m|_6xf#Dt;y3#H3$@UFNX}mXTWEbj7on}?oAI5lhSi)6_#k(W!d*Ia zq_QRH4RfF$sc#4Tp%;CGdLid;e1cDYB-5zoBQ(9$=^QQprDA6t&d~A4`Q9}CH+qRi zf9R2zJ82YgqGZWt2R+?kK4AlNy~Oil8?hfiyC2#HeO5$-muURDM%edXr6Nqoz9t!_ zPLxe0eK7I7i>C9*v3JY)0$Hx)9*!c!A%bm{3Pr|sD!f1^b+kcRDDvuOr zA!MNV!Ce_G;+J)<^>bqBeoG7reMrY`PPfDUr%VIRp_5}hwBy(ltSd)Zt)#h$%V@tF z4BB~s*fZXa#ul3WUetRZQIX@{UR0h&J4<7YOE~kL>@2RBQ`3oCEv`f}YP7sgjx>L! z35_pYd49&NapaJ(KwVimNk`A6oqyB=1%V)Y>1~E9wKi zcFZRfpF7Ic8qzBu)4q+1)?=?J+xfEHLI*n&+vPjh8QCt+!A`JUuC#;faL!_<m1ly zc>lMfeP7a9Q3grBG-KST^c2zsJ)w_z=saU^Z~5`Qcz-Yi1mhgMwKiS*wMEcLG5@CN z#I@L~uKZ~_FzJvw%-ONdO6?oj3y1I>(_y1Il#Y`tEiQ>++V91aDyy-+I0JcK1e!bU z2C_i@M(m%UvP^3@`%%V&I`Umx;GcAXgK+PIm+p~ChmJ>nji+#SX8`5|bk;_?t05G9 zkI4`HGQOdxMfoAU?#&T~kIAinL_6#`Z65PAx6A0q_}f}?=e-U0{UDFYA9%SaFqSQI z5&LYQi51*;2FU#jq$A{EO<)N4qxwK@bh_cPe_hf1y_>*3b&ZxZyaaRRvQKfOV?Sjg z?n_O-mQbCJ@ba#is`p@yL*oJaUkW`UE>}SHkylrGXu9Ce24_XEoQneK0CA+!CE z8P0`mOJ>Fdto0~u+Dq!8oifCCf*F}|8d8+>3y-6--ht$+{k|J)mzqhRBr*T zxbHH|(W5@m+;r^KgrbM=okQ$ZQ<0K+49-|1zkQL`tM|Rv;w*3~H~79`kB3f_*hX0^ zqw!s(JOsW8)1C!+M&M1EySE{)@Y0?Vss~gT-39hC_CkA4Ci*OEgr%6i zS)qE@a-Xf#F-X56-I(O8ecz7tSvrUH1C$MtJM_eTMc}Il>q-5P=WCGvmXg@Y2CTu@ zux6s^?Im87w6>p)HE)$a@|!5{a?HnVT7-TL-ze;*d0C9X_9{1v@H-vi3XX9C1ETxt7oG(;TG__NCd^I#u}?s{BxU+V*}S znRevA*8UhB+MhDCKf}Ot5oiwW&lC&VpK6Q^1CcMZuYtxQB=d5({gu~>)BoFTPxE}E z%%|4wS$tKiAAcZEE+fBQK%6u_rt;>c6D65J*1n7Ut(AXV(C@7GE*#ag+;wLm@;)Od zqHj|Q)>pB=@o|*nD7vZc)0AAWv)tkHcZulhK*J4Z1VGq0Z2^+<0~Q@wzCKJ@j)$*t4TU*r%|^mD(lC zDA9?|E0|Y%)D6J z;Dy|goc6YZTu?Wms|-m;d8k8~tR8KxbT2QOdgv`nu}z2eh}ThV(4YKuT{EdqwROY& z9ry++3BP^r&Tq(r+%$f)zDV+;g?|I`Dr|#C&A*aYlxU=T47E6@oEh}Z<;+dW>({Fo zYeQyQo?Ndd48@-QO;i_GB0e4F&1m1;yRbWrwn(d^u!}d)+K*p(73@9nI`nr~>u9MX zV2-QaQAZ#>j&+1Rq$5zDO5stysQjS1Rf_n3o94Qc^bF@P&LU3etmRklJ6QGy=8IbD zc<?Wd{%u0}O=Q19^ZO?JI{Wh&34N<7xYz?>x}%@Md|X|zR3&a34NsJ7;FCvXMkznZ34=dD80UKQt6WOU{VL6GJ!U19rht$&d$5M6ZANY zg@&xaH>iZaL>Wm$A0)sJqVc#JrMe`j@}2=`uK&l~z9TIbZ4(|XwH zPQ-ub^6lXFAoeCydr94d`bT%yBAg=#nL$^ng^c7r9Uu79zQrrwx=-E3KYaHUEx?H5URwA!_Deb}-v7{7sl+TWO;N@3w-;qf)Ho)}8T+vPV z?t#vpNy(iD7KLao$Np&Vh4=~aIocJ`TH($Jy8m_vGRMatbcdG<{e>u;1A72%71q}p z6U^e`8Ti`{`6pxU*lccUpJ;xweKN+>W`W;=ev7a`VI2;0xEf1Pe36CXq5=9fnJM`G zJiuJo7%6lY4?vF^<0a4gT@Eu5VVlh-V|(HauogB3Bo;O%nzzQL;*Avgi-A~kN!Evk zfp;IXi*6L`={INBhfC6+9tM+Bsrf( z{JnA4&x3^t4QQ(x|EtiyLCOCuC; zcas|zAD!J@2h`Cf9qK635d{7RdRf;8e7|(PL3B7Ai8I#CtF5~YNKfEH5xXO;`ip&$ z_lc{;Zu9EM-N_H>b_YI0bw*c$yg}X2T|~PbzuM>i-Q=dwWx3|&8K2R+OI;*i11~r2 zT?HBa1M#dzJc#2kx!VDGt@OHo_p@5O`is$shh$hPo*r4~vJCX6MsD|d5^k36UX5}< zaTyU8rIErWS#0+jt)-39sAS++zJrB0Tz5dc+|FoSpWtr&`4rOp9QMl?(&sejS6ds# z3ghANC&bIEMep-g5p@}75X6rYhC|N@m-2VLsy>gzn}YYC3BSz;x1`{Wyi#yHp*HHm zw>!3eUiHz|En2+w%hszf&vnop#{BO3cYDIEXjh(s_gjp;a2Dxay^l3K2yX(`Ev=@g zB|17&!)X0E0`Zc+0EE*Ae6{O!aLae_cNYA8%lz$!e0m{0Zs}x;y1wVl9@fjXXb0XCuH|r3@*T_ad&qJ% z{Wc%jlHe74VKvMY$TYEb>|s&ZEKiaJ#c{ra=`h9PPT%+SIs}=Tc14D6{X9jum#@ZK zOX)y;41|0PkZn)AkuJC|wJ2gqqIv9+M93sjxE!W@%0?d48%&2y9#)h?(_!U)Ip)&H z?SAlU!hNPT_~~E2>^Rj66XN~J;B}aIvGm+uW(xdp%TG8b=XqV|Fqd9Owpao_+(I%~ zjs0zDkORqJ4rD=ccvb|j4c5DspZ~o{$_wtv4bdM!IU{<4*W?Q%qwhrM+Vk*p=(nbQ z6c4reE(n|4NxDhWf0bb&&SR8zv_0CG-!b2A^tax{S`dkS%~(4vx4MX7+qf^?-XHa) zYRttW=x<8%tg*VX1zCZjcMr&T$g@_Q?GL&5n++eA=AE?Dpz{m`?U>k{I7zJb>Y&Ho z$4G&CLv`m$)bUc3U8_rBG37%5+?{e;ad*&KL}b9n*pDPY~y-!_NvsG5L)`I)ko6 zZE#Ev%FvJ$@J0JI0#HY_J`gfLWzZdd)&kg}h1W`Sz9| z&{B{`DafP5E0M>sLGgllU7^;dVGd23*By&ISUnbZl?ay$ILj{HRc8*1giLivuhr|V zkrZy=GXYr?Cc@iib*UQlY@xVF_N$OKMZ^5mUid4a_^k+24oj(YglB+Tu_4lhdANf4$li5o* zN!Pt}k{+)CFX=q}@qLjF`3v_Z_l)FlBi22H&i|7EeqGGBbnD#F(ksxvyRVNaB-rUu z5w&4j7i{&i2E5tc8X($J9k*khxdo2U$#H?CJJKBdIrwRIFT5yR8_Fy8ffeH`hgd6K z*`Bmy%2f;m2n#|zkAWTZEGy{BF0CWtn2j+P5WB&=NO8uz4hl(lzC$W zomFLoK4m;?T~HJojWYnKy>x!HI085hoDC59N7G)C_pVD#@f3F?en;SUTk#@4)~WH= z`^Z0o(3R78Xnzl>qdw#-;-k#Ml%Ad(o?0%mPxFiOp{;b^ zaC-lT`AK!FANqRKt7DF3AANN^=Bq9QV0_?cpG0Fl*wb1()x|l`4`G&&zB%z5{DwZ@ z$ona@9kA2-ax>~JewReKw5)$^)v>nnNYzy`F+W0cV4A)M{p1+wuT+mvzB5;#d?#Z) zO3_ibwC`e69p3cS)&s@g?R3$ZU%UzS5{zP|3h_3OM`CegQ_ z29xz?w+Hr2N<{i2Fwa5yQxN*|UJseG#=;yAGo^cpAJ!*LqJ{ z@LI1)!K00nQW};YH$r!I?R8*FztO&vVsyRMX5tM-zpaPvKHH;SPF3hO31IU4r-_UWgCkf#OejEojo5k;X~Uv^J;nIFzY# zKd-|7209LOtCA|0afh1()@@BZ)*wdOcv1^jOm%5j(Lz2-Hl-*NZOLXw_wTaa#=8Lg zuP50+7K7fn71s|Ht|xOx&jVX};ro{zB4}+2#-mCejxyLWG9c^ik%0)~nMs458*OZKAYY&UurC9O(oE7s?@S7-BVm|45caB+2=}p1B7wXZ@p1#(diKelaOU3xh zDaw-Voqr?-$d28`=_;{Ar^ znN}n;`~>}u=9ge!3H!=#XpU)rLd7}Q)BGUp7sGxr!p%H|Z`~pDsmQ;uGnj93xtM^y z*kyv&*E^*t;E$&G-k7_27qSfy?T{DkP^Mr)&a|HAC{F?1KL9VuU2m)!6@*uG56Ft> z9+ai`0>&#aR&eopdt@)XWWTa|udIM)0<*|oJY&vkhq6~yWW8L9{gjBKS;NR@ z3-X!Vw|8<+;jVS&0kU4@6#@xs?3i)`9^Mis*9w!URK zszSZ0a`aa}>^gr`fBHGcHyY>OH~Xs>Rr(hGn%9zb<)u;9;bWecyivVs!a6gxE7Yzz zyBm4~?w-f}*3>@ySymcvTWh|ywfc=c_!ez)a2}E zw^$qZlKqfo`#$JjFsENbXKvEE?K7THJGWsSU%>qYtlO5sJx<)k{*uC}FQE;$T-#do z62381ruo{*BI4C??N!OT`TKtG+7)rGlDw87kE$_GNm%hl=|Gs((mYx1-VS&0KqlJy zHpVc@-q;OxIM-}H^cZD#&;@P-q}x*HC(6FBgMBU9b!ETC(CmH%+|NfE70rk>W5t&@ z`i^waX#M#9_hdPsw2xsK*wgOtZ9b)4)90|xYwu;l=f&~a#x7o z?)+Paw9|LQGhVYxY-W!Ow7HOGw z1MW)UF8T)CQJut`+mJ-KA9W+{rHmSVkNvgW%dkI<g`fp0>*Z@`_( z%UR$3HJ_!ZTld_EJEZ;dqkkQqDi>u)lM&ylwAHqY{}-OkFJL5xXUQwl{1@Yo*gVgs zcu~^**bHSelFd8V%x3d`HrKQH44bd9S;J->n+^SL{l1K1qNW(=Emu{noL zRrJhDM4=8K4vWHMs>gaj*8(ehW%~A!^zcVocb9&Xw4eEsG_97_-Kp@DnKeH(Z9rN< zn!<%k^%@`bt6K7BdSBY_VY7kV74??xve}HgU%IPU)6zaUDb<`bckcX@q`ZvzSwdtl z`xAFLIZ2DnS!s*RbJMbB=cN;G1#N`e(1yKAPhZh89m*bii?oOOQf2r5rMYK|klmBGKD*9B$m`Bo{Y{n}50>-MYEoCfQ4HOP1 zyxPfqkrVwPC%n!HZ)U9W=NTt@8DquI^G@_VPI#}A`wA!gniH;KY+!l3?L@D3!bhC& zQN~I>A34!aIMKg!qSrdn&pOfTobdNfxZcVCB`4hAgs(VZaaEQ_#lMRaHacN1#!6l$ zC+y?o-q#6tcfw}Is=kCeVJl-*K5dLud6~&rO^Rka(F+-4f3x;%X55`|1>;*7pKzi# zIMIEtNqH&%;f#aWeX55=`y_0`alZ?;a{xVkb@5vbBaP0|o za&Kj<_)BH1+M{g7O8$k6Z{qOQF;?l>!&s&NDC1u2zJYO1#%3YgYely)#y%76DPXL^ z-^|#|^eV=IjO!R%82jpE_`!^AjBjR~&RDh21&li~eH~-femb|8rB3cE7_0D4FjnQO zp0SdTNiX?Rou+g6&h49X`{Z1|o!bNF_Q|>YhjRR#IlT!9$w|3ssY-kzAt5C-EoCmu z?4;b3q`B-2I5#g9SXm?_|K~s z)8?k-r3oy8iP58l7&%gi0Q?>WivYZL-z#0t&rZup0{?_U;Eu{uKNQlC5WMsoFV)eI z(ZOOMOe)U^N{mDpa4tr}><`n@SBU#z{tfR^yuIORDSiZl-VbjG-hp^~;T;W6BN0Gv z{9cOpZ+H_I&dN(lo}1POe(6bEnCyf}!hJ+y-s0@E`LmQWkw~iITyt(7^u-kO?EIvh zRE?HMLd%-n>~10DKcoWte9ADUB+BfQ;(`Zf7m2B9vy$@X=9yFGXDP4dl)3YB^K&#_ z6RB(@<+Sae!*}$aktbzNEHuY&58aV$r%U$R3WdVS$jQrxBvLbSv*#wIq|Hmq$}`VP z%2w%2OE>=oD^l*w?wVy~Ze}U5nf|9NC8o^JUfh<@DBby~dFGt7y!@OjEt?X@*+k;h z`1ojz3(#f0+Dk=C+C^v&jMPjp%WKD6*RN~K>e)qjC zTqMp*%1WA@mikvhb#~XPP$|AXGhm?pSn0JX&$OZMpi~%23n3K zX*uRu^K;CqTqt@{URps$obcJy2h38MBv%9+?<|y6Q$?-43Z&Ba#(sR--wBRzt~qbyhUV#`2P&q+yN?BH2*VUC(VFPmCu^W@|?=t0b}G~~56OCm8T zYkt<^dGqsg&B(vB+-y{74qD?^NJWPJYeNL>pFy*2C-9MnaB*>^KVknfxRUOq^d;PU zXVeP=rgL45SnM|nVQ@1DgK%|&3pa{Fk3ZOJzX6TyL_2!5Kcc%C-RRZ+G8_rX|CF&GIp@YI>$;zKQ7eA58 zDy937{Vl+1Y)JSqC;Tw5OcGIrc*F_+!wIi*!uZ&@h5z+Vc!Lw(t8eKZ8+Tgx_mXTN z$K>PArZPxA>bEk^ti|MqMqY-bYFU<<$R6#YD;`{vK&8T$4RrE z-7C|#zqGGlv!30BPLcMC*8&c=+EdzFyrgOEC(U(i+HRLvg&!9su?k0-if+eFe3vq| zyJKA8#6QWR3tq}2dMR(oq%w>}dQzIzPQ+AApzK4rLs{IErx`O!708?WeIh115!kCtiwsE?x>}9$quv zY`op^=HMk>3-J=KE7)8Klk8XFrEpf`CI6^rT1|hN@iv&m=QDUIp8vv2?q0=9e0_kI z=%3;x`kfFU>A=1a3du)3t(8wjYpr~?=3Yl0wlvo#!qf7z#6(PV#^vME-0sEiGG2#z14z{Dop{T8b7f{6ME(O#X7TAFzf0lysN~BZKk{ zVWg2`gadA2pFcZ4mpfX7nU^*%IgRp>9)v%i5aMvOrughQVj}82UV0`@9GIFqaIqFH z;Z!{R%@gsM>BnL`WG!sA+vd%?janZ0Ohc=W>0>bwZ55gEz{KPX{7=PSe>0Zh%K47&Y?|5Z$)<(P{%nS_IfTuTY}(k2V>6M>xoj4&xth%) zHXmYh9h)23+|1^aY?iY544Y+aKF{VJHY?byV)F=_AF-*z|B~@pHos@HflZ^kl#7W? zGn@U{3}rK%O&gnuY-(*LM4ipJfXzZSi`ZPp=4Lia*(_tTg3W3+Pq10frqM(4>C0v) zo8fF)*|f14$L35n6WL5>Gn>rr?!e)(+0)6iFji)N#>xzJqFbHlaZdC^Cwlax@ggH@mY5VV zNo1j)f@ztCe|@={!OoJ4e+m;ldeT@0$&V!?D^ya6)T+2U;P=77$ zTSqo%fPeP48XB1MUrYBZpu4F*ow%_-#}HPE=06&n-pHTwZ~nhN{|i^FT($b|4?I}3 zW^M694?pscbtUUJY<%>wO`9Lz^2C!*J-xMb+x8vLJp0^#mF+Cw_5AJ^UflE2%dhO+ z_s>@=D*yHG*Is|)&8q!x9eDelci*c%c;?u2+*RV4Y_4PTD4S+qNiSsc zD4V8k(msyOb!;AG)8r@ViELJ}X}X#1*(_jl9h+rrR zev_W7W_m)PA=CIM4Q~eAq=7b948ni;iof(f3;v|OPvJNFy_FgGtN6+PT!(mXMJPky z_rK$h;`~qiQCyTxicdX%<$o^9OA2C{3%-}YB)QQ@RHa>psY3f}e%cO?#!R^go1{ZI z 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 diff --git a/week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node b/week-5/solution/frontend/node_modules/bcrypt/prebuilds/win32-x64/bcrypt.node deleted file mode 100644 index f7ef025e230112617c80cd625f94a892c2402bce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195584 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 SL0M#=Gzw52F!g`M_WuDtu)iJv diff --git a/week-5/solution/frontend/node_modules/bcrypt/promises.js b/week-5/solution/frontend/node_modules/bcrypt/promises.js deleted file mode 100644 index 6685cc254..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/promises.js +++ /dev/null @@ -1,45 +0,0 @@ -let Promise = global.Promise; - -/// encapsulate a method with a node-style callback in a Promise -/// @param {object} 'this' of the encapsulated function -/// @param {function} function to be encapsulated -/// @param {Array-like} args to be passed to the called function -/// @return {Promise} a Promise encapsulating the function -function promise(fn, context, args) { - if (!Array.isArray(args)) { - args = Array.prototype.slice.call(args); - } - - if (typeof fn !== 'function') { - return Promise.reject(new Error('fn must be a function')); - } - - return new Promise((resolve, reject) => { - args.push((err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - - fn.apply(context, args); - }); -} - -/// @param {err} the error to be thrown -function reject(err) { - return Promise.reject(err); -} - -/// changes the promise implementation that bcrypt uses -/// @param {Promise} the implementation to use -function use(promise) { - Promise = promise; -} - -module.exports = { - promise, - reject, - use -} diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc deleted file mode 100644 index bd8c57355..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt.cc +++ /dev/null @@ -1,315 +0,0 @@ -/* $OpenBSD: bcrypt.c,v 1.31 2014/03/22 23:02:03 tedu Exp $ */ - -/* - * Copyright (c) 1997 Niels Provos - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* This password hashing algorithm was designed by David Mazieres - * and works as follows: - * - * 1. state := InitState () - * 2. state := ExpandKey (state, salt, password) - * 3. REPEAT rounds: - * state := ExpandKey (state, 0, password) - * state := ExpandKey (state, 0, salt) - * 4. ctext := "OrpheanBeholderScryDoubt" - * 5. REPEAT 64: - * ctext := Encrypt_ECB (state, ctext); - * 6. RETURN Concatenate (salt, ctext); - * - */ - -#include -#include -#include -#include - -#include "node_blf.h" - -#ifdef _WIN32 -#define snprintf _snprintf -#endif - -//#if !defined(__APPLE__) && !defined(__MACH__) -//#include "bsd/stdlib.h" -//#endif - -/* This implementation is adaptable to current computing power. - * You can have up to 2^31 rounds which should be enough for some - * time to come. - */ - -static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t); -static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *); - -const static char* error = ":"; - -const static u_int8_t Base64Code[] = -"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -const static u_int8_t index_64[128] = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 0, 1, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63, 255, 255, - 255, 255, 255, 255, 255, 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, - 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255 -}; -#define CHAR64(c) ( (c) > 127 ? 255 : index_64[(c)]) - -static void -decode_base64(u_int8_t *buffer, u_int16_t len, u_int8_t *data) -{ - u_int8_t *bp = buffer; - u_int8_t *p = data; - u_int8_t c1, c2, c3, c4; - while (bp < buffer + len) { - c1 = CHAR64(*p); - c2 = CHAR64(*(p + 1)); - - /* Invalid data */ - if (c1 == 255 || c2 == 255) - break; - - *bp++ = (c1 << 2) | ((c2 & 0x30) >> 4); - if (bp >= buffer + len) - break; - - c3 = CHAR64(*(p + 2)); - if (c3 == 255) - break; - - *bp++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2); - if (bp >= buffer + len) - break; - - c4 = CHAR64(*(p + 3)); - if (c4 == 255) - break; - *bp++ = ((c3 & 0x03) << 6) | c4; - - p += 4; - } -} - -void -encode_salt(char *salt, u_int8_t *csalt, char minor, u_int16_t clen, u_int8_t logr) -{ - salt[0] = '$'; - salt[1] = BCRYPT_VERSION; - salt[2] = minor; - salt[3] = '$'; - - // Max rounds are 31 - snprintf(salt + 4, 4, "%2.2u$", logr & 0x001F); - - encode_base64((u_int8_t *) salt + 7, csalt, clen); -} - - -/* Generates a salt for this version of crypt. - Since versions may change. Keeping this here - seems sensible. - from: http://mail-index.netbsd.org/tech-crypto/2002/05/24/msg000204.html -*/ -void -bcrypt_gensalt(char minor, u_int8_t log_rounds, u_int8_t *seed, char *gsalt) -{ - if (log_rounds < 4) - log_rounds = 4; - else if (log_rounds > 31) - log_rounds = 31; - - encode_salt(gsalt, seed, minor, BCRYPT_MAXSALT, log_rounds); -} - -/* We handle $Vers$log2(NumRounds)$salt+passwd$ - i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */ - -void -bcrypt(const char *key, size_t key_len, const char *salt, char *encrypted) -{ - blf_ctx state; - u_int32_t rounds, i, k; - u_int16_t j; - u_int8_t salt_len, logr, minor; - u_int8_t ciphertext[4 * BCRYPT_BLOCKS+1] = "OrpheanBeholderScryDoubt"; - u_int8_t csalt[BCRYPT_MAXSALT]; - u_int32_t cdata[BCRYPT_BLOCKS]; - int n; - - /* Discard "$" identifier */ - salt++; - - if (*salt > BCRYPT_VERSION) { - /* How do I handle errors ? Return ':' */ - strcpy(encrypted, error); - return; - } - - /* Check for minor versions */ - if (salt[1] != '$') { - switch (salt[1]) { - case 'a': /* 'ab' should not yield the same as 'abab' */ - case 'b': /* cap input length at 72 bytes */ - minor = salt[1]; - salt++; - break; - default: - strcpy(encrypted, error); - return; - } - } else - minor = 0; - - /* Discard version + "$" identifier */ - salt += 2; - - if (salt[2] != '$') { - /* Out of sync with passwd entry */ - strcpy(encrypted, error); - return; - } - - /* Computer power doesn't increase linear, 2^x should be fine */ - n = atoi(salt); - if (n > 31 || n < 0) { - strcpy(encrypted, error); - return; - } - logr = (u_int8_t)n; - if ((rounds = (u_int32_t) 1 << logr) < BCRYPT_MINROUNDS) { - strcpy(encrypted, error); - return; - } - - /* Discard num rounds + "$" identifier */ - salt += 3; - - if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) { - strcpy(encrypted, error); - return; - } - - /* We dont want the base64 salt but the raw data */ - decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt); - salt_len = BCRYPT_MAXSALT; - if (minor <= 'a') - key_len = (u_int8_t)(key_len + (minor >= 'a' ? 1 : 0)); - else - { - /* cap key_len at the actual maximum supported - * length here to avoid integer wraparound */ - if (key_len > 72) - key_len = 72; - key_len++; /* include the NUL */ - } - - - /* Setting up S-Boxes and Subkeys */ - Blowfish_initstate(&state); - Blowfish_expandstate(&state, csalt, salt_len, - (u_int8_t *) key, key_len); - for (k = 0; k < rounds; k++) { - Blowfish_expand0state(&state, (u_int8_t *) key, key_len); - Blowfish_expand0state(&state, csalt, salt_len); - } - - /* This can be precomputed later */ - j = 0; - for (i = 0; i < BCRYPT_BLOCKS; i++) - cdata[i] = Blowfish_stream2word(ciphertext, 4 * BCRYPT_BLOCKS, &j); - - /* Now do the encryption */ - for (k = 0; k < 64; k++) - blf_enc(&state, cdata, BCRYPT_BLOCKS / 2); - - for (i = 0; i < BCRYPT_BLOCKS; i++) { - ciphertext[4 * i + 3] = cdata[i] & 0xff; - cdata[i] = cdata[i] >> 8; - ciphertext[4 * i + 2] = cdata[i] & 0xff; - cdata[i] = cdata[i] >> 8; - ciphertext[4 * i + 1] = cdata[i] & 0xff; - cdata[i] = cdata[i] >> 8; - ciphertext[4 * i + 0] = cdata[i] & 0xff; - } - - i = 0; - encrypted[i++] = '$'; - encrypted[i++] = BCRYPT_VERSION; - if (minor) - encrypted[i++] = minor; - encrypted[i++] = '$'; - - snprintf(encrypted + i, 4, "%2.2u$", logr & 0x001F); - - encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT); - encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext, - 4 * BCRYPT_BLOCKS - 1); - memset(&state, 0, sizeof(state)); - memset(ciphertext, 0, sizeof(ciphertext)); - memset(csalt, 0, sizeof(csalt)); - memset(cdata, 0, sizeof(cdata)); -} - -u_int32_t bcrypt_get_rounds(const char * hash) -{ - /* skip past the leading "$" */ - if (!hash || *(hash++) != '$') return 0; - - /* skip past version */ - if (0 == (*hash++)) return 0; - if (*hash && *hash != '$') hash++; - if (*hash++ != '$') return 0; - - return atoi(hash); -} - -static void -encode_base64(u_int8_t *buffer, u_int8_t *data, u_int16_t len) -{ - u_int8_t *bp = buffer; - u_int8_t *p = data; - u_int8_t c1, c2; - while (p < data + len) { - c1 = *p++; - *bp++ = Base64Code[(c1 >> 2)]; - c1 = (c1 & 0x03) << 4; - if (p >= data + len) { - *bp++ = Base64Code[c1]; - break; - } - c2 = *p++; - c1 |= (c2 >> 4) & 0x0f; - *bp++ = Base64Code[c1]; - c1 = (c2 & 0x0f) << 2; - if (p >= data + len) { - *bp++ = Base64Code[c1]; - break; - } - c2 = *p++; - c1 |= (c2 >> 6) & 0x03; - *bp++ = Base64Code[c1]; - *bp++ = Base64Code[c2 & 0x3f]; - } - *bp = '\0'; -} diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc b/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc deleted file mode 100644 index 2f072a4f9..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/src/bcrypt_node.cc +++ /dev/null @@ -1,288 +0,0 @@ -#define NAPI_VERSION 3 - -#include - -#include -#include -#include -#include // atoi - -#include "node_blf.h" - -#define NODE_LESS_THAN (!(NODE_VERSION_AT_LEAST(0, 5, 4))) - -namespace { - - bool ValidateSalt(const char* salt) { - - if (!salt || *salt != '$') { - return false; - } - - // discard $ - salt++; - - if (*salt > BCRYPT_VERSION) { - return false; - } - - if (salt[1] != '$') { - switch (salt[1]) { - case 'a': - case 'b': - salt++; - break; - default: - return false; - } - } - - // discard version + $ - salt += 2; - - if (salt[2] != '$') { - return false; - } - - int n = atoi(salt); - if (n > 31 || n < 0) { - return false; - } - - if (((uint8_t)1 << (uint8_t)n) < BCRYPT_MINROUNDS) { - return false; - } - - salt += 3; - if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT) { - return false; - } - - return true; - } - - inline char ToCharVersion(const std::string& str) { - return str[0]; - } - - /* SALT GENERATION */ - - class SaltAsyncWorker : public Napi::AsyncWorker { - public: - SaltAsyncWorker(const Napi::Function& callback, const std::string& seed, ssize_t rounds, char minor_ver) - : Napi::AsyncWorker(callback, "bcrypt:SaltAsyncWorker"), seed(seed), rounds(rounds), minor_ver(minor_ver) { - } - - ~SaltAsyncWorker() {} - - void Execute() { - bcrypt_gensalt(minor_ver, rounds, (u_int8_t *)&seed[0], salt); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - Callback().Call({Env().Undefined(), Napi::String::New(Env(), salt)}); - } - - private: - std::string seed; - ssize_t rounds; - char minor_ver; - char salt[_SALT_LEN]; - }; - - Napi::Value GenerateSalt(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (info.Length() < 4) { - throw Napi::TypeError::New(env, "4 arguments expected"); - } - if (!info[0].IsString()) { - throw Napi::TypeError::New(env, "First argument must be a string"); - } - if (!info[2].IsBuffer() || (info[2].As>()).Length() != 16) { - throw Napi::TypeError::New(env, "Second argument must be a 16 byte Buffer"); - } - - const char minor_ver = ToCharVersion(info[0].As()); - const int32_t rounds = info[1].As(); - Napi::Buffer seed = info[2].As>(); - Napi::Function callback = info[3].As(); - SaltAsyncWorker* saltWorker = new SaltAsyncWorker(callback, std::string(seed.Data(), 16), rounds, minor_ver); - saltWorker->Queue(); - return env.Undefined(); - } - - Napi::Value GenerateSaltSync(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (info.Length() < 3) { - throw Napi::TypeError::New(env, "3 arguments expected"); - } - if (!info[0].IsString()) { - throw Napi::TypeError::New(env, "First argument must be a string"); - } - if (!info[2].IsBuffer() || (info[2].As>()).Length() != 16) { - throw Napi::TypeError::New(env, "Third argument must be a 16 byte Buffer"); - } - const char minor_ver = ToCharVersion(info[0].As()); - const int32_t rounds = info[1].As(); - Napi::Buffer buffer = info[2].As>(); - u_int8_t* seed = (u_int8_t*) buffer.Data(); - char salt[_SALT_LEN]; - bcrypt_gensalt(minor_ver, rounds, seed, salt); - return Napi::String::New(env, salt, strlen(salt)); - } - - inline std::string BufferToString(const Napi::Buffer &buf) { - return std::string(buf.Data(), buf.Length()); - } - - /* ENCRYPT DATA - USED TO BE HASHPW */ - - class EncryptAsyncWorker : public Napi::AsyncWorker { - public: - EncryptAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& salt) - : Napi::AsyncWorker(callback, "bcrypt:EncryptAsyncWorker"), input(input), salt(salt) { - } - - ~EncryptAsyncWorker() {} - - void Execute() { - if (!(ValidateSalt(salt.c_str()))) { - SetError("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); - } - bcrypt(input.c_str(), input.length(), salt.c_str(), bcrypted); - } - - void OnOK() { - Napi::HandleScope scope(Env()); - Callback().Call({Env().Undefined(),Napi::String::New(Env(), bcrypted)}); - } - private: - std::string input; - std::string salt; - char bcrypted[_PASSWORD_LEN]; - }; - - Napi::Value Encrypt(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 arguments expected"); - } - std::string data = info[0].IsBuffer() - ? BufferToString(info[0].As>()) - : info[0].As(); - std::string salt = info[1].As(); - Napi::Function callback = info[2].As(); - EncryptAsyncWorker* encryptWorker = new EncryptAsyncWorker(callback, data, salt); - encryptWorker->Queue(); - return info.Env().Undefined(); - } - - Napi::Value EncryptSync(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 arguments expected"); - } - std::string data = info[0].IsBuffer() - ? BufferToString(info[0].As>()) - : info[0].As(); - std::string salt = info[1].As(); - if (!(ValidateSalt(salt.c_str()))) { - throw Napi::Error::New(env, "Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); - } - char bcrypted[_PASSWORD_LEN]; - bcrypt(data.c_str(), data.length(), salt.c_str(), bcrypted); - return Napi::String::New(env, bcrypted, strlen(bcrypted)); - } - - /* COMPARATOR */ - inline bool CompareStrings(const char* s1, const char* s2) { - return strcmp(s1, s2) == 0; - } - - class CompareAsyncWorker : public Napi::AsyncWorker { - public: - CompareAsyncWorker(const Napi::Function& callback, const std::string& input, const std::string& encrypted) - : Napi::AsyncWorker(callback, "bcrypt:CompareAsyncWorker"), input(input), encrypted(encrypted) { - result = false; - } - - ~CompareAsyncWorker() {} - - void Execute() { - char bcrypted[_PASSWORD_LEN]; - if (ValidateSalt(encrypted.c_str())) { - bcrypt(input.c_str(), input.length(), encrypted.c_str(), bcrypted); - result = CompareStrings(bcrypted, encrypted.c_str()); - } - } - - void OnOK() { - Napi::HandleScope scope(Env()); - Callback().Call({Env().Undefined(), Napi::Boolean::New(Env(), result)}); - } - - private: - std::string input; - std::string encrypted; - bool result; - }; - - Napi::Value Compare(const Napi::CallbackInfo& info) { - if (info.Length() < 3) { - throw Napi::TypeError::New(info.Env(), "3 arguments expected"); - } - std::string input = info[0].IsBuffer() - ? BufferToString(info[0].As>()) - : info[0].As(); - std::string encrypted = info[1].As(); - Napi::Function callback = info[2].As(); - CompareAsyncWorker* compareWorker = new CompareAsyncWorker(callback, input, encrypted); - compareWorker->Queue(); - return info.Env().Undefined(); - } - - Napi::Value CompareSync(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (info.Length() < 2) { - throw Napi::TypeError::New(info.Env(), "2 arguments expected"); - } - std::string pw = info[0].IsBuffer() - ? BufferToString(info[0].As>()) - : info[0].As(); - std::string hash = info[1].As(); - char bcrypted[_PASSWORD_LEN]; - if (ValidateSalt(hash.c_str())) { - bcrypt(pw.c_str(), pw.length(), hash.c_str(), bcrypted); - return Napi::Boolean::New(env, CompareStrings(bcrypted, hash.c_str())); - } else { - return Napi::Boolean::New(env, false); - } - } - - Napi::Value GetRounds(const Napi::CallbackInfo& info) { - Napi::Env env = info.Env(); - if (info.Length() < 1) { - throw Napi::TypeError::New(env, "1 argument expected"); - } - std::string hash = info[0].As(); - u_int32_t rounds; - if (!(rounds = bcrypt_get_rounds(hash.c_str()))) { - throw Napi::Error::New(env, "invalid hash provided"); - } - return Napi::Number::New(env, rounds); - } - -} // anonymous namespace - -Napi::Object init(Napi::Env env, Napi::Object exports) { - exports.Set(Napi::String::New(env, "gen_salt_sync"), Napi::Function::New(env, GenerateSaltSync)); - exports.Set(Napi::String::New(env, "encrypt_sync"), Napi::Function::New(env, EncryptSync)); - exports.Set(Napi::String::New(env, "compare_sync"), Napi::Function::New(env, CompareSync)); - exports.Set(Napi::String::New(env, "get_rounds"), Napi::Function::New(env, GetRounds)); - exports.Set(Napi::String::New(env, "gen_salt"), Napi::Function::New(env, GenerateSalt)); - exports.Set(Napi::String::New(env, "encrypt"), Napi::Function::New(env, Encrypt)); - exports.Set(Napi::String::New(env, "compare"), Napi::Function::New(env, Compare)); - return exports; -} - -NODE_API_MODULE(NODE_GYP_MODULE_NAME, init) diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc b/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc deleted file mode 100644 index 1fc6cf19e..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/src/blowfish.cc +++ /dev/null @@ -1,679 +0,0 @@ -/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ -/* - * Blowfish block cipher for OpenBSD - * Copyright 1997 Niels Provos - * All rights reserved. - * - * Implementation advice by David Mazieres . - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * This code is derived from section 14.3 and the given source - * in section V of Applied Cryptography, second edition. - * Blowfish is an unpatented fast block cipher designed by - * Bruce Schneier. - */ - -#include "node_blf.h" - -#undef inline -#ifdef __GNUC__ -#define inline __inline -#else /* !__GNUC__ */ -#define inline -#endif /* !__GNUC__ */ - -/* Function for Feistel Networks */ - -#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ - + (s)[0x100 + (((x)>>16)&0xFF)]) \ - ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ - + (s)[0x300 + ( (x) &0xFF)]) - -#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) - -void -Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) -{ - u_int32_t Xl; - u_int32_t Xr; - u_int32_t *s = c->S[0]; - u_int32_t *p = c->P; - - Xl = *xl; - Xr = *xr; - - Xl ^= p[0]; - BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); - BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); - BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); - BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); - BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); - BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); - BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); - BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); - - *xl = Xr ^ p[17]; - *xr = Xl; -} - -void -Blowfish_decipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) -{ - u_int32_t Xl; - u_int32_t Xr; - u_int32_t *s = c->S[0]; - u_int32_t *p = c->P; - - Xl = *xl; - Xr = *xr; - - Xl ^= p[17]; - BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); - BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); - BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); - BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); - BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); - BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); - BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); - BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); - - *xl = Xr ^ p[0]; - *xr = Xl; -} - -void -Blowfish_initstate(blf_ctx *c) -{ - /* P-box and S-box tables initialized with digits of Pi */ - - static const blf_ctx initstate = - { { - { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, - { - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, - { - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, - { - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} - }, - { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - } }; - - *c = initstate; -} - -u_int32_t -Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes, - u_int16_t *current) -{ - u_int8_t i; - u_int16_t j; - u_int32_t temp; - - temp = 0x00000000; - j = *current; - - for (i = 0; i < 4; i++, j++) { - if (j >= databytes) - j = 0; - temp = (temp << 8) | data[j]; - } - - *current = j; - return temp; -} - -void -Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes) -{ - u_int16_t i; - u_int16_t j; - u_int16_t k; - u_int32_t temp; - u_int32_t datal; - u_int32_t datar; - - j = 0; - for (i = 0; i < BLF_N + 2; i++) { - /* Extract 4 int8 to 1 int32 from keystream */ - temp = Blowfish_stream2word(key, keybytes, &j); - c->P[i] = c->P[i] ^ temp; - } - - j = 0; - datal = 0x00000000; - datar = 0x00000000; - for (i = 0; i < BLF_N + 2; i += 2) { - Blowfish_encipher(c, &datal, &datar); - - c->P[i] = datal; - c->P[i + 1] = datar; - } - - for (i = 0; i < 4; i++) { - for (k = 0; k < 256; k += 2) { - Blowfish_encipher(c, &datal, &datar); - - c->S[i][k] = datal; - c->S[i][k + 1] = datar; - } - } -} - - -void -Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes, - const u_int8_t *key, u_int16_t keybytes) -{ - u_int16_t i; - u_int16_t j; - u_int16_t k; - u_int32_t temp; - u_int32_t datal; - u_int32_t datar; - - j = 0; - for (i = 0; i < BLF_N + 2; i++) { - /* Extract 4 int8 to 1 int32 from keystream */ - temp = Blowfish_stream2word(key, keybytes, &j); - c->P[i] = c->P[i] ^ temp; - } - - j = 0; - datal = 0x00000000; - datar = 0x00000000; - for (i = 0; i < BLF_N + 2; i += 2) { - datal ^= Blowfish_stream2word(data, databytes, &j); - datar ^= Blowfish_stream2word(data, databytes, &j); - Blowfish_encipher(c, &datal, &datar); - - c->P[i] = datal; - c->P[i + 1] = datar; - } - - for (i = 0; i < 4; i++) { - for (k = 0; k < 256; k += 2) { - datal ^= Blowfish_stream2word(data, databytes, &j); - datar ^= Blowfish_stream2word(data, databytes, &j); - Blowfish_encipher(c, &datal, &datar); - - c->S[i][k] = datal; - c->S[i][k + 1] = datar; - } - } - -} - -void -blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len) -{ - /* Initialize S-boxes and subkeys with Pi */ - Blowfish_initstate(c); - - /* Transform S-boxes and subkeys with key */ - Blowfish_expand0state(c, k, len); -} - -void -blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks) -{ - u_int32_t *d; - u_int16_t i; - - d = data; - for (i = 0; i < blocks; i++) { - Blowfish_encipher(c, d, d + 1); - d += 2; - } -} - -void -blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks) -{ - u_int32_t *d; - u_int16_t i; - - d = data; - for (i = 0; i < blocks; i++) { - Blowfish_decipher(c, d, d + 1); - d += 2; - } -} - -void -blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) -{ - u_int32_t l, r; - u_int32_t i; - - for (i = 0; i < len; i += 8) { - l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; - Blowfish_encipher(c, &l, &r); - data[0] = l >> 24 & 0xff; - data[1] = l >> 16 & 0xff; - data[2] = l >> 8 & 0xff; - data[3] = l & 0xff; - data[4] = r >> 24 & 0xff; - data[5] = r >> 16 & 0xff; - data[6] = r >> 8 & 0xff; - data[7] = r & 0xff; - data += 8; - } -} - -void -blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) -{ - u_int32_t l, r; - u_int32_t i; - - for (i = 0; i < len; i += 8) { - l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; - Blowfish_decipher(c, &l, &r); - data[0] = l >> 24 & 0xff; - data[1] = l >> 16 & 0xff; - data[2] = l >> 8 & 0xff; - data[3] = l & 0xff; - data[4] = r >> 24 & 0xff; - data[5] = r >> 16 & 0xff; - data[6] = r >> 8 & 0xff; - data[7] = r & 0xff; - data += 8; - } -} - -void -blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len) -{ - u_int32_t l, r; - u_int32_t i, j; - - for (i = 0; i < len; i += 8) { - for (j = 0; j < 8; j++) - data[j] ^= iv[j]; - l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; - Blowfish_encipher(c, &l, &r); - data[0] = l >> 24 & 0xff; - data[1] = l >> 16 & 0xff; - data[2] = l >> 8 & 0xff; - data[3] = l & 0xff; - data[4] = r >> 24 & 0xff; - data[5] = r >> 16 & 0xff; - data[6] = r >> 8 & 0xff; - data[7] = r & 0xff; - iv = data; - data += 8; - } -} - -void -blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len) -{ - u_int32_t l, r; - u_int8_t *iv; - u_int32_t i, j; - - iv = data + len - 16; - data = data + len - 8; - for (i = len - 8; i >= 8; i -= 8) { - l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; - Blowfish_decipher(c, &l, &r); - data[0] = l >> 24 & 0xff; - data[1] = l >> 16 & 0xff; - data[2] = l >> 8 & 0xff; - data[3] = l & 0xff; - data[4] = r >> 24 & 0xff; - data[5] = r >> 16 & 0xff; - data[6] = r >> 8 & 0xff; - data[7] = r & 0xff; - for (j = 0; j < 8; j++) - data[j] ^= iv[j]; - iv -= 8; - data -= 8; - } - l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; - Blowfish_decipher(c, &l, &r); - data[0] = l >> 24 & 0xff; - data[1] = l >> 16 & 0xff; - data[2] = l >> 8 & 0xff; - data[3] = l & 0xff; - data[4] = r >> 24 & 0xff; - data[5] = r >> 16 & 0xff; - data[6] = r >> 8 & 0xff; - data[7] = r & 0xff; - for (j = 0; j < 8; j++) - data[j] ^= iva[j]; -} - -#if 0 -void -report(u_int32_t data[], u_int16_t len) -{ - u_int16_t i; - for (i = 0; i < len; i += 2) - printf("Block %0hd: %08lx %08lx.\n", - i / 2, data[i], data[i + 1]); -} -void -main(void) -{ - - blf_ctx c; - char key[] = "AAAAA"; - char key2[] = "abcdefghijklmnopqrstuvwxyz"; - - u_int32_t data[10]; - u_int32_t data2[] = - {0x424c4f57l, 0x46495348l}; - - u_int16_t i; - - /* First test */ - for (i = 0; i < 10; i++) - data[i] = i; - - blf_key(&c, (u_int8_t *) key, 5); - blf_enc(&c, data, 5); - blf_dec(&c, data, 1); - blf_dec(&c, data + 2, 4); - printf("Should read as 0 - 9.\n"); - report(data, 10); - - /* Second test */ - blf_key(&c, (u_int8_t *) key2, strlen(key2)); - blf_enc(&c, data2, 1); - printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); - report(data2, 2); - blf_dec(&c, data2, 1); - report(data2, 2); -} -#endif diff --git a/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h b/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h deleted file mode 100644 index 2d50a39bf..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/src/node_blf.h +++ /dev/null @@ -1,132 +0,0 @@ -/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ -/* - * Blowfish - a fast block cipher designed by Bruce Schneier - * - * Copyright 1997 Niels Provos - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _NODE_BLF_H_ -#define _NODE_BLF_H_ - -#include - -/* Solaris compatibility */ -#ifdef __sun -#define u_int8_t uint8_t -#define u_int16_t uint16_t -#define u_int32_t uint32_t -#define u_int64_t uint64_t -#endif - -#ifdef _WIN32 -#define u_int8_t unsigned __int8 -#define u_int16_t unsigned __int16 -#define u_int32_t unsigned __int32 -#define u_int64_t unsigned __int64 -#endif - -/* Windows ssize_t compatibility */ -#if defined(_WIN32) || defined(_WIN64) -# if defined(_WIN64) - typedef __int64 LONG_PTR; -# else - typedef long LONG_PTR; -# endif - typedef LONG_PTR SSIZE_T; - typedef SSIZE_T ssize_t; -#endif - -/* z/OS compatibility */ -#ifdef __MVS__ -typedef unsigned char u_int8_t; -typedef unsigned short u_int16_t; -typedef unsigned int u_int32_t; -typedef unsigned long long u_int64_t; -#endif - -#define BCRYPT_VERSION '2' -#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */ -#define BCRYPT_BLOCKS 6 /* Ciphertext blocks */ -#define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */ - -/* Schneier specifies a maximum key length of 56 bytes. - * This ensures that every key bit affects every cipher - * bit. However, the subkeys can hold up to 72 bytes. - * Warning: For normal blowfish encryption only 56 bytes - * of the key affect all cipherbits. - */ - -#define BLF_N 16 /* Number of Subkeys */ -#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ -#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ - -#define _PASSWORD_LEN 128 /* max length, not counting NUL */ -#define _SALT_LEN 32 /* max length */ - -/* Blowfish context */ -typedef struct BlowfishContext { - u_int32_t S[4][256]; /* S-Boxes */ - u_int32_t P[BLF_N + 2]; /* Subkeys */ -} blf_ctx; - -/* Raw access to customized Blowfish - * blf_key is just: - * Blowfish_initstate( state ) - * Blowfish_expand0state( state, key, keylen ) - */ - -void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *); -void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *); -void Blowfish_initstate(blf_ctx *); -void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t); -void Blowfish_expandstate -(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t); - -/* Standard Blowfish */ - -void blf_key(blf_ctx *, const u_int8_t *, u_int16_t); -void blf_enc(blf_ctx *, u_int32_t *, u_int16_t); -void blf_dec(blf_ctx *, u_int32_t *, u_int16_t); - -void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t); -void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t); - -void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); -void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); - -/* Converts u_int8_t to u_int32_t */ -u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *); - -/* bcrypt functions*/ -void bcrypt_gensalt(char, u_int8_t, u_int8_t*, char *); -void bcrypt(const char *, size_t key_len, const char *, char *); -void encode_salt(char *, u_int8_t *, char, u_int16_t, u_int8_t); -u_int32_t bcrypt_get_rounds(const char *); - -#endif diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js deleted file mode 100644 index fb59367a3..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/test/async.test.js +++ /dev/null @@ -1,209 +0,0 @@ -const bcrypt = require('../bcrypt'); - -test('salt_length', done => { - expect.assertions(1); - bcrypt.genSalt(10, function (err, salt) { - expect(salt).toHaveLength(29); - done(); - }); -}) - -test('salt_only_cb', () => { - expect.assertions(1); - expect(() => { - bcrypt.genSalt((err, salt) => { - }); - }).not.toThrow(); -}) - -test('salt_rounds_is_string_number', done => { - expect.assertions(2); - bcrypt.genSalt('10', void 0, function (err, salt) { - expect(err instanceof Error).toBe(true) - expect(err.message).toBe('rounds must be a number') - done(); - }); -}) - -test('salt_rounds_is_string_non_number', done => { - expect.assertions(2); - bcrypt.genSalt('z', function (err, salt) { - expect(err instanceof Error).toBe(true) - expect(err.message).toBe('rounds must be a number') - done(); - }); -}) - -test('salt_minor', done => { - expect.assertions(3); - bcrypt.genSalt(10, 'a', function (err, value) { - expect(value).toHaveLength(29); - const [_, minor, salt] = value.split('$'); - expect(minor).toEqual('2a'); - expect(salt).toEqual('10'); - done(); - }); -}) - -test('salt_minor_b', done => { - expect.assertions(3); - bcrypt.genSalt(10, 'b', function (err, value) { - expect(value).toHaveLength(29); - const [_, minor, salt] = value.split('$'); - expect(minor).toEqual('2b'); - expect(salt).toEqual('10'); - done(); - }); -}) - -test('hash', done => { - expect.assertions(2); - bcrypt.genSalt(10, function (err, salt) { - bcrypt.hash('password', salt, function (err, res) { - expect(res).toBeDefined(); - expect(err).toBeUndefined(); - done(); - }); - }); -}) - -test('hash_rounds', done => { - expect.assertions(1); - bcrypt.hash('bacon', 8, function (err, hash) { - expect(bcrypt.getRounds(hash)).toEqual(8); - done(); - }); -}) - -test('hash_empty_strings', done => { - expect.assertions(1); - bcrypt.genSalt(10, function (err, salt) { - bcrypt.hash('', salt, function (err, res) { - expect(res).toBeDefined(); - done(); - }); - }); -}) - -test('hash_fails_with_empty_salt', done => { - expect.assertions(1); - bcrypt.hash('', '', function (err, res) { - expect(err.message).toBe('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue') - done(); - }); -}) - -test('hash_no_params', done => { - expect.assertions(1); - bcrypt.hash(function (err, hash) { - expect(err.message).toBe('data must be a string or Buffer and salt must either be a salt string or a number of rounds') - done(); - }); -}) - -test('hash_one_param', done => { - expect.assertions(1); - bcrypt.hash('password', function (err, hash) { - expect(err.message).toBe('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); - done(); - }); -}) - -test('hash_salt_validity', done => { - expect.assertions(2); - bcrypt.hash('password', '$2a$10$somesaltyvaluertsetrse', function (err, enc) { - expect(err).toBeUndefined(); - bcrypt.hash('password', 'some$value', function (err, enc) { - expect(err.message).toBe("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue"); - done(); - }); - }); -}) - -test('verify_salt', done => { - expect.assertions(2); - bcrypt.genSalt(10, function (err, value) { - const [_, version, rounds] = value.split('$'); - expect(version).toEqual('2b'); - expect(rounds).toEqual('10'); - done(); - }); -}) - -test('verify_salt_min_rounds', done => { - expect.assertions(2); - bcrypt.genSalt(1, function (err, value) { - const [_, version, rounds] = value.split('$'); - expect(version).toEqual('2b'); - expect(rounds).toEqual('04'); - done(); - }); -}) - -test('verify_salt_max_rounds', done => { - expect.assertions(2); - bcrypt.genSalt(100, function (err, value) { - const [_, version, rounds] = value.split('$'); - expect(version).toEqual('2b'); - expect(rounds).toEqual('31'); - done(); - }); -}) - -test('hash_compare', done => { - expect.assertions(2); - bcrypt.genSalt(10, function (err, salt) { - bcrypt.hash("test", salt, function (err, hash) { - bcrypt.compare("test", hash, function (err, res) { - expect(hash).toBeDefined(); - bcrypt.compare("blah", hash, function (err, res) { - expect(res).toBe(false); - done(); - }); - }); - }); - }); -}) - -test('hash_compare_empty_strings', done => { - expect.assertions(2); - const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(10)); - - bcrypt.compare("", hash, function (err, res) { - expect(res).toEqual(false) - bcrypt.compare("", "", function (err, res) { - expect(res).toEqual(false); - done(); - }); - }); -}) - -test('hash_compare_invalid_strings', done => { - expect.assertions(2); - const fullString = 'envy1362987212538'; - const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; - const wut = ':'; - bcrypt.compare(fullString, hash, function (err, res) { - expect(res).toBe(true); - bcrypt.compare(fullString, wut, function (err, res) { - expect(res).toBe(false); - done(); - }); - }); -}) - -test('compare_no_params', done => { - expect.assertions(1); - bcrypt.compare(function (err, hash) { - expect(err.message).toBe('data and hash arguments required'); - done(); - }); -}) - -test('hash_compare_one_param', done => { - expect.assertions(1); - bcrypt.compare('password', function (err, hash) { - expect(err.message).toBe('data and hash arguments required'); - done(); - }); -}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js deleted file mode 100644 index 647f32a92..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/test/implementation.test.js +++ /dev/null @@ -1,48 +0,0 @@ -const bcrypt = require('../bcrypt'); - -// some tests were adapted from https://github.com/riverrun/bcrypt_elixir/blob/master/test/base_test.exs -// which are under the BSD LICENSE - -test('openwall', () => { - expect(bcrypt.hashSync("U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"); - expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"); - expect(bcrypt.hashSync("U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO")).toStrictEqual("$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"); - expect(bcrypt.hashSync("", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"); - expect(bcrypt.hashSync("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu")).toStrictEqual("$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"); -}) - -test('openbsd', () => { - expect(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") - expect(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") -}) - -test('long_passwords', () => { - // bcrypt wrap-around bug in $2a$ - expect(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") - expect(bcrypt.hashSync("01XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS") - - // tests for $2b$ which fixes wrap-around bugs - expect(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.XxrQqgBi/5Sxuq9soXzDtjIZ7w5pMfK") - expect(bcrypt.hashSync("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.XxrQqgBi/5Sxuq9soXzDtjIZ7w5pMfK") -}) - -test('embedded_nulls', () => { - expect(bcrypt.hashSync("Passw\0rd123", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.VHy/kzL4sCcX3Ib3wN5rNGiRt.TpfxS") - expect(bcrypt.hashSync("Passw\0 you can literally write anything after the NUL character", "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.4vJLJQ6nZ/70INTjjSZWQ0iyUek92tu") - expect(bcrypt.hashSync(Buffer.from("Passw\0 you can literally write anything after the NUL character"), "$2b$05$CCCCCCCCCCCCCCCCCCCCC.")).toStrictEqual("$2b$05$CCCCCCCCCCCCCCCCCCCCC.4vJLJQ6nZ/70INTjjSZWQ0iyUek92tu") -}) - -test('shorten_salt_to_128_bits', () => { - expect(bcrypt.hashSync("test", "$2a$10$1234567899123456789012")).toStrictEqual("$2a$10$123456789912345678901u.OtL1A1eGK5wmvBKUDYKvuVKI7h2XBu") - expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCh")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCCeUQ7VjYZ2hd4bLYZdhuPpZMUpEUJDw1S") - expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCM")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK") - expect(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCA")).toStrictEqual("$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK") -}) - -test('consistency', () => { - expect(bcrypt.hashSync("ππππππππ", "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeu")).toStrictEqual("$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle") - expect(bcrypt.hashSync("p@5sw0rd", "$2b$12$zQ4CooEXdGqcwi0PHsgc8e")).toStrictEqual("$2b$12$zQ4CooEXdGqcwi0PHsgc8eAf0DLXE/XHoBE8kCSGQ97rXwuClaPam") - expect(bcrypt.hashSync("C'est bon, la vie!", "$2b$12$cbo7LZ.wxgW4yxAA5Vqlv.")).toStrictEqual("$2b$12$cbo7LZ.wxgW4yxAA5Vqlv.KR6QFPt4qCdc9RYJNXxa/rbUOp.1sw.") - expect(bcrypt.hashSync("ἓν οἶδα ὅτι οὐδὲν οἶδα", "$2b$12$LeHKWR2bmrazi/6P22Jpau")).toStrictEqual("$2b$12$LeHKWR2bmrazi/6P22JpauX5my/eKwwKpWqL7L5iEByBnxNc76FRW") - expect(bcrypt.hashSync(Buffer.from("ἓν οἶδα ὅτι οὐδὲν οἶδα"), "$2b$12$LeHKWR2bmrazi/6P22Jpau")).toStrictEqual("$2b$12$LeHKWR2bmrazi/6P22JpauX5my/eKwwKpWqL7L5iEByBnxNc76FRW") -}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js deleted file mode 100644 index 010341825..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/test/promise.test.js +++ /dev/null @@ -1,168 +0,0 @@ -const bcrypt = require('../bcrypt'); -const promises = require('../promises'); - -test('salt_returns_promise_on_no_args', () => { - // make sure test passes with non-native implementations such as bluebird - // http://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise - expect(typeof bcrypt.genSalt().then).toEqual('function') -}) - -test('salt_returns_promise_on_null_callback', () => { - expect(typeof bcrypt.genSalt(13, null, null).then).toEqual('function') -}) - -test('salt_length', () => { - return expect(bcrypt.genSalt(10)).resolves.toHaveLength(29); -}) - -test('salt_rounds_is_string_number', () => { - return expect(bcrypt.genSalt('10')).rejects.toThrow('rounds must be a number'); -}) - -test('salt_rounds_is_string_non_number', () => { - return expect(bcrypt.genSalt('b')).rejects.toThrow('rounds must be a number'); -}) - -test('hash_returns_promise_on_null_callback', () => { - expect(typeof bcrypt.hash('password', 10, null).then).toStrictEqual('function') -}) - -test('hash', () => { - return expect(bcrypt.genSalt(10) - .then(salt => bcrypt.hash('password', salt))).resolves.toBeDefined() -}) - -test('hash_rounds', () => { - return bcrypt.hash('bacon', 8).then(hash => { - expect(bcrypt.getRounds(hash)).toStrictEqual(8) - }); -}) - -test('hash_empty_strings', () => { - expect.assertions(2); - return Promise.all([ - expect(bcrypt.genSalt(10) - .then(salt => bcrypt.hash('', salt))) - .resolves.toBeDefined(), - expect(bcrypt.hash('', '')).rejects.toThrow(''), - ]); -}) - -test('hash_no_params', () => { - expect.assertions(1); - return expect(bcrypt.hash()).rejects.toThrow('data and salt arguments required'); -}) - -test('hash_one_param', () => { - return expect(bcrypt.hash('password')).rejects.toThrow('data and salt arguments required'); -}) - -test('hash_salt_validity', () => { - expect.assertions(2); - return Promise.all( - [ - expect(bcrypt.hash('password', '$2a$10$somesaltyvaluertsetrse')).resolves.toBeDefined(), - expect(bcrypt.hash('password', 'some$value')).rejects.toThrow("Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue") - ]); -}) - -test('verify_salt', () => { - expect.assertions(2); - return bcrypt.genSalt(10).then(result => { - const [_, version, salt] = result.split('$'); - expect(version).toEqual('2b') - expect(salt).toEqual('10') - }); -}) - -test('verify_salt_min_rounds', () => { - expect.assertions(2); - return bcrypt.genSalt(1).then(value => { - const [_, version, rounds] = value.split('$'); - expect(version).toEqual('2b'); - expect(rounds).toEqual('04'); - }); -}) - -test('verify_salt_max_rounds', () => { - expect.assertions(2); - return bcrypt.genSalt(100).then(value => { - const [_, version, rounds] = value.split('$'); - expect(version).toEqual('2b'); - expect(rounds).toEqual('31'); - }); -}) - -test('hash_compare_returns_promise_on_null_callback', () => { - expect(typeof bcrypt.compare('password', 'something', null).then).toStrictEqual('function') -}) - -test('hash_compare', () => { - expect.assertions(3); - return bcrypt.genSalt(10).then(function (salt) { - expect(salt).toHaveLength(29); - return bcrypt.hash("test", salt); - }).then(hash => Promise.all( - [ - expect(bcrypt.compare("test", hash)).resolves.toEqual(true), - expect(bcrypt.compare("blah", hash)).resolves.toEqual(false) - ])); -}) - -test('hash_compare_empty_strings', () => { - expect.assertions(2); - const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(10)); - return Promise.all([ - expect(bcrypt.compare("", hash)).resolves.toEqual(false), - expect(bcrypt.compare("", "")).resolves.toEqual(false) - ]); -}) - -test('hash_compare_invalid_strings', () => { - const fullString = 'envy1362987212538'; - const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; - const wut = ':'; - return Promise.all([ - expect(bcrypt.compare(fullString, hash)).resolves.toEqual(true), - expect(bcrypt.compare(fullString, wut)).resolves.toEqual(false), - ]); -}) - -test('hash_compare_no_params', () => { - expect.assertions(1); - return expect(bcrypt.compare()).rejects.toThrow('data and hash arguments required') -}) - -test('hash_compare_one_param', () => { - expect.assertions(1); - return expect(bcrypt.compare('password')).rejects.toThrow('data and hash arguments required') -}) - -test('change_promise_impl_reject', () => { - - promises.use({ - reject: function () { - return 'mock'; - } - }); - - expect(promises.reject()).toEqual('mock'); - - // need to reset the promise implementation because of require cache - promises.use(global.Promise); -}) - -test('change_promise_impl_promise', () => { - - promises.use({ - reject: function (err) { - expect(err.message).toEqual('fn must be a function'); - return 'mock'; - } - }); - - expect(promises.promise('', '', '')).toEqual('mock'); - - // need to reset the promise implementation because of require cache - promises.use(global.Promise); -}) diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js deleted file mode 100644 index 63ff40777..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/test/repetitions.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const bcrypt = require('../bcrypt'); - -const EXPECTED = 2500; //number of times to iterate these tests.) -const { TEST_TIMEOUT_SECONDS } = process.env; -let timeout = 5e3; // default test timeout - -// it is necessary to increase the test timeout when emulating cross-architecture -// environments (i.e. arm64 from x86-64 host) which have significantly reduced performance: -if ( TEST_TIMEOUT_SECONDS ) - timeout = Number.parseInt(TEST_TIMEOUT_SECONDS, 10) * 1e3; - -jest.setTimeout(timeout); - -test('salt_length', () => { - expect.assertions(EXPECTED); - - return Promise.all(Array.from({length: EXPECTED}, - () => bcrypt.genSalt(10) - .then(salt => expect(salt).toHaveLength(29)))); -}) - -test('test_hash_length', () => { - expect.assertions(EXPECTED); - const SALT = '$2a$04$TnjywYklQbbZjdjBgBoA4e'; - return Promise.all(Array.from({length: EXPECTED}, - () => bcrypt.hash('test', SALT) - .then(hash => expect(hash).toHaveLength(60)))); -}) - -test('test_compare', () => { - expect.assertions(EXPECTED); - const HASH = '$2a$04$TnjywYklQbbZjdjBgBoA4e9G7RJt9blgMgsCvUvus4Iv4TENB5nHy'; - return Promise.all(Array.from({length: EXPECTED}, - () => bcrypt.compare('test', HASH) - .then(match => expect(match).toEqual(true)))); -}) - -test('test_hash_and_compare', () => { - expect.assertions(EXPECTED * 3); - const salt = bcrypt.genSaltSync(4) - - return Promise.all(Array.from({length: EXPECTED}, - () => { - const password = 'secret' + Math.random(); - return bcrypt.hash(password, salt) - .then(hash => { - expect(hash).toHaveLength(60); - const goodCompare = bcrypt.compare(password, hash).then(res => expect(res).toEqual(true)); - const badCompare = bcrypt.compare('bad' + password, hash).then(res => expect(res).toEqual(false)); - - return Promise.all([goodCompare, badCompare]); - }); - })); -}, timeout * 3); - diff --git a/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js b/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js deleted file mode 100644 index 2e6809af4..000000000 --- a/week-5/solution/frontend/node_modules/bcrypt/test/sync.test.js +++ /dev/null @@ -1,125 +0,0 @@ -const bcrypt = require('../bcrypt') - -test('salt_length', () => { - const salt = bcrypt.genSaltSync(13); - expect(salt).toHaveLength(29); - const [_, version, rounds] = salt.split('$'); - expect(version).toStrictEqual('2b') - expect(rounds).toStrictEqual('13') -}) - -test('salt_no_params', () => { - const salt = bcrypt.genSaltSync(); - const [_, version, rounds] = salt.split('$'); - expect(version).toStrictEqual('2b') - expect(rounds).toStrictEqual('10') -}) - -test('salt_rounds_is_string_number', () => { - expect(() => bcrypt.genSaltSync('10')).toThrowError('rounds must be a number'); -}) - -test('salt_rounds_is_NaN', () => { - expect(() => bcrypt.genSaltSync('b')).toThrowError("rounds must be a number"); -}) - -test('salt_minor_a', () => { - const salt = bcrypt.genSaltSync(10, 'a'); - const [_, version, rounds] = salt.split('$'); - expect(version).toStrictEqual('2a') - expect(rounds).toStrictEqual('10') -}) - -test('salt_minor_b', () => { - const salt = bcrypt.genSaltSync(10, 'b'); - const [_, version, rounds] = salt.split('$'); - expect(version).toStrictEqual('2b') - expect(rounds).toStrictEqual('10') -}) - -test('hash', () => { - expect(() => bcrypt.hashSync('password', bcrypt.genSaltSync(10))).not.toThrow() -}) - -test('hash_rounds', () => { - const hash = bcrypt.hashSync('password', 8); - expect(bcrypt.getRounds(hash)).toStrictEqual(8) -}) - -test('hash_empty_string', () => { - expect(() => bcrypt.hashSync('', bcrypt.genSaltSync(10))).not.toThrow(); - expect(() => bcrypt.hashSync('password', '')).toThrowError('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue'); - expect(() => bcrypt.hashSync('', '')).toThrowError('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue'); -}) - -test('hash_pw_no_params', () => { - expect(() => bcrypt.hashSync()).toThrow('data and salt arguments required'); -}) - -test('hash_pw_one_param', () => { - expect(() => bcrypt.hashSync('password')).toThrow('data and salt arguments required'); -}) - -test('hash_pw_not_hash_str', () => { - expect(() => bcrypt.hashSync('password', {})).toThrow("data must be a string or Buffer and salt must either be a salt string or a number of rounds") -}) - -test('hash_salt_validity', () => { - expect(2); - expect(bcrypt.hashSync('password', '$2a$10$somesaltyvaluertsetrse')).toBeDefined() - expect(() => bcrypt.hashSync('password', 'some$value')).toThrow('Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue') -}) - -test('verify_salt', () => { - const salt = bcrypt.genSaltSync(10); - const split_salt = salt.split('$'); - expect(split_salt[1]).toStrictEqual('2b') - expect(split_salt[2]).toStrictEqual('10') -}) - -test('verify_salt_min_rounds', () => { - const salt = bcrypt.genSaltSync(1); - const split_salt = salt.split('$'); - expect(split_salt[1]).toStrictEqual('2b') - expect(split_salt[2]).toStrictEqual('04') -}) - -test('verify_salt_max_rounds', () => { - const salt = bcrypt.genSaltSync(100); - const split_salt = salt.split('$'); - expect(split_salt[1]).toStrictEqual('2b') - expect(split_salt[2]).toStrictEqual('31') -}) - -test('hash_compare', () => { - const salt = bcrypt.genSaltSync(10); - expect(29).toStrictEqual(salt.length) - const hash = bcrypt.hashSync("test", salt); - expect(bcrypt.compareSync("test", hash)).toBeDefined() - expect(!(bcrypt.compareSync("blah", hash))).toBeDefined() -}) - -test('hash_compare_empty_strings', () => { - expect(!(bcrypt.compareSync("", "password"))).toBeDefined() - expect(!(bcrypt.compareSync("", ""))).toBeDefined() - expect(!(bcrypt.compareSync("password", ""))).toBeDefined() -}) - -test('hash_compare_invalid_strings', () => { - const fullString = 'envy1362987212538'; - const hash = '$2a$10$XOPbrlUPQdwdJUpSrIF6X.LbE14qsMmKGhM1A8W9iqaG3vv1BD7WC'; - const wut = ':'; - expect(bcrypt.compareSync(fullString, hash)).toBe(true); - expect(bcrypt.compareSync(fullString, wut)).toBe(false); -}) - -test('getRounds', () => { - const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(9)); - expect(9).toStrictEqual(bcrypt.getRounds(hash)) -}) - -test('getRounds', () => { - const hash = bcrypt.hashSync("test", bcrypt.genSaltSync(9)); - expect(9).toStrictEqual(bcrypt.getRounds(hash)) - expect(() => bcrypt.getRounds('')).toThrow("invalid hash provided"); -}); diff --git a/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md b/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md deleted file mode 100644 index 819d91a5b..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 [Node.js API collaborators](https://github.com/nodejs/node-addon-api#collaborators) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/week-5/solution/frontend/node_modules/node-addon-api/README.md b/week-5/solution/frontend/node_modules/node-addon-api/README.md deleted file mode 100644 index 89a36ef28..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# **node-addon-api module** - -[![codecov](https://codecov.io/gh/nodejs/node-addon-api/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nodejs/node-addon-api/tree/main) - -[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) - -This module contains **header-only C++ wrapper classes** which simplify -the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) -provided by Node.js when using C++. It provides a C++ object model -and exception handling semantics with low overhead. - -- [API References](doc/README.md) -- [Badges](#badges) -- [Contributing](#contributing) -- [License](#license) - -## API References - -API references are available in the [doc](doc/README.md) directory. - - -## Current version: 8.5.0 - - -(See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) - -node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. -This allows addons built with it to run with Node.js versions which support the targeted Node-API version. -**However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that -every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. - -The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. - -## Badges - -The use of badges is recommended to indicate the minimum version of Node-API -required for the module. This helps to determine which Node.js major versions are -supported. Addon maintainers can consult the [Node-API support matrix][] to determine -which Node.js versions provide a given Node-API version. The following badges are -available: - -![Node-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v1%20Badge.svg) -![Node-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v2%20Badge.svg) -![Node-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v3%20Badge.svg) -![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) -![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) -![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) -![Node-API v7 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v7%20Badge.svg) -![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg) -![Node-API v9 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v9%20Badge.svg) -![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) - -## Contributing - -We love contributions from the community to **node-addon-api**! -See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. - -## Team members - -### Active - -| Name | GitHub Link | -| ------------------- | ----------------------------------------------------- | -| Anna Henningsen | [addaleax](https://github.com/addaleax) | -| Chengzhong Wu | [legendecas](https://github.com/legendecas) | -| Jack Xia | [JckXia](https://github.com/JckXia) | -| Kevin Eady | [KevinEady](https://github.com/KevinEady) | -| Michael Dawson | [mhdawson](https://github.com/mhdawson) | -| Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | -| Vladimir Morozov | [vmoroz](https://github.com/vmoroz) | - -

    - -Emeritus - -### Emeritus - -| Name | GitHub Link | -| ------------------- | ----------------------------------------------------- | -| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | -| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | -| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | -| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | -| Jason Ginchereau | [jasongin](https://github.com/jasongin) | -| Jim Schlight | [jschlight](https://github.com/jschlight) | -| Sampson Gao | [sampsongao](https://github.com/sampsongao) | -| Taylor Woll | [boingoing](https://github.com/boingoing) | - -
    - -## License - -Licensed under [MIT](./LICENSE.md) - -[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#node-api-version-matrix diff --git a/week-5/solution/frontend/node_modules/node-addon-api/common.gypi b/week-5/solution/frontend/node_modules/node-addon-api/common.gypi deleted file mode 100644 index e594f14ff..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/common.gypi +++ /dev/null @@ -1,21 +0,0 @@ -{ - 'variables': { - 'NAPI_VERSION%': " -inline PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { - using CbData = details::CallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({getter, nullptr}); - - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { - return Accessor(utf8name.c_str(), getter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - napi_value name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { - using CbData = details::CallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({getter, nullptr}); - - return PropertyDescriptor({nullptr, - name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Name name, Getter getter, napi_property_attributes attributes, void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { - using CbData = details::AccessorCallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({getter, setter, nullptr}); - - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - return Accessor(utf8name.c_str(), getter, setter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { - using CbData = details::AccessorCallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({getter, setter, nullptr}); - - return PropertyDescriptor({nullptr, - name, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor( - nameValue, getter, setter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { - using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); - using CbData = details::CallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({cb, nullptr}); - - return PropertyDescriptor({utf8name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return Function(utf8name.c_str(), cb, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - napi_value name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { - using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); - using CbData = details::CallbackData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({cb, nullptr}); - - return PropertyDescriptor({nullptr, - name, - CbData::Wrapper, - nullptr, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - Name name, Callable cb, napi_property_attributes attributes, void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Function(nameValue, cb, attributes, data); -} - -#endif // !SRC_NAPI_INL_DEPRECATED_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h b/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h deleted file mode 100644 index 54651c1b5..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/napi-inl.h +++ /dev/null @@ -1,7033 +0,0 @@ -#ifndef SRC_NAPI_INL_H_ -#define SRC_NAPI_INL_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Node-API C++ Wrapper Classes -// -// Inline header-only implementations for "Node-API" ABI-stable C APIs for -// Node.js. -//////////////////////////////////////////////////////////////////////////////// - -// Note: Do not include this file directly! Include "napi.h" instead. -// This should be a no-op and is intended for better IDE integration. -#include "napi.h" - -#include -#include -#include -#if NAPI_HAS_THREADS -#include -#endif // NAPI_HAS_THREADS -#include -#include - -namespace Napi { - -#ifdef NAPI_CPP_CUSTOM_NAMESPACE -namespace NAPI_CPP_CUSTOM_NAMESPACE { -#endif - -// Helpers to handle functions exposed from C++ and internal constants. -namespace details { - -// New napi_status constants not yet available in all supported versions of -// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h. -constexpr int napi_no_external_buffers_allowed = 22; - -template -inline void default_basic_finalizer(node_addon_api_basic_env /*env*/, - void* data, - void* /*hint*/) { - delete static_cast(data); -} - -// Attach a data item to an object and delete it when the object gets -// garbage-collected. -// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes -// available on all supported versions of Node.js. -template < - typename FreeType, - node_addon_api_basic_finalize finalizer = default_basic_finalizer> -inline napi_status AttachData(napi_env env, - napi_value obj, - FreeType* data, - void* hint = nullptr) { - napi_status status; -#if (NAPI_VERSION < 5) - napi_value symbol, external; - status = napi_create_symbol(env, nullptr, &symbol); - if (status == napi_ok) { - status = napi_create_external(env, data, finalizer, hint, &external); - if (status == napi_ok) { - napi_property_descriptor desc = {nullptr, - symbol, - nullptr, - nullptr, - nullptr, - external, - napi_default, - nullptr}; - status = napi_define_properties(env, obj, 1, &desc); - } - } -#else // NAPI_VERSION >= 5 - status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr); -#endif - return status; -} - -// For use in JS to C++ callback wrappers to catch any Napi::Error exceptions -// and rethrow them as JavaScript exceptions before returning from the callback. -template -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL -inline napi_value WrapCallback(napi_env env, Callable callback) { -#else -inline napi_value WrapCallback(napi_env, Callable callback) { -#endif -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - try { - return callback(); - } catch (const Error& e) { - e.ThrowAsJavaScriptException(); - return nullptr; - } -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL - catch (const std::exception& e) { - Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); - return nullptr; - } catch (...) { - Napi::Error::New(env, "A native exception was thrown") - .ThrowAsJavaScriptException(); - return nullptr; - } -#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL -#else // NODE_ADDON_API_CPP_EXCEPTIONS - // When C++ exceptions are disabled, errors are immediately thrown as JS - // exceptions, so there is no need to catch and rethrow them here. - return callback(); -#endif // NODE_ADDON_API_CPP_EXCEPTIONS -} - -// For use in JS to C++ void callback wrappers to catch any Napi::Error -// exceptions and rethrow them as JavaScript exceptions before returning from -// the callback. -template -inline void WrapVoidCallback(Callable callback) { -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - try { - callback(); - } catch (const Error& e) { - e.ThrowAsJavaScriptException(); - } -#else // NAPI_CPP_EXCEPTIONS - // When C++ exceptions are disabled, errors are immediately thrown as JS - // exceptions, so there is no need to catch and rethrow them here. - callback(); -#endif // NAPI_CPP_EXCEPTIONS -} - -// For use in JS to C++ void callback wrappers to catch _any_ thrown exception -// and rethrow them as JavaScript exceptions before returning from the callback, -// wrapping in an Napi::Error as needed. -template -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL -inline void WrapVoidCallback(napi_env env, Callable callback) { -#else -inline void WrapVoidCallback(napi_env, Callable callback) { -#endif -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - try { - callback(); - } catch (const Error& e) { - e.ThrowAsJavaScriptException(); - } -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL - catch (const std::exception& e) { - Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); - } catch (...) { - Napi::Error::New(env, "A native exception was thrown") - .ThrowAsJavaScriptException(); - } -#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL -#else - // When C++ exceptions are disabled, there is no need to catch and rethrow C++ - // exceptions. JS errors should be thrown with - // `Error::ThrowAsJavaScriptException`. - callback(); -#endif // NODE_ADDON_API_CPP_EXCEPTIONS -} - -template -struct CallbackData { - static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - CallbackData* callbackData = - static_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - return callbackData->callback(callbackInfo); - }); - } - - Callable callback; - void* data; -}; - -template -struct CallbackData { - static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - CallbackData* callbackData = - static_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - callbackData->callback(callbackInfo); - return nullptr; - }); - } - - Callable callback; - void* data; -}; - -template -napi_value TemplatedVoidCallback(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - CallbackInfo cbInfo(env, info); - Callback(cbInfo); - return nullptr; - }); -} - -template -napi_value TemplatedCallback(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - CallbackInfo cbInfo(env, info); - // MSVC requires to copy 'Callback' function pointer to a local variable - // before invoking it. - auto callback = Callback; - return callback(cbInfo); - }); -} - -template -napi_value TemplatedInstanceCallback(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - CallbackInfo cbInfo(env, info); - T* instance = T::Unwrap(cbInfo.This().As()); - return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); - }); -} - -template -napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) - NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - CallbackInfo cbInfo(env, info); - T* instance = T::Unwrap(cbInfo.This().As()); - if (instance) (instance->*UnwrapCallback)(cbInfo); - return nullptr; - }); -} - -template -struct FinalizeData { -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - template >> -#endif - static inline void Wrapper(node_addon_api_basic_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback([&] { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(env, static_cast(data)); - delete finalizeData; - }); - } - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - template >, - typename = void> - static inline void Wrapper(node_addon_api_basic_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { -#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS - static_assert(false, - "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " - "must be basic."); -#endif - napi_status status = - node_api_post_finalizer(env, WrapperGC, data, finalizeHint); - NAPI_FATAL_IF_FAILED( - status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); - } -#endif - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - template >> -#endif - static inline void WrapperWithHint(node_addon_api_basic_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback([&] { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(env, static_cast(data), finalizeData->hint); - delete finalizeData; - }); - } - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - template >, - typename = void> - static inline void WrapperWithHint(node_addon_api_basic_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { -#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS - static_assert(false, - "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " - "must be basic."); -#endif - napi_status status = - node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint); - NAPI_FATAL_IF_FAILED( - status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); - } -#endif - - static inline void WrapperGCWithoutData(napi_env env, - void* /*data*/, - void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback(env, [&] { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(env); - delete finalizeData; - }); - } - - static inline void WrapperGC(napi_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback(env, [&] { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(env, static_cast(data)); - delete finalizeData; - }); - } - - static inline void WrapperGCWithHint(napi_env env, - void* data, - void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback(env, [&] { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(env, static_cast(data), finalizeData->hint); - delete finalizeData; - }); - } - - Finalizer callback; - Hint* hint; -}; - -#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) -template , - typename FinalizerDataType = void> -struct ThreadSafeFinalize { - static inline void Wrapper(napi_env env, - void* rawFinalizeData, - void* /* rawContext */) { - if (rawFinalizeData == nullptr) return; - - ThreadSafeFinalize* finalizeData = - static_cast(rawFinalizeData); - finalizeData->callback(Env(env)); - delete finalizeData; - } - - static inline void FinalizeWrapperWithData(napi_env env, - void* rawFinalizeData, - void* /* rawContext */) { - if (rawFinalizeData == nullptr) return; - - ThreadSafeFinalize* finalizeData = - static_cast(rawFinalizeData); - finalizeData->callback(Env(env), finalizeData->data); - delete finalizeData; - } - - static inline void FinalizeWrapperWithContext(napi_env env, - void* rawFinalizeData, - void* rawContext) { - if (rawFinalizeData == nullptr) return; - - ThreadSafeFinalize* finalizeData = - static_cast(rawFinalizeData); - finalizeData->callback(Env(env), static_cast(rawContext)); - delete finalizeData; - } - - static inline void FinalizeFinalizeWrapperWithDataAndContext( - napi_env env, void* rawFinalizeData, void* rawContext) { - if (rawFinalizeData == nullptr) return; - - ThreadSafeFinalize* finalizeData = - static_cast(rawFinalizeData); - finalizeData->callback( - Env(env), finalizeData->data, static_cast(rawContext)); - delete finalizeData; - } - - FinalizerDataType* data; - Finalizer callback; -}; - -template -inline typename std::enable_if(nullptr)>::type -CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { - details::WrapVoidCallback(env, [&]() { - call(env, - Function(env, jsCallback), - static_cast(context), - static_cast(data)); - }); -} - -template -inline typename std::enable_if(nullptr)>::type -CallJsWrapper(napi_env env, - napi_value jsCallback, - void* /*context*/, - void* /*data*/) { - details::WrapVoidCallback(env, [&]() { - if (jsCallback != nullptr) { - Function(env, jsCallback).Call(0, nullptr); - } - }); -} - -#if NAPI_VERSION > 4 - -template -napi_value DefaultCallbackWrapper(napi_env /*env*/, std::nullptr_t /*cb*/) { - return nullptr; -} - -template -napi_value DefaultCallbackWrapper(napi_env /*env*/, Napi::Function cb) { - return cb; -} - -#else -template -napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { - if (cb.IsEmpty()) { - return TSFN::EmptyFunctionFactory(env); - } - return cb; -} -#endif // NAPI_VERSION > 4 -#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS - -template -struct AccessorCallbackData { - static inline napi_value GetterWrapper(napi_env env, - napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - AccessorCallbackData* callbackData = - static_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - return callbackData->getterCallback(callbackInfo); - }); - } - - static inline napi_value SetterWrapper(napi_env env, - napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - AccessorCallbackData* callbackData = - static_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - callbackData->setterCallback(callbackInfo); - return nullptr; - }); - } - - Getter getterCallback; - Setter setterCallback; - void* data; -}; - -// Debugging-purpose C++-style variant of sprintf(). -inline std::string StringFormat(const char* format, ...) { - std::string result; - va_list args; - va_start(args, format); - int len = vsnprintf(nullptr, 0, format, args); - result.resize(len); - vsnprintf(&result[0], len + 1, format, args); - va_end(args); - return result; -} - -template -class HasExtendedFinalizer { - private: - template - struct SFINAE {}; - template - static char test(SFINAE*); - template - static int test(...); - - public: - static constexpr bool value = sizeof(test(0)) == sizeof(char); -}; - -template -class HasBasicFinalizer { - private: - template - struct SFINAE {}; - template - static char test(SFINAE*); - template - static int test(...); - - public: - static constexpr bool value = sizeof(test(0)) == sizeof(char); -}; - -} // namespace details - -#ifndef NODE_ADDON_API_DISABLE_DEPRECATED -#include "napi-inl.deprecated.h" -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED - -//////////////////////////////////////////////////////////////////////////////// -// Module registration -//////////////////////////////////////////////////////////////////////////////// - -// Register an add-on based on an initializer function. -#define NODE_API_MODULE(modname, regfunc) \ - static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \ - return Napi::RegisterModule(env, exports, regfunc); \ - } \ - NAPI_MODULE(modname, __napi_##regfunc) - -// Register an add-on based on a subclass of `Addon` with a custom Node.js -// module name. -#define NODE_API_NAMED_ADDON(modname, classname) \ - static napi_value __napi_##classname(napi_env env, napi_value exports) { \ - return Napi::RegisterModule(env, exports, &classname::Init); \ - } \ - NAPI_MODULE(modname, __napi_##classname) - -// Register an add-on based on a subclass of `Addon` with the Node.js module -// name given by node-gyp from the `target_name` in binding.gyp. -#define NODE_API_ADDON(classname) \ - NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) - -// Adapt the NAPI_MODULE registration function: -// - Wrap the arguments in NAPI wrappers. -// - Catch any NAPI errors and rethrow as JS exceptions. -inline napi_value RegisterModule(napi_env env, - napi_value exports, - ModuleRegisterCallback registerCallback) { - return details::WrapCallback(env, [&] { - return napi_value( - registerCallback(Napi::Env(env), Napi::Object(env, exports))); - }); -} - -//////////////////////////////////////////////////////////////////////////////// -// Maybe class -//////////////////////////////////////////////////////////////////////////////// - -template -bool Maybe::IsNothing() const { - return !_has_value; -} - -template -bool Maybe::IsJust() const { - return _has_value; -} - -template -void Maybe::Check() const { - NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); -} - -template -T Maybe::Unwrap() const { - NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); - return _value; -} - -template -T Maybe::UnwrapOr(const T& default_value) const { - return _has_value ? _value : default_value; -} - -template -bool Maybe::UnwrapTo(T* out) const { - if (IsJust()) { - *out = _value; - return true; - }; - return false; -} - -template -bool Maybe::operator==(const Maybe& other) const { - return (IsJust() == other.IsJust()) && - (!IsJust() || Unwrap() == other.Unwrap()); -} - -template -bool Maybe::operator!=(const Maybe& other) const { - return !operator==(other); -} - -template -Maybe::Maybe() : _has_value(false) {} - -template -Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} - -template -inline Maybe Nothing() { - return Maybe(); -} - -template -inline Maybe Just(const T& t) { - return Maybe(t); -} - -//////////////////////////////////////////////////////////////////////////////// -// BasicEnv / Env class -//////////////////////////////////////////////////////////////////////////////// - -inline BasicEnv::BasicEnv(node_addon_api_basic_env env) : _env(env) {} - -inline BasicEnv::operator node_addon_api_basic_env() const { - return _env; -} - -inline Env::Env(napi_env env) : BasicEnv(env) {} - -inline Env::operator napi_env() const { - return const_cast(_env); -} - -inline Object Env::Global() const { - napi_value value; - napi_status status = napi_get_global(*this, &value); - NAPI_THROW_IF_FAILED(*this, status, Object()); - return Object(*this, value); -} - -inline Value Env::Undefined() const { - napi_value value; - napi_status status = napi_get_undefined(*this, &value); - NAPI_THROW_IF_FAILED(*this, status, Value()); - return Value(*this, value); -} - -inline Value Env::Null() const { - napi_value value; - napi_status status = napi_get_null(*this, &value); - NAPI_THROW_IF_FAILED(*this, status, Value()); - return Value(*this, value); -} - -inline bool Env::IsExceptionPending() const { - bool result; - napi_status status = napi_is_exception_pending(*this, &result); - if (status != napi_ok) - result = false; // Checking for a pending exception shouldn't throw. - return result; -} - -inline Error Env::GetAndClearPendingException() const { - napi_value value; - napi_status status = napi_get_and_clear_last_exception(*this, &value); - if (status != napi_ok) { - // Don't throw another exception when failing to get the exception! - return Error(); - } - return Error(*this, value); -} - -inline MaybeOrValue Env::RunScript(const char* utf8script) const { - String script = String::New(*this, utf8script); - return RunScript(script); -} - -inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { - return RunScript(utf8script.c_str()); -} - -inline MaybeOrValue Env::RunScript(String script) const { - napi_value result; - napi_status status = napi_run_script(*this, script, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - *this, status, Napi::Value(*this, result), Napi::Value); -} - -#if NAPI_VERSION > 2 -template -void BasicEnv::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { - auto* cleanupData = static_cast< - typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); - cleanupData->hook(); - delete cleanupData; -} - -template -void BasicEnv::CleanupHook::WrapperWithArg(void* data) - NAPI_NOEXCEPT { - auto* cleanupData = static_cast< - typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); - cleanupData->hook(static_cast(cleanupData->arg)); - delete cleanupData; -} -#endif // NAPI_VERSION > 2 - -#if NAPI_VERSION > 5 -template fini> -inline void BasicEnv::SetInstanceData(T* data) const { - napi_status status = napi_set_instance_data( - _env, - data, - [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, - nullptr); - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::SetInstanceData", "invalid arguments"); -} - -template fini> -inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const { - napi_status status = napi_set_instance_data( - _env, - data, - [](napi_env env, void* data, void* hint) { - fini(env, static_cast(data), static_cast(hint)); - }, - hint); - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::SetInstanceData", "invalid arguments"); -} - -template -inline T* BasicEnv::GetInstanceData() const { - void* data = nullptr; - - napi_status status = napi_get_instance_data(_env, &data); - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::GetInstanceData", "invalid arguments"); - - return static_cast(data); -} - -template -void BasicEnv::DefaultFini(Env, T* data) { - delete data; -} - -template -void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) { - delete data; -} -#endif // NAPI_VERSION > 5 - -#if NAPI_VERSION > 8 -inline const char* BasicEnv::GetModuleFileName() const { - const char* result; - napi_status status = node_api_get_module_file_name(_env, &result); - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::GetModuleFileName", "invalid arguments"); - return result; -} -#endif // NAPI_VERSION > 8 -//////////////////////////////////////////////////////////////////////////////// -// Value class -//////////////////////////////////////////////////////////////////////////////// - -inline Value::Value() : _env(nullptr), _value(nullptr) {} - -inline Value::Value(napi_env env, napi_value value) - : _env(env), _value(value) {} - -inline Value::operator napi_value() const { - return _value; -} - -inline bool Value::operator==(const Value& other) const { - return StrictEquals(other); -} - -inline bool Value::operator!=(const Value& other) const { - return !this->operator==(other); -} - -inline bool Value::StrictEquals(const Value& other) const { - bool result; - napi_status status = napi_strict_equals(_env, *this, other, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline Napi::Env Value::Env() const { - return Napi::Env(_env); -} - -inline bool Value::IsEmpty() const { - return _value == nullptr; -} - -inline napi_valuetype Value::Type() const { - if (IsEmpty()) { - return napi_undefined; - } - - napi_valuetype type; - napi_status status = napi_typeof(_env, _value, &type); - NAPI_THROW_IF_FAILED(_env, status, napi_undefined); - return type; -} - -inline bool Value::IsUndefined() const { - return Type() == napi_undefined; -} - -inline bool Value::IsNull() const { - return Type() == napi_null; -} - -inline bool Value::IsBoolean() const { - return Type() == napi_boolean; -} - -inline bool Value::IsNumber() const { - return Type() == napi_number; -} - -#if NAPI_VERSION > 5 -inline bool Value::IsBigInt() const { - return Type() == napi_bigint; -} -#endif // NAPI_VERSION > 5 - -#if (NAPI_VERSION > 4) -inline bool Value::IsDate() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_date(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} -#endif - -inline bool Value::IsString() const { - return Type() == napi_string; -} - -inline bool Value::IsSymbol() const { - return Type() == napi_symbol; -} - -inline bool Value::IsArray() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_array(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsArrayBuffer() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_arraybuffer(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsTypedArray() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_typedarray(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsObject() const { - return Type() == napi_object || IsFunction(); -} - -inline bool Value::IsFunction() const { - return Type() == napi_function; -} - -inline bool Value::IsPromise() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_promise(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsDataView() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_dataview(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsBuffer() const { - if (IsEmpty()) { - return false; - } - - bool result; - napi_status status = napi_is_buffer(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -inline bool Value::IsExternal() const { - return Type() == napi_external; -} - -template -inline T Value::As() const { -#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS - T::CheckCast(_env, _value); -#endif - return T(_env, _value); -} - -template -inline T Value::UnsafeAs() const { - return T(_env, _value); -} - -// static -inline void Value::CheckCast(napi_env /* env */, napi_value value) { - NAPI_CHECK(value != nullptr, "Value::CheckCast", "empty value"); -} - -inline MaybeOrValue Value::ToBoolean() const { - napi_value result; - napi_status status = napi_coerce_to_bool(_env, _value, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Boolean(_env, result), Napi::Boolean); -} - -inline MaybeOrValue Value::ToNumber() const { - napi_value result; - napi_status status = napi_coerce_to_number(_env, _value, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Number(_env, result), Napi::Number); -} - -inline MaybeOrValue Value::ToString() const { - napi_value result; - napi_status status = napi_coerce_to_string(_env, _value, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::String(_env, result), Napi::String); -} - -inline MaybeOrValue Value::ToObject() const { - napi_value result; - napi_status status = napi_coerce_to_object(_env, _value, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Object(_env, result), Napi::Object); -} - -//////////////////////////////////////////////////////////////////////////////// -// Boolean class -//////////////////////////////////////////////////////////////////////////////// - -inline Boolean Boolean::New(napi_env env, bool val) { - napi_value value; - napi_status status = napi_get_boolean(env, val, &value); - NAPI_THROW_IF_FAILED(env, status, Boolean()); - return Boolean(env, value); -} - -inline void Boolean::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_boolean, "%d", "Boolean::CheckCast"); -} - -inline Boolean::Boolean() : Napi::Value() {} - -inline Boolean::Boolean(napi_env env, napi_value value) - : Napi::Value(env, value) {} - -inline Boolean::operator bool() const { - return Value(); -} - -inline bool Boolean::Value() const { - bool result; - napi_status status = napi_get_value_bool(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// Number class -//////////////////////////////////////////////////////////////////////////////// - -inline Number Number::New(napi_env env, double val) { - napi_value value; - napi_status status = napi_create_double(env, val, &value); - NAPI_THROW_IF_FAILED(env, status, Number()); - return Number(env, value); -} - -inline void Number::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_number, "%d", "Number::CheckCast"); -} - -inline Number::Number() : Value() {} - -inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} - -inline Number::operator int32_t() const { - return Int32Value(); -} - -inline Number::operator uint32_t() const { - return Uint32Value(); -} - -inline Number::operator int64_t() const { - return Int64Value(); -} - -inline Number::operator float() const { - return FloatValue(); -} - -inline Number::operator double() const { - return DoubleValue(); -} - -inline int32_t Number::Int32Value() const { - int32_t result; - napi_status status = napi_get_value_int32(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -inline uint32_t Number::Uint32Value() const { - uint32_t result; - napi_status status = napi_get_value_uint32(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -inline int64_t Number::Int64Value() const { - int64_t result; - napi_status status = napi_get_value_int64(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -inline float Number::FloatValue() const { - return static_cast(DoubleValue()); -} - -inline double Number::DoubleValue() const { - double result; - napi_status status = napi_get_value_double(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -#if NAPI_VERSION > 5 -//////////////////////////////////////////////////////////////////////////////// -// BigInt Class -//////////////////////////////////////////////////////////////////////////////// - -inline BigInt BigInt::New(napi_env env, int64_t val) { - napi_value value; - napi_status status = napi_create_bigint_int64(env, val, &value); - NAPI_THROW_IF_FAILED(env, status, BigInt()); - return BigInt(env, value); -} - -inline BigInt BigInt::New(napi_env env, uint64_t val) { - napi_value value; - napi_status status = napi_create_bigint_uint64(env, val, &value); - NAPI_THROW_IF_FAILED(env, status, BigInt()); - return BigInt(env, value); -} - -inline BigInt BigInt::New(napi_env env, - int sign_bit, - size_t word_count, - const uint64_t* words) { - napi_value value; - napi_status status = - napi_create_bigint_words(env, sign_bit, word_count, words, &value); - NAPI_THROW_IF_FAILED(env, status, BigInt()); - return BigInt(env, value); -} - -inline void BigInt::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_bigint, "%d", "BigInt::CheckCast"); -} - -inline BigInt::BigInt() : Value() {} - -inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} - -inline int64_t BigInt::Int64Value(bool* lossless) const { - int64_t result; - napi_status status = - napi_get_value_bigint_int64(_env, _value, &result, lossless); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -inline uint64_t BigInt::Uint64Value(bool* lossless) const { - uint64_t result; - napi_status status = - napi_get_value_bigint_uint64(_env, _value, &result, lossless); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -inline size_t BigInt::WordCount() const { - size_t word_count; - napi_status status = - napi_get_value_bigint_words(_env, _value, nullptr, &word_count, nullptr); - NAPI_THROW_IF_FAILED(_env, status, 0); - return word_count; -} - -inline void BigInt::ToWords(int* sign_bit, - size_t* word_count, - uint64_t* words) { - napi_status status = - napi_get_value_bigint_words(_env, _value, sign_bit, word_count, words); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} -#endif // NAPI_VERSION > 5 - -#if (NAPI_VERSION > 4) -//////////////////////////////////////////////////////////////////////////////// -// Date Class -//////////////////////////////////////////////////////////////////////////////// - -inline Date Date::New(napi_env env, double val) { - napi_value value; - napi_status status = napi_create_date(env, val, &value); - NAPI_THROW_IF_FAILED(env, status, Date()); - return Date(env, value); -} - -inline void Date::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_date(env, value, &result); - NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); - NAPI_CHECK(result, "Date::CheckCast", "value is not date"); -} - -inline Date::Date() : Value() {} - -inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} - -inline Date::operator double() const { - return ValueOf(); -} - -inline double Date::ValueOf() const { - double result; - napi_status status = napi_get_date_value(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Name class -//////////////////////////////////////////////////////////////////////////////// -inline void Name::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK(type == napi_string || type == napi_symbol, - "Name::CheckCast", - "value is not napi_string or napi_symbol, got %d.", - type); -} - -inline Name::Name() : Value() {} - -inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} - -//////////////////////////////////////////////////////////////////////////////// -// String class -//////////////////////////////////////////////////////////////////////////////// - -inline String String::New(napi_env env, const std::string& val) { - return String::New(env, val.c_str(), val.size()); -} - -inline String String::New(napi_env env, const std::u16string& val) { - return String::New(env, val.c_str(), val.size()); -} - -inline String String::New(napi_env env, const char* val) { - // TODO(@gabrielschulhof) Remove if-statement when core's error handling is - // available in all supported versions. - if (val == nullptr) { - // Throw an error that looks like it came from core. - NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); - } - napi_value value; - napi_status status = - napi_create_string_utf8(env, val, std::strlen(val), &value); - NAPI_THROW_IF_FAILED(env, status, String()); - return String(env, value); -} - -inline String String::New(napi_env env, const char16_t* val) { - napi_value value; - // TODO(@gabrielschulhof) Remove if-statement when core's error handling is - // available in all supported versions. - if (val == nullptr) { - // Throw an error that looks like it came from core. - NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); - } - napi_status status = - napi_create_string_utf16(env, val, std::u16string(val).size(), &value); - NAPI_THROW_IF_FAILED(env, status, String()); - return String(env, value); -} - -inline String String::New(napi_env env, const char* val, size_t length) { - napi_value value; - napi_status status = napi_create_string_utf8(env, val, length, &value); - NAPI_THROW_IF_FAILED(env, status, String()); - return String(env, value); -} - -inline String String::New(napi_env env, const char16_t* val, size_t length) { - napi_value value; - napi_status status = napi_create_string_utf16(env, val, length, &value); - NAPI_THROW_IF_FAILED(env, status, String()); - return String(env, value); -} - -inline void String::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_string, "%d", "String::CheckCast"); -} - -inline String::String() : Name() {} - -inline String::String(napi_env env, napi_value value) : Name(env, value) {} - -inline String::operator std::string() const { - return Utf8Value(); -} - -inline String::operator std::u16string() const { - return Utf16Value(); -} - -inline std::string String::Utf8Value() const { - size_t length; - napi_status status = - napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); - NAPI_THROW_IF_FAILED(_env, status, ""); - - std::string value; - value.reserve(length + 1); - value.resize(length); - status = napi_get_value_string_utf8( - _env, _value, &value[0], value.capacity(), nullptr); - NAPI_THROW_IF_FAILED(_env, status, ""); - return value; -} - -inline std::u16string String::Utf16Value() const { - size_t length; - napi_status status = - napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); - NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); - - std::u16string value; - value.reserve(length + 1); - value.resize(length); - status = napi_get_value_string_utf16( - _env, _value, &value[0], value.capacity(), nullptr); - NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); - return value; -} - -//////////////////////////////////////////////////////////////////////////////// -// Symbol class -//////////////////////////////////////////////////////////////////////////////// - -inline Symbol Symbol::New(napi_env env, const char* description) { - napi_value descriptionValue = description != nullptr - ? String::New(env, description) - : static_cast(nullptr); - return Symbol::New(env, descriptionValue); -} - -inline Symbol Symbol::New(napi_env env, const std::string& description) { - napi_value descriptionValue = String::New(env, description); - return Symbol::New(env, descriptionValue); -} - -inline Symbol Symbol::New(napi_env env, String description) { - napi_value descriptionValue = description; - return Symbol::New(env, descriptionValue); -} - -inline Symbol Symbol::New(napi_env env, napi_value description) { - napi_value value; - napi_status status = napi_create_symbol(env, description, &value); - NAPI_THROW_IF_FAILED(env, status, Symbol()); - return Symbol(env, value); -} - -inline MaybeOrValue Symbol::WellKnown(napi_env env, - const std::string& name) { - // No need to check if the return value is a symbol or undefined. - // Well known symbols are definite and it is an develop time error - // if the symbol does not exist. -#if defined(NODE_ADDON_API_ENABLE_MAYBE) - Value symbol_obj; - Value symbol_value; - if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && - symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { - return Just(symbol_value.UnsafeAs()); - } - return Nothing(); -#else - return Napi::Env(env) - .Global() - .Get("Symbol") - .As() - .Get(name) - .UnsafeAs(); -#endif -} - -inline MaybeOrValue Symbol::For(napi_env env, - const std::string& description) { - napi_value descriptionValue = String::New(env, description); - return Symbol::For(env, descriptionValue); -} - -inline MaybeOrValue Symbol::For(napi_env env, const char* description) { - napi_value descriptionValue = String::New(env, description); - return Symbol::For(env, descriptionValue); -} - -inline MaybeOrValue Symbol::For(napi_env env, String description) { - return Symbol::For(env, static_cast(description)); -} - -inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { -#if defined(NODE_ADDON_API_ENABLE_MAYBE) - Value symbol_obj; - Value symbol_for_value; - Value symbol_value; - if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && - symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && - symbol_for_value.As() - .Call(symbol_obj, {description}) - .UnwrapTo(&symbol_value)) { - return Just(symbol_value.As()); - } - return Nothing(); -#else - Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); - return symbol_obj.Get("for") - .As() - .Call(symbol_obj, {description}) - .As(); -#endif -} - -inline void Symbol::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_symbol, "%d", "Symbol::CheckCast"); -} - -inline Symbol::Symbol() : Name() {} - -inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} - -//////////////////////////////////////////////////////////////////////////////// -// Automagic value creation -//////////////////////////////////////////////////////////////////////////////// - -namespace details { -template -struct vf_number { - static Number From(napi_env env, T value) { - return Number::New(env, static_cast(value)); - } -}; - -template <> -struct vf_number { - static Boolean From(napi_env env, bool value) { - return Boolean::New(env, value); - } -}; - -struct vf_utf8_charp { - static String From(napi_env env, const char* value) { - return String::New(env, value); - } -}; - -struct vf_utf16_charp { - static String From(napi_env env, const char16_t* value) { - return String::New(env, value); - } -}; -struct vf_utf8_string { - static String From(napi_env env, const std::string& value) { - return String::New(env, value); - } -}; - -struct vf_utf16_string { - static String From(napi_env env, const std::u16string& value) { - return String::New(env, value); - } -}; - -template -struct vf_fallback { - static Value From(napi_env env, const T& value) { return Value(env, value); } -}; - -template -struct disjunction : std::false_type {}; -template -struct disjunction : B {}; -template -struct disjunction - : std::conditional>::type {}; - -template -struct can_make_string - : disjunction::type, - typename std::is_convertible::type, - typename std::is_convertible::type, - typename std::is_convertible::type> {}; -} // namespace details - -template -Value Value::From(napi_env env, const T& value) { - using Helper = typename std::conditional< - std::is_integral::value || std::is_floating_point::value, - details::vf_number, - typename std::conditional::value, - String, - details::vf_fallback>::type>::type; - return Helper::From(env, value); -} - -template -String String::From(napi_env env, const T& value) { - struct Dummy {}; - using Helper = typename std::conditional< - std::is_convertible::value, - details::vf_utf8_charp, - typename std::conditional< - std::is_convertible::value, - details::vf_utf16_charp, - typename std::conditional< - std::is_convertible::value, - details::vf_utf8_string, - typename std::conditional< - std::is_convertible::value, - details::vf_utf16_string, - Dummy>::type>::type>::type>::type; - return Helper::From(env, value); -} - -//////////////////////////////////////////////////////////////////////////////// -// TypeTaggable class -//////////////////////////////////////////////////////////////////////////////// - -inline TypeTaggable::TypeTaggable() : Value() {} - -inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value) - : Value(_env, _value) {} - -#if NAPI_VERSION >= 8 - -inline void TypeTaggable::TypeTag(const napi_type_tag* type_tag) const { - napi_status status = napi_type_tag_object(_env, _value, type_tag); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const { - bool result; - napi_status status = - napi_check_object_type_tag(_env, _value, type_tag, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; -} - -#endif // NAPI_VERSION >= 8 - -//////////////////////////////////////////////////////////////////////////////// -// Object class -//////////////////////////////////////////////////////////////////////////////// - -template -inline Object::PropertyLValue::operator Value() const { - MaybeOrValue val = Object(_env, _object).Get(_key); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - return val.Unwrap(); -#else - return val; -#endif -} - -template -template -inline Object::PropertyLValue& Object::PropertyLValue::operator=( - ValueType value) { -#ifdef NODE_ADDON_API_ENABLE_MAYBE - MaybeOrValue result = -#endif - Object(_env, _object).Set(_key, value); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - result.Unwrap(); -#endif - return *this; -} - -template -inline Value Object::PropertyLValue::AsValue() const { - return Value(*this); -} - -template -inline Object::PropertyLValue::PropertyLValue(Object object, Key key) - : _env(object.Env()), _object(object), _key(key) {} - -inline Object Object::New(napi_env env) { - napi_value value; - napi_status status = napi_create_object(env, &value); - NAPI_THROW_IF_FAILED(env, status, Object()); - return Object(env, value); -} - -inline void Object::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK(type == napi_object || type == napi_function, - "Object::CheckCast", - "Expect napi_object or napi_function, but got %d.", - type); -} - -inline Object::Object() : TypeTaggable() {} - -inline Object::Object(napi_env env, napi_value value) - : TypeTaggable(env, value) {} - -inline Object::PropertyLValue Object::operator[]( - const char* utf8name) { - return PropertyLValue(*this, utf8name); -} - -inline Object::PropertyLValue Object::operator[]( - const std::string& utf8name) { - return PropertyLValue(*this, utf8name); -} - -inline Object::PropertyLValue Object::operator[](uint32_t index) { - return PropertyLValue(*this, index); -} - -inline Object::PropertyLValue Object::operator[](Value index) const { - return PropertyLValue(*this, index); -} - -inline MaybeOrValue Object::operator[](const char* utf8name) const { - return Get(utf8name); -} - -inline MaybeOrValue Object::operator[]( - const std::string& utf8name) const { - return Get(utf8name); -} - -inline MaybeOrValue Object::operator[](uint32_t index) const { - return Get(index); -} - -inline MaybeOrValue Object::Has(napi_value key) const { - bool result; - napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Has(Value key) const { - bool result; - napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Has(const char* utf8name) const { - bool result; - napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Has(const std::string& utf8name) const { - return Has(utf8name.c_str()); -} - -inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { - bool result; - napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::HasOwnProperty(Value key) const { - bool result; - napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { - napi_value key; - napi_status status = - napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); - NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); - return HasOwnProperty(key); -} - -inline MaybeOrValue Object::HasOwnProperty( - const std::string& utf8name) const { - return HasOwnProperty(utf8name.c_str()); -} - -inline MaybeOrValue Object::Get(napi_value key) const { - napi_value result; - napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); -} - -inline MaybeOrValue Object::Get(Value key) const { - napi_value result; - napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); -} - -inline MaybeOrValue Object::Get(const char* utf8name) const { - napi_value result; - napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); -} - -inline MaybeOrValue Object::Get(const std::string& utf8name) const { - return Get(utf8name.c_str()); -} - -template -inline MaybeOrValue Object::Set(napi_value key, - const ValueType& value) const { - napi_status status = - napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -template -inline MaybeOrValue Object::Set(Value key, const ValueType& value) const { - napi_status status = - napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -template -inline MaybeOrValue Object::Set(const char* utf8name, - const ValueType& value) const { - napi_status status = - napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -template -inline MaybeOrValue Object::Set(const std::string& utf8name, - const ValueType& value) const { - return Set(utf8name.c_str(), value); -} - -inline MaybeOrValue Object::Delete(napi_value key) const { - bool result; - napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Delete(Value key) const { - bool result; - napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Delete(const char* utf8name) const { - return Delete(String::New(_env, utf8name)); -} - -inline MaybeOrValue Object::Delete(const std::string& utf8name) const { - return Delete(String::New(_env, utf8name)); -} - -inline MaybeOrValue Object::Has(uint32_t index) const { - bool result; - napi_status status = napi_has_element(_env, _value, index, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::Get(uint32_t index) const { - napi_value value; - napi_status status = napi_get_element(_env, _value, index, &value); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); -} - -template -inline MaybeOrValue Object::Set(uint32_t index, - const ValueType& value) const { - napi_status status = - napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -inline MaybeOrValue Object::Delete(uint32_t index) const { - bool result; - napi_status status = napi_delete_element(_env, _value, index, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -inline MaybeOrValue Object::GetPropertyNames() const { - napi_value result; - napi_status status = napi_get_property_names(_env, _value, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); -} - -inline MaybeOrValue Object::DefineProperty( - const PropertyDescriptor& property) const { - napi_status status = napi_define_properties( - _env, - _value, - 1, - reinterpret_cast(&property)); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -inline MaybeOrValue Object::DefineProperties( - const std::initializer_list& properties) const { - napi_status status = napi_define_properties( - _env, - _value, - properties.size(), - reinterpret_cast(properties.begin())); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -inline MaybeOrValue Object::DefineProperties( - const std::vector& properties) const { - napi_status status = napi_define_properties( - _env, - _value, - properties.size(), - reinterpret_cast(properties.data())); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -inline MaybeOrValue Object::InstanceOf( - const Function& constructor) const { - bool result; - napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); -} - -template -inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - napi_status status = - details::AttachData::Wrapper>( - _env, *this, data, finalizeData); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED_VOID(_env, status); - } -} - -template -inline void Object::AddFinalizer(Finalizer finalizeCallback, - T* data, - Hint* finalizeHint) const { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); - napi_status status = details:: - AttachData::WrapperWithHint>( - _env, *this, data, finalizeData); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED_VOID(_env, status); - } -} - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS -inline Object::const_iterator::const_iterator(const Object* object, - const Type type) { - _object = object; - _keys = object->GetPropertyNames(); - _index = type == Type::BEGIN ? 0 : _keys.Length(); -} - -inline Object::const_iterator Napi::Object::begin() const { - const_iterator it(this, Object::const_iterator::Type::BEGIN); - return it; -} - -inline Object::const_iterator Napi::Object::end() const { - const_iterator it(this, Object::const_iterator::Type::END); - return it; -} - -inline Object::const_iterator& Object::const_iterator::operator++() { - ++_index; - return *this; -} - -inline bool Object::const_iterator::operator==( - const const_iterator& other) const { - return _index == other._index; -} - -inline bool Object::const_iterator::operator!=( - const const_iterator& other) const { - return _index != other._index; -} - -inline const std::pair> -Object::const_iterator::operator*() const { - const Value key = _keys[_index]; - const PropertyLValue value = (*_object)[key]; - return {key, value}; -} - -inline Object::iterator::iterator(Object* object, const Type type) { - _object = object; - _keys = object->GetPropertyNames(); - _index = type == Type::BEGIN ? 0 : _keys.Length(); -} - -inline Object::iterator Napi::Object::begin() { - iterator it(this, Object::iterator::Type::BEGIN); - return it; -} - -inline Object::iterator Napi::Object::end() { - iterator it(this, Object::iterator::Type::END); - return it; -} - -inline Object::iterator& Object::iterator::operator++() { - ++_index; - return *this; -} - -inline bool Object::iterator::operator==(const iterator& other) const { - return _index == other._index; -} - -inline bool Object::iterator::operator!=(const iterator& other) const { - return _index != other._index; -} - -inline std::pair> -Object::iterator::operator*() { - Value key = _keys[_index]; - PropertyLValue value = (*_object)[key]; - return {key, value}; -} -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - -#if NAPI_VERSION >= 8 -inline MaybeOrValue Object::Freeze() const { - napi_status status = napi_object_freeze(_env, _value); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} - -inline MaybeOrValue Object::Seal() const { - napi_status status = napi_object_seal(_env, _value); - NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); -} -#endif // NAPI_VERSION >= 8 - -//////////////////////////////////////////////////////////////////////////////// -// External class -//////////////////////////////////////////////////////////////////////////////// - -template -inline External External::New(napi_env env, T* data) { - napi_value value; - napi_status status = - napi_create_external(env, data, nullptr, nullptr, &value); - NAPI_THROW_IF_FAILED(env, status, External()); - return External(env, value); -} - -template -template -inline External External::New(napi_env env, - T* data, - Finalizer finalizeCallback) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - napi_status status = - napi_create_external(env, - data, - details::FinalizeData::Wrapper, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, External()); - } - return External(env, value); -} - -template -template -inline External External::New(napi_env env, - T* data, - Finalizer finalizeCallback, - Hint* finalizeHint) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); - napi_status status = napi_create_external( - env, - data, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, External()); - } - return External(env, value); -} - -template -inline void External::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_external, "%d", "External::CheckCast"); -} - -template -inline External::External() : TypeTaggable() {} - -template -inline External::External(napi_env env, napi_value value) - : TypeTaggable(env, value) {} - -template -inline T* External::Data() const { - void* data; - napi_status status = napi_get_value_external(_env, _value, &data); - NAPI_THROW_IF_FAILED(_env, status, nullptr); - return reinterpret_cast(data); -} - -//////////////////////////////////////////////////////////////////////////////// -// Array class -//////////////////////////////////////////////////////////////////////////////// - -inline Array Array::New(napi_env env) { - napi_value value; - napi_status status = napi_create_array(env, &value); - NAPI_THROW_IF_FAILED(env, status, Array()); - return Array(env, value); -} - -inline Array Array::New(napi_env env, size_t length) { - napi_value value; - napi_status status = napi_create_array_with_length(env, length, &value); - NAPI_THROW_IF_FAILED(env, status, Array()); - return Array(env, value); -} - -inline void Array::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_array(env, value, &result); - NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); - NAPI_CHECK(result, "Array::CheckCast", "value is not array"); -} - -inline Array::Array() : Object() {} - -inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} - -inline uint32_t Array::Length() const { - uint32_t result; - napi_status status = napi_get_array_length(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// ArrayBuffer class -//////////////////////////////////////////////////////////////////////////////// - -inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { - napi_value value; - void* data; - napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); - NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - - return ArrayBuffer(env, value); -} - -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -inline ArrayBuffer ArrayBuffer::New(napi_env env, - void* externalData, - size_t byteLength) { - napi_value value; - napi_status status = napi_create_external_arraybuffer( - env, externalData, byteLength, nullptr, nullptr, &value); - NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - - return ArrayBuffer(env, value); -} - -template -inline ArrayBuffer ArrayBuffer::New(napi_env env, - void* externalData, - size_t byteLength, - Finalizer finalizeCallback) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - napi_status status = napi_create_external_arraybuffer( - env, - externalData, - byteLength, - details::FinalizeData::Wrapper, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - } - - return ArrayBuffer(env, value); -} - -template -inline ArrayBuffer ArrayBuffer::New(napi_env env, - void* externalData, - size_t byteLength, - Finalizer finalizeCallback, - Hint* finalizeHint) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); - napi_status status = napi_create_external_arraybuffer( - env, - externalData, - byteLength, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - } - - return ArrayBuffer(env, value); -} -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - -inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_arraybuffer(env, value, &result); - NAPI_CHECK(status == napi_ok, - "ArrayBuffer::CheckCast", - "napi_is_arraybuffer failed"); - NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); -} - -inline ArrayBuffer::ArrayBuffer() : Object() {} - -inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) - : Object(env, value) {} - -inline void* ArrayBuffer::Data() { - void* data; - napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); - NAPI_THROW_IF_FAILED(_env, status, nullptr); - return data; -} - -inline size_t ArrayBuffer::ByteLength() { - size_t length; - napi_status status = - napi_get_arraybuffer_info(_env, _value, nullptr, &length); - NAPI_THROW_IF_FAILED(_env, status, 0); - return length; -} - -#if NAPI_VERSION >= 7 -inline bool ArrayBuffer::IsDetached() const { - bool detached; - napi_status status = napi_is_detached_arraybuffer(_env, _value, &detached); - NAPI_THROW_IF_FAILED(_env, status, false); - return detached; -} - -inline void ArrayBuffer::Detach() { - napi_status status = napi_detach_arraybuffer(_env, _value); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} -#endif // NAPI_VERSION >= 7 - -//////////////////////////////////////////////////////////////////////////////// -// DataView class -//////////////////////////////////////////////////////////////////////////////// -inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer) { - return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); -} - -inline DataView DataView::New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset) { - if (byteOffset > arrayBuffer.ByteLength()) { - NAPI_THROW(RangeError::New( - env, "Start offset is outside the bounds of the buffer"), - DataView()); - } - return New( - env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); -} - -inline DataView DataView::New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset, - size_t byteLength) { - if (byteOffset + byteLength > arrayBuffer.ByteLength()) { - NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); - } - napi_value value; - napi_status status = - napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); - NAPI_THROW_IF_FAILED(env, status, DataView()); - return DataView(env, value); -} - -inline void DataView::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_dataview(env, value, &result); - NAPI_CHECK( - status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); - NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); -} - -inline DataView::DataView() : Object() {} - -inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { - napi_status status = napi_get_dataview_info(_env, - _value /* dataView */, - &_length /* byteLength */, - &_data /* data */, - nullptr /* arrayBuffer */, - nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline Napi::ArrayBuffer DataView::ArrayBuffer() const { - napi_value arrayBuffer; - napi_status status = napi_get_dataview_info(_env, - _value /* dataView */, - nullptr /* byteLength */, - nullptr /* data */, - &arrayBuffer /* arrayBuffer */, - nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); - return Napi::ArrayBuffer(_env, arrayBuffer); -} - -inline size_t DataView::ByteOffset() const { - size_t byteOffset; - napi_status status = napi_get_dataview_info(_env, - _value /* dataView */, - nullptr /* byteLength */, - nullptr /* data */, - nullptr /* arrayBuffer */, - &byteOffset /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, 0); - return byteOffset; -} - -inline size_t DataView::ByteLength() const { - return _length; -} - -inline void* DataView::Data() const { - return _data; -} - -inline float DataView::GetFloat32(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline double DataView::GetFloat64(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline int8_t DataView::GetInt8(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline int16_t DataView::GetInt16(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline int32_t DataView::GetInt32(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline uint8_t DataView::GetUint8(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline uint16_t DataView::GetUint16(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline uint32_t DataView::GetUint32(size_t byteOffset) const { - return ReadData(byteOffset); -} - -inline void DataView::SetFloat32(size_t byteOffset, float value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetFloat64(size_t byteOffset, double value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetInt8(size_t byteOffset, int8_t value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetInt16(size_t byteOffset, int16_t value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetInt32(size_t byteOffset, int32_t value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const { - WriteData(byteOffset, value); -} - -inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const { - WriteData(byteOffset, value); -} - -template -inline T DataView::ReadData(size_t byteOffset) const { - if (byteOffset + sizeof(T) > _length || - byteOffset + sizeof(T) < byteOffset) { // overflow - NAPI_THROW( - RangeError::New(_env, "Offset is outside the bounds of the DataView"), - 0); - } - - return *reinterpret_cast(static_cast(_data) + byteOffset); -} - -template -inline void DataView::WriteData(size_t byteOffset, T value) const { - if (byteOffset + sizeof(T) > _length || - byteOffset + sizeof(T) < byteOffset) { // overflow - NAPI_THROW_VOID( - RangeError::New(_env, "Offset is outside the bounds of the DataView")); - } - - *reinterpret_cast(static_cast(_data) + byteOffset) = value; -} - -//////////////////////////////////////////////////////////////////////////////// -// TypedArray class -//////////////////////////////////////////////////////////////////////////////// -inline void TypedArray::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_typedarray(env, value, &result); - NAPI_CHECK( - status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); - NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); -} - -inline TypedArray::TypedArray() - : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} - -inline TypedArray::TypedArray(napi_env env, napi_value value) - : Object(env, value), - _type(napi_typedarray_type::napi_int8_array), - _length(0) { - if (value != nullptr) { - napi_status status = - napi_get_typedarray_info(_env, - _value, - &const_cast(this)->_type, - &const_cast(this)->_length, - nullptr, - nullptr, - nullptr); - NAPI_THROW_IF_FAILED_VOID(_env, status); - } -} - -inline TypedArray::TypedArray(napi_env env, - napi_value value, - napi_typedarray_type type, - size_t length) - : Object(env, value), _type(type), _length(length) {} - -inline napi_typedarray_type TypedArray::TypedArrayType() const { - return _type; -} - -inline uint8_t TypedArray::ElementSize() const { - switch (_type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: -#if (NAPI_VERSION > 5) - case napi_bigint64_array: - case napi_biguint64_array: -#endif // (NAPI_VERSION > 5) - return 8; - default: - return 0; - } -} - -inline size_t TypedArray::ElementLength() const { - return _length; -} - -inline size_t TypedArray::ByteOffset() const { - size_t byteOffset; - napi_status status = napi_get_typedarray_info( - _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); - NAPI_THROW_IF_FAILED(_env, status, 0); - return byteOffset; -} - -inline size_t TypedArray::ByteLength() const { - return ElementSize() * ElementLength(); -} - -inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { - napi_value arrayBuffer; - napi_status status = napi_get_typedarray_info( - _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); - NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); - return Napi::ArrayBuffer(_env, arrayBuffer); -} - -//////////////////////////////////////////////////////////////////////////////// -// TypedArrayOf class -//////////////////////////////////////////////////////////////////////////////// -template -inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { - TypedArray::CheckCast(env, value); - napi_typedarray_type type; - napi_status status = napi_get_typedarray_info( - env, value, &type, nullptr, nullptr, nullptr, nullptr); - NAPI_CHECK(status == napi_ok, - "TypedArrayOf::CheckCast", - "napi_is_typedarray failed"); - - NAPI_INTERNAL_CHECK( - (type == TypedArrayTypeForPrimitiveType() || - (type == napi_uint8_clamped_array && std::is_same::value)), - "TypedArrayOf::CheckCast", - "Array type must match the template parameter, (Uint8 arrays may " - "optionally have the \"clamped\" array type.), got %d.", - type); -} - -template -inline TypedArrayOf TypedArrayOf::New(napi_env env, - size_t elementLength, - napi_typedarray_type type) { - Napi::ArrayBuffer arrayBuffer = - Napi::ArrayBuffer::New(env, elementLength * sizeof(T)); - return New(env, elementLength, arrayBuffer, 0, type); -} - -template -inline TypedArrayOf TypedArrayOf::New(napi_env env, - size_t elementLength, - Napi::ArrayBuffer arrayBuffer, - size_t bufferOffset, - napi_typedarray_type type) { - napi_value value; - napi_status status = napi_create_typedarray( - env, type, elementLength, arrayBuffer, bufferOffset, &value); - NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); - - return TypedArrayOf( - env, - value, - type, - elementLength, - reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + - bufferOffset)); -} - -template -inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} - -template -inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) - : TypedArray(env, value), _data(nullptr) { - napi_status status = napi_ok; - if (value != nullptr) { - void* data = nullptr; - status = napi_get_typedarray_info( - _env, _value, &_type, &_length, &data, nullptr, nullptr); - _data = static_cast(data); - } else { - _type = TypedArrayTypeForPrimitiveType(); - _length = 0; - } - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -template -inline TypedArrayOf::TypedArrayOf(napi_env env, - napi_value value, - napi_typedarray_type type, - size_t length, - T* data) - : TypedArray(env, value, type, length), _data(data) { - if (!(type == TypedArrayTypeForPrimitiveType() || - (type == napi_uint8_clamped_array && - std::is_same::value))) { - NAPI_THROW_VOID(TypeError::New( - env, - "Array type must match the template parameter. " - "(Uint8 arrays may optionally have the \"clamped\" array type.)")); - } -} - -template -inline T& TypedArrayOf::operator[](size_t index) { - return _data[index]; -} - -template -inline const T& TypedArrayOf::operator[](size_t index) const { - return _data[index]; -} - -template -inline T* TypedArrayOf::Data() { - return _data; -} - -template -inline const T* TypedArrayOf::Data() const { - return _data; -} - -//////////////////////////////////////////////////////////////////////////////// -// Function class -//////////////////////////////////////////////////////////////////////////////// - -template -inline napi_status CreateFunction(napi_env env, - const char* utf8name, - napi_callback cb, - CbData* data, - napi_value* result) { - napi_status status = - napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); - if (status == napi_ok) { - status = Napi::details::AttachData(env, *result, data); - } - - return status; -} - -template -inline Function Function::New(napi_env env, const char* utf8name, void* data) { - napi_value result = nullptr; - napi_status status = napi_create_function(env, - utf8name, - NAPI_AUTO_LENGTH, - details::TemplatedVoidCallback, - data, - &result); - NAPI_THROW_IF_FAILED(env, status, Function()); - return Function(env, result); -} - -template -inline Function Function::New(napi_env env, const char* utf8name, void* data) { - napi_value result = nullptr; - napi_status status = napi_create_function(env, - utf8name, - NAPI_AUTO_LENGTH, - details::TemplatedCallback, - data, - &result); - NAPI_THROW_IF_FAILED(env, status, Function()); - return Function(env, result); -} - -template -inline Function Function::New(napi_env env, - const std::string& utf8name, - void* data) { - return Function::New(env, utf8name.c_str(), data); -} - -template -inline Function Function::New(napi_env env, - const std::string& utf8name, - void* data) { - return Function::New(env, utf8name.c_str(), data); -} - -template -inline Function Function::New(napi_env env, - Callable cb, - const char* utf8name, - void* data) { - using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); - using CbData = details::CallbackData; - auto callbackData = new CbData{std::move(cb), data}; - - napi_value value; - napi_status status = - CreateFunction(env, utf8name, CbData::Wrapper, callbackData, &value); - if (status != napi_ok) { - delete callbackData; - NAPI_THROW_IF_FAILED(env, status, Function()); - } - - return Function(env, value); -} - -template -inline Function Function::New(napi_env env, - Callable cb, - const std::string& utf8name, - void* data) { - return New(env, cb, utf8name.c_str(), data); -} - -inline void Function::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); - - napi_valuetype type; - napi_status status = napi_typeof(env, value, &type); - NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); - NAPI_INTERNAL_CHECK_EQ(type, napi_function, "%d", "Function::CheckCast"); -} - -inline Function::Function() : Object() {} - -inline Function::Function(napi_env env, napi_value value) - : Object(env, value) {} - -inline MaybeOrValue Function::operator()( - const std::initializer_list& args) const { - return Call(Env().Undefined(), args); -} - -inline MaybeOrValue Function::Call( - const std::initializer_list& args) const { - return Call(Env().Undefined(), args); -} - -inline MaybeOrValue Function::Call( - const std::vector& args) const { - return Call(Env().Undefined(), args); -} - -inline MaybeOrValue Function::Call( - const std::vector& args) const { - return Call(Env().Undefined(), args); -} - -inline MaybeOrValue Function::Call(size_t argc, - const napi_value* args) const { - return Call(Env().Undefined(), argc, args); -} - -inline MaybeOrValue Function::Call( - napi_value recv, const std::initializer_list& args) const { - return Call(recv, args.size(), args.begin()); -} - -inline MaybeOrValue Function::Call( - napi_value recv, const std::vector& args) const { - return Call(recv, args.size(), args.data()); -} - -inline MaybeOrValue Function::Call( - napi_value recv, const std::vector& args) const { - const size_t argc = args.size(); - const size_t stackArgsCount = 6; - napi_value stackArgs[stackArgsCount]; - std::vector heapArgs; - napi_value* argv; - if (argc <= stackArgsCount) { - argv = stackArgs; - } else { - heapArgs.resize(argc); - argv = heapArgs.data(); - } - - for (size_t index = 0; index < argc; index++) { - argv[index] = static_cast(args[index]); - } - - return Call(recv, argc, argv); -} - -inline MaybeOrValue Function::Call(napi_value recv, - size_t argc, - const napi_value* args) const { - napi_value result; - napi_status status = - napi_call_function(_env, recv, _value, argc, args, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Value(_env, result), Napi::Value); -} - -inline MaybeOrValue Function::MakeCallback( - napi_value recv, - const std::initializer_list& args, - napi_async_context context) const { - return MakeCallback(recv, args.size(), args.begin(), context); -} - -inline MaybeOrValue Function::MakeCallback( - napi_value recv, - const std::vector& args, - napi_async_context context) const { - return MakeCallback(recv, args.size(), args.data(), context); -} - -inline MaybeOrValue Function::MakeCallback( - napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context) const { - napi_value result; - napi_status status = - napi_make_callback(_env, context, recv, _value, argc, args, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Value(_env, result), Napi::Value); -} - -inline MaybeOrValue Function::New( - const std::initializer_list& args) const { - return New(args.size(), args.begin()); -} - -inline MaybeOrValue Function::New( - const std::vector& args) const { - return New(args.size(), args.data()); -} - -inline MaybeOrValue Function::New(size_t argc, - const napi_value* args) const { - napi_value result; - napi_status status = napi_new_instance(_env, _value, argc, args, &result); - NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Object(_env, result), Napi::Object); -} - -//////////////////////////////////////////////////////////////////////////////// -// Promise class -//////////////////////////////////////////////////////////////////////////////// - -inline Promise::Deferred Promise::Deferred::New(napi_env env) { - return Promise::Deferred(env); -} - -inline Promise::Deferred::Deferred(napi_env env) : _env(env) { - napi_status status = napi_create_promise(_env, &_deferred, &_promise); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline Promise Promise::Deferred::Promise() const { - return Napi::Promise(_env, _promise); -} - -inline Napi::Env Promise::Deferred::Env() const { - return Napi::Env(_env); -} - -inline void Promise::Deferred::Resolve(napi_value value) const { - napi_status status = napi_resolve_deferred(_env, _deferred, value); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline void Promise::Deferred::Reject(napi_value value) const { - napi_status status = napi_reject_deferred(_env, _deferred, value); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline void Promise::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_promise(env, value, &result); - NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); - NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); -} - -inline Promise::Promise() : Object() {} - -inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} - -inline MaybeOrValue Promise::Then(napi_value onFulfilled) const { - EscapableHandleScope scope(_env); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - Value thenMethod; - if (!Get("then").UnwrapTo(&thenMethod)) { - return Nothing(); - } - MaybeOrValue result = - thenMethod.As().Call(*this, {onFulfilled}); - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap()).As()); - } - return Nothing(); -#else - Function thenMethod = Get("then").As(); - MaybeOrValue result = thenMethod.Call(*this, {onFulfilled}); - if (scope.Env().IsExceptionPending()) { - return Promise(); - } - return scope.Escape(result).As(); -#endif -} - -inline MaybeOrValue Promise::Then(napi_value onFulfilled, - napi_value onRejected) const { - EscapableHandleScope scope(_env); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - Value thenMethod; - if (!Get("then").UnwrapTo(&thenMethod)) { - return Nothing(); - } - MaybeOrValue result = - thenMethod.As().Call(*this, {onFulfilled, onRejected}); - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap()).As()); - } - return Nothing(); -#else - Function thenMethod = Get("then").As(); - MaybeOrValue result = - thenMethod.Call(*this, {onFulfilled, onRejected}); - if (scope.Env().IsExceptionPending()) { - return Promise(); - } - return scope.Escape(result).As(); -#endif -} - -inline MaybeOrValue Promise::Catch(napi_value onRejected) const { - EscapableHandleScope scope(_env); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - Value catchMethod; - if (!Get("catch").UnwrapTo(&catchMethod)) { - return Nothing(); - } - MaybeOrValue result = - catchMethod.As().Call(*this, {onRejected}); - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap()).As()); - } - return Nothing(); -#else - Function catchMethod = Get("catch").As(); - MaybeOrValue result = catchMethod.Call(*this, {onRejected}); - if (scope.Env().IsExceptionPending()) { - return Promise(); - } - return scope.Escape(result).As(); -#endif -} - -inline MaybeOrValue Promise::Then(const Function& onFulfilled) const { - return Then(static_cast(onFulfilled)); -} - -inline MaybeOrValue Promise::Then(const Function& onFulfilled, - const Function& onRejected) const { - return Then(static_cast(onFulfilled), - static_cast(onRejected)); -} - -inline MaybeOrValue Promise::Catch(const Function& onRejected) const { - return Catch(static_cast(onRejected)); -} - -//////////////////////////////////////////////////////////////////////////////// -// Buffer class -//////////////////////////////////////////////////////////////////////////////// - -template -inline Buffer Buffer::New(napi_env env, size_t length) { - napi_value value; - void* data; - napi_status status = - napi_create_buffer(env, length * sizeof(T), &data, &value); - NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value); -} - -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -template -inline Buffer Buffer::New(napi_env env, T* data, size_t length) { - napi_value value; - napi_status status = napi_create_external_buffer( - env, length * sizeof(T), data, nullptr, nullptr, &value); - NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value); -} - -template -template -inline Buffer Buffer::New(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - napi_status status = - napi_create_external_buffer(env, - length * sizeof(T), - data, - details::FinalizeData::Wrapper, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, Buffer()); - } - return Buffer(env, value); -} - -template -template -inline Buffer Buffer::New(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback, - Hint* finalizeHint) { - napi_value value; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); - napi_status status = napi_create_external_buffer( - env, - length * sizeof(T), - data, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, Buffer()); - } - return Buffer(env, value); -} -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - -template -inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - napi_value value; - napi_status status = napi_create_external_buffer( - env, length * sizeof(T), data, nullptr, nullptr, &value); - if (status == details::napi_no_external_buffers_allowed) { -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - // If we can't create an external buffer, we'll just copy the data. - return Buffer::Copy(env, data, length); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - } - NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -} - -template -template -inline Buffer Buffer::NewOrCopy(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback) { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - napi_value value; - napi_status status = - napi_create_external_buffer(env, - length * sizeof(T), - data, - details::FinalizeData::Wrapper, - finalizeData, - &value); - if (status == details::napi_no_external_buffers_allowed) { -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - // If we can't create an external buffer, we'll just copy the data. - Buffer ret = Buffer::Copy(env, data, length); - details::FinalizeData::WrapperGC(env, data, finalizeData); - return ret; -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - } - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, Buffer()); - } - return Buffer(env, value); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -} - -template -template -inline Buffer Buffer::NewOrCopy(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback, - Hint* finalizeHint) { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - napi_value value; - napi_status status = napi_create_external_buffer( - env, - length * sizeof(T), - data, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); - if (status == details::napi_no_external_buffers_allowed) { -#endif - // If we can't create an external buffer, we'll just copy the data. - Buffer ret = Buffer::Copy(env, data, length); - details::FinalizeData::WrapperGCWithHint( - env, data, finalizeData); - return ret; -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - } - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, Buffer()); - } - return Buffer(env, value); -#endif -} - -template -inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { - napi_value value; - napi_status status = - napi_create_buffer_copy(env, length * sizeof(T), data, nullptr, &value); - NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value); -} - -template -inline void Buffer::CheckCast(napi_env env, napi_value value) { - NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); - - bool result; - napi_status status = napi_is_buffer(env, value, &result); - NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); - NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); -} - -template -inline Buffer::Buffer() : Uint8Array() {} - -template -inline Buffer::Buffer(napi_env env, napi_value value) - : Uint8Array(env, value) {} - -template -inline size_t Buffer::Length() const { - return ByteLength() / sizeof(T); -} - -template -inline T* Buffer::Data() const { - return reinterpret_cast(const_cast(Uint8Array::Data())); -} - -//////////////////////////////////////////////////////////////////////////////// -// Error class -//////////////////////////////////////////////////////////////////////////////// - -inline Error Error::New(napi_env env) { - napi_status status; - napi_value error = nullptr; - bool is_exception_pending; - napi_extended_error_info last_error_info_copy; - - { - // We must retrieve the last error info before doing anything else because - // doing anything else will replace the last error info. - const napi_extended_error_info* last_error_info; - status = napi_get_last_error_info(env, &last_error_info); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); - - // All fields of the `napi_extended_error_info` structure gets reset in - // subsequent Node-API function calls on the same `env`. This includes a - // call to `napi_is_exception_pending()`. So here it is necessary to make a - // copy of the information as the `error_code` field is used later on. - memcpy(&last_error_info_copy, - last_error_info, - sizeof(napi_extended_error_info)); - } - - status = napi_is_exception_pending(env, &is_exception_pending); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); - - // A pending exception takes precedence over any internal error status. - if (is_exception_pending) { - status = napi_get_and_clear_last_exception(env, &error); - NAPI_FATAL_IF_FAILED( - status, "Error::New", "napi_get_and_clear_last_exception"); - } else { - const char* error_message = last_error_info_copy.error_message != nullptr - ? last_error_info_copy.error_message - : "Error in native callback"; - - napi_value message; - status = napi_create_string_utf8( - env, error_message, std::strlen(error_message), &message); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); - - switch (last_error_info_copy.error_code) { - case napi_object_expected: - case napi_string_expected: - case napi_boolean_expected: - case napi_number_expected: - status = napi_create_type_error(env, nullptr, message, &error); - break; - default: - status = napi_create_error(env, nullptr, message, &error); - break; - } - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); - } - - return Error(env, error); -} - -inline Error Error::New(napi_env env, const char* message) { - return Error::New( - env, message, std::strlen(message), napi_create_error); -} - -inline Error Error::New(napi_env env, const std::string& message) { - return Error::New( - env, message.c_str(), message.size(), napi_create_error); -} - -inline NAPI_NO_RETURN void Error::Fatal(const char* location, - const char* message) { - napi_fatal_error(location, NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH); -} - -inline Error::Error() : ObjectReference() {} - -inline Error::Error(napi_env env, napi_value value) - : ObjectReference(env, nullptr) { - if (value != nullptr) { - // Attempting to create a reference on the error object. - // If it's not a Object/Function/Symbol, this call will return an error - // status. - napi_status status = napi_create_reference(env, value, 1, &_ref); - - if (status != napi_ok) { - napi_value wrappedErrorObj; - - // Create an error object - status = napi_create_object(env, &wrappedErrorObj); - NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); - - // property flag that we attach to show the error object is wrapped - napi_property_descriptor wrapObjFlag = { - ERROR_WRAP_VALUE(), // Unique GUID identifier since Symbol isn't a - // viable option - nullptr, - nullptr, - nullptr, - nullptr, - Value::From(env, value), - napi_enumerable, - nullptr}; - - status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); -#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS - if (status == napi_pending_exception) { - // Test if the pending exception was reported because the environment is - // shutting down. We assume that a status of napi_pending_exception - // coupled with the absence of an actual pending exception means that - // the environment is shutting down. If so, we replace the - // napi_pending_exception status with napi_ok. - bool is_exception_pending = false; - status = napi_is_exception_pending(env, &is_exception_pending); - if (status == napi_ok && !is_exception_pending) { - status = napi_ok; - } else { - status = napi_pending_exception; - } - } -#endif // NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS - NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); - - // Create a reference on the newly wrapped object - status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); - } - - // Avoid infinite recursion in the failure case. - NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); - } -} - -inline Object Error::Value() const { - if (_ref == nullptr) { - return Object(_env, nullptr); - } - - napi_value refValue; - napi_status status = napi_get_reference_value(_env, _ref, &refValue); - NAPI_THROW_IF_FAILED(_env, status, Object()); - - napi_valuetype type; - status = napi_typeof(_env, refValue, &type); - NAPI_THROW_IF_FAILED(_env, status, Object()); - - // If refValue isn't a symbol, then we proceed to whether the refValue has the - // wrapped error flag - if (type != napi_symbol) { - // We are checking if the object is wrapped - bool isWrappedObject = false; - - status = napi_has_property(_env, - refValue, - String::From(_env, ERROR_WRAP_VALUE()), - &isWrappedObject); - - // Don't care about status - if (isWrappedObject) { - napi_value unwrappedValue; - status = napi_get_property(_env, - refValue, - String::From(_env, ERROR_WRAP_VALUE()), - &unwrappedValue); - NAPI_THROW_IF_FAILED(_env, status, Object()); - - return Object(_env, unwrappedValue); - } - } - - return Object(_env, refValue); -} - -inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {} - -inline Error& Error::operator=(Error&& other) { - static_cast*>(this)->operator=(std::move(other)); - return *this; -} - -inline Error::Error(const Error& other) : ObjectReference(other) {} - -inline Error& Error::operator=(const Error& other) { - Reset(); - - _env = other.Env(); - HandleScope scope(_env); - - napi_value value = other.Value(); - if (value != nullptr) { - napi_status status = napi_create_reference(_env, value, 1, &_ref); - NAPI_THROW_IF_FAILED(_env, status, *this); - } - - return *this; -} - -inline const std::string& Error::Message() const NAPI_NOEXCEPT { - if (_message.size() == 0 && _env != nullptr) { -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - try { - _message = Get("message").As(); - } catch (...) { - // Catch all errors here, to include e.g. a std::bad_alloc from - // the std::string::operator=, because this method may not throw. - } -#else // NODE_ADDON_API_CPP_EXCEPTIONS -#if defined(NODE_ADDON_API_ENABLE_MAYBE) - Napi::Value message_val; - if (Get("message").UnwrapTo(&message_val)) { - _message = message_val.As(); - } -#else - _message = Get("message").As(); -#endif -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - } - return _message; -} - -// we created an object on the &_ref -inline void Error::ThrowAsJavaScriptException() const { - HandleScope scope(_env); - if (!IsEmpty()) { -#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS - bool pendingException = false; - - // check if there is already a pending exception. If so don't try to throw a - // new one as that is not allowed/possible - napi_status status = napi_is_exception_pending(_env, &pendingException); - - if ((status != napi_ok) || - ((status == napi_ok) && (pendingException == false))) { - // We intentionally don't use `NAPI_THROW_*` macros here to ensure - // that there is no possible recursion as `ThrowAsJavaScriptException` - // is part of `NAPI_THROW_*` macro definition for noexcept. - - status = napi_throw(_env, Value()); - -#if (NAPI_VERSION >= 10) - napi_status expected_failure_mode = napi_cannot_run_js; -#else - napi_status expected_failure_mode = napi_pending_exception; -#endif - if (status == expected_failure_mode) { - // The environment must be terminating as we checked earlier and there - // was no pending exception. In this case continuing will result - // in a fatal error and there is nothing the author has done incorrectly - // in their code that is worth flagging through a fatal error - return; - } - } else { - status = napi_pending_exception; - } -#else - // We intentionally don't use `NAPI_THROW_*` macros here to ensure - // that there is no possible recursion as `ThrowAsJavaScriptException` - // is part of `NAPI_THROW_*` macro definition for noexcept. - - napi_status status = napi_throw(_env, Value()); -#endif - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - if (status != napi_ok) { - throw Error::New(_env); - } -#else // NODE_ADDON_API_CPP_EXCEPTIONS - NAPI_FATAL_IF_FAILED( - status, "Error::ThrowAsJavaScriptException", "napi_throw"); -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - } -} - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - -inline const char* Error::what() const NAPI_NOEXCEPT { - return Message().c_str(); -} - -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - -inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { - return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; -} - -template -inline TError Error::New(napi_env env, - const char* message, - size_t length, - create_error_fn create_error) { - napi_value str; - napi_status status = napi_create_string_utf8(env, message, length, &str); - NAPI_THROW_IF_FAILED(env, status, TError()); - - napi_value error; - status = create_error(env, nullptr, str, &error); - NAPI_THROW_IF_FAILED(env, status, TError()); - - return TError(env, error); -} - -inline TypeError TypeError::New(napi_env env, const char* message) { - return Error::New( - env, message, std::strlen(message), napi_create_type_error); -} - -inline TypeError TypeError::New(napi_env env, const std::string& message) { - return Error::New( - env, message.c_str(), message.size(), napi_create_type_error); -} - -inline TypeError::TypeError() : Error() {} - -inline TypeError::TypeError(napi_env env, napi_value value) - : Error(env, value) {} - -inline RangeError RangeError::New(napi_env env, const char* message) { - return Error::New( - env, message, std::strlen(message), napi_create_range_error); -} - -inline RangeError RangeError::New(napi_env env, const std::string& message) { - return Error::New( - env, message.c_str(), message.size(), napi_create_range_error); -} - -inline RangeError::RangeError() : Error() {} - -inline RangeError::RangeError(napi_env env, napi_value value) - : Error(env, value) {} - -#if NAPI_VERSION > 8 -inline SyntaxError SyntaxError::New(napi_env env, const char* message) { - return Error::New( - env, message, std::strlen(message), node_api_create_syntax_error); -} - -inline SyntaxError SyntaxError::New(napi_env env, const std::string& message) { - return Error::New( - env, message.c_str(), message.size(), node_api_create_syntax_error); -} - -inline SyntaxError::SyntaxError() : Error() {} - -inline SyntaxError::SyntaxError(napi_env env, napi_value value) - : Error(env, value) {} -#endif // NAPI_VERSION > 8 - -//////////////////////////////////////////////////////////////////////////////// -// Reference class -//////////////////////////////////////////////////////////////////////////////// - -template -inline Reference Reference::New(const T& value, - uint32_t initialRefcount) { - napi_env env = value.Env(); - napi_value val = value; - - if (val == nullptr) { - return Reference(env, nullptr); - } - - napi_ref ref; - napi_status status = napi_create_reference(env, value, initialRefcount, &ref); - NAPI_THROW_IF_FAILED(env, status, Reference()); - - return Reference(env, ref); -} - -template -inline Reference::Reference() - : _env(nullptr), _ref(nullptr), _suppressDestruct(false) {} - -template -inline Reference::Reference(napi_env env, napi_ref ref) - : _env(env), _ref(ref), _suppressDestruct(false) {} - -template -inline Reference::~Reference() { - if (_ref != nullptr) { - if (!_suppressDestruct) { - // TODO(legendecas): napi_delete_reference should be invoked immediately. - // Fix this when https://github.com/nodejs/node/pull/55620 lands. -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - Env().PostFinalizer( - [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); }, - _ref); -#else - napi_delete_reference(_env, _ref); -#endif - } - - _ref = nullptr; - } -} - -template -inline Reference::Reference(Reference&& other) - : _env(other._env), - _ref(other._ref), - _suppressDestruct(other._suppressDestruct) { - other._env = nullptr; - other._ref = nullptr; - other._suppressDestruct = false; -} - -template -inline Reference& Reference::operator=(Reference&& other) { - Reset(); - _env = other._env; - _ref = other._ref; - _suppressDestruct = other._suppressDestruct; - other._env = nullptr; - other._ref = nullptr; - other._suppressDestruct = false; - return *this; -} - -template -inline Reference::Reference(const Reference& other) - : _env(other._env), _ref(nullptr), _suppressDestruct(false) { - HandleScope scope(_env); - - napi_value value = other.Value(); - if (value != nullptr) { - // Copying is a limited scenario (currently only used for Error object) and - // always creates a strong reference to the given value even if the incoming - // reference is weak. - napi_status status = napi_create_reference(_env, value, 1, &_ref); - NAPI_FATAL_IF_FAILED( - status, "Reference::Reference", "napi_create_reference"); - } -} - -template -inline Reference::operator napi_ref() const { - return _ref; -} - -template -inline bool Reference::operator==(const Reference& other) const { - HandleScope scope(_env); - return this->Value().StrictEquals(other.Value()); -} - -template -inline bool Reference::operator!=(const Reference& other) const { - return !this->operator==(other); -} - -template -inline Napi::Env Reference::Env() const { - return Napi::Env(_env); -} - -template -inline bool Reference::IsEmpty() const { - return _ref == nullptr; -} - -template -inline T Reference::Value() const { - if (_ref == nullptr) { - return T(_env, nullptr); - } - - napi_value value; - napi_status status = napi_get_reference_value(_env, _ref, &value); - NAPI_THROW_IF_FAILED(_env, status, T()); - return T(_env, value); -} - -template -inline uint32_t Reference::Ref() const { - uint32_t result; - napi_status status = napi_reference_ref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -template -inline uint32_t Reference::Unref() const { - uint32_t result; - napi_status status = napi_reference_unref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; -} - -template -inline void Reference::Reset() { - if (_ref != nullptr) { - napi_status status = napi_delete_reference(_env, _ref); - NAPI_THROW_IF_FAILED_VOID(_env, status); - _ref = nullptr; - } -} - -template -inline void Reference::Reset(const T& value, uint32_t refcount) { - Reset(); - _env = value.Env(); - - napi_value val = value; - if (val != nullptr) { - napi_status status = napi_create_reference(_env, value, refcount, &_ref); - NAPI_THROW_IF_FAILED_VOID(_env, status); - } -} - -template -inline void Reference::SuppressDestruct() { - _suppressDestruct = true; -} - -template -inline Reference Weak(T value) { - return Reference::New(value, 0); -} - -inline ObjectReference Weak(Object value) { - return Reference::New(value, 0); -} - -inline FunctionReference Weak(Function value) { - return Reference::New(value, 0); -} - -template -inline Reference Persistent(T value) { - return Reference::New(value, 1); -} - -inline ObjectReference Persistent(Object value) { - return Reference::New(value, 1); -} - -inline FunctionReference Persistent(Function value) { - return Reference::New(value, 1); -} - -//////////////////////////////////////////////////////////////////////////////// -// ObjectReference class -//////////////////////////////////////////////////////////////////////////////// - -inline ObjectReference::ObjectReference() : Reference() {} - -inline ObjectReference::ObjectReference(napi_env env, napi_ref ref) - : Reference(env, ref) {} - -inline ObjectReference::ObjectReference(Reference&& other) - : Reference(std::move(other)) {} - -inline ObjectReference& ObjectReference::operator=(Reference&& other) { - static_cast*>(this)->operator=(std::move(other)); - return *this; -} - -inline ObjectReference::ObjectReference(ObjectReference&& other) - : Reference(std::move(other)) {} - -inline ObjectReference& ObjectReference::operator=(ObjectReference&& other) { - static_cast*>(this)->operator=(std::move(other)); - return *this; -} - -inline ObjectReference::ObjectReference(const ObjectReference& other) - : Reference(other) {} - -inline MaybeOrValue ObjectReference::Get( - const char* utf8name) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Get(utf8name); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue ObjectReference::Get( - const std::string& utf8name) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Get(utf8name); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue ObjectReference::Set(const char* utf8name, - napi_value value) const { - HandleScope scope(_env); - return Value().Set(utf8name, value); -} - -inline MaybeOrValue ObjectReference::Set(const char* utf8name, - Napi::Value value) const { - HandleScope scope(_env); - return Value().Set(utf8name, value); -} - -inline MaybeOrValue ObjectReference::Set(const char* utf8name, - const char* utf8value) const { - HandleScope scope(_env); - return Value().Set(utf8name, utf8value); -} - -inline MaybeOrValue ObjectReference::Set(const char* utf8name, - bool boolValue) const { - HandleScope scope(_env); - return Value().Set(utf8name, boolValue); -} - -inline MaybeOrValue ObjectReference::Set(const char* utf8name, - double numberValue) const { - HandleScope scope(_env); - return Value().Set(utf8name, numberValue); -} - -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - napi_value value) const { - HandleScope scope(_env); - return Value().Set(utf8name, value); -} - -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - Napi::Value value) const { - HandleScope scope(_env); - return Value().Set(utf8name, value); -} - -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - std::string& utf8value) const { - HandleScope scope(_env); - return Value().Set(utf8name, utf8value); -} - -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - bool boolValue) const { - HandleScope scope(_env); - return Value().Set(utf8name, boolValue); -} - -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - double numberValue) const { - HandleScope scope(_env); - return Value().Set(utf8name, numberValue); -} - -inline MaybeOrValue ObjectReference::Get(uint32_t index) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Get(index); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue ObjectReference::Set(uint32_t index, - napi_value value) const { - HandleScope scope(_env); - return Value().Set(index, value); -} - -inline MaybeOrValue ObjectReference::Set(uint32_t index, - Napi::Value value) const { - HandleScope scope(_env); - return Value().Set(index, value); -} - -inline MaybeOrValue ObjectReference::Set(uint32_t index, - const char* utf8value) const { - HandleScope scope(_env); - return Value().Set(index, utf8value); -} - -inline MaybeOrValue ObjectReference::Set( - uint32_t index, const std::string& utf8value) const { - HandleScope scope(_env); - return Value().Set(index, utf8value); -} - -inline MaybeOrValue ObjectReference::Set(uint32_t index, - bool boolValue) const { - HandleScope scope(_env); - return Value().Set(index, boolValue); -} - -inline MaybeOrValue ObjectReference::Set(uint32_t index, - double numberValue) const { - HandleScope scope(_env); - return Value().Set(index, numberValue); -} - -//////////////////////////////////////////////////////////////////////////////// -// FunctionReference class -//////////////////////////////////////////////////////////////////////////////// - -inline FunctionReference::FunctionReference() : Reference() {} - -inline FunctionReference::FunctionReference(napi_env env, napi_ref ref) - : Reference(env, ref) {} - -inline FunctionReference::FunctionReference(Reference&& other) - : Reference(std::move(other)) {} - -inline FunctionReference& FunctionReference::operator=( - Reference&& other) { - static_cast*>(this)->operator=(std::move(other)); - return *this; -} - -inline FunctionReference::FunctionReference(FunctionReference&& other) - : Reference(std::move(other)) {} - -inline FunctionReference& FunctionReference::operator=( - FunctionReference&& other) { - static_cast*>(this)->operator=(std::move(other)); - return *this; -} - -inline MaybeOrValue FunctionReference::operator()( - const std::initializer_list& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value()(args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::Call( - const std::initializer_list& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Call(args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::Call( - const std::vector& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Call(args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::Call( - napi_value recv, const std::initializer_list& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Call(recv, args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::Call( - napi_value recv, const std::vector& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Call(recv, args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::Call( - napi_value recv, size_t argc, const napi_value* args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().Call(recv, argc, args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::MakeCallback( - napi_value recv, - const std::initializer_list& args, - napi_async_context context) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().MakeCallback(recv, args, context); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::MakeCallback( - napi_value recv, - const std::vector& args, - napi_async_context context) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().MakeCallback(recv, args, context); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::MakeCallback( - napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = - Value().MakeCallback(recv, argc, args, context); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap())); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Value(); - } - return scope.Escape(result); -#endif -} - -inline MaybeOrValue FunctionReference::New( - const std::initializer_list& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().New(args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap()).As()); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Object(); - } - return scope.Escape(result).As(); -#endif -} - -inline MaybeOrValue FunctionReference::New( - const std::vector& args) const { - EscapableHandleScope scope(_env); - MaybeOrValue result = Value().New(args); -#ifdef NODE_ADDON_API_ENABLE_MAYBE - if (result.IsJust()) { - return Just(scope.Escape(result.Unwrap()).As()); - } - return result; -#else - if (scope.Env().IsExceptionPending()) { - return Object(); - } - return scope.Escape(result).As(); -#endif -} - -//////////////////////////////////////////////////////////////////////////////// -// CallbackInfo class -//////////////////////////////////////////////////////////////////////////////// - -inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) - : _env(env), - _info(info), - _this(nullptr), - _dynamicArgs(nullptr), - _data(nullptr) { - _argc = _staticArgCount; - _argv = _staticArgs; - napi_status status = - napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); - NAPI_THROW_IF_FAILED_VOID(_env, status); - - if (_argc > _staticArgCount) { - // Use either a fixed-size array (on the stack) or a dynamically-allocated - // array (on the heap) depending on the number of args. - _dynamicArgs = new napi_value[_argc]; - _argv = _dynamicArgs; - - status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); - NAPI_THROW_IF_FAILED_VOID(_env, status); - } -} - -inline CallbackInfo::~CallbackInfo() { - if (_dynamicArgs != nullptr) { - delete[] _dynamicArgs; - } -} - -inline CallbackInfo::operator napi_callback_info() const { - return _info; -} - -inline Value CallbackInfo::NewTarget() const { - napi_value newTarget; - napi_status status = napi_get_new_target(_env, _info, &newTarget); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, newTarget); -} - -inline bool CallbackInfo::IsConstructCall() const { - return !NewTarget().IsEmpty(); -} - -inline Napi::Env CallbackInfo::Env() const { - return Napi::Env(_env); -} - -inline size_t CallbackInfo::Length() const { - return _argc; -} - -inline const Value CallbackInfo::operator[](size_t index) const { - return index < _argc ? Value(_env, _argv[index]) : Env().Undefined(); -} - -inline Value CallbackInfo::This() const { - if (_this == nullptr) { - return Env().Undefined(); - } - return Object(_env, _this); -} - -inline void* CallbackInfo::Data() const { - return _data; -} - -inline void CallbackInfo::SetData(void* data) { - _data = data; -} - -//////////////////////////////////////////////////////////////////////////////// -// PropertyDescriptor class -//////////////////////////////////////////////////////////////////////////////// - -template -PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - - desc.utf8name = utf8name; - desc.getter = details::TemplatedCallback; - desc.attributes = attributes; - desc.data = data; - - return desc; -} - -template -PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - napi_property_attributes attributes, - void* data) { - return Accessor(utf8name.c_str(), attributes, data); -} - -template -PropertyDescriptor PropertyDescriptor::Accessor( - Name name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - - desc.name = name; - desc.getter = details::TemplatedCallback; - desc.attributes = attributes; - desc.data = data; - - return desc; -} - -template -PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - - desc.utf8name = utf8name; - desc.getter = details::TemplatedCallback; - desc.setter = details::TemplatedVoidCallback; - desc.attributes = attributes; - desc.data = data; - - return desc; -} - -template -PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - napi_property_attributes attributes, - void* data) { - return Accessor(utf8name.c_str(), attributes, data); -} - -template -PropertyDescriptor PropertyDescriptor::Accessor( - Name name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - - desc.name = name; - desc.getter = details::TemplatedCallback; - desc.setter = details::TemplatedVoidCallback; - desc.attributes = attributes; - desc.data = data; - - return desc; -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { - using CbData = details::CallbackData; - auto callbackData = new CbData({getter, data}); - - napi_status status = AttachData(env, object, callbackData); - if (status != napi_ok) { - delete callbackData; - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); - } - - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { - return Accessor(env, object, utf8name.c_str(), getter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { - using CbData = details::CallbackData; - auto callbackData = new CbData({getter, data}); - - napi_status status = AttachData(env, object, callbackData); - if (status != napi_ok) { - delete callbackData; - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); - } - - return PropertyDescriptor({nullptr, - name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - using CbData = details::AccessorCallbackData; - auto callbackData = new CbData({getter, setter, data}); - - napi_status status = AttachData(env, object, callbackData); - if (status != napi_ok) { - delete callbackData; - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); - } - - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - return Accessor( - env, object, utf8name.c_str(), getter, setter, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - using CbData = details::AccessorCallbackData; - auto callbackData = new CbData({getter, setter, data}); - - napi_status status = AttachData(env, object, callbackData); - if (status != napi_ok) { - delete callbackData; - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); - } - - return PropertyDescriptor({nullptr, - name, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object /*object*/, - const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - nullptr, - nullptr, - Napi::Function::New(env, cb, utf8name, data), - attributes, - nullptr}); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return Function(env, object, utf8name.c_str(), cb, attributes, data); -} - -template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object /*object*/, - Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return PropertyDescriptor({nullptr, - name, - nullptr, - nullptr, - nullptr, - Napi::Function::New(env, cb, nullptr, data), - attributes, - nullptr}); -} - -inline PropertyDescriptor PropertyDescriptor::Value( - const char* utf8name, - napi_value value, - napi_property_attributes attributes) { - return PropertyDescriptor({utf8name, - nullptr, - nullptr, - nullptr, - nullptr, - value, - attributes, - nullptr}); -} - -inline PropertyDescriptor PropertyDescriptor::Value( - const std::string& utf8name, - napi_value value, - napi_property_attributes attributes) { - return Value(utf8name.c_str(), value, attributes); -} - -inline PropertyDescriptor PropertyDescriptor::Value( - napi_value name, napi_value value, napi_property_attributes attributes) { - return PropertyDescriptor( - {nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr}); -} - -inline PropertyDescriptor PropertyDescriptor::Value( - Name name, Napi::Value value, napi_property_attributes attributes) { - napi_value nameValue = name; - napi_value valueValue = value; - return PropertyDescriptor::Value(nameValue, valueValue, attributes); -} - -inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) - : _desc(desc) {} - -inline PropertyDescriptor::operator napi_property_descriptor&() { - return _desc; -} - -inline PropertyDescriptor::operator const napi_property_descriptor&() const { - return _desc; -} - -//////////////////////////////////////////////////////////////////////////////// -// InstanceWrap class -//////////////////////////////////////////////////////////////////////////////// - -template -inline void InstanceWrap::AttachPropData( - napi_env env, napi_value value, const napi_property_descriptor* prop) { - napi_status status; - if (!(prop->attributes & napi_static)) { - if (prop->method == T::InstanceVoidMethodCallbackWrapper) { - status = Napi::details::AttachData( - env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED_VOID(env, status); - } else if (prop->method == T::InstanceMethodCallbackWrapper) { - status = Napi::details::AttachData( - env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED_VOID(env, status); - } else if (prop->getter == T::InstanceGetterCallbackWrapper || - prop->setter == T::InstanceSetterCallbackWrapper) { - status = Napi::details::AttachData( - env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED_VOID(env, status); - } - } -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = T::InstanceVoidMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceMethodCallbackData* callbackData = - new InstanceMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = T::InstanceMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = T::InstanceVoidMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceMethodCallbackData* callbackData = - new InstanceMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = T::InstanceMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = details::TemplatedInstanceVoidCallback; - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = details::TemplatedInstanceCallback; - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = details::TemplatedInstanceVoidCallback; - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = details::TemplatedInstanceCallback; - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes, - void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({getter, setter, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes, - void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({getter, setter, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; - desc.data = callbackData; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceGetterCallback getter, - typename InstanceWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.getter = details::TemplatedInstanceCallback; - desc.setter = This::WrapSetter(This::SetterTag()); - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -template ::InstanceGetterCallback getter, - typename InstanceWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.getter = details::TemplatedInstanceCallback; - desc.setter = This::WrapSetter(This::SetterTag()); - desc.data = data; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor InstanceWrap::InstanceValue( - Symbol name, Napi::Value value, napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - -template -inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - InstanceVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = T::Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - if (instance) (instance->*cb)(callbackInfo); - return nullptr; - }); -} - -template -inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - InstanceMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = T::Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); - }); -} - -template -inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = T::Unwrap(callbackInfo.This().As()); - auto cb = callbackData->getterCallback; - return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); - }); -} - -template -inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = T::Unwrap(callbackInfo.This().As()); - auto cb = callbackData->setterCallback; - if (instance) (instance->*cb)(callbackInfo, callbackInfo[0]); - return nullptr; - }); -} - -template -template ::InstanceSetterCallback method> -inline napi_value InstanceWrap::WrappedMethod( - napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - const CallbackInfo cbInfo(env, info); - T* instance = T::Unwrap(cbInfo.This().As()); - if (instance) (instance->*method)(cbInfo, cbInfo[0]); - return nullptr; - }); -} - -//////////////////////////////////////////////////////////////////////////////// -// ObjectWrap class -//////////////////////////////////////////////////////////////////////////////// - -template -inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { - napi_env env = callbackInfo.Env(); - napi_value wrapper = callbackInfo.This(); - napi_status status; - napi_ref ref; - T* instance = static_cast(this); - status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); - NAPI_THROW_IF_FAILED_VOID(env, status); - - Reference* instanceRef = instance; - *instanceRef = Reference(env, ref); -} - -template -inline ObjectWrap::~ObjectWrap() { - // If the JS object still exists at this point, remove the finalizer added - // through `napi_wrap()`. - if (!IsEmpty() && !_finalized) { - Object object = Value(); - // It is not valid to call `napi_remove_wrap()` with an empty `object`. - // This happens e.g. during garbage collection. - if (!object.IsEmpty() && _construction_failed) { - napi_remove_wrap(Env(), object, nullptr); - } - } -} - -template -inline T* ObjectWrap::Unwrap(Object wrapper) { - void* unwrapped; - napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); - NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); - return static_cast(unwrapped); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* descriptors, - void* data) { - napi_status status; - std::vector props(props_count); - - // We copy the descriptors to a local array because before defining the class - // we must replace static method property descriptors with value property - // descriptors such that the value is a function-valued `napi_value` created - // with `CreateFunction()`. - // - // This replacement could be made for instance methods as well, but V8 aborts - // if we do that, because it expects methods defined on the prototype template - // to have `FunctionTemplate`s. - for (size_t index = 0; index < props_count; index++) { - props[index] = descriptors[index]; - napi_property_descriptor* prop = &props[index]; - if (prop->method == T::StaticMethodCallbackWrapper) { - status = - CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { - status = - CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } - } - - napi_value value; - status = napi_define_class(env, - utf8name, - NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, - data, - props_count, - props.data(), - &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - // After defining the class we iterate once more over the property descriptors - // and attach the data associated with accessors and instance methods to the - // newly created JavaScript class. - for (size_t idx = 0; idx < props_count; idx++) { - const napi_property_descriptor* prop = &props[idx]; - - if (prop->getter == T::StaticGetterCallbackWrapper || - prop->setter == T::StaticSetterCallbackWrapper) { - status = Napi::details::AttachData( - env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } else { - // InstanceWrap::AttachPropData is responsible for attaching the data - // of instance methods and accessors. - T::AttachPropData(env, value, prop); - } - } - - return Function(env, value); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::initializer_list>& properties, - void* data) { - return DefineClass( - env, - utf8name, - properties.size(), - reinterpret_cast(properties.begin()), - data); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::vector>& properties, - void* data) { - return DefineClass( - env, - utf8name, - properties.size(), - reinterpret_cast(properties.data()), - data); -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, - StaticVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - StaticVoidMethodCallbackData* callbackData = - new StaticVoidMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = T::StaticVoidMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, - StaticMethodCallback method, - napi_property_attributes attributes, - void* data) { - StaticMethodCallbackData* callbackData = - new StaticMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = T::StaticMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, - StaticVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - StaticVoidMethodCallbackData* callbackData = - new StaticVoidMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = T::StaticVoidMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, - StaticMethodCallback method, - napi_property_attributes attributes, - void* data) { - StaticMethodCallbackData* callbackData = - new StaticMethodCallbackData({method, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = T::StaticMethodCallbackWrapper; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = details::TemplatedVoidCallback; - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = details::TemplatedVoidCallback; - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = details::TemplatedCallback; - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = details::TemplatedCallback; - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - const char* utf8name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes, - void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({getter, setter, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - Symbol name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes, - void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({getter, setter, data}); - - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; - desc.data = callbackData; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - const char* utf8name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.getter = details::TemplatedCallback; - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - Symbol name, napi_property_attributes attributes, void* data) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.getter = details::TemplatedCallback; - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); - desc.data = data; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.value = value; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::StaticValue( - Symbol name, Napi::Value value, napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.value = value; - desc.attributes = - static_cast(attributes | napi_static); - return desc; -} - -template -inline Value ObjectWrap::OnCalledAsFunction( - const Napi::CallbackInfo& callbackInfo) { - NAPI_THROW( - TypeError::New(callbackInfo.Env(), - "Class constructors cannot be invoked without 'new'"), - Napi::Value()); -} - -template -inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} - -template -inline void ObjectWrap::Finalize(BasicEnv /*env*/) {} - -template -inline napi_value ObjectWrap::ConstructorCallbackWrapper( - napi_env env, napi_callback_info info) { - napi_value new_target; - napi_status status = napi_get_new_target(env, info, &new_target); - if (status != napi_ok) return nullptr; - - bool isConstructCall = (new_target != nullptr); - if (!isConstructCall) { - return details::WrapCallback( - env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); - } - - napi_value wrapper = details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - T* instance = new T(callbackInfo); -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - instance->_construction_failed = false; -#else - if (callbackInfo.Env().IsExceptionPending()) { - // We need to clear the exception so that removing the wrap might work. - Error e = callbackInfo.Env().GetAndClearPendingException(); - delete instance; - e.ThrowAsJavaScriptException(); - } else { - instance->_construction_failed = false; - } -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - return callbackInfo.This(); - }); - - return wrapper; -} - -template -inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - StaticVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - callbackData->callback(callbackInfo); - return nullptr; - }); -} - -template -inline napi_value ObjectWrap::StaticMethodCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - StaticMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - return callbackData->callback(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::StaticGetterCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - StaticAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - return callbackData->getterCallback(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::StaticSetterCallbackWrapper( - napi_env env, napi_callback_info info) { - return details::WrapCallback(env, [&] { - CallbackInfo callbackInfo(env, info); - StaticAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - callbackData->setterCallback(callbackInfo, callbackInfo[0]); - return nullptr; - }); -} - -template -inline void ObjectWrap::FinalizeCallback(node_addon_api_basic_env env, - void* data, - void* /*hint*/) { - // If the child class does not override _any_ Finalize() method, `env` will be - // unused because of the constexpr guards. Explicitly reference it here to - // bypass compiler warnings. - (void)env; - T* instance = static_cast(data); - - // Prevent ~ObjectWrap from calling napi_remove_wrap. - // The instance->_ref should be deleted with napi_delete_reference in - // ~Reference. - instance->_finalized = true; - - // If class overrides the basic finalizer, execute it. - if constexpr (details::HasBasicFinalizer::value) { -#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - HandleScope scope(env); -#endif - - instance->Finalize(Napi::BasicEnv(env)); - } - - // If class overrides the (extended) finalizer, either schedule it or - // execute it immediately (depending on experimental features enabled). - if constexpr (details::HasExtendedFinalizer::value) { -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - // In experimental, attach via node_api_post_finalizer. - // `PostFinalizeCallback` is responsible for deleting the `T* instance`, - // after calling the user-provided finalizer. - napi_status status = - node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr); - NAPI_FATAL_IF_FAILED(status, - "ObjectWrap::FinalizeCallback", - "node_api_post_finalizer failed"); -#else - // In non-experimental, this `FinalizeCallback` already executes from a - // non-basic environment. Execute the override directly. - // `PostFinalizeCallback` is responsible for deleting the `T* instance`, - // after calling the user-provided finalizer. - HandleScope scope(env); - PostFinalizeCallback(env, data, static_cast(nullptr)); -#endif - } - // If the instance does _not_ override the (extended) finalizer, delete the - // `T* instance` immediately. - else { - delete instance; - } -} - -template -inline void ObjectWrap::PostFinalizeCallback(napi_env env, - void* data, - void* /*hint*/) { - T* instance = static_cast(data); - instance->Finalize(Napi::Env(env)); - delete instance; -} - -template -template ::StaticSetterCallback method> -inline napi_value ObjectWrap::WrappedMethod( - napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback(env, [&] { - const CallbackInfo cbInfo(env, info); - // MSVC requires to copy 'method' function pointer to a local variable - // before invoking it. - auto m = method; - m(cbInfo, cbInfo[0]); - return nullptr; - }); -} - -//////////////////////////////////////////////////////////////////////////////// -// HandleScope class -//////////////////////////////////////////////////////////////////////////////// - -inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) - : _env(env), _scope(scope) {} - -inline HandleScope::HandleScope(Napi::Env env) : _env(env) { - napi_status status = napi_open_handle_scope(_env, &_scope); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline HandleScope::~HandleScope() { - napi_status status = napi_close_handle_scope(_env, _scope); - NAPI_FATAL_IF_FAILED( - status, "HandleScope::~HandleScope", "napi_close_handle_scope"); -} - -inline HandleScope::operator napi_handle_scope() const { - return _scope; -} - -inline Napi::Env HandleScope::Env() const { - return Napi::Env(_env); -} - -//////////////////////////////////////////////////////////////////////////////// -// EscapableHandleScope class -//////////////////////////////////////////////////////////////////////////////// - -inline EscapableHandleScope::EscapableHandleScope( - napi_env env, napi_escapable_handle_scope scope) - : _env(env), _scope(scope) {} - -inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { - napi_status status = napi_open_escapable_handle_scope(_env, &_scope); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline EscapableHandleScope::~EscapableHandleScope() { - napi_status status = napi_close_escapable_handle_scope(_env, _scope); - NAPI_FATAL_IF_FAILED(status, - "EscapableHandleScope::~EscapableHandleScope", - "napi_close_escapable_handle_scope"); -} - -inline EscapableHandleScope::operator napi_escapable_handle_scope() const { - return _scope; -} - -inline Napi::Env EscapableHandleScope::Env() const { - return Napi::Env(_env); -} - -inline Value EscapableHandleScope::Escape(napi_value escapee) { - napi_value result; - napi_status status = napi_escape_handle(_env, _scope, escapee, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); -} - -#if (NAPI_VERSION > 2) -//////////////////////////////////////////////////////////////////////////////// -// CallbackScope class -//////////////////////////////////////////////////////////////////////////////// - -inline CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope) - : _env(env), _scope(scope) {} - -inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) - : _env(env) { - napi_status status = - napi_open_callback_scope(_env, Object::New(env), context, &_scope); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline CallbackScope::~CallbackScope() { - napi_status status = napi_close_callback_scope(_env, _scope); - NAPI_FATAL_IF_FAILED( - status, "CallbackScope::~CallbackScope", "napi_close_callback_scope"); -} - -inline CallbackScope::operator napi_callback_scope() const { - return _scope; -} - -inline Napi::Env CallbackScope::Env() const { - return Napi::Env(_env); -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -// AsyncContext class -//////////////////////////////////////////////////////////////////////////////// - -inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) - : AsyncContext(env, resource_name, Object::New(env)) {} - -inline AsyncContext::AsyncContext(napi_env env, - const char* resource_name, - const Object& resource) - : _env(env), _context(nullptr) { - napi_value resource_id; - napi_status status = napi_create_string_utf8( - _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); - NAPI_THROW_IF_FAILED_VOID(_env, status); - - status = napi_async_init(_env, resource, resource_id, &_context); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline AsyncContext::~AsyncContext() { - if (_context != nullptr) { - napi_async_destroy(_env, _context); - _context = nullptr; - } -} - -inline AsyncContext::AsyncContext(AsyncContext&& other) { - _env = other._env; - other._env = nullptr; - _context = other._context; - other._context = nullptr; -} - -inline AsyncContext& AsyncContext::operator=(AsyncContext&& other) { - _env = other._env; - other._env = nullptr; - _context = other._context; - other._context = nullptr; - return *this; -} - -inline AsyncContext::operator napi_async_context() const { - return _context; -} - -inline Napi::Env AsyncContext::Env() const { - return Napi::Env(_env); -} - -//////////////////////////////////////////////////////////////////////////////// -// AsyncWorker class -//////////////////////////////////////////////////////////////////////////////// - -#if NAPI_HAS_THREADS - -inline AsyncWorker::AsyncWorker(const Function& callback) - : AsyncWorker(callback, "generic") {} - -inline AsyncWorker::AsyncWorker(const Function& callback, - const char* resource_name) - : AsyncWorker(callback, resource_name, Object::New(callback.Env())) {} - -inline AsyncWorker::AsyncWorker(const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncWorker( - Object::New(callback.Env()), callback, resource_name, resource) {} - -inline AsyncWorker::AsyncWorker(const Object& receiver, - const Function& callback) - : AsyncWorker(receiver, callback, "generic") {} - -inline AsyncWorker::AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name) - : AsyncWorker( - receiver, callback, resource_name, Object::New(callback.Env())) {} - -inline AsyncWorker::AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource) - : _env(callback.Env()), - _receiver(Napi::Persistent(receiver)), - _callback(Napi::Persistent(callback)), - _suppress_destruct(false) { - napi_value resource_id; - napi_status status = napi_create_string_latin1( - _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); - NAPI_THROW_IF_FAILED_VOID(_env, status); - - status = napi_create_async_work(_env, - resource, - resource_id, - OnAsyncWorkExecute, - OnAsyncWorkComplete, - this, - &_work); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline AsyncWorker::AsyncWorker(Napi::Env env) : AsyncWorker(env, "generic") {} - -inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name) - : AsyncWorker(env, resource_name, Object::New(env)) {} - -inline AsyncWorker::AsyncWorker(Napi::Env env, - const char* resource_name, - const Object& resource) - : _env(env), _receiver(), _callback(), _suppress_destruct(false) { - napi_value resource_id; - napi_status status = napi_create_string_latin1( - _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); - NAPI_THROW_IF_FAILED_VOID(_env, status); - - status = napi_create_async_work(_env, - resource, - resource_id, - OnAsyncWorkExecute, - OnAsyncWorkComplete, - this, - &_work); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline AsyncWorker::~AsyncWorker() { - if (_work != nullptr) { - napi_delete_async_work(_env, _work); - _work = nullptr; - } -} - -inline void AsyncWorker::Destroy() { - delete this; -} - -inline AsyncWorker::operator napi_async_work() const { - return _work; -} - -inline Napi::Env AsyncWorker::Env() const { - return Napi::Env(_env); -} - -inline void AsyncWorker::Queue() { - napi_status status = napi_queue_async_work(_env, _work); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline void AsyncWorker::Cancel() { - napi_status status = napi_cancel_async_work(_env, _work); - NAPI_THROW_IF_FAILED_VOID(_env, status); -} - -inline ObjectReference& AsyncWorker::Receiver() { - return _receiver; -} - -inline FunctionReference& AsyncWorker::Callback() { - return _callback; -} - -inline void AsyncWorker::SuppressDestruct() { - _suppress_destruct = true; -} - -inline void AsyncWorker::OnOK() { - if (!_callback.IsEmpty()) { - _callback.Call(_receiver.Value(), GetResult(_callback.Env())); - } -} - -inline void AsyncWorker::OnError(const Error& e) { - if (!_callback.IsEmpty()) { - _callback.Call(_receiver.Value(), - std::initializer_list{e.Value()}); - } -} - -inline void AsyncWorker::SetError(const std::string& error) { - _error = error; -} - -inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { - return {}; -} -// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT -// use it within this method, as it does not run on the JavaScript thread and -// must not run any method that would cause JavaScript to run. In practice, -// this means that almost any use of napi_env will be incorrect. -inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { - AsyncWorker* self = static_cast(asyncworker); - self->OnExecute(env); -} -// The OnExecute method receives an napi_env argument. However, do NOT -// use it within this method, as it does not run on the JavaScript thread and -// must not run any method that would cause JavaScript to run. In practice, -// this means that almost any use of napi_env will be incorrect. -inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - try { - Execute(); - } catch (const std::exception& e) { - SetError(e.what()); - } -#else // NODE_ADDON_API_CPP_EXCEPTIONS - Execute(); -#endif // NODE_ADDON_API_CPP_EXCEPTIONS -} - -inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, - napi_status status, - void* asyncworker) { - AsyncWorker* self = static_cast(asyncworker); - self->OnWorkComplete(env, status); -} -inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { - if (status != napi_cancelled) { - HandleScope scope(_env); - details::WrapCallback(env, [&] { - if (_error.size() == 0) { - OnOK(); - } else { - OnError(Error::New(_env, _error)); - } - return nullptr; - }); - } - if (!_suppress_destruct) { - Destroy(); - } -} - -#endif // NAPI_HAS_THREADS - -#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) -//////////////////////////////////////////////////////////////////////////////// -// TypedThreadSafeFunction class -//////////////////////////////////////////////////////////////////////////////// - -// Starting with NAPI 5, the JavaScript function `func` parameter of -// `napi_create_threadsafe_function` is optional. -#if NAPI_VERSION > 4 -// static, with Callback [missing] Resource [missing] Finalizer [missing] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - TypedThreadSafeFunction tsfn; - - napi_status status = - napi_create_threadsafe_function(env, - nullptr, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - nullptr, - nullptr, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with Callback [missing] Resource [passed] Finalizer [missing] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - TypedThreadSafeFunction tsfn; - - napi_status status = - napi_create_threadsafe_function(env, - nullptr, - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - nullptr, - nullptr, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with Callback [missing] Resource [missing] Finalizer [passed] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - TypedThreadSafeFunction tsfn; - - auto* finalizeData = new details:: - ThreadSafeFinalize( - {data, finalizeCallback}); - auto fini = - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext; - napi_status status = - napi_create_threadsafe_function(env, - nullptr, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, - fini, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with Callback [missing] Resource [passed] Finalizer [passed] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - TypedThreadSafeFunction tsfn; - - auto* finalizeData = new details:: - ThreadSafeFinalize( - {data, finalizeCallback}); - auto fini = - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext; - napi_status status = - napi_create_threadsafe_function(env, - nullptr, - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, - fini, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} -#endif - -// static, with Callback [passed] Resource [missing] Finalizer [missing] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - TypedThreadSafeFunction tsfn; - - napi_status status = - napi_create_threadsafe_function(env, - callback, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - nullptr, - nullptr, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with Callback [passed] Resource [passed] Finalizer [missing] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - TypedThreadSafeFunction tsfn; - - napi_status status = - napi_create_threadsafe_function(env, - callback, - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - nullptr, - nullptr, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with Callback [passed] Resource [missing] Finalizer [passed] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - TypedThreadSafeFunction tsfn; - - auto* finalizeData = new details:: - ThreadSafeFinalize( - {data, finalizeCallback}); - auto fini = - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext; - napi_status status = - napi_create_threadsafe_function(env, - callback, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, - fini, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -// static, with: Callback [passed] Resource [passed] Finalizer [passed] -template -template -inline TypedThreadSafeFunction -TypedThreadSafeFunction::New( - napi_env env, - CallbackType callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - TypedThreadSafeFunction tsfn; - - auto* finalizeData = new details:: - ThreadSafeFinalize( - {data, finalizeCallback}); - auto fini = - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext; - napi_status status = napi_create_threadsafe_function( - env, - details::DefaultCallbackWrapper< - CallbackType, - TypedThreadSafeFunction>(env, - callback), - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, - fini, - context, - CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED( - env, status, TypedThreadSafeFunction()); - } - - return tsfn; -} - -template -inline TypedThreadSafeFunction:: - TypedThreadSafeFunction() - : _tsfn() {} - -template -inline TypedThreadSafeFunction:: - TypedThreadSafeFunction(napi_threadsafe_function tsfn) - : _tsfn(tsfn) {} - -template -inline TypedThreadSafeFunction:: -operator napi_threadsafe_function() const { - return _tsfn; -} - -template -inline napi_status -TypedThreadSafeFunction::BlockingCall( - DataType* data) const { - return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); -} - -template -inline napi_status -TypedThreadSafeFunction::NonBlockingCall( - DataType* data) const { - return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); -} - -template -inline void TypedThreadSafeFunction::Ref( - napi_env env) const { - if (_tsfn != nullptr) { - napi_status status = napi_ref_threadsafe_function(env, _tsfn); - NAPI_THROW_IF_FAILED_VOID(env, status); - } -} - -template -inline void TypedThreadSafeFunction::Unref( - napi_env env) const { - if (_tsfn != nullptr) { - napi_status status = napi_unref_threadsafe_function(env, _tsfn); - NAPI_THROW_IF_FAILED_VOID(env, status); - } -} - -template -inline napi_status -TypedThreadSafeFunction::Acquire() const { - return napi_acquire_threadsafe_function(_tsfn); -} - -template -inline napi_status -TypedThreadSafeFunction::Release() const { - return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); -} - -template -inline napi_status -TypedThreadSafeFunction::Abort() const { - return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); -} - -template -inline ContextType* -TypedThreadSafeFunction::GetContext() const { - void* context; - napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); - NAPI_FATAL_IF_FAILED(status, - "TypedThreadSafeFunction::GetContext", - "napi_get_threadsafe_function_context"); - return static_cast(context); -} - -// static -template -void TypedThreadSafeFunction::CallJsInternal( - napi_env env, napi_value jsCallback, void* context, void* data) { - details::CallJsWrapper( - env, jsCallback, context, data); -} - -#if NAPI_VERSION == 4 -// static -template -Napi::Function -TypedThreadSafeFunction::EmptyFunctionFactory( - Napi::Env env) { - return Napi::Function::New(env, [](const CallbackInfo& cb) {}); -} - -// static -template -Napi::Function -TypedThreadSafeFunction::FunctionOrEmpty( - Napi::Env env, Napi::Function& callback) { - if (callback.IsEmpty()) { - return EmptyFunctionFactory(env); - } - return callback; -} - -#else -// static -template -std::nullptr_t -TypedThreadSafeFunction::EmptyFunctionFactory( - Napi::Env /*env*/) { - return nullptr; -} - -// static -template -Napi::Function -TypedThreadSafeFunction::FunctionOrEmpty( - Napi::Env /*env*/, Napi::Function& callback) { - return callback; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////// -// ThreadSafeFunction class -//////////////////////////////////////////////////////////////////////////////// - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount) { - return New( - env, callback, Object(), resourceName, maxQueueSize, initialThreadCount); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - return New(env, - callback, - Object(), - resourceName, - maxQueueSize, - initialThreadCount, - context); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback) { - return New(env, - callback, - Object(), - resourceName, - maxQueueSize, - initialThreadCount, - finalizeCallback); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, - callback, - Object(), - resourceName, - maxQueueSize, - initialThreadCount, - finalizeCallback, - data); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback) { - return New(env, - callback, - Object(), - resourceName, - maxQueueSize, - initialThreadCount, - context, - finalizeCallback); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, - callback, - Object(), - resourceName, - maxQueueSize, - initialThreadCount, - context, - finalizeCallback, - data); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount) { - return New(env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - static_cast(nullptr) /* context */); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - return New(env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - context, - [](Env, ContextType*) {} /* empty finalizer */); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback) { - return New(env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - static_cast(nullptr) /* context */, - finalizeCallback, - static_cast(nullptr) /* data */, - details::ThreadSafeFinalize::Wrapper); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - static_cast(nullptr) /* context */, - finalizeCallback, - data, - details::ThreadSafeFinalize:: - FinalizeWrapperWithData); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback) { - return New( - env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - context, - finalizeCallback, - static_cast(nullptr) /* data */, - details::ThreadSafeFinalize::FinalizeWrapperWithContext); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New( - env, - callback, - resource, - resourceName, - maxQueueSize, - initialThreadCount, - context, - finalizeCallback, - data, - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext); -} - -inline ThreadSafeFunction::ThreadSafeFunction() : _tsfn() {} - -inline ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn) - : _tsfn(tsfn) {} - -inline ThreadSafeFunction::operator napi_threadsafe_function() const { - return _tsfn; -} - -inline napi_status ThreadSafeFunction::BlockingCall() const { - return CallInternal(nullptr, napi_tsfn_blocking); -} - -template <> -inline napi_status ThreadSafeFunction::BlockingCall(void* data) const { - return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); -} - -template -inline napi_status ThreadSafeFunction::BlockingCall(Callback callback) const { - return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); -} - -template -inline napi_status ThreadSafeFunction::BlockingCall(DataType* data, - Callback callback) const { - auto wrapper = [data, callback](Env env, Function jsCallback) { - callback(env, jsCallback, data); - }; - return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking); -} - -inline napi_status ThreadSafeFunction::NonBlockingCall() const { - return CallInternal(nullptr, napi_tsfn_nonblocking); -} - -template <> -inline napi_status ThreadSafeFunction::NonBlockingCall(void* data) const { - return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); -} - -template -inline napi_status ThreadSafeFunction::NonBlockingCall( - Callback callback) const { - return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking); -} - -template -inline napi_status ThreadSafeFunction::NonBlockingCall( - DataType* data, Callback callback) const { - auto wrapper = [data, callback](Env env, Function jsCallback) { - callback(env, jsCallback, data); - }; - return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); -} - -inline void ThreadSafeFunction::Ref(napi_env env) const { - if (_tsfn != nullptr) { - napi_status status = napi_ref_threadsafe_function(env, _tsfn); - NAPI_THROW_IF_FAILED_VOID(env, status); - } -} - -inline void ThreadSafeFunction::Unref(napi_env env) const { - if (_tsfn != nullptr) { - napi_status status = napi_unref_threadsafe_function(env, _tsfn); - NAPI_THROW_IF_FAILED_VOID(env, status); - } -} - -inline napi_status ThreadSafeFunction::Acquire() const { - return napi_acquire_threadsafe_function(_tsfn); -} - -inline napi_status ThreadSafeFunction::Release() const { - return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); -} - -inline napi_status ThreadSafeFunction::Abort() const { - return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); -} - -inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() - const { - void* context; - napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); - NAPI_FATAL_IF_FAILED(status, - "ThreadSafeFunction::GetContext", - "napi_get_threadsafe_function_context"); - return ConvertibleContext({context}); -} - -// static -template -inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper) { - static_assert(details::can_make_string::value || - std::is_convertible::value, - "Resource name should be convertible to the string type"); - - ThreadSafeFunction tsfn; - auto* finalizeData = new details:: - ThreadSafeFinalize( - {data, finalizeCallback}); - napi_status status = - napi_create_threadsafe_function(env, - callback, - resource, - Value::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, - wrapper, - context, - CallJS, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); - } - - return tsfn; -} - -inline napi_status ThreadSafeFunction::CallInternal( - CallbackWrapper* callbackWrapper, - napi_threadsafe_function_call_mode mode) const { - napi_status status = - napi_call_threadsafe_function(_tsfn, callbackWrapper, mode); - if (status != napi_ok && callbackWrapper != nullptr) { - delete callbackWrapper; - } - - return status; -} - -// static -inline void ThreadSafeFunction::CallJS(napi_env env, - napi_value jsCallback, - void* /* context */, - void* data) { - if (env == nullptr && jsCallback == nullptr) { - return; - } - - details::WrapVoidCallback(env, [&]() { - if (data != nullptr) { - auto* callbackWrapper = static_cast(data); - (*callbackWrapper)(env, Function(env, jsCallback)); - delete callbackWrapper; - } else if (jsCallback != nullptr) { - Function(env, jsCallback).Call({}); - } - }); -} - -//////////////////////////////////////////////////////////////////////////////// -// Async Progress Worker Base class -//////////////////////////////////////////////////////////////////////////////// -template -inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( - const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource, - size_t queue_size) - : AsyncWorker(receiver, callback, resource_name, resource) { - // Fill all possible arguments to work around ambiguous - // ThreadSafeFunction::New signatures. - _tsfn = ThreadSafeFunction::New(callback.Env(), - callback, - resource, - resource_name, - queue_size, - /** initialThreadCount */ 1, - /** context */ this, - OnThreadSafeFunctionFinalize, - /** finalizeData */ this); -} - -#if NAPI_VERSION > 4 -template -inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( - Napi::Env env, - const char* resource_name, - const Object& resource, - size_t queue_size) - : AsyncWorker(env, resource_name, resource) { - // TODO: Once the changes to make the callback optional for threadsafe - // functions are available on all versions we can remove the dummy Function - // here. - Function callback; - // Fill all possible arguments to work around ambiguous - // ThreadSafeFunction::New signatures. - _tsfn = ThreadSafeFunction::New(env, - callback, - resource, - resource_name, - queue_size, - /** initialThreadCount */ 1, - /** context */ this, - OnThreadSafeFunctionFinalize, - /** finalizeData */ this); -} -#endif - -template -inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { - // Abort pending tsfn call. - // Don't send progress events after we've already completed. - // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release - // duplicated. - _tsfn.Abort(); -} - -template -inline void AsyncProgressWorkerBase::OnAsyncWorkProgress( - Napi::Env /* env */, Napi::Function /* jsCallback */, void* data) { - ThreadSafeData* tsd = static_cast(data); - tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); - delete tsd; -} - -template -inline napi_status AsyncProgressWorkerBase::NonBlockingCall( - DataType* data) { - auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); - auto ret = _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); - if (ret != napi_ok) { - delete tsd; - } - return ret; -} - -template -inline void AsyncProgressWorkerBase::OnWorkComplete( - Napi::Env /* env */, napi_status status) { - _work_completed = true; - _complete_status = status; - _tsfn.Release(); -} - -template -inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize( - Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { - if (context->_work_completed) { - context->AsyncWorker::OnWorkComplete(env, context->_complete_status); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Async Progress Worker class -//////////////////////////////////////////////////////////////////////////////// -template -inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) - : AsyncProgressWorker(callback, "generic") {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, - const char* resource_name) - : AsyncProgressWorker( - callback, resource_name, Object::New(callback.Env())) {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressWorker( - Object::New(callback.Env()), callback, resource_name, resource) {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, - const Function& callback) - : AsyncProgressWorker(receiver, callback, "generic") {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name) - : AsyncProgressWorker( - receiver, callback, resource_name, Object::New(callback.Env())) {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), - _asyncdata(nullptr), - _asyncsize(0), - _signaled(false) {} - -#if NAPI_VERSION > 4 -template -inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) - : AsyncProgressWorker(env, "generic") {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, - const char* resource_name) - : AsyncProgressWorker(env, resource_name, Object::New(env)) {} - -template -inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, - const char* resource_name, - const Object& resource) - : AsyncProgressWorkerBase(env, resource_name, resource), - _asyncdata(nullptr), - _asyncsize(0) {} -#endif - -template -inline AsyncProgressWorker::~AsyncProgressWorker() { - { - std::lock_guard lock(this->_mutex); - _asyncdata = nullptr; - _asyncsize = 0; - } -} - -template -inline void AsyncProgressWorker::Execute() { - ExecutionProgress progress(this); - Execute(progress); -} - -template -inline void AsyncProgressWorker::OnWorkProgress(void*) { - T* data; - size_t size; - bool signaled; - { - std::lock_guard lock(this->_mutex); - data = this->_asyncdata; - size = this->_asyncsize; - signaled = this->_signaled; - this->_asyncdata = nullptr; - this->_asyncsize = 0; - this->_signaled = false; - } - - /** - * The callback of ThreadSafeFunction is not been invoked immediately on the - * callback of uv_async_t (uv io poll), rather the callback of TSFN is - * invoked on the right next uv idle callback. There are chances that during - * the deferring the signal of uv_async_t is been sent again, i.e. potential - * not coalesced two calls of the TSFN callback. - */ - if (data == nullptr && !signaled) { - return; - } - - this->OnProgress(data, size); - delete[] data; -} - -template -inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { - T* new_data = new T[count]; - std::copy(data, data + count, new_data); - - T* old_data; - { - std::lock_guard lock(this->_mutex); - old_data = _asyncdata; - _asyncdata = new_data; - _asyncsize = count; - _signaled = false; - } - this->NonBlockingCall(nullptr); - - delete[] old_data; -} - -template -inline void AsyncProgressWorker::Signal() { - { - std::lock_guard lock(this->_mutex); - _signaled = true; - } - this->NonBlockingCall(static_cast(nullptr)); -} - -template -inline void AsyncProgressWorker::ExecutionProgress::Signal() const { - this->_worker->Signal(); -} - -template -inline void AsyncProgressWorker::ExecutionProgress::Send( - const T* data, size_t count) const { - _worker->SendProgress_(data, count); -} - -//////////////////////////////////////////////////////////////////////////////// -// Async Progress Queue Worker class -//////////////////////////////////////////////////////////////////////////////// -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Function& callback) - : AsyncProgressQueueWorker(callback, "generic") {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Function& callback, const char* resource_name) - : AsyncProgressQueueWorker( - callback, resource_name, Object::New(callback.Env())) {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Function& callback, const char* resource_name, const Object& resource) - : AsyncProgressQueueWorker( - Object::New(callback.Env()), callback, resource_name, resource) {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Object& receiver, const Function& callback) - : AsyncProgressQueueWorker(receiver, callback, "generic") {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Object& receiver, const Function& callback, const char* resource_name) - : AsyncProgressQueueWorker( - receiver, callback, resource_name, Object::New(callback.Env())) {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressWorkerBase>( - receiver, - callback, - resource_name, - resource, - /** unlimited queue size */ 0) {} - -#if NAPI_VERSION > 4 -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) - : AsyncProgressQueueWorker(env, "generic") {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - Napi::Env env, const char* resource_name) - : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) {} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( - Napi::Env env, const char* resource_name, const Object& resource) - : AsyncProgressWorkerBase>( - env, resource_name, resource, /** unlimited queue size */ 0) {} -#endif - -template -inline void AsyncProgressQueueWorker::Execute() { - ExecutionProgress progress(this); - Execute(progress); -} - -template -inline void AsyncProgressQueueWorker::OnWorkProgress( - std::pair* datapair) { - if (datapair == nullptr) { - return; - } - - T* data = datapair->first; - size_t size = datapair->second; - - this->OnProgress(data, size); - delete datapair; - delete[] data; -} - -template -inline void AsyncProgressQueueWorker::SendProgress_(const T* data, - size_t count) { - T* new_data = new T[count]; - std::copy(data, data + count, new_data); - - auto pair = new std::pair(new_data, count); - this->NonBlockingCall(pair); -} - -template -inline void AsyncProgressQueueWorker::Signal() const { - this->SendProgress_(static_cast(nullptr), 0); -} - -template -inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, - napi_status status) { - // Draining queued items in TSFN. - AsyncProgressWorkerBase>::OnWorkComplete(env, status); -} - -template -inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { - _worker->SendProgress_(static_cast(nullptr), 0); -} - -template -inline void AsyncProgressQueueWorker::ExecutionProgress::Send( - const T* data, size_t count) const { - _worker->SendProgress_(data, count); -} -#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS - -//////////////////////////////////////////////////////////////////////////////// -// Memory Management class -//////////////////////////////////////////////////////////////////////////////// - -inline int64_t MemoryManagement::AdjustExternalMemory(BasicEnv env, - int64_t change_in_bytes) { - int64_t result; - napi_status status = - napi_adjust_external_memory(env, change_in_bytes, &result); - NAPI_FATAL_IF_FAILED(status, - "MemoryManagement::AdjustExternalMemory", - "napi_adjust_external_memory"); - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// Version Management class -//////////////////////////////////////////////////////////////////////////////// - -inline uint32_t VersionManagement::GetNapiVersion(BasicEnv env) { - uint32_t result; - napi_status status = napi_get_version(env, &result); - NAPI_FATAL_IF_FAILED( - status, "VersionManagement::GetNapiVersion", "napi_get_version"); - return result; -} - -inline const napi_node_version* VersionManagement::GetNodeVersion( - BasicEnv env) { - const napi_node_version* result; - napi_status status = napi_get_node_version(env, &result); - NAPI_FATAL_IF_FAILED( - status, "VersionManagement::GetNodeVersion", "napi_get_node_version"); - return result; -} - -#if NAPI_VERSION > 5 -//////////////////////////////////////////////////////////////////////////////// -// Addon class -//////////////////////////////////////////////////////////////////////////////// - -template -inline Object Addon::Init(Env env, Object exports) { - T* addon = new T(env, exports); - env.SetInstanceData(addon); - return addon->entry_point_; -} - -template -inline T* Addon::Unwrap(Object wrapper) { - return wrapper.Env().GetInstanceData(); -} - -template -inline void Addon::DefineAddon( - Object exports, const std::initializer_list& props) { - DefineProperties(exports, props); - entry_point_ = exports; -} - -template -inline Napi::Object Addon::DefineProperties( - Object object, const std::initializer_list& props) { - const napi_property_descriptor* properties = - reinterpret_cast(props.begin()); - size_t size = props.size(); - napi_status status = - napi_define_properties(object.Env(), object, size, properties); - NAPI_THROW_IF_FAILED(object.Env(), status, object); - for (size_t idx = 0; idx < size; idx++) - T::AttachPropData(object.Env(), object, &properties[idx]); - return object; -} -#endif // NAPI_VERSION > 5 - -#if NAPI_VERSION > 2 -template -Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook, Arg* arg) { - return CleanupHook(*this, hook, arg); -} - -template -Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook) { - return CleanupHook(*this, hook); -} - -template -Env::CleanupHook::CleanupHook() { - data = nullptr; -} - -template -Env::CleanupHook::CleanupHook(Napi::BasicEnv env, Hook hook) - : wrapper(Env::CleanupHook::Wrapper) { - data = new CleanupData{std::move(hook), nullptr}; - napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); - if (status != napi_ok) { - delete data; - data = nullptr; - } -} - -template -Env::CleanupHook::CleanupHook(Napi::BasicEnv env, - Hook hook, - Arg* arg) - : wrapper(Env::CleanupHook::WrapperWithArg) { - data = new CleanupData{std::move(hook), arg}; - napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); - if (status != napi_ok) { - delete data; - data = nullptr; - } -} - -template -bool Env::CleanupHook::Remove(BasicEnv env) { - napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); - delete data; - data = nullptr; - return status == napi_ok; -} - -template -bool Env::CleanupHook::IsEmpty() const { - return data == nullptr; -} -#endif // NAPI_VERSION > 2 - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER -template -inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { - using T = void*; - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - - napi_status status = node_api_post_finalizer( - _env, - details::FinalizeData::WrapperGCWithoutData, - static_cast(nullptr), - finalizeData); - if (status != napi_ok) { - delete finalizeData; - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::PostFinalizer", "invalid arguments"); - } -} - -template -inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, - T* data) const { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), nullptr}); - - napi_status status = node_api_post_finalizer( - _env, - details::FinalizeData::WrapperGC, - data, - finalizeData); - if (status != napi_ok) { - delete finalizeData; - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::PostFinalizer", "invalid arguments"); - } -} - -template -inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, - T* data, - Hint* finalizeHint) const { - details::FinalizeData* finalizeData = - new details::FinalizeData( - {std::move(finalizeCallback), finalizeHint}); - napi_status status = node_api_post_finalizer( - _env, - details::FinalizeData::WrapperGCWithHint, - data, - finalizeData); - if (status != napi_ok) { - delete finalizeData; - NAPI_FATAL_IF_FAILED( - status, "BasicEnv::PostFinalizer", "invalid arguments"); - } -} -#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - -#ifdef NAPI_CPP_CUSTOM_NAMESPACE -} // namespace NAPI_CPP_CUSTOM_NAMESPACE -#endif - -} // namespace Napi - -#endif // SRC_NAPI_INL_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/napi.h b/week-5/solution/frontend/node_modules/node-addon-api/napi.h deleted file mode 100644 index ba0e13416..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/napi.h +++ /dev/null @@ -1,3309 +0,0 @@ -#ifndef SRC_NAPI_H_ -#define SRC_NAPI_H_ - -#ifndef NAPI_HAS_THREADS -#if !defined(__wasm__) || (defined(__EMSCRIPTEN_PTHREADS__) || \ - (defined(__wasi__) && defined(_REENTRANT))) -#define NAPI_HAS_THREADS 1 -#else -#define NAPI_HAS_THREADS 0 -#endif -#endif - -#include -#include -#include -#include -#if NAPI_HAS_THREADS -#include -#endif // NAPI_HAS_THREADS -#include -#include - -// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known -// good version) -#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 -#define NAPI_HAS_CONSTEXPR 1 -#endif - -// VS2013 does not support char16_t literal strings, so we'll work around it -// using wchar_t strings and casting them. This is safe as long as the character -// sizes are the same. -#if defined(_MSC_VER) && _MSC_VER <= 1800 -static_assert(sizeof(char16_t) == sizeof(wchar_t), - "Size mismatch between char16_t and wchar_t"); -#define NAPI_WIDE_TEXT(x) reinterpret_cast(L##x) -#else -#define NAPI_WIDE_TEXT(x) u##x -#endif - -// Backwards-compatibility to handle the rename of this macro definition, in -// case they are used within userland code. -#ifdef NAPI_CPP_EXCEPTIONS -#define NODE_ADDON_API_CPP_EXCEPTIONS -#endif -#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) -#define NAPI_CPP_EXCEPTIONS -#endif -#ifdef NAPI_DISABLE_CPP_EXCEPTIONS -#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS -#endif -#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ - !defined(NAPI_DISABLE_CPP_EXCEPTIONS) -#define NAPI_DISABLE_CPP_EXCEPTIONS -#endif - -// If C++ exceptions are not explicitly enabled or disabled, enable them -// if exceptions were enabled in the compiler settings. -#if !defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ - !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) -#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) -#define NODE_ADDON_API_CPP_EXCEPTIONS -#else -#error Exception support not detected. \ - Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. -#endif -#endif - -// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE -// should not be set -#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ - defined(NODE_ADDON_API_ENABLE_MAYBE) -#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ - NODE_ADDON_API_CPP_EXCEPTIONS is defined. -#endif - -#ifdef _NOEXCEPT -#define NAPI_NOEXCEPT _NOEXCEPT -#else -#define NAPI_NOEXCEPT noexcept -#endif - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - -// When C++ exceptions are enabled, Errors are thrown directly. There is no need -// to return anything after the throw statements. The variadic parameter is an -// optional return value that is ignored. -// We need _VOID versions of the macros to avoid warnings resulting from -// leaving the NAPI_THROW_* `...` argument empty. - -#define NAPI_THROW(e, ...) throw e -#define NAPI_THROW_VOID(e) throw e - -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) throw Napi::Error::New(env); - -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) throw Napi::Error::New(env); - -#else // NODE_ADDON_API_CPP_EXCEPTIONS - -// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, -// which are pending until the callback returns to JS. The variadic parameter -// is an optional return value; usually it is an empty result. -// We need _VOID versions of the macros to avoid warnings resulting from -// leaving the NAPI_THROW_* `...` argument empty. - -#define NAPI_THROW(e, ...) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } while (0) - -#define NAPI_THROW_VOID(e) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return; \ - } while (0) - -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } - -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return; \ - } - -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - -#ifdef NODE_ADDON_API_ENABLE_MAYBE -#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ - NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) - -#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ - NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ - return Napi::Just(result); -#else -#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ - NAPI_THROW_IF_FAILED(env, status, type()) - -#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ - NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ - return result; -#endif - -#define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; -#define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; - -#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ - NAPI_DISALLOW_ASSIGN(CLASS) \ - NAPI_DISALLOW_COPY(CLASS) - -#define NAPI_CHECK(condition, location, message) \ - do { \ - if (!(condition)) { \ - Napi::Error::Fatal((location), (message)); \ - } \ - } while (0) - -// Internal check helper. Be careful that the formatted message length should be -// max 255 size and null terminated. -#define NAPI_INTERNAL_CHECK(expr, location, ...) \ - do { \ - if (!(expr)) { \ - std::string msg = Napi::details::StringFormat(__VA_ARGS__); \ - Napi::Error::Fatal(location, msg.c_str()); \ - } \ - } while (0) - -#define NAPI_INTERNAL_CHECK_EQ(actual, expected, value_format, location) \ - do { \ - auto actual_value = (actual); \ - NAPI_INTERNAL_CHECK(actual_value == (expected), \ - location, \ - "Expected " #actual " to be equal to " #expected \ - ", but got " value_format ".", \ - actual_value); \ - } while (0) - -#define NAPI_FATAL_IF_FAILED(status, location, message) \ - NAPI_CHECK((status) == napi_ok, location, message) - -//////////////////////////////////////////////////////////////////////////////// -/// Node-API C++ Wrapper Classes -/// -/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a -/// C++ object model and C++ exception-handling semantics with low overhead. -/// The wrappers are all header-only so that they do not affect the ABI. -//////////////////////////////////////////////////////////////////////////////// -namespace Napi { - -#ifdef NAPI_CPP_CUSTOM_NAMESPACE -// NAPI_CPP_CUSTOM_NAMESPACE can be #define'd per-addon to avoid symbol -// conflicts between different instances of node-addon-api - -// First dummy definition of the namespace to make sure that Napi::(name) still -// refers to the right things inside this file. -namespace NAPI_CPP_CUSTOM_NAMESPACE {} -using namespace NAPI_CPP_CUSTOM_NAMESPACE; - -namespace NAPI_CPP_CUSTOM_NAMESPACE { -#endif - -// Forward declarations -class Env; -class Value; -class Boolean; -class Number; -#if NAPI_VERSION > 5 -class BigInt; -#endif // NAPI_VERSION > 5 -#if (NAPI_VERSION > 4) -class Date; -#endif -class String; -class Object; -class Array; -class ArrayBuffer; -class Function; -class Error; -class PropertyDescriptor; -class CallbackInfo; -class TypedArray; -template -class TypedArrayOf; - -using Int8Array = - TypedArrayOf; ///< Typed-array of signed 8-bit integers -using Uint8Array = - TypedArrayOf; ///< Typed-array of unsigned 8-bit integers -using Int16Array = - TypedArrayOf; ///< Typed-array of signed 16-bit integers -using Uint16Array = - TypedArrayOf; ///< Typed-array of unsigned 16-bit integers -using Int32Array = - TypedArrayOf; ///< Typed-array of signed 32-bit integers -using Uint32Array = - TypedArrayOf; ///< Typed-array of unsigned 32-bit integers -using Float32Array = - TypedArrayOf; ///< Typed-array of 32-bit floating-point values -using Float64Array = - TypedArrayOf; ///< Typed-array of 64-bit floating-point values -#if NAPI_VERSION > 5 -using BigInt64Array = - TypedArrayOf; ///< Typed array of signed 64-bit integers -using BigUint64Array = - TypedArrayOf; ///< Typed array of unsigned 64-bit integers -#endif // NAPI_VERSION > 5 - -/// Defines the signature of a Node-API C++ module's registration callback -/// (init) function. -using ModuleRegisterCallback = Object (*)(Env env, Object exports); - -class MemoryManagement; - -/// A simple Maybe type, representing an object which may or may not have a -/// value. -/// -/// If an API method returns a Maybe<>, the API method can potentially fail -/// either because an exception is thrown, or because an exception is pending, -/// e.g. because a previous API call threw an exception that hasn't been -/// caught yet. In that case, a "Nothing" value is returned. -template -class Maybe { - public: - bool IsNothing() const; - bool IsJust() const; - - /// Short-hand for Unwrap(), which doesn't return a value. Could be used - /// where the actual value of the Maybe is not needed like Object::Set. - /// If this Maybe is nothing (empty), node-addon-api will crash the - /// process. - void Check() const; - - /// Return the value of type T contained in the Maybe. If this Maybe is - /// nothing (empty), node-addon-api will crash the process. - T Unwrap() const; - - /// Return the value of type T contained in the Maybe, or using a default - /// value if this Maybe is nothing (empty). - T UnwrapOr(const T& default_value) const; - - /// Converts this Maybe to a value of type T in the out. If this Maybe is - /// nothing (empty), `false` is returned and `out` is left untouched. - bool UnwrapTo(T* out) const; - - bool operator==(const Maybe& other) const; - bool operator!=(const Maybe& other) const; - - private: - Maybe(); - explicit Maybe(const T& t); - - bool _has_value; - T _value; - - template - friend Maybe Nothing(); - template - friend Maybe Just(const U& u); -}; - -template -inline Maybe Nothing(); - -template -inline Maybe Just(const T& t); - -#if defined(NODE_ADDON_API_ENABLE_MAYBE) -template -using MaybeOrValue = Maybe; -#else -template -using MaybeOrValue = T; -#endif - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER -using node_addon_api_basic_env = node_api_nogc_env; -using node_addon_api_basic_finalize = node_api_nogc_finalize; -#else -using node_addon_api_basic_env = napi_env; -using node_addon_api_basic_finalize = napi_finalize; -#endif - -/// Environment for Node-API values and operations. -/// -/// All Node-API values and operations must be associated with an environment. -/// An environment instance is always provided to callback functions; that -/// environment must then be used for any creation of Node-API values or other -/// Node-API operations within the callback. (Many methods infer the -/// environment from the `this` instance that the method is called on.) -/// -/// Multiple environments may co-exist in a single process or a thread. -/// -/// In the V8 JavaScript engine, a Node-API environment approximately -/// corresponds to an Isolate. -class BasicEnv { - private: - node_addon_api_basic_env _env; -#if NAPI_VERSION > 5 - template - static void DefaultFini(Env, T* data); - template - static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); -#endif // NAPI_VERSION > 5 - public: - BasicEnv(node_addon_api_basic_env env); - - operator node_addon_api_basic_env() const; - - // Without these operator overloads, the error: - // - // Use of overloaded operator '==' is ambiguous (with operand types - // 'Napi::Env' and 'Napi::Env') - // - // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr - bool operator==(const BasicEnv& other) const { - return _env == other._env; - }; - bool operator==(std::nullptr_t /*other*/) const { - return _env == nullptr; - }; - -#if NAPI_VERSION > 2 - template - class CleanupHook; - - template - CleanupHook AddCleanupHook(Hook hook); - - template - CleanupHook AddCleanupHook(Hook hook, Arg* arg); -#endif // NAPI_VERSION > 2 - -#if NAPI_VERSION > 5 - template - T* GetInstanceData() const; - - template - using Finalizer = void (*)(Env, T*); - template fini = BasicEnv::DefaultFini> - void SetInstanceData(T* data) const; - - template - using FinalizerWithHint = void (*)(Env, DataType*, HintType*); - template fini = - BasicEnv::DefaultFiniWithHint> - void SetInstanceData(DataType* data, HintType* hint) const; -#endif // NAPI_VERSION > 5 - -#if NAPI_VERSION > 2 - template - class CleanupHook { - public: - CleanupHook(); - CleanupHook(BasicEnv env, Hook hook, Arg* arg); - CleanupHook(BasicEnv env, Hook hook); - bool Remove(BasicEnv env); - bool IsEmpty() const; - - private: - static inline void Wrapper(void* data) NAPI_NOEXCEPT; - static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; - - void (*wrapper)(void* arg); - struct CleanupData { - Hook hook; - Arg* arg; - } * data; - }; -#endif // NAPI_VERSION > 2 - -#if NAPI_VERSION > 8 - const char* GetModuleFileName() const; -#endif // NAPI_VERSION > 8 - -#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - template - inline void PostFinalizer(FinalizerType finalizeCallback) const; - - template - inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; - - template - inline void PostFinalizer(FinalizerType finalizeCallback, - T* data, - Hint* finalizeHint) const; -#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER - - friend class Env; -}; - -class Env : public BasicEnv { - public: - Env(napi_env env); - - operator napi_env() const; - - Object Global() const; - Value Undefined() const; - Value Null() const; - - bool IsExceptionPending() const; - Error GetAndClearPendingException() const; - - MaybeOrValue RunScript(const char* utf8script) const; - MaybeOrValue RunScript(const std::string& utf8script) const; - MaybeOrValue RunScript(String script) const; -}; - -/// A JavaScript value of unknown type. -/// -/// For type-specific operations, convert to one of the Value subclasses using a -/// `To*` or `As()` method. The `To*` methods do type coercion; the `As()` -/// method does not. -/// -/// Napi::Value value = ... -/// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid -/// arg..."); Napi::String str = value.As(); // Cast to a -/// string value -/// -/// Napi::Value anotherValue = ... -/// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value -class Value { - public: - Value(); ///< Creates a new _empty_ Value instance. - Value(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - /// Creates a JS value from a C++ primitive. - /// - /// `value` may be any of: - /// - bool - /// - Any integer type - /// - Any floating point type - /// - const char* (encoded using UTF-8, null-terminated) - /// - const char16_t* (encoded using UTF-16-LE, null-terminated) - /// - std::string (encoded using UTF-8) - /// - std::u16string - /// - napi::Value - /// - napi_value - template - static Value From(napi_env env, const T& value); - - static void CheckCast(napi_env env, napi_value value); - - /// Converts to a Node-API value primitive. - /// - /// If the instance is _empty_, this returns `nullptr`. - operator napi_value() const; - - /// Tests if this value strictly equals another value. - bool operator==(const Value& other) const; - - /// Tests if this value does not strictly equal another value. - bool operator!=(const Value& other) const; - - /// Tests if this value strictly equals another value. - bool StrictEquals(const Value& other) const; - - /// Gets the environment the value is associated with. - Napi::Env Env() const; - - /// Checks if the value is empty (uninitialized). - /// - /// An empty value is invalid, and most attempts to perform an operation on an - /// empty value will result in an exception. Note an empty value is distinct - /// from JavaScript `null` or `undefined`, which are valid values. - /// - /// When C++ exceptions are disabled at compile time, a method with a `Value` - /// return type may return an empty value to indicate a pending exception. So - /// when not using C++ exceptions, callers should check whether the value is - /// empty before attempting to use it. - bool IsEmpty() const; - - napi_valuetype Type() const; ///< Gets the type of the value. - - bool IsUndefined() - const; ///< Tests if a value is an undefined JavaScript value. - bool IsNull() const; ///< Tests if a value is a null JavaScript value. - bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. - bool IsNumber() const; ///< Tests if a value is a JavaScript number. -#if NAPI_VERSION > 5 - bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. -#endif // NAPI_VERSION > 5 -#if (NAPI_VERSION > 4) - bool IsDate() const; ///< Tests if a value is a JavaScript date. -#endif - bool IsString() const; ///< Tests if a value is a JavaScript string. - bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. - bool IsArray() const; ///< Tests if a value is a JavaScript array. - bool IsArrayBuffer() - const; ///< Tests if a value is a JavaScript array buffer. - bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. - bool IsObject() const; ///< Tests if a value is a JavaScript object. - bool IsFunction() const; ///< Tests if a value is a JavaScript function. - bool IsPromise() const; ///< Tests if a value is a JavaScript promise. - bool IsDataView() const; ///< Tests if a value is a JavaScript data view. - bool IsBuffer() const; ///< Tests if a value is a Node buffer. - bool IsExternal() const; ///< Tests if a value is a pointer to external data. - - /// Casts to another type of `Napi::Value`, when the actual type is known or - /// assumed. - /// - /// This conversion does NOT coerce the type. Calling any methods - /// inappropriate for the actual value type will throw `Napi::Error`. - /// - /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method - /// asserts that the actual type is the expected type. - template - T As() const; - - // Unsafe Value::As(), should be avoided. - template - T UnsafeAs() const; - - MaybeOrValue ToBoolean() - const; ///< Coerces a value to a JavaScript boolean. - MaybeOrValue ToNumber() - const; ///< Coerces a value to a JavaScript number. - MaybeOrValue ToString() - const; ///< Coerces a value to a JavaScript string. - MaybeOrValue ToObject() - const; ///< Coerces a value to a JavaScript object. - - protected: - /// !cond INTERNAL - napi_env _env; - napi_value _value; - /// !endcond -}; - -/// A JavaScript boolean value. -class Boolean : public Value { - public: - static Boolean New(napi_env env, ///< Node-API environment - bool value ///< Boolean value - ); - - static void CheckCast(napi_env env, napi_value value); - - Boolean(); ///< Creates a new _empty_ Boolean instance. - Boolean(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - operator bool() const; ///< Converts a Boolean value to a boolean primitive. - bool Value() const; ///< Converts a Boolean value to a boolean primitive. -}; - -/// A JavaScript number value. -class Number : public Value { - public: - static Number New(napi_env env, ///< Node-API environment - double value ///< Number value - ); - - static void CheckCast(napi_env env, napi_value value); - - Number(); ///< Creates a new _empty_ Number instance. - Number(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - operator int32_t() - const; ///< Converts a Number value to a 32-bit signed integer value. - operator uint32_t() - const; ///< Converts a Number value to a 32-bit unsigned integer value. - operator int64_t() - const; ///< Converts a Number value to a 64-bit signed integer value. - operator float() - const; ///< Converts a Number value to a 32-bit floating-point value. - operator double() - const; ///< Converts a Number value to a 64-bit floating-point value. - - int32_t Int32Value() - const; ///< Converts a Number value to a 32-bit signed integer value. - uint32_t Uint32Value() - const; ///< Converts a Number value to a 32-bit unsigned integer value. - int64_t Int64Value() - const; ///< Converts a Number value to a 64-bit signed integer value. - float FloatValue() - const; ///< Converts a Number value to a 32-bit floating-point value. - double DoubleValue() - const; ///< Converts a Number value to a 64-bit floating-point value. -}; - -#if NAPI_VERSION > 5 -/// A JavaScript bigint value. -class BigInt : public Value { - public: - static BigInt New(napi_env env, ///< Node-API environment - int64_t value ///< Number value - ); - static BigInt New(napi_env env, ///< Node-API environment - uint64_t value ///< Number value - ); - - /// Creates a new BigInt object using a specified sign bit and a - /// specified list of digits/words. - /// The resulting number is calculated as: - /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) - static BigInt New(napi_env env, ///< Node-API environment - int sign_bit, ///< Sign bit. 1 if negative. - size_t word_count, ///< Number of words in array - const uint64_t* words ///< Array of words - ); - - static void CheckCast(napi_env env, napi_value value); - - BigInt(); ///< Creates a new _empty_ BigInt instance. - BigInt(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - int64_t Int64Value(bool* lossless) - const; ///< Converts a BigInt value to a 64-bit signed integer value. - uint64_t Uint64Value(bool* lossless) - const; ///< Converts a BigInt value to a 64-bit unsigned integer value. - - size_t WordCount() const; ///< The number of 64-bit words needed to store - ///< the result of ToWords(). - - /// Writes the contents of this BigInt to a specified memory location. - /// `sign_bit` must be provided and will be set to 1 if this BigInt is - /// negative. - /// `*word_count` has to be initialized to the length of the `words` array. - /// Upon return, it will be set to the actual number of words that would - /// be needed to store this BigInt (i.e. the return value of `WordCount()`). - void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); -}; -#endif // NAPI_VERSION > 5 - -#if (NAPI_VERSION > 4) -/// A JavaScript date value. -class Date : public Value { - public: - /// Creates a new Date value from a double primitive. - static Date New(napi_env env, ///< Node-API environment - double value ///< Number value - ); - - static void CheckCast(napi_env env, napi_value value); - - Date(); ///< Creates a new _empty_ Date instance. - Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. - operator double() const; ///< Converts a Date value to double primitive - - double ValueOf() const; ///< Converts a Date value to a double primitive. -}; -#endif - -/// A JavaScript string or symbol value (that can be used as a property name). -class Name : public Value { - public: - static void CheckCast(napi_env env, napi_value value); - - Name(); ///< Creates a new _empty_ Name instance. - Name(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. -}; - -/// A JavaScript string value. -class String : public Name { - public: - /// Creates a new String value from a UTF-8 encoded C++ string. - static String New(napi_env env, ///< Node-API environment - const std::string& value ///< UTF-8 encoded C++ string - ); - - /// Creates a new String value from a UTF-16 encoded C++ string. - static String New(napi_env env, ///< Node-API environment - const std::u16string& value ///< UTF-16 encoded C++ string - ); - - /// Creates a new String value from a UTF-8 encoded C string. - static String New( - napi_env env, ///< Node-API environment - const char* value ///< UTF-8 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-16 encoded C string. - static String New( - napi_env env, ///< Node-API environment - const char16_t* value ///< UTF-16 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-8 encoded C string with specified - /// length. - static String New(napi_env env, ///< Node-API environment - const char* value, ///< UTF-8 encoded C string (not - ///< necessarily null-terminated) - size_t length ///< length of the string in bytes - ); - - /// Creates a new String value from a UTF-16 encoded C string with specified - /// length. - static String New( - napi_env env, ///< Node-API environment - const char16_t* value, ///< UTF-16 encoded C string (not necessarily - ///< null-terminated) - size_t length ///< Length of the string in 2-byte code units - ); - - /// Creates a new String based on the original object's type. - /// - /// `value` may be any of: - /// - const char* (encoded using UTF-8, null-terminated) - /// - const char16_t* (encoded using UTF-16-LE, null-terminated) - /// - std::string (encoded using UTF-8) - /// - std::u16string - template - static String From(napi_env env, const T& value); - - static void CheckCast(napi_env env, napi_value value); - - String(); ///< Creates a new _empty_ String instance. - String(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - operator std::string() - const; ///< Converts a String value to a UTF-8 encoded C++ string. - operator std::u16string() - const; ///< Converts a String value to a UTF-16 encoded C++ string. - std::string Utf8Value() - const; ///< Converts a String value to a UTF-8 encoded C++ string. - std::u16string Utf16Value() - const; ///< Converts a String value to a UTF-16 encoded C++ string. -}; - -/// A JavaScript symbol value. -class Symbol : public Name { - public: - /// Creates a new Symbol value with an optional description. - static Symbol New( - napi_env env, ///< Node-API environment - const char* description = - nullptr ///< Optional UTF-8 encoded null-terminated C string - /// describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< Node-API environment - const std::string& - description ///< UTF-8 encoded C++ string describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New(napi_env env, ///< Node-API environment - String description ///< String value describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< Node-API environment - napi_value description ///< String value describing the symbol - ); - - /// Get a public Symbol (e.g. Symbol.iterator). - static MaybeOrValue WellKnown(napi_env, const std::string& name); - - // Create a symbol in the global registry, UTF-8 Encoded cpp string - static MaybeOrValue For(napi_env env, const std::string& description); - - // Create a symbol in the global registry, C style string (null terminated) - static MaybeOrValue For(napi_env env, const char* description); - - // Create a symbol in the global registry, String value describing the symbol - static MaybeOrValue For(napi_env env, String description); - - // Create a symbol in the global registry, napi_value describing the symbol - static MaybeOrValue For(napi_env env, napi_value description); - - static void CheckCast(napi_env env, napi_value value); - - Symbol(); ///< Creates a new _empty_ Symbol instance. - Symbol(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. -}; - -class TypeTaggable : public Value { - public: -#if NAPI_VERSION >= 8 - void TypeTag(const napi_type_tag* type_tag) const; - bool CheckTypeTag(const napi_type_tag* type_tag) const; -#endif // NAPI_VERSION >= 8 - protected: - TypeTaggable(); - TypeTaggable(napi_env env, napi_value value); -}; - -/// A JavaScript object value. -class Object : public TypeTaggable { - public: - /// Enables property and element assignments using indexing syntax. - /// - /// This is a convenient helper to get and set object properties. As - /// getting and setting object properties may throw with JavaScript - /// exceptions, it is notable that these operations may fail. - /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort - /// on JavaScript exceptions. - /// - /// Example: - /// - /// Napi::Value propertyValue = object1['A']; - /// object2['A'] = propertyValue; - /// Napi::Value elementValue = array[0]; - /// array[1] = elementValue; - template - class PropertyLValue { - public: - /// Converts an L-value to a value. - operator Value() const; - - /// Assigns a value to the property. The type of value can be - /// anything supported by `Object::Set`. - template - PropertyLValue& operator=(ValueType value); - - /// Converts an L-value to a value. For convenience. - Value AsValue() const; - - private: - PropertyLValue() = delete; - PropertyLValue(Object object, Key key); - napi_env _env; - napi_value _object; - Key _key; - - friend class Napi::Object; - }; - - /// Creates a new Object value. - static Object New(napi_env env ///< Node-API environment - ); - - static void CheckCast(napi_env env, napi_value value); - - Object(); ///< Creates a new _empty_ Object instance. - Object(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - /// Gets or sets a named property. - PropertyLValue operator[]( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ); - - /// Gets or sets a named property. - PropertyLValue operator[]( - const std::string& utf8name ///< UTF-8 encoded property name - ); - - /// Gets or sets an indexed property or array element. - PropertyLValue operator[]( - uint32_t index /// Property / element index - ); - - /// Gets or sets an indexed property or array element. - PropertyLValue operator[](Value index /// Property / element index - ) const; - - /// Gets a named property. - MaybeOrValue operator[]( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Gets a named property. - MaybeOrValue operator[]( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Gets an indexed property or array element. - MaybeOrValue operator[](uint32_t index ///< Property / element index - ) const; - - /// Checks whether a property is present. - MaybeOrValue Has(napi_value key ///< Property key primitive - ) const; - - /// Checks whether a property is present. - MaybeOrValue Has(Value key ///< Property key - ) const; - - /// Checks whether a named property is present. - MaybeOrValue Has( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Checks whether a named property is present. - MaybeOrValue Has( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Checks whether a own property is present. - MaybeOrValue HasOwnProperty(napi_value key ///< Property key primitive - ) const; - - /// Checks whether a own property is present. - MaybeOrValue HasOwnProperty(Value key ///< Property key - ) const; - - /// Checks whether a own property is present. - MaybeOrValue HasOwnProperty( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Checks whether a own property is present. - MaybeOrValue HasOwnProperty( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Gets a property. - MaybeOrValue Get(napi_value key ///< Property key primitive - ) const; - - /// Gets a property. - MaybeOrValue Get(Value key ///< Property key - ) const; - - /// Gets a named property. - MaybeOrValue Get( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Gets a named property. - MaybeOrValue Get( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Sets a property. - template - MaybeOrValue Set(napi_value key, ///< Property key primitive - const ValueType& value ///< Property value primitive - ) const; - - /// Sets a property. - template - MaybeOrValue Set(Value key, ///< Property key - const ValueType& value ///< Property value - ) const; - - /// Sets a named property. - template - MaybeOrValue Set( - const char* utf8name, ///< UTF-8 encoded null-terminated property name - const ValueType& value) const; - - /// Sets a named property. - template - MaybeOrValue Set( - const std::string& utf8name, ///< UTF-8 encoded property name - const ValueType& value ///< Property value primitive - ) const; - - /// Delete property. - MaybeOrValue Delete(napi_value key ///< Property key primitive - ) const; - - /// Delete property. - MaybeOrValue Delete(Value key ///< Property key - ) const; - - /// Delete property. - MaybeOrValue Delete( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Delete property. - MaybeOrValue Delete( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Checks whether an indexed property is present. - MaybeOrValue Has(uint32_t index ///< Property / element index - ) const; - - /// Gets an indexed property or array element. - MaybeOrValue Get(uint32_t index ///< Property / element index - ) const; - - /// Sets an indexed property or array element. - template - MaybeOrValue Set(uint32_t index, ///< Property / element index - const ValueType& value ///< Property value primitive - ) const; - - /// Deletes an indexed property or array element. - MaybeOrValue Delete(uint32_t index ///< Property / element index - ) const; - - /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and - /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: - /// - - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys - /// - - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p - MaybeOrValue GetPropertyNames() const; ///< Get all property names - - /// Defines a property on the object. - /// - /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling - /// into JavaScript. See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc - MaybeOrValue DefineProperty( - const PropertyDescriptor& - property ///< Descriptor for the property to be defined - ) const; - - /// Defines properties on the object. - /// - /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling - /// into JavaScript. See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc - MaybeOrValue DefineProperties( - const std::initializer_list& properties - ///< List of descriptors for the properties to be defined - ) const; - - /// Defines properties on the object. - /// - /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling - /// into JavaScript. See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc - MaybeOrValue DefineProperties( - const std::vector& properties - ///< Vector of descriptors for the properties to be defined - ) const; - - /// Checks if an object is an instance created by a constructor function. - /// - /// This is equivalent to the JavaScript `instanceof` operator. - /// - /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into - /// JavaScript. - /// See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof - MaybeOrValue InstanceOf( - const Function& constructor ///< Constructor function - ) const; - - template - inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; - - template - inline void AddFinalizer(Finalizer finalizeCallback, - T* data, - Hint* finalizeHint) const; - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - class const_iterator; - - inline const_iterator begin() const; - - inline const_iterator end() const; - - class iterator; - - inline iterator begin(); - - inline iterator end(); -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - -#if NAPI_VERSION >= 8 - /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into - /// JavaScript. - /// See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof - MaybeOrValue Freeze() const; - /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into - /// JavaScript. - /// See - /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof - MaybeOrValue Seal() const; -#endif // NAPI_VERSION >= 8 -}; - -template -class External : public TypeTaggable { - public: - static External New(napi_env env, T* data); - - // Finalizer must implement `void operator()(Env env, T* data)`. - template - static External New(napi_env env, T* data, Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - template - static External New(napi_env env, - T* data, - Finalizer finalizeCallback, - Hint* finalizeHint); - - static void CheckCast(napi_env env, napi_value value); - - External(); - External(napi_env env, napi_value value); - - T* Data() const; -}; - -class Array : public Object { - public: - static Array New(napi_env env); - static Array New(napi_env env, size_t length); - - static void CheckCast(napi_env env, napi_value value); - - Array(); - Array(napi_env env, napi_value value); - - uint32_t Length() const; -}; - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS -class Object::const_iterator { - private: - enum class Type { BEGIN, END }; - - inline const_iterator(const Object* object, const Type type); - - public: - inline const_iterator& operator++(); - - inline bool operator==(const const_iterator& other) const; - - inline bool operator!=(const const_iterator& other) const; - - inline const std::pair> operator*() - const; - - private: - const Napi::Object* _object; - Array _keys; - uint32_t _index; - - friend class Object; -}; - -class Object::iterator { - private: - enum class Type { BEGIN, END }; - - inline iterator(Object* object, const Type type); - - public: - inline iterator& operator++(); - - inline bool operator==(const iterator& other) const; - - inline bool operator!=(const iterator& other) const; - - inline std::pair> operator*(); - - private: - Napi::Object* _object; - Array _keys; - uint32_t _index; - - friend class Object; -}; -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - -/// A JavaScript array buffer value. -class ArrayBuffer : public Object { - public: - /// Creates a new ArrayBuffer instance over a new automatically-allocated - /// buffer. - static ArrayBuffer New( - napi_env env, ///< Node-API environment - size_t byteLength ///< Length of the buffer to be allocated, in bytes - ); - -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - /// Creates a new ArrayBuffer instance, using an external buffer with - /// specified byte length. - static ArrayBuffer New( - napi_env env, ///< Node-API environment - void* externalData, ///< Pointer to the external buffer to be used by - ///< the array - size_t byteLength ///< Length of the external buffer to be used by the - ///< array, in bytes - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with - /// specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< Node-API environment - void* externalData, ///< Pointer to the external buffer to be used by - ///< the array - size_t byteLength, ///< Length of the external buffer to be used by the - ///< array, - /// in bytes - Finalizer finalizeCallback ///< Function to be called when the array - ///< buffer is destroyed; - /// must implement `void operator()(Env env, - /// void* externalData)` - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with - /// specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< Node-API environment - void* externalData, ///< Pointer to the external buffer to be used by - ///< the array - size_t byteLength, ///< Length of the external buffer to be used by the - ///< array, - /// in bytes - Finalizer finalizeCallback, ///< Function to be called when the array - ///< buffer is destroyed; - /// must implement `void operator()(Env - /// env, void* externalData, Hint* hint)` - Hint* finalizeHint ///< Hint (second parameter) to be passed to the - ///< finalize callback - ); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - - static void CheckCast(napi_env env, napi_value value); - - ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. - ArrayBuffer(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - void* Data(); ///< Gets a pointer to the data buffer. - size_t ByteLength(); ///< Gets the length of the array buffer in bytes. - -#if NAPI_VERSION >= 7 - bool IsDetached() const; - void Detach(); -#endif // NAPI_VERSION >= 7 -}; - -/// A JavaScript typed-array value with unknown array type. -/// -/// For type-specific operations, cast to a `TypedArrayOf` instance using the -/// `As()` method: -/// -/// Napi::TypedArray array = ... -/// if (t.TypedArrayType() == napi_int32_array) { -/// Napi::Int32Array int32Array = t.As(); -/// } -class TypedArray : public Object { - public: - static void CheckCast(napi_env env, napi_value value); - - TypedArray(); ///< Creates a new _empty_ TypedArray instance. - TypedArray(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - napi_typedarray_type TypedArrayType() - const; ///< Gets the type of this typed-array. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. - - uint8_t ElementSize() - const; ///< Gets the size in bytes of one element in the array. - size_t ElementLength() const; ///< Gets the number of elements in the array. - size_t ByteOffset() - const; ///< Gets the offset into the buffer where the array starts. - size_t ByteLength() const; ///< Gets the length of the array in bytes. - - protected: - /// !cond INTERNAL - napi_typedarray_type _type; - size_t _length; - - TypedArray(napi_env env, - napi_value value, - napi_typedarray_type type, - size_t length); - - template - static -#if defined(NAPI_HAS_CONSTEXPR) - constexpr -#endif - napi_typedarray_type - TypedArrayTypeForPrimitiveType() { - return std::is_same::value ? napi_int8_array - : std::is_same::value ? napi_uint8_array - : std::is_same::value ? napi_int16_array - : std::is_same::value ? napi_uint16_array - : std::is_same::value ? napi_int32_array - : std::is_same::value ? napi_uint32_array - : std::is_same::value ? napi_float32_array - : std::is_same::value ? napi_float64_array -#if NAPI_VERSION > 5 - : std::is_same::value ? napi_bigint64_array - : std::is_same::value ? napi_biguint64_array -#endif // NAPI_VERSION > 5 - : napi_int8_array; - } - /// !endcond -}; - -/// A JavaScript typed-array value with known array type. -/// -/// Note while it is possible to create and access Uint8 "clamped" arrays using -/// this class, the _clamping_ behavior is only applied in JavaScript. -template -class TypedArrayOf : public TypedArray { - public: - /// Creates a new TypedArray instance over a new automatically-allocated array - /// buffer. - /// - /// The array type parameter can normally be omitted (because it is inferred - /// from the template parameter T), except when creating a "clamped" array: - /// - /// Uint8Array::New(env, length, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< Node-API environment - size_t elementLength, ///< Length of the created array, as a number of - ///< elements -#if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = - TypedArray::TypedArrayTypeForPrimitiveType() -#else - napi_typedarray_type type -#endif - ///< Type of array, if different from the default array type for the - ///< template parameter T. - ); - - /// Creates a new TypedArray instance over a provided array buffer. - /// - /// The array type parameter can normally be omitted (because it is inferred - /// from the template parameter T), except when creating a "clamped" array: - /// - /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< Node-API environment - size_t elementLength, ///< Length of the created array, as a number of - ///< elements - Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use - size_t bufferOffset, ///< Offset into the array buffer where the - ///< typed-array starts -#if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = - TypedArray::TypedArrayTypeForPrimitiveType() -#else - napi_typedarray_type type -#endif - ///< Type of array, if different from the default array type for the - ///< template parameter T. - ); - - static void CheckCast(napi_env env, napi_value value); - - TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. - TypedArrayOf(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - T& operator[](size_t index); ///< Gets or sets an element in the array. - const T& operator[](size_t index) const; ///< Gets an element in the array. - - /// Gets a pointer to the array's backing buffer. - /// - /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, - /// because the typed-array may have a non-zero `ByteOffset()` into the - /// `ArrayBuffer`. - T* Data(); - - /// Gets a pointer to the array's backing buffer. - /// - /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, - /// because the typed-array may have a non-zero `ByteOffset()` into the - /// `ArrayBuffer`. - const T* Data() const; - - private: - T* _data; - - TypedArrayOf(napi_env env, - napi_value value, - napi_typedarray_type type, - size_t length, - T* data); -}; - -/// The DataView provides a low-level interface for reading/writing multiple -/// number types in an ArrayBuffer irrespective of the platform's endianness. -class DataView : public Object { - public: - static DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); - static DataView New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset); - static DataView New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset, - size_t byteLength); - - static void CheckCast(napi_env env, napi_value value); - - DataView(); ///< Creates a new _empty_ DataView instance. - DataView(napi_env env, - napi_value value); ///< Wraps a Node-API value primitive. - - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. - size_t ByteOffset() - const; ///< Gets the offset into the buffer where the array starts. - size_t ByteLength() const; ///< Gets the length of the array in bytes. - - void* Data() const; - - float GetFloat32(size_t byteOffset) const; - double GetFloat64(size_t byteOffset) const; - int8_t GetInt8(size_t byteOffset) const; - int16_t GetInt16(size_t byteOffset) const; - int32_t GetInt32(size_t byteOffset) const; - uint8_t GetUint8(size_t byteOffset) const; - uint16_t GetUint16(size_t byteOffset) const; - uint32_t GetUint32(size_t byteOffset) const; - - void SetFloat32(size_t byteOffset, float value) const; - void SetFloat64(size_t byteOffset, double value) const; - void SetInt8(size_t byteOffset, int8_t value) const; - void SetInt16(size_t byteOffset, int16_t value) const; - void SetInt32(size_t byteOffset, int32_t value) const; - void SetUint8(size_t byteOffset, uint8_t value) const; - void SetUint16(size_t byteOffset, uint16_t value) const; - void SetUint32(size_t byteOffset, uint32_t value) const; - - private: - template - T ReadData(size_t byteOffset) const; - - template - void WriteData(size_t byteOffset, T value) const; - - void* _data{}; - size_t _length{}; -}; - -class Function : public Object { - public: - using VoidCallback = void (*)(const CallbackInfo& info); - using Callback = Value (*)(const CallbackInfo& info); - - template - static Function New(napi_env env, - const char* utf8name = nullptr, - void* data = nullptr); - - template - static Function New(napi_env env, - const char* utf8name = nullptr, - void* data = nullptr); - - template - static Function New(napi_env env, - const std::string& utf8name, - void* data = nullptr); - - template - static Function New(napi_env env, - const std::string& utf8name, - void* data = nullptr); - - /// Callable must implement operator() accepting a const CallbackInfo& - /// and return either void or Value. - template - static Function New(napi_env env, - Callable cb, - const char* utf8name = nullptr, - void* data = nullptr); - /// Callable must implement operator() accepting a const CallbackInfo& - /// and return either void or Value. - template - static Function New(napi_env env, - Callable cb, - const std::string& utf8name, - void* data = nullptr); - - static void CheckCast(napi_env env, napi_value value); - - Function(); - Function(napi_env env, napi_value value); - - MaybeOrValue operator()( - const std::initializer_list& args) const; - - MaybeOrValue Call(const std::initializer_list& args) const; - MaybeOrValue Call(const std::vector& args) const; - MaybeOrValue Call(const std::vector& args) const; - MaybeOrValue Call(size_t argc, const napi_value* args) const; - MaybeOrValue Call(napi_value recv, - const std::initializer_list& args) const; - MaybeOrValue Call(napi_value recv, - const std::vector& args) const; - MaybeOrValue Call(napi_value recv, - const std::vector& args) const; - MaybeOrValue Call(napi_value recv, - size_t argc, - const napi_value* args) const; - - MaybeOrValue MakeCallback( - napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - MaybeOrValue MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - MaybeOrValue MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - MaybeOrValue New(const std::initializer_list& args) const; - MaybeOrValue New(const std::vector& args) const; - MaybeOrValue New(size_t argc, const napi_value* args) const; -}; - -class Promise : public Object { - public: - class Deferred { - public: - static Deferred New(napi_env env); - Deferred(napi_env env); - - Napi::Promise Promise() const; - Napi::Env Env() const; - - void Resolve(napi_value value) const; - void Reject(napi_value value) const; - - private: - napi_env _env; - napi_deferred _deferred; - napi_value _promise; - }; - - static void CheckCast(napi_env env, napi_value value); - - Promise(); - Promise(napi_env env, napi_value value); - - MaybeOrValue Then(napi_value onFulfilled) const; - MaybeOrValue Then(napi_value onFulfilled, - napi_value onRejected) const; - MaybeOrValue Catch(napi_value onRejected) const; - - MaybeOrValue Then(const Function& onFulfilled) const; - MaybeOrValue Then(const Function& onFulfilled, - const Function& onRejected) const; - MaybeOrValue Catch(const Function& onRejected) const; -}; - -template -class Buffer : public Uint8Array { - public: - static Buffer New(napi_env env, size_t length); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - static Buffer New(napi_env env, T* data, size_t length); - - // Finalizer must implement `void operator()(Env env, T* data)`. - template - static Buffer New(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - template - static Buffer New(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback, - Hint* finalizeHint); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - - static Buffer NewOrCopy(napi_env env, T* data, size_t length); - // Finalizer must implement `void operator()(Env env, T* data)`. - template - static Buffer NewOrCopy(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - template - static Buffer NewOrCopy(napi_env env, - T* data, - size_t length, - Finalizer finalizeCallback, - Hint* finalizeHint); - - static Buffer Copy(napi_env env, const T* data, size_t length); - - static void CheckCast(napi_env env, napi_value value); - - Buffer(); - Buffer(napi_env env, napi_value value); - size_t Length() const; - T* Data() const; - - private: -}; - -/// Holds a counted reference to a value; initially a weak reference unless -/// otherwise specified, may be changed to/from a strong reference by adjusting -/// the refcount. -/// -/// The referenced value is not immediately destroyed when the reference count -/// is zero; it is merely then eligible for garbage-collection if there are no -/// other references to the value. -template -class Reference { - public: - static Reference New(const T& value, uint32_t initialRefcount = 0); - - Reference(); - Reference(napi_env env, napi_ref ref); - ~Reference(); - - // A reference can be moved but cannot be copied. - Reference(Reference&& other); - Reference& operator=(Reference&& other); - NAPI_DISALLOW_ASSIGN(Reference) - - operator napi_ref() const; - bool operator==(const Reference& other) const; - bool operator!=(const Reference& other) const; - - Napi::Env Env() const; - bool IsEmpty() const; - - // Note when getting the value of a Reference it is usually correct to do so - // within a HandleScope so that the value handle gets cleaned up efficiently. - T Value() const; - - uint32_t Ref() const; - uint32_t Unref() const; - void Reset(); - void Reset(const T& value, uint32_t refcount = 0); - - // Call this on a reference that is declared as static data, to prevent its - // destructor from running at program shutdown time, which would attempt to - // reset the reference when the environment is no longer valid. Avoid using - // this if at all possible. If you do need to use static data, MAKE SURE to - // warn your users that your addon is NOT threadsafe. - void SuppressDestruct(); - - protected: - Reference(const Reference&); - - /// !cond INTERNAL - napi_env _env; - napi_ref _ref; - /// !endcond - - private: - bool _suppressDestruct; -}; - -class ObjectReference : public Reference { - public: - ObjectReference(); - ObjectReference(napi_env env, napi_ref ref); - - // A reference can be moved but cannot be copied. - ObjectReference(Reference&& other); - ObjectReference& operator=(Reference&& other); - ObjectReference(ObjectReference&& other); - ObjectReference& operator=(ObjectReference&& other); - NAPI_DISALLOW_ASSIGN(ObjectReference) - - MaybeOrValue Get(const char* utf8name) const; - MaybeOrValue Get(const std::string& utf8name) const; - MaybeOrValue Set(const char* utf8name, napi_value value) const; - MaybeOrValue Set(const char* utf8name, Napi::Value value) const; - MaybeOrValue Set(const char* utf8name, const char* utf8value) const; - MaybeOrValue Set(const char* utf8name, bool boolValue) const; - MaybeOrValue Set(const char* utf8name, double numberValue) const; - MaybeOrValue Set(const std::string& utf8name, napi_value value) const; - MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; - MaybeOrValue Set(const std::string& utf8name, - std::string& utf8value) const; - MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; - MaybeOrValue Set(const std::string& utf8name, double numberValue) const; - - MaybeOrValue Get(uint32_t index) const; - MaybeOrValue Set(uint32_t index, const napi_value value) const; - MaybeOrValue Set(uint32_t index, const Napi::Value value) const; - MaybeOrValue Set(uint32_t index, const char* utf8value) const; - MaybeOrValue Set(uint32_t index, const std::string& utf8value) const; - MaybeOrValue Set(uint32_t index, bool boolValue) const; - MaybeOrValue Set(uint32_t index, double numberValue) const; - - protected: - ObjectReference(const ObjectReference&); -}; - -class FunctionReference : public Reference { - public: - FunctionReference(); - FunctionReference(napi_env env, napi_ref ref); - - // A reference can be moved but cannot be copied. - FunctionReference(Reference&& other); - FunctionReference& operator=(Reference&& other); - FunctionReference(FunctionReference&& other); - FunctionReference& operator=(FunctionReference&& other); - NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) - - MaybeOrValue operator()( - const std::initializer_list& args) const; - - MaybeOrValue Call( - const std::initializer_list& args) const; - MaybeOrValue Call(const std::vector& args) const; - MaybeOrValue Call( - napi_value recv, const std::initializer_list& args) const; - MaybeOrValue Call(napi_value recv, - const std::vector& args) const; - MaybeOrValue Call(napi_value recv, - size_t argc, - const napi_value* args) const; - - MaybeOrValue MakeCallback( - napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - MaybeOrValue MakeCallback( - napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - MaybeOrValue MakeCallback( - napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - MaybeOrValue New(const std::initializer_list& args) const; - MaybeOrValue New(const std::vector& args) const; -}; - -// Shortcuts to creating a new reference with inferred type and refcount = 0. -template -Reference Weak(T value); -ObjectReference Weak(Object value); -FunctionReference Weak(Function value); - -// Shortcuts to creating a new reference with inferred type and refcount = 1. -template -Reference Persistent(T value); -ObjectReference Persistent(Object value); -FunctionReference Persistent(Function value); - -/// A persistent reference to a JavaScript error object. Use of this class -/// depends somewhat on whether C++ exceptions are enabled at compile time. -/// -/// ### Handling Errors With C++ Exceptions -/// -/// If C++ exceptions are enabled, then the `Error` class extends -/// `std::exception` and enables integrated error-handling for C++ exceptions -/// and JavaScript exceptions. -/// -/// If a Node-API call fails without executing any JavaScript code (for -/// example due to an invalid argument), then the Node-API wrapper -/// automatically converts and throws the error as a C++ exception of type -/// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API -/// throws a JavaScript exception, then the Node-API wrapper automatically -/// converts and throws it as a C++ exception of type `Napi::Error`. -/// -/// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ -/// callback, then the Node-API wrapper automatically converts and throws it -/// as a JavaScript exception. Therefore, catching a C++ exception of type -/// `Napi::Error` prevents a JavaScript exception from being thrown. -/// -/// #### Example 1A - Throwing a C++ exception: -/// -/// Napi::Env env = ... -/// throw Napi::Error::New(env, "Example exception"); -/// -/// Following C++ statements will not be executed. The exception will bubble -/// up as a C++ exception of type `Napi::Error`, until it is either caught -/// while still in C++, or else automatically propagated as a JavaScript -/// exception when the callback returns to JavaScript. -/// -/// #### Example 2A - Propagating a Node-API C++ exception: -/// -/// Napi::Function jsFunctionThatThrows = someObj.As(); -/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); -/// -/// Following C++ statements will not be executed. The exception will bubble -/// up as a C++ exception of type `Napi::Error`, until it is either caught -/// while still in C++, or else automatically propagated as a JavaScript -/// exception when the callback returns to JavaScript. -/// -/// #### Example 3A - Handling a Node-API C++ exception: -/// -/// Napi::Function jsFunctionThatThrows = someObj.As(); -/// Napi::Value result; -/// try { -/// result = jsFunctionThatThrows({ arg1, arg2 }); -/// } catch (const Napi::Error& e) { -/// cerr << "Caught JavaScript exception: " + e.what(); -/// } -/// -/// Since the exception was caught here, it will not be propagated as a -/// JavaScript exception. -/// -/// ### Handling Errors Without C++ Exceptions -/// -/// If C++ exceptions are disabled (by defining -/// `NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS`) then this class does not extend -/// `std::exception`, and APIs in the `Napi` namespace do not throw C++ -/// exceptions when they fail. Instead, they raise _pending_ JavaScript -/// exceptions and return _empty_ `Value`s. Calling code should check -/// `Value::IsEmpty()` before attempting to use a returned value, and may use -/// methods on the `Env` class to check for, get, and clear a pending JavaScript -/// exception. If the pending exception is not cleared, it will be thrown when -/// the native callback returns to JavaScript. -/// -/// #### Example 1B - Throwing a JS exception -/// -/// Napi::Env env = ... -/// Napi::Error::New(env, "Example -/// exception").ThrowAsJavaScriptException(); return; -/// -/// After throwing a JS exception, the code should generally return -/// immediately from the native callback, after performing any necessary -/// cleanup. -/// -/// #### Example 2B - Propagating a Node-API JS exception: -/// -/// Napi::Function jsFunctionThatThrows = someObj.As(); -/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); -/// if (result.IsEmpty()) return; -/// -/// An empty value result from a Node-API call indicates an error occurred, -/// and a JavaScript exception is pending. To let the exception propagate, the -/// code should generally return immediately from the native callback, after -/// performing any necessary cleanup. -/// -/// #### Example 3B - Handling a Node-API JS exception: -/// -/// Napi::Function jsFunctionThatThrows = someObj.As(); -/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); -/// if (result.IsEmpty()) { -/// Napi::Error e = env.GetAndClearPendingException(); -/// cerr << "Caught JavaScript exception: " + e.Message(); -/// } -/// -/// Since the exception was cleared here, it will not be propagated as a -/// JavaScript exception after the native callback returns. -class Error : public ObjectReference -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - , - public std::exception -#endif // NODE_ADDON_API_CPP_EXCEPTIONS -{ - public: - static Error New(napi_env env); - static Error New(napi_env env, const char* message); - static Error New(napi_env env, const std::string& message); - - static NAPI_NO_RETURN void Fatal(const char* location, const char* message); - - Error(); - Error(napi_env env, napi_value value); - - // An error can be moved or copied. - Error(Error&& other); - Error& operator=(Error&& other); - Error(const Error&); - Error& operator=(const Error&); - - const std::string& Message() const NAPI_NOEXCEPT; - void ThrowAsJavaScriptException() const; - - Object Value() const; - -#ifdef NODE_ADDON_API_CPP_EXCEPTIONS - const char* what() const NAPI_NOEXCEPT override; -#endif // NODE_ADDON_API_CPP_EXCEPTIONS - - protected: - /// !cond INTERNAL - using create_error_fn = napi_status (*)(napi_env envb, - napi_value code, - napi_value msg, - napi_value* result); - - template - static TError New(napi_env env, - const char* message, - size_t length, - create_error_fn create_error); - /// !endcond - - private: - static inline const char* ERROR_WRAP_VALUE() NAPI_NOEXCEPT; - mutable std::string _message; -}; - -class TypeError : public Error { - public: - static TypeError New(napi_env env, const char* message); - static TypeError New(napi_env env, const std::string& message); - - TypeError(); - TypeError(napi_env env, napi_value value); -}; - -class RangeError : public Error { - public: - static RangeError New(napi_env env, const char* message); - static RangeError New(napi_env env, const std::string& message); - - RangeError(); - RangeError(napi_env env, napi_value value); -}; - -#if NAPI_VERSION > 8 -class SyntaxError : public Error { - public: - static SyntaxError New(napi_env env, const char* message); - static SyntaxError New(napi_env env, const std::string& message); - - SyntaxError(); - SyntaxError(napi_env env, napi_value value); -}; -#endif // NAPI_VERSION > 8 - -class CallbackInfo { - public: - CallbackInfo(napi_env env, napi_callback_info info); - ~CallbackInfo(); - - // Disallow copying to prevent multiple free of _dynamicArgs - NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) - - Napi::Env Env() const; - Value NewTarget() const; - bool IsConstructCall() const; - size_t Length() const; - const Value operator[](size_t index) const; - Value This() const; - void* Data() const; - void SetData(void* data); - explicit operator napi_callback_info() const; - - private: - const size_t _staticArgCount = 6; - napi_env _env; - napi_callback_info _info; - napi_value _this; - size_t _argc; - napi_value* _argv; - napi_value _staticArgs[6]{}; - napi_value* _dynamicArgs; - void* _data; -}; - -class PropertyDescriptor { - public: - using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); - using SetterCallback = void (*)(const Napi::CallbackInfo& info); - -#ifndef NODE_ADDON_API_DISABLE_DEPRECATED - template - static PropertyDescriptor Accessor( - const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - napi_value name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - napi_value name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED - - template - static PropertyDescriptor Accessor( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - const std::string& utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - Name name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - const std::string& utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - Name name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor Value( - const char* utf8name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value( - const std::string& utf8name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value( - napi_value name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value( - Name name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - - PropertyDescriptor(napi_property_descriptor desc); - - operator napi_property_descriptor&(); - operator const napi_property_descriptor&() const; - - private: - napi_property_descriptor _desc; -}; - -/// Property descriptor for use with `ObjectWrap::DefineClass()`. -/// -/// This is different from the standalone `PropertyDescriptor` because it is -/// specific to each `ObjectWrap` subclass. This prevents using descriptors -/// from a different class when defining a new class (preventing the callbacks -/// from having incorrect `this` pointers). -template -class ClassPropertyDescriptor { - public: - ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} - - operator napi_property_descriptor&() { return _desc; } - operator const napi_property_descriptor&() const { return _desc; } - - private: - napi_property_descriptor _desc; -}; - -template -struct MethodCallbackData { - TCallback callback; - void* data; -}; - -template -struct AccessorCallbackData { - TGetterCallback getterCallback; - TSetterCallback setterCallback; - void* data; -}; - -template -class InstanceWrap { - public: - using InstanceVoidMethodCallback = void (T::*)(const CallbackInfo& info); - using InstanceMethodCallback = Napi::Value (T::*)(const CallbackInfo& info); - using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info); - using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, - const Napi::Value& value); - - using PropertyDescriptor = ClassPropertyDescriptor; - - static PropertyDescriptor InstanceMethod( - const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod( - const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod( - Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod( - Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor( - const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor( - Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue( - Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - - protected: - static void AttachPropData(napi_env env, - napi_value value, - const napi_property_descriptor* prop); - - private: - using This = InstanceWrap; - - using InstanceVoidMethodCallbackData = - MethodCallbackData; - using InstanceMethodCallbackData = - MethodCallbackData; - using InstanceAccessorCallbackData = - AccessorCallbackData; - - static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value InstanceMethodCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value InstanceGetterCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value InstanceSetterCallbackWrapper(napi_env env, - napi_callback_info info); - - template - static napi_value WrappedMethod(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT; - - template - struct SetterTag {}; - - template - static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { - return &This::WrappedMethod; - } - static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { - return nullptr; - } -}; - -/// Base class to be extended by C++ classes exposed to JavaScript; each C++ -/// class instance gets "wrapped" by a JavaScript object that is managed by this -/// class. -/// -/// At initialization time, the `DefineClass()` method must be used to -/// hook up the accessor and method callbacks. It takes a list of -/// property descriptors, which can be constructed via the various -/// static methods on the base class. -/// -/// #### Example: -/// -/// class Example: public Napi::ObjectWrap { -/// public: -/// static void Initialize(Napi::Env& env, Napi::Object& target) { -/// Napi::Function constructor = DefineClass(env, "Example", { -/// InstanceAccessor<&Example::GetSomething, -/// &Example::SetSomething>("value"), -/// InstanceMethod<&Example::DoSomething>("doSomething"), -/// }); -/// target.Set("Example", constructor); -/// } -/// -/// Example(const Napi::CallbackInfo& info); // Constructor -/// Napi::Value GetSomething(const Napi::CallbackInfo& info); -/// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& -/// value); Napi::Value DoSomething(const Napi::CallbackInfo& info); -/// } -template -class ObjectWrap : public InstanceWrap, public Reference { - public: - ObjectWrap(const CallbackInfo& callbackInfo); - virtual ~ObjectWrap(); - - static T* Unwrap(Object wrapper); - - // Methods exposed to JavaScript must conform to one of these callback - // signatures. - using StaticVoidMethodCallback = void (*)(const CallbackInfo& info); - using StaticMethodCallback = Napi::Value (*)(const CallbackInfo& info); - using StaticGetterCallback = Napi::Value (*)(const CallbackInfo& info); - using StaticSetterCallback = void (*)(const CallbackInfo& info, - const Napi::Value& value); - - using PropertyDescriptor = ClassPropertyDescriptor; - - static Function DefineClass( - Napi::Env env, - const char* utf8name, - const std::initializer_list& properties, - void* data = nullptr); - static Function DefineClass(Napi::Env env, - const char* utf8name, - const std::vector& properties, - void* data = nullptr); - static PropertyDescriptor StaticMethod( - const char* utf8name, - StaticVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod( - const char* utf8name, - StaticMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod( - Symbol name, - StaticVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod( - Symbol name, - StaticMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticAccessor( - const char* utf8name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticAccessor( - Symbol name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticAccessor( - const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticAccessor( - Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor StaticValue( - Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); - virtual void Finalize(Napi::Env env); - virtual void Finalize(BasicEnv env); - - private: - using This = ObjectWrap; - - static napi_value ConstructorCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value StaticVoidMethodCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value StaticMethodCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value StaticGetterCallbackWrapper(napi_env env, - napi_callback_info info); - static napi_value StaticSetterCallbackWrapper(napi_env env, - napi_callback_info info); - static void FinalizeCallback(node_addon_api_basic_env env, - void* data, - void* hint); - - static void PostFinalizeCallback(napi_env env, void* data, void* hint); - - static Function DefineClass(Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* props, - void* data = nullptr); - - using StaticVoidMethodCallbackData = - MethodCallbackData; - using StaticMethodCallbackData = MethodCallbackData; - - using StaticAccessorCallbackData = - AccessorCallbackData; - - template - static napi_value WrappedMethod(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT; - - template - struct StaticSetterTag {}; - - template - static napi_callback WrapStaticSetter(StaticSetterTag) NAPI_NOEXCEPT { - return &This::WrappedMethod; - } - static napi_callback WrapStaticSetter(StaticSetterTag) - NAPI_NOEXCEPT { - return nullptr; - } - - bool _construction_failed = true; - bool _finalized = false; -}; - -class HandleScope { - public: - HandleScope(napi_env env, napi_handle_scope scope); - explicit HandleScope(Napi::Env env); - ~HandleScope(); - - // Disallow copying to prevent double close of napi_handle_scope - NAPI_DISALLOW_ASSIGN_COPY(HandleScope) - - operator napi_handle_scope() const; - - Napi::Env Env() const; - - private: - napi_env _env; - napi_handle_scope _scope; -}; - -class EscapableHandleScope { - public: - EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); - explicit EscapableHandleScope(Napi::Env env); - ~EscapableHandleScope(); - - // Disallow copying to prevent double close of napi_escapable_handle_scope - NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) - - operator napi_escapable_handle_scope() const; - - Napi::Env Env() const; - Value Escape(napi_value escapee); - - private: - napi_env _env; - napi_escapable_handle_scope _scope; -}; - -#if (NAPI_VERSION > 2) -class CallbackScope { - public: - CallbackScope(napi_env env, napi_callback_scope scope); - CallbackScope(napi_env env, napi_async_context context); - virtual ~CallbackScope(); - - // Disallow copying to prevent double close of napi_callback_scope - NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) - - operator napi_callback_scope() const; - - Napi::Env Env() const; - - private: - napi_env _env; - napi_callback_scope _scope; -}; -#endif - -class AsyncContext { - public: - explicit AsyncContext(napi_env env, const char* resource_name); - explicit AsyncContext(napi_env env, - const char* resource_name, - const Object& resource); - virtual ~AsyncContext(); - - AsyncContext(AsyncContext&& other); - AsyncContext& operator=(AsyncContext&& other); - NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) - - operator napi_async_context() const; - - Napi::Env Env() const; - - private: - napi_env _env; - napi_async_context _context; -}; - -#if NAPI_HAS_THREADS -class AsyncWorker { - public: - virtual ~AsyncWorker(); - - NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) - - operator napi_async_work() const; - - Napi::Env Env() const; - - void Queue(); - void Cancel(); - void SuppressDestruct(); - - ObjectReference& Receiver(); - FunctionReference& Callback(); - - virtual void OnExecute(Napi::Env env); - virtual void OnWorkComplete(Napi::Env env, napi_status status); - - protected: - explicit AsyncWorker(const Function& callback); - explicit AsyncWorker(const Function& callback, const char* resource_name); - explicit AsyncWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncWorker(const Object& receiver, const Function& callback); - explicit AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - - explicit AsyncWorker(Napi::Env env); - explicit AsyncWorker(Napi::Env env, const char* resource_name); - explicit AsyncWorker(Napi::Env env, - const char* resource_name, - const Object& resource); - - virtual void Execute() = 0; - virtual void OnOK(); - virtual void OnError(const Error& e); - virtual void Destroy(); - virtual std::vector GetResult(Napi::Env env); - - void SetError(const std::string& error); - - private: - static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); - static inline void OnAsyncWorkComplete(napi_env env, - napi_status status, - void* asyncworker); - - napi_env _env; - napi_async_work _work; - ObjectReference _receiver; - FunctionReference _callback; - std::string _error; - bool _suppress_destruct; -}; -#endif // NAPI_HAS_THREADS - -#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) -class ThreadSafeFunction { - public: - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data); - - ThreadSafeFunction(); - ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); - - operator napi_threadsafe_function() const; - - // This API may be called from any thread. - napi_status BlockingCall() const; - - // This API may be called from any thread. - template - napi_status BlockingCall(Callback callback) const; - - // This API may be called from any thread. - template - napi_status BlockingCall(DataType* data, Callback callback) const; - - // This API may be called from any thread. - napi_status NonBlockingCall() const; - - // This API may be called from any thread. - template - napi_status NonBlockingCall(Callback callback) const; - - // This API may be called from any thread. - template - napi_status NonBlockingCall(DataType* data, Callback callback) const; - - // This API may only be called from the main thread. - void Ref(napi_env env) const; - - // This API may only be called from the main thread. - void Unref(napi_env env) const; - - // This API may be called from any thread. - napi_status Acquire() const; - - // This API may be called from any thread. - napi_status Release() const; - - // This API may be called from any thread. - napi_status Abort() const; - - struct ConvertibleContext { - template - operator T*() { - return static_cast(context); - } - void* context; - }; - - // This API may be called from any thread. - ConvertibleContext GetContext() const; - - private: - using CallbackWrapper = std::function; - - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper); - - napi_status CallInternal(CallbackWrapper* callbackWrapper, - napi_threadsafe_function_call_mode mode) const; - - static void CallJS(napi_env env, - napi_value jsCallback, - void* context, - void* data); - - napi_threadsafe_function _tsfn; -}; - -// A TypedThreadSafeFunction by default has no context (nullptr) and can -// accept any type (void) to its CallJs. -template -class TypedThreadSafeFunction { - public: - // This API may only be called from the main thread. - // Helper function that returns nullptr if running Node-API 5+, otherwise a - // non-empty, no-op Function. This provides the ability to specify at - // compile-time a callback parameter to `New` that safely does no action - // when targeting _any_ Node-API version. -#if NAPI_VERSION > 4 - static std::nullptr_t EmptyFunctionFactory(Napi::Env env); -#else - static Napi::Function EmptyFunctionFactory(Napi::Env env); -#endif - static Napi::Function FunctionOrEmpty(Napi::Env env, - Napi::Function& callback); - -#if NAPI_VERSION > 4 - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [missing] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [passed] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [missing] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [passed] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); -#endif - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [missing] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [passed] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [missing] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [passed] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - CallbackType callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - TypedThreadSafeFunction(); - TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); - - operator napi_threadsafe_function() const; - - // This API may be called from any thread. - napi_status BlockingCall(DataType* data = nullptr) const; - - // This API may be called from any thread. - napi_status NonBlockingCall(DataType* data = nullptr) const; - - // This API may only be called from the main thread. - void Ref(napi_env env) const; - - // This API may only be called from the main thread. - void Unref(napi_env env) const; - - // This API may be called from any thread. - napi_status Acquire() const; - - // This API may be called from any thread. - napi_status Release() const; - - // This API may be called from any thread. - napi_status Abort() const; - - // This API may be called from any thread. - ContextType* GetContext() const; - - private: - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper); - - static void CallJsInternal(napi_env env, - napi_value jsCallback, - void* context, - void* data); - - protected: - napi_threadsafe_function _tsfn; -}; -template -class AsyncProgressWorkerBase : public AsyncWorker { - public: - virtual void OnWorkProgress(DataType* data) = 0; - class ThreadSafeData { - public: - ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) - : _asyncprogressworker(asyncprogressworker), _data(data) {} - - AsyncProgressWorkerBase* asyncprogressworker() { - return _asyncprogressworker; - }; - DataType* data() { return _data; }; - - private: - AsyncProgressWorkerBase* _asyncprogressworker; - DataType* _data; - }; - void OnWorkComplete(Napi::Env env, napi_status status) override; - - protected: - explicit AsyncProgressWorkerBase(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource, - size_t queue_size = 1); - virtual ~AsyncProgressWorkerBase(); - -// Optional callback of Napi::ThreadSafeFunction only available after -// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 -#if NAPI_VERSION > 4 - explicit AsyncProgressWorkerBase(Napi::Env env, - const char* resource_name, - const Object& resource, - size_t queue_size = 1); -#endif - - static inline void OnAsyncWorkProgress(Napi::Env env, - Napi::Function jsCallback, - void* data); - - napi_status NonBlockingCall(DataType* data); - - private: - ThreadSafeFunction _tsfn; - bool _work_completed = false; - napi_status _complete_status; - static inline void OnThreadSafeFunctionFinalize( - Napi::Env env, void* data, AsyncProgressWorkerBase* context); -}; - -template -class AsyncProgressWorker : public AsyncProgressWorkerBase { - public: - virtual ~AsyncProgressWorker(); - - class ExecutionProgress { - friend class AsyncProgressWorker; - - public: - void Signal() const; - void Send(const T* data, size_t count) const; - - private: - explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} - AsyncProgressWorker* const _worker; - }; - - void OnWorkProgress(void*) override; - - protected: - explicit AsyncProgressWorker(const Function& callback); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - -// Optional callback of Napi::ThreadSafeFunction only available after -// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 -#if NAPI_VERSION > 4 - explicit AsyncProgressWorker(Napi::Env env); - explicit AsyncProgressWorker(Napi::Env env, const char* resource_name); - explicit AsyncProgressWorker(Napi::Env env, - const char* resource_name, - const Object& resource); -#endif - virtual void Execute(const ExecutionProgress& progress) = 0; - virtual void OnProgress(const T* data, size_t count) = 0; - - private: - void Execute() override; - void Signal(); - void SendProgress_(const T* data, size_t count); - - std::mutex _mutex; - T* _asyncdata; - size_t _asyncsize; - bool _signaled; -}; - -template -class AsyncProgressQueueWorker - : public AsyncProgressWorkerBase> { - public: - virtual ~AsyncProgressQueueWorker(){}; - - class ExecutionProgress { - friend class AsyncProgressQueueWorker; - - public: - void Signal() const; - void Send(const T* data, size_t count) const; - - private: - explicit ExecutionProgress(AsyncProgressQueueWorker* worker) - : _worker(worker) {} - AsyncProgressQueueWorker* const _worker; - }; - - void OnWorkComplete(Napi::Env env, napi_status status) override; - void OnWorkProgress(std::pair*) override; - - protected: - explicit AsyncProgressQueueWorker(const Function& callback); - explicit AsyncProgressQueueWorker(const Function& callback, - const char* resource_name); - explicit AsyncProgressQueueWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - -// Optional callback of Napi::ThreadSafeFunction only available after -// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 -#if NAPI_VERSION > 4 - explicit AsyncProgressQueueWorker(Napi::Env env); - explicit AsyncProgressQueueWorker(Napi::Env env, const char* resource_name); - explicit AsyncProgressQueueWorker(Napi::Env env, - const char* resource_name, - const Object& resource); -#endif - virtual void Execute(const ExecutionProgress& progress) = 0; - virtual void OnProgress(const T* data, size_t count) = 0; - - private: - void Execute() override; - void Signal() const; - void SendProgress_(const T* data, size_t count); -}; -#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS - -// Memory management. -class MemoryManagement { - public: - static int64_t AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes); -}; - -// Version management -class VersionManagement { - public: - static uint32_t GetNapiVersion(BasicEnv env); - static const napi_node_version* GetNodeVersion(BasicEnv env); -}; - -#if NAPI_VERSION > 5 -template -class Addon : public InstanceWrap { - public: - static inline Object Init(Env env, Object exports); - static T* Unwrap(Object wrapper); - - protected: - using AddonProp = ClassPropertyDescriptor; - void DefineAddon(Object exports, - const std::initializer_list& props); - Napi::Object DefineProperties(Object object, - const std::initializer_list& props); - - private: - Object entry_point_; -}; -#endif // NAPI_VERSION > 5 - -#ifdef NAPI_CPP_CUSTOM_NAMESPACE -} // namespace NAPI_CPP_CUSTOM_NAMESPACE -#endif - -} // namespace Napi - -// Inline implementations of all the above class methods are included here. -#include "napi-inl.h" - -#endif // SRC_NAPI_H_ diff --git a/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp b/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp deleted file mode 100644 index 8c099262a..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/node_addon_api.gyp +++ /dev/null @@ -1,42 +0,0 @@ -{ - 'targets': [ - { - 'target_name': 'node_addon_api', - 'type': 'none', - 'sources': [ 'napi.h', 'napi-inl.h' ], - 'direct_dependent_settings': { - 'include_dirs': [ '.' ], - 'includes': ['noexcept.gypi'], - } - }, - { - 'target_name': 'node_addon_api_except', - 'type': 'none', - 'sources': [ 'napi.h', 'napi-inl.h' ], - 'direct_dependent_settings': { - 'include_dirs': [ '.' ], - 'includes': ['except.gypi'], - } - }, - { - 'target_name': 'node_addon_api_except_all', - 'type': 'none', - 'sources': [ 'napi.h', 'napi-inl.h' ], - 'direct_dependent_settings': { - 'include_dirs': [ '.' ], - 'includes': ['except.gypi'], - 'defines': [ 'NODE_ADDON_API_CPP_EXCEPTIONS_ALL' ] - } - }, - { - 'target_name': 'node_addon_api_maybe', - 'type': 'none', - 'sources': [ 'napi.h', 'napi-inl.h' ], - 'direct_dependent_settings': { - 'include_dirs': [ '.' ], - 'includes': ['noexcept.gypi'], - 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] - } - }, - ] -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp b/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp deleted file mode 100644 index 4ff0ae7df..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/node_api.gyp +++ /dev/null @@ -1,9 +0,0 @@ -{ - 'targets': [ - { - 'target_name': 'nothing', - 'type': 'static_library', - 'sources': [ 'nothing.c' ] - } - ] -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi b/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi deleted file mode 100644 index 83df4ddf0..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/noexcept.gypi +++ /dev/null @@ -1,26 +0,0 @@ -{ - 'defines': [ 'NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS' ], - 'cflags': [ '-fno-exceptions' ], - 'cflags_cc': [ '-fno-exceptions' ], - 'conditions': [ - ["OS=='win'", { - # _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi - #"defines": [ - # "_HAS_EXCEPTIONS=0" - #], - "msvs_settings": { - "VCCLCompilerTool": { - 'ExceptionHandling': 0, - 'EnablePREfast': 'true', - }, - }, - }], - ["OS=='mac'", { - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', - }, - }], - ], -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/nothing.c b/week-5/solution/frontend/node_modules/node-addon-api/nothing.c deleted file mode 100644 index e69de29bb..000000000 diff --git a/week-5/solution/frontend/node_modules/node-addon-api/package-support.json b/week-5/solution/frontend/node_modules/node-addon-api/package-support.json deleted file mode 100644 index 10d3607ac..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/package-support.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "versions": [ - { - "version": "*", - "target": { - "node": "active" - }, - "response": { - "type": "time-permitting", - "paid": false, - "contact": { - "name": "node-addon-api team", - "url": "https://github.com/nodejs/node-addon-api/issues" - } - }, - "backing": [ { "project": "https://github.com/nodejs" }, - { "foundation": "https://openjsf.org/" } - ] - } - ] -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/package.json b/week-5/solution/frontend/node_modules/node-addon-api/package.json deleted file mode 100644 index f04d661fa..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/package.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "bugs": { - "url": "https://github.com/nodejs/node-addon-api/issues" - }, - "contributors": [ - { - "name": "Abhishek Kumar Singh", - "url": "https://github.com/abhi11210646" - }, - { - "name": "Alba Mendez", - "url": "https://github.com/jmendeth" - }, - { - "name": "Alexander Floh", - "url": "https://github.com/alexanderfloh" - }, - { - "name": "Ammar Faizi", - "url": "https://github.com/ammarfaizi2" - }, - { - "name": "András Timár, Dr", - "url": "https://github.com/timarandras" - }, - { - "name": "Andrew Petersen", - "url": "https://github.com/kirbysayshi" - }, - { - "name": "Anisha Rohra", - "url": "https://github.com/anisha-rohra" - }, - { - "name": "Anna Henningsen", - "url": "https://github.com/addaleax" - }, - { - "name": "Arnaud Botella", - "url": "https://github.com/BotellaA" - }, - { - "name": "Arunesh Chandra", - "url": "https://github.com/aruneshchandra" - }, - { - "name": "Azlan Mukhtar", - "url": "https://github.com/azlan" - }, - { - "name": "Ben Berman", - "url": "https://github.com/rivertam" - }, - { - "name": "Benjamin Byholm", - "url": "https://github.com/kkoopa" - }, - { - "name": "Bill Gallafent", - "url": "https://github.com/gallafent" - }, - { - "name": "blagoev", - "url": "https://github.com/blagoev" - }, - { - "name": "Bruce A. MacNaughton", - "url": "https://github.com/bmacnaughton" - }, - { - "name": "Cory Mickelson", - "url": "https://github.com/corymickelson" - }, - { - "name": "Daniel Bevenius", - "url": "https://github.com/danbev" - }, - { - "name": "Dante Calderón", - "url": "https://github.com/dantehemerson" - }, - { - "name": "Darshan Sen", - "url": "https://github.com/RaisinTen" - }, - { - "name": "David Halls", - "url": "https://github.com/davedoesdev" - }, - { - "name": "Deepak Rajamohan", - "url": "https://github.com/deepakrkris" - }, - { - "name": "Dmitry Ashkadov", - "url": "https://github.com/dmitryash" - }, - { - "name": "Dongjin Na", - "url": "https://github.com/nadongguri" - }, - { - "name": "Doni Rubiagatra", - "url": "https://github.com/rubiagatra" - }, - { - "name": "Eric Bickle", - "url": "https://github.com/ebickle" - }, - { - "name": "extremeheat", - "url": "https://github.com/extremeheat" - }, - { - "name": "Feng Yu", - "url": "https://github.com/F3n67u" - }, - { - "name": "Ferdinand Holzer", - "url": "https://github.com/fholzer" - }, - { - "name": "Gabriel Schulhof", - "url": "https://github.com/gabrielschulhof" - }, - { - "name": "Guenter Sandner", - "url": "https://github.com/gms1" - }, - { - "name": "Gus Caplan", - "url": "https://github.com/devsnek" - }, - { - "name": "Helio Frota", - "url": "https://github.com/helio-frota" - }, - { - "name": "Hitesh Kanwathirtha", - "url": "https://github.com/digitalinfinity" - }, - { - "name": "ikokostya", - "url": "https://github.com/ikokostya" - }, - { - "name": "Jack Xia", - "url": "https://github.com/JckXia" - }, - { - "name": "Jake Barnes", - "url": "https://github.com/DuBistKomisch" - }, - { - "name": "Jake Yoon", - "url": "https://github.com/yjaeseok" - }, - { - "name": "Jason Ginchereau", - "url": "https://github.com/jasongin" - }, - { - "name": "Jenny", - "url": "https://github.com/egg-bread" - }, - { - "name": "Jeroen Janssen", - "url": "https://github.com/japj" - }, - { - "name": "Jim Schlight", - "url": "https://github.com/jschlight" - }, - { - "name": "Jinho Bang", - "url": "https://github.com/romandev" - }, - { - "name": "José Expósito", - "url": "https://github.com/JoseExposito" - }, - { - "name": "joshgarde", - "url": "https://github.com/joshgarde" - }, - { - "name": "Julian Mesa", - "url": "https://github.com/julianmesa-gitkraken" - }, - { - "name": "Kasumi Hanazuki", - "url": "https://github.com/hanazuki" - }, - { - "name": "Kelvin", - "url": "https://github.com/kelvinhammond" - }, - { - "name": "Kevin Eady", - "url": "https://github.com/KevinEady" - }, - { - "name": "Kévin VOYER", - "url": "https://github.com/kecsou" - }, - { - "name": "kidneysolo", - "url": "https://github.com/kidneysolo" - }, - { - "name": "Koki Nishihara", - "url": "https://github.com/Nishikoh" - }, - { - "name": "Konstantin Tarkus", - "url": "https://github.com/koistya" - }, - { - "name": "Kyle Farnung", - "url": "https://github.com/kfarnung" - }, - { - "name": "Kyle Kovacs", - "url": "https://github.com/nullromo" - }, - { - "name": "legendecas", - "url": "https://github.com/legendecas" - }, - { - "name": "LongYinan", - "url": "https://github.com/Brooooooklyn" - }, - { - "name": "Lovell Fuller", - "url": "https://github.com/lovell" - }, - { - "name": "Luciano Martorella", - "url": "https://github.com/lmartorella" - }, - { - "name": "mastergberry", - "url": "https://github.com/mastergberry" - }, - { - "name": "Mathias Küsel", - "url": "https://github.com/mathiask88" - }, - { - "name": "Mathias Stearn", - "url": "https://github.com/RedBeard0531" - }, - { - "name": "Matteo Collina", - "url": "https://github.com/mcollina" - }, - { - "name": "Michael Dawson", - "url": "https://github.com/mhdawson" - }, - { - "name": "Michael Price", - "url": "https://github.com/mikepricedev" - }, - { - "name": "Michele Campus", - "url": "https://github.com/kYroL01" - }, - { - "name": "Mikhail Cheshkov", - "url": "https://github.com/mcheshkov" - }, - { - "name": "nempoBu4", - "url": "https://github.com/nempoBu4" - }, - { - "name": "Nicola Del Gobbo", - "url": "https://github.com/NickNaso" - }, - { - "name": "Nick Soggin", - "url": "https://github.com/iSkore" - }, - { - "name": "Nikolai Vavilov", - "url": "https://github.com/seishun" - }, - { - "name": "Nurbol Alpysbayev", - "url": "https://github.com/anurbol" - }, - { - "name": "pacop", - "url": "https://github.com/pacop" - }, - { - "name": "Peter Šándor", - "url": "https://github.com/petersandor" - }, - { - "name": "Philipp Renoth", - "url": "https://github.com/DaAitch" - }, - { - "name": "rgerd", - "url": "https://github.com/rgerd" - }, - { - "name": "Richard Lau", - "url": "https://github.com/richardlau" - }, - { - "name": "Rolf Timmermans", - "url": "https://github.com/rolftimmermans" - }, - { - "name": "Ross Weir", - "url": "https://github.com/ross-weir" - }, - { - "name": "Ryuichi Okumura", - "url": "https://github.com/okuryu" - }, - { - "name": "Saint Gabriel", - "url": "https://github.com/chineduG" - }, - { - "name": "Sampson Gao", - "url": "https://github.com/sampsongao" - }, - { - "name": "Sam Roberts", - "url": "https://github.com/sam-github" - }, - { - "name": "strager", - "url": "https://github.com/strager" - }, - { - "name": "Taylor Woll", - "url": "https://github.com/boingoing" - }, - { - "name": "Thomas Gentilhomme", - "url": "https://github.com/fraxken" - }, - { - "name": "Tim Rach", - "url": "https://github.com/timrach" - }, - { - "name": "Tobias Nießen", - "url": "https://github.com/tniessen" - }, - { - "name": "todoroff", - "url": "https://github.com/todoroff" - }, - { - "name": "Toyo Li", - "url": "https://github.com/toyobayashi" - }, - { - "name": "Tux3", - "url": "https://github.com/tux3" - }, - { - "name": "Vlad Velmisov", - "url": "https://github.com/Velmisov" - }, - { - "name": "Vladimir Morozov", - "url": "https://github.com/vmoroz" - }, - { - "name": "WenheLI", - "url": "https://github.com/WenheLI" - }, - { - "name": "Xuguang Mei", - "url": "https://github.com/meixg" - }, - { - "name": "Yohei Kishimoto", - "url": "https://github.com/morokosi" - }, - { - "name": "Yulong Wang", - "url": "https://github.com/fs-eire" - }, - { - "name": "Ziqiu Zhao", - "url": "https://github.com/ZzqiZQute" - }, - { - "name": "Feng Yu", - "url": "https://github.com/F3n67u" - }, - { - "name": "wanlu wang", - "url": "https://github.com/wanlu" - }, - { - "name": "Caleb Hearon", - "url": "https://github.com/chearon" - }, - { - "name": "Marx", - "url": "https://github.com/MarxJiao" - }, - { - "name": "Ömer AKGÜL", - "url": "https://github.com/tuhalf" - } - ], - "description": "Node.js API (Node-API)", - "devDependencies": { - "benchmark": "^2.1.4", - "bindings": "^1.5.0", - "clang-format": "^1.4.0", - "eslint": "^9.13.0", - "fs-extra": "^11.1.1", - "neostandard": "^0.12.0", - "pre-commit": "^1.2.2", - "semver": "^7.6.0" - }, - "directories": {}, - "gypfile": false, - "homepage": "https://github.com/nodejs/node-addon-api", - "keywords": [ - "n-api", - "napi", - "addon", - "native", - "bindings", - "c", - "c++", - "nan", - "node-addon-api" - ], - "license": "MIT", - "main": "index.js", - "name": "node-addon-api", - "readme": "README.md", - "repository": { - "type": "git", - "url": "git://github.com/nodejs/node-addon-api.git" - }, - "files": [ - "*.{c,h,gyp,gypi}", - "package-support.json", - "tools/" - ], - "scripts": { - "prebenchmark": "node-gyp rebuild -C benchmark", - "benchmark": "node benchmark", - "create-coverage": "npm test --coverage", - "report-coverage-html": "rm -rf coverage-html && mkdir coverage-html && gcovr -e test --merge-mode-functions merge-use-line-max --html-nested ./coverage-html/index.html test", - "report-coverage-xml": "rm -rf coverage-xml && mkdir coverage-xml && gcovr -e test --merge-mode-functions merge-use-line-max --xml -o ./coverage-xml/coverage-cxx.xml test", - "pretest": "node-gyp rebuild -C test", - "test": "node test", - "test:debug": "node-gyp rebuild -C test --debug && NODE_API_BUILD_CONFIG=Debug node ./test/index.js", - "predev": "node-gyp rebuild -C test --debug", - "dev": "node test", - "predev:incremental": "node-gyp configure build -C test --debug", - "dev:incremental": "node test", - "doc": "doxygen doc/Doxyfile", - "lint": "eslint && node tools/clang-format", - "lint:fix": "eslint --fix && node tools/clang-format --fix" - }, - "pre-commit": "lint", - "version": "8.5.0", - "support": true, - "engines": { - "node": "^18 || ^20 || >= 21" - } -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md b/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md deleted file mode 100644 index 6b80e94f5..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/tools/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Tools - -## clang-format - -The clang-format checking tools is designed to check changed lines of code compared to given git-refs. - -## Migration Script - -The migration tool is designed to reduce repetitive work in the migration process. However, the script is not aiming to convert every thing for you. There are usually some small fixes and major reconstruction required. - -### How To Use - -To run the conversion script, first make sure you have the latest `node-addon-api` in your `node_modules` directory. -``` -npm install node-addon-api -``` - -Then run the script passing your project directory -``` -node ./node_modules/node-addon-api/tools/conversion.js ./ -``` - -After finish, recompile and debug things that are missed by the script. - - -### Quick Fixes -Here is the list of things that can be fixed easily. - 1. Change your methods' return value to void if it doesn't return value to JavaScript. - 2. Use `.` to access attribute or to invoke member function in Napi::Object instead of `->`. - 3. `Napi::New(env, value);` to `Napi::[Type]::New(env, value); - - -### Major Reconstructions -The implementation of `Napi::ObjectWrap` is significantly different from NAN's. `Napi::ObjectWrap` takes a pointer to the wrapped object and creates a reference to the wrapped object inside ObjectWrap constructor. `Napi::ObjectWrap` also associates wrapped object's instance methods to Javascript module instead of static methods like NAN. - -So if you use Nan::ObjectWrap in your module, you will need to execute the following steps. - - 1. Convert your [ClassName]::New function to a constructor function that takes a `Napi::CallbackInfo`. Declare it as -``` -[ClassName](const Napi::CallbackInfo& info); -``` -and define it as -``` -[ClassName]::[ClassName](const Napi::CallbackInfo& info) : Napi::ObjectWrap<[ClassName]>(info){ - ... -} -``` -This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instantiated and `Napi::ObjectWrap` can use the `this` pointer to create a reference to the wrapped object. - - 2. Move your original constructor code into the new constructor. Delete your original constructor. - 3. In your class initialization function, associate native methods in the following way. -``` -Napi::FunctionReference constructor; - -void [ClassName]::Init(Napi::Env env, Napi::Object exports, Napi::Object module) { - Napi::HandleScope scope(env); - Napi::Function ctor = DefineClass(env, "Canvas", { - InstanceMethod<&[ClassName]::Func1>("Func1"), - InstanceMethod<&[ClassName]::Func2>("Func2"), - InstanceAccessor<&[ClassName]::ValueGetter>("Value"), - StaticMethod<&[ClassName]::StaticMethod>("MethodName"), - InstanceValue("Value", Napi::[Type]::New(env, value)), - }); - - constructor = Napi::Persistent(ctor); - constructor .SuppressDestruct(); - exports.Set("[ClassName]", ctor); -} -``` - 4. In function where you need to Unwrap the ObjectWrap in NAN like `[ClassName]* native = Nan::ObjectWrap::Unwrap<[ClassName]>(info.This());`, use `this` pointer directly as the unwrapped object as each ObjectWrap instance is associated with a unique object instance. - - -If you still find issues after following this guide, please leave us an issue describing your problem and we will try to resolve it. diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js deleted file mode 100644 index 9199af334..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/tools/check-napi.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; -// Descend into a directory structure and, for each file matching *.node, output -// based on the imports found in the file whether it's an N-API module or not. - -const fs = require('fs'); -const path = require('path'); - -// Read the output of the command, break it into lines, and use the reducer to -// decide whether the file is an N-API module or not. -function checkFile (file, command, argv, reducer) { - const child = require('child_process').spawn(command, argv, { - stdio: ['inherit', 'pipe', 'inherit'] - }); - let leftover = ''; - let isNapi; - child.stdout.on('data', (chunk) => { - if (isNapi === undefined) { - chunk = (leftover + chunk.toString()).split(/[\r\n]+/); - leftover = chunk.pop(); - isNapi = chunk.reduce(reducer, isNapi); - if (isNapi !== undefined) { - child.kill(); - } - } - }); - child.on('close', (code, signal) => { - if ((code === null && signal !== null) || (code !== 0)) { - console.log( - command + ' exited with code: ' + code + ' and signal: ' + signal); - } else { - // Green if it's a N-API module, red otherwise. - console.log( - '\x1b[' + (isNapi ? '42' : '41') + 'm' + - (isNapi ? ' N-API' : 'Not N-API') + - '\x1b[0m: ' + file); - } - }); -} - -// Use nm -a to list symbols. -function checkFileUNIX (file) { - checkFile(file, 'nm', ['-a', file], (soFar, line) => { - if (soFar === undefined) { - line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/); - if (line[2] === 'U') { - if (/^napi/.test(line[3])) { - soFar = true; - } - } - } - return soFar; - }); -} - -// Use dumpbin /imports to list symbols. -function checkFileWin32 (file) { - checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => { - if (soFar === undefined) { - line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/); - if (line && /^napi/.test(line[line.length - 1])) { - soFar = true; - } - } - return soFar; - }); -} - -// Descend into a directory structure and pass each file ending in '.node' to -// one of the above checks, depending on the OS. -function recurse (top) { - fs.readdir(top, (error, items) => { - if (error) { - throw new Error('error reading directory ' + top + ': ' + error); - } - items.forEach((item) => { - item = path.join(top, item); - fs.stat(item, ((item) => (error, stats) => { - if (error) { - throw new Error('error about ' + item + ': ' + error); - } - if (stats.isDirectory()) { - recurse(item); - } else if (/[.]node$/.test(item) && - // Explicitly ignore files called 'nothing.node' because they are - // artefacts of node-addon-api having identified a version of - // Node.js that ships with a correct implementation of N-API. - path.basename(item) !== 'nothing.node') { - process.platform === 'win32' - ? checkFileWin32(item) - : checkFileUNIX(item); - } - })(item)); - }); - }); -} - -// Start with the directory given on the command line or the current directory -// if nothing was given. -recurse(process.argv.length > 3 ? process.argv[2] : '.'); diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js deleted file mode 100644 index e4bb4f52e..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/tools/clang-format.js +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node - -const spawn = require('child_process').spawnSync; -const path = require('path'); - -const filesToCheck = ['*.h', '*.cc']; -const FORMAT_START = process.env.FORMAT_START || 'main'; - -function main (args) { - let fix = false; - while (args.length > 0) { - switch (args[0]) { - case '-f': - case '--fix': - fix = true; - break; - default: - } - args.shift(); - } - - const clangFormatPath = path.dirname(require.resolve('clang-format')); - const binary = process.platform === 'win32' - ? 'node_modules\\.bin\\clang-format.cmd' - : 'node_modules/.bin/clang-format'; - const options = ['--binary=' + binary, '--style=file']; - if (fix) { - options.push(FORMAT_START); - } else { - options.push('--diff', FORMAT_START); - } - - const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); - const result = spawn( - 'python', - [gitClangFormatPath, ...options, '--', ...filesToCheck], - { encoding: 'utf-8' } - ); - - if (result.stderr) { - console.error('Error running git-clang-format:', result.stderr); - return 2; - } - - const clangFormatOutput = result.stdout.trim(); - // Bail fast if in fix mode. - if (fix) { - console.log(clangFormatOutput); - return 0; - } - // Detect if there is any complains from clang-format - if ( - clangFormatOutput !== '' && - clangFormatOutput !== 'no modified files to format' && - clangFormatOutput !== 'clang-format did not modify any files' - ) { - console.error(clangFormatOutput); - const fixCmd = 'npm run lint:fix'; - console.error(` - ERROR: please run "${fixCmd}" to format changes in your commit - Note that when running the command locally, please keep your local - main branch and working branch up to date with nodejs/node-addon-api - to exclude un-related complains. - Or you can run "env FORMAT_START=upstream/main ${fixCmd}".`); - return 1; - } -} - -if (require.main === module) { - process.exitCode = main(process.argv.slice(2)); -} diff --git a/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js b/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js deleted file mode 100644 index e92a03a26..000000000 --- a/week-5/solution/frontend/node_modules/node-addon-api/tools/conversion.js +++ /dev/null @@ -1,301 +0,0 @@ -#! /usr/bin/env node - -'use strict'; - -const fs = require('fs'); -const path = require('path'); - -const args = process.argv.slice(2); -const dir = args[0]; -if (!dir) { - console.log('Usage: node ' + path.basename(__filename) + ' '); - process.exit(1); -} - -const NodeApiVersion = require('../').version; - -const disable = args[1]; -let ConfigFileOperations; -if (disable !== '--disable' && dir !== '--disable') { - ConfigFileOperations = { - 'package.json': [ - [/([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'], - [/[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, ''] - ], - 'binding.gyp': [ - [/([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \'\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], - [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);\s+(\w+)\.Reset\((\1)\);\s+\1->SetClassName\((Nan::String::New|Nan::New<(v8::)*String>)\("(.+?)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$6", {'], - [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], - [/Nan::New\(([\w\d:]+)\)->GetFunction\(\)/g, 'Napi::Function::New(env, $1)'], - [/Nan::New\(([\w\d:]+)\)->GetFunction()/g, 'Napi::Function::New(env, $1);'], - [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], - [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], - - // FunctionTemplate to FunctionReference - [/Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'], - [/Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'], - [/v8::Local/g, 'Napi::FunctionReference'], - [/Local/g, 'Napi::FunctionReference'], - [/v8::FunctionTemplate/g, 'Napi::FunctionReference'], - [/FunctionTemplate/g, 'Napi::FunctionReference'], - - [/([ ]*)Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, '$1InstanceMethod("$2", &$3),'], - [/([ ]*)(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm, - '});\n\n' + - '$1constructor = Napi::Persistent($3);\n' + - '$1constructor.SuppressDestruct();\n' + - '$1target.Set("$2", $3);'], - - // TODO: Other attribute combinations - [/static_cast\(ReadOnly\s*\|\s*DontDelete\)/gm, - 'static_cast(napi_enumerable | napi_configurable)'], - - [/([\w\d:<>]+?)::Cast\((.+?)\)/g, '$2.As<$1>()'], - - [/\*Nan::Utf8String\(([^)]+)\)/g, '$1->As().Utf8Value().c_str()'], - [/Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As()'], - [/Nan::Utf8String/g, 'std::string'], - - [/v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], - [/String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], - [/\.length\(\)/g, '.Length()'], - - [/Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,'], - - [/class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>'], - [/(\w+)\(([^)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3'], - - // HandleOKCallback to OnOK - [/HandleOKCallback/g, 'OnOK'], - // HandleErrorCallback to OnError - [/HandleErrorCallback/g, 'OnError'], - - // ex. .As() to .As() - [/\.As\(\)/g, '.As()'], - [/\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As()'], - - // ex. Nan::New(info[0]) to Napi::Number::New(info[0]) - [/Nan::New<(v8::)*Integer>\((.+?)\)/g, 'Napi::Number::New(env, $2)'], - [/Nan::New\(([0-9.]+)\)/g, 'Napi::Number::New(env, $1)'], - [/Nan::New<(v8::)*String>\("(.+?)"\)/g, 'Napi::String::New(env, "$2")'], - [/Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")'], - [/Nan::New<(v8::)*(.+?)>\(\)/g, 'Napi::$2::New(env)'], - [/Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)'], - [/Nan::New<(v8::)*(.+?)>\(/g, 'Napi::$2::New(env, '], - [/Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, '], - [/Nan::NewBuffer\(/g, 'Napi::Buffer::New(env, '], - // TODO: Properly handle this - [/Nan::New\(/g, 'Napi::New(env, '], - - [/\.IsInt32\(\)/g, '.IsNumber()'], - [/->IsInt32\(\)/g, '.IsNumber()'], - - [/(.+?)->BooleanValue\(\)/g, '$1.As().Value()'], - [/(.+?)->Int32Value\(\)/g, '$1.As().Int32Value()'], - [/(.+?)->Uint32Value\(\)/g, '$1.As().Uint32Value()'], - [/(.+?)->IntegerValue\(\)/g, '$1.As().Int64Value()'], - [/(.+?)->NumberValue\(\)/g, '$1.As().DoubleValue()'], - - // ex. Nan::To(info[0]) to info[0].Value() - [/Nan::To\((.+?)\)/g, '$2.To()'], - [/Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To()'], - // ex. Nan::To(info[0]) to info[0].As().Value() - [/Nan::To\((.+?)\)/g, '$1.As().Value()'], - // ex. Nan::To(info[0]) to info[0].As().Int32Value() - [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], - // ex. Nan::To(info[0]) to info[0].As().Int32Value() - [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], - // ex. Nan::To(info[0]) to info[0].As().Uint32Value() - [/Nan::To\((.+?)\)/g, '$1.As().Uint32Value()'], - // ex. Nan::To(info[0]) to info[0].As().Int64Value() - [/Nan::To\((.+?)\)/g, '$1.As().Int64Value()'], - // ex. Nan::To(info[0]) to info[0].As().FloatValue() - [/Nan::To\((.+?)\)/g, '$1.As().FloatValue()'], - // ex. Nan::To(info[0]) to info[0].As().DoubleValue() - [/Nan::To\((.+?)\)/g, '$1.As().DoubleValue()'], - - [/Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())'], - - [/Nan::Has\(([^,]+),\s*/gm, '($1).Has('], - [/\.Has\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Has($1)'], - [/\.Has\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Has($1)'], - - [/Nan::Get\(([^,]+),\s*/gm, '($1).Get('], - [/\.Get\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Get($1)'], - [/\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)'], - - [/Nan::Set\(([^,]+),\s*/gm, '($1).Set('], - [/\.Set\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\s*,/gm, '.Set($1,'], - [/\.Set\([\s|\\]*Nan::New\(([^)]+)\)\s*,/gm, '.Set($1,'], - - // ex. node::Buffer::HasInstance(info[0]) to info[0].IsBuffer() - [/node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()'], - // ex. node::Buffer::Length(info[0]) to info[0].Length() - [/node::Buffer::Length\((.+?)\)/g, '$1.As>().Length()'], - // ex. node::Buffer::Data(info[0]) to info[0].Data() - [/node::Buffer::Data\((.+?)\)/g, '$1.As>().Data()'], - [/Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, '], - - // Nan::AsyncQueueWorker(worker) - [/Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();'], - [/Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()'], - - // Nan::ThrowError(error) to Napi::Error::New(env, error).ThrowAsJavaScriptException() - [/([ ]*)return Nan::Throw(\w*?)Error\((.+?)\);/g, '$1Napi::$2Error::New(env, $3).ThrowAsJavaScriptException();\n$1return env.Null();'], - [/Nan::Throw(\w*?)Error\((.+?)\);\n(\s*)return;/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n$3return env.Null();'], - [/Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n'], - // Nan::RangeError(error) to Napi::RangeError::New(env, error) - [/Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)'], - - [/Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)'], - - [/Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);'], - [/Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope'], - [/Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty('], - [/\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", '], - // [ /Nan::GetPropertyNames\(([^,]+)\)/, '$1->GetPropertyNames()' ], - [/Nan::Equals\(([^,]+),/g, '$1.StrictEquals('], - - [/(.+)->Set\(/g, '$1.Set('], - - [/Nan::Callback/g, 'Napi::FunctionReference'], - - [/Nan::Persistent/g, 'Napi::ObjectReference'], - [/Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target'], - - [/(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;'], - [/Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();'], - - [/Nan::NAN_METHOD_RETURN_TYPE/g, 'void'], - [/NAN_INLINE/g, 'inline'], - - [/Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&'], - [/NAN_METHOD\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], - [/static\s*NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], - [/NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], - [/static\s*NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], - [/NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], - [/void Init\((v8::)*Local<(v8::)*Object> exports\)/g, 'Napi::Object Init(Napi::Env env, Napi::Object exports)'], - [/NAN_MODULE_INIT\(([\w\d:]+?)\);/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports);'], - [/NAN_MODULE_INIT\(([\w\d:]+?)\)/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports)'], - - [/::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)'], - [/constructor_template/g, 'constructor'], - - [/Nan::FunctionCallbackInfo<(v8::)?Value>[ ]*& [ ]*info\)[ ]*{\n*([ ]*)/gm, 'Napi::CallbackInfo& info) {\n$2Napi::Env env = info.Env();\n$2'], - [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&\s*info\);/g, 'Napi::CallbackInfo& info);'], - [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&/g, 'Napi::CallbackInfo&'], - - [/Buffer::HasInstance\(([^)]+)\)/g, '$1.IsBuffer()'], - - [/info\[(\d+)\]->/g, 'info[$1].'], - [/info\[([\w\d]+)\]->/g, 'info[$1].'], - [/info\.This\(\)->/g, 'info.This().'], - [/->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()'], - [/info.GetReturnValue\(\).SetUndefined\(\)/g, 'return env.Undefined()'], - [/info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;'], - - // ex. Local to Napi::Value - [/v8::Local/g, 'Napi::$1'], - [/Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'], - - // Declare an env in helper functions that take a Napi::Value - [/(\w+)\(Napi::Value (\w+)(,\s*[^()]+)?\)\s*{\n*([ ]*)/gm, '$1(Napi::Value $2$3) {\n$4Napi::Env env = $2.Env();\n$4'], - - // delete #include and/or - [/#include +(<|")(?:node|nan).h("|>)/g, '#include $1napi.h$2\n#include $1uv.h$2'], - // NODE_MODULE to NODE_API_MODULE - [/NODE_MODULE/g, 'NODE_API_MODULE'], - [/Nan::/g, 'Napi::'], - [/nan.h/g, 'napi.h'], - - // delete .FromJust() - [/\.FromJust\(\)/g, ''], - // delete .ToLocalCheck() - [/\.ToLocalChecked\(\)/g, ''], - [/^.*->SetInternalFieldCount\(.*$/gm, ''], - - // replace using node; and/or using v8; to using Napi; - [/using (node|v8);/g, 'using Napi;'], - [/using namespace (node|Nan|v8);/g, 'using namespace Napi;'], - // delete using v8::Local; - [/using v8::Local;\n/g, ''], - // replace using v8::XXX; with using Napi::XXX - [/using v8::([A-Za-z]+);/g, 'using Napi::$1;'] - -]; - -const paths = listFiles(dir); -paths.forEach(function (dirEntry) { - const filename = dirEntry.split('\\').pop().split('/').pop(); - - // Check whether the file is a source file or a config file - // then execute function accordingly - const sourcePattern = /.+\.h|.+\.cc|.+\.cpp/; - if (sourcePattern.test(filename)) { - convertFile(dirEntry, SourceFileOperations); - } else if (ConfigFileOperations[filename] != null) { - convertFile(dirEntry, ConfigFileOperations[filename]); - } -}); - -function listFiles (dir, filelist) { - const files = fs.readdirSync(dir); - filelist = filelist || []; - files.forEach(function (file) { - if (file === 'node_modules') { - return; - } - - if (fs.statSync(path.join(dir, file)).isDirectory()) { - filelist = listFiles(path.join(dir, file), filelist); - } else { - filelist.push(path.join(dir, file)); - } - }); - return filelist; -} - -function convert (content, operations) { - for (let i = 0; i < operations.length; i++) { - const operation = operations[i]; - content = content.replace(operation[0], operation[1]); - } - return content; -} - -function convertFile (fileName, operations) { - fs.readFile(fileName, 'utf-8', function (err, file) { - if (err) throw err; - - file = convert(file, operations); - - fs.writeFile(fileName, file, function (err) { - if (err) throw err; - }); - }); -} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE b/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE deleted file mode 100644 index 56fce0895..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Mathias Buus - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/README.md b/week-5/solution/frontend/node_modules/node-gyp-build/README.md deleted file mode 100644 index f712ca686..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# node-gyp-build - -> Build tool and bindings loader for [`node-gyp`][node-gyp] that supports prebuilds. - -``` -npm install node-gyp-build -``` - -[![Test](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml/badge.svg)](https://github.com/prebuild/node-gyp-build/actions/workflows/test.yml) - -Use together with [`prebuildify`][prebuildify] to easily support prebuilds for your native modules. - -## Usage - -> **Note.** Prebuild names have changed in [`prebuildify@3`][prebuildify] and `node-gyp-build@4`. Please see the documentation below. - -`node-gyp-build` works similar to [`node-gyp build`][node-gyp] except that it will check if a build or prebuild is present before rebuilding your project. - -It's main intended use is as an npm install script and bindings loader for native modules that bundle prebuilds using [`prebuildify`][prebuildify]. - -First add `node-gyp-build` as an install script to your native project - -``` js -{ - ... - "scripts": { - "install": "node-gyp-build" - } -} -``` - -Then in your `index.js`, instead of using the [`bindings`](https://www.npmjs.com/package/bindings) module use `node-gyp-build` to load your binding. - -``` js -var binding = require('node-gyp-build')(__dirname) -``` - -If you do these two things and bundle prebuilds with [`prebuildify`][prebuildify] your native module will work for most platforms -without having to compile on install time AND will work in both node and electron without the need to recompile between usage. - -Users can override `node-gyp-build` and force compiling by doing `npm install --build-from-source`. - -Prebuilds will be attempted loaded from `MODULE_PATH/prebuilds/...` and then next `EXEC_PATH/prebuilds/...` (the latter allowing use with `zeit/pkg`) - -## Supported prebuild names - -If so desired you can bundle more specific flavors, for example `musl` builds to support Alpine, or targeting a numbered ARM architecture version. - -These prebuilds can be bundled in addition to generic prebuilds; `node-gyp-build` will try to find the most specific flavor first. Prebuild filenames are composed of _tags_. The runtime tag takes precedence, as does an `abi` tag over `napi`. For more details on tags, please see [`prebuildify`][prebuildify]. - -Values for the `libc` and `armv` tags are auto-detected but can be overridden through the `LIBC` and `ARM_VERSION` environment variables, respectively. - -## License - -MIT - -[prebuildify]: https://github.com/prebuild/prebuildify -[node-gyp]: https://www.npmjs.com/package/node-gyp diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md b/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md deleted file mode 100644 index da9c516dd..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -## Security contact information - -To report a security vulnerability, please use the -[Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure. diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/bin.js b/week-5/solution/frontend/node_modules/node-gyp-build/bin.js deleted file mode 100644 index c778e0ab9..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/bin.js +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node - -var proc = require('child_process') -var os = require('os') -var path = require('path') - -if (!buildFromSource()) { - proc.exec('node-gyp-build-test', function (err, stdout, stderr) { - if (err) { - if (verbose()) console.error(stderr) - preinstall() - } - }) -} else { - preinstall() -} - -function build () { - var win32 = os.platform() === 'win32' - var shell = win32 - var args = [win32 ? 'node-gyp.cmd' : 'node-gyp', 'rebuild'] - - try { - var pkg = require('node-gyp/package.json') - args = [ - process.execPath, - path.join(require.resolve('node-gyp/package.json'), '..', typeof pkg.bin === 'string' ? pkg.bin : pkg.bin['node-gyp']), - 'rebuild' - ] - shell = false - } catch (_) {} - - proc.spawn(args[0], args.slice(1), { stdio: 'inherit', shell, windowsHide: true }).on('exit', function (code) { - if (code || !process.argv[3]) process.exit(code) - exec(process.argv[3]).on('exit', function (code) { - process.exit(code) - }) - }) -} - -function preinstall () { - if (!process.argv[2]) return build() - exec(process.argv[2]).on('exit', function (code) { - if (code) process.exit(code) - build() - }) -} - -function exec (cmd) { - if (process.platform !== 'win32') { - var shell = os.platform() === 'android' ? 'sh' : true - return proc.spawn(cmd, [], { - shell, - stdio: 'inherit' - }) - } - - return proc.spawn(cmd, [], { - windowsVerbatimArguments: true, - stdio: 'inherit', - shell: true, - windowsHide: true - }) -} - -function buildFromSource () { - return hasFlag('--build-from-source') || process.env.npm_config_build_from_source === 'true' -} - -function verbose () { - return hasFlag('--verbose') || process.env.npm_config_loglevel === 'verbose' -} - -// TODO (next major): remove in favor of env.npm_config_* which works since npm -// 0.1.8 while npm_config_argv will stop working in npm 7. See npm/rfcs#90 -function hasFlag (flag) { - if (!process.env.npm_config_argv) return false - - try { - return JSON.parse(process.env.npm_config_argv).original.indexOf(flag) !== -1 - } catch (_) { - return false - } -} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js b/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js deleted file mode 100644 index b6622a5c2..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/build-test.js +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node - -process.env.NODE_ENV = 'test' - -var path = require('path') -var test = null - -try { - var pkg = require(path.join(process.cwd(), 'package.json')) - if (pkg.name && process.env[pkg.name.toUpperCase().replace(/-/g, '_')]) { - process.exit(0) - } - test = pkg.prebuild.test -} catch (err) { - // do nothing -} - -if (test) require(path.join(process.cwd(), test)) -else require('./')() diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/index.js b/week-5/solution/frontend/node_modules/node-gyp-build/index.js deleted file mode 100644 index 07eb14ffd..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line -if (typeof runtimeRequire.addon === 'function') { // if the platform supports native resolving prefer that - module.exports = runtimeRequire.addon.bind(runtimeRequire) -} else { // else use the runtime version here - module.exports = require('./node-gyp-build.js') -} diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js b/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js deleted file mode 100644 index 76b96e107..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/node-gyp-build.js +++ /dev/null @@ -1,207 +0,0 @@ -var fs = require('fs') -var path = require('path') -var os = require('os') - -// Workaround to fix webpack's build warnings: 'the request of a dependency is an expression' -var runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line - -var vars = (process.config && process.config.variables) || {} -var prebuildsOnly = !!process.env.PREBUILDS_ONLY -var abi = process.versions.modules // TODO: support old node where this is undef -var runtime = isElectron() ? 'electron' : (isNwjs() ? 'node-webkit' : 'node') - -var arch = process.env.npm_config_arch || os.arch() -var platform = process.env.npm_config_platform || os.platform() -var libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc') -var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || '' -var uv = (process.versions.uv || '').split('.')[0] - -module.exports = load - -function load (dir) { - return runtimeRequire(load.resolve(dir)) -} - -load.resolve = load.path = function (dir) { - dir = path.resolve(dir || '.') - - try { - var name = runtimeRequire(path.join(dir, 'package.json')).name.toUpperCase().replace(/-/g, '_') - if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD'] - } catch (err) {} - - if (!prebuildsOnly) { - var release = getFirst(path.join(dir, 'build/Release'), matchBuild) - if (release) return release - - var debug = getFirst(path.join(dir, 'build/Debug'), matchBuild) - if (debug) return debug - } - - var prebuild = resolve(dir) - if (prebuild) return prebuild - - var nearby = resolve(path.dirname(process.execPath)) - if (nearby) return nearby - - var target = [ - 'platform=' + platform, - 'arch=' + arch, - 'runtime=' + runtime, - 'abi=' + abi, - 'uv=' + uv, - armv ? 'armv=' + armv : '', - 'libc=' + libc, - 'node=' + process.versions.node, - process.versions.electron ? 'electron=' + process.versions.electron : '', - typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line - ].filter(Boolean).join(' ') - - throw new Error('No native build was found for ' + target + '\n loaded from: ' + dir + '\n') - - function resolve (dir) { - // Find matching "prebuilds/-" directory - var tuples = readdirSync(path.join(dir, 'prebuilds')).map(parseTuple) - var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0] - if (!tuple) return - - // Find most specific flavor first - var prebuilds = path.join(dir, 'prebuilds', tuple.name) - var parsed = readdirSync(prebuilds).map(parseTags) - var candidates = parsed.filter(matchTags(runtime, abi)) - var winner = candidates.sort(compareTags(runtime))[0] - if (winner) return path.join(prebuilds, winner.file) - } -} - -function readdirSync (dir) { - try { - return fs.readdirSync(dir) - } catch (err) { - return [] - } -} - -function getFirst (dir, filter) { - var files = readdirSync(dir).filter(filter) - return files[0] && path.join(dir, files[0]) -} - -function matchBuild (name) { - return /\.node$/.test(name) -} - -function parseTuple (name) { - // Example: darwin-x64+arm64 - var arr = name.split('-') - if (arr.length !== 2) return - - var platform = arr[0] - var architectures = arr[1].split('+') - - if (!platform) return - if (!architectures.length) return - if (!architectures.every(Boolean)) return - - return { name, platform, architectures } -} - -function matchTuple (platform, arch) { - return function (tuple) { - if (tuple == null) return false - if (tuple.platform !== platform) return false - return tuple.architectures.includes(arch) - } -} - -function compareTuples (a, b) { - // Prefer single-arch prebuilds over multi-arch - return a.architectures.length - b.architectures.length -} - -function parseTags (file) { - var arr = file.split('.') - var extension = arr.pop() - var tags = { file: file, specificity: 0 } - - if (extension !== 'node') return - - for (var i = 0; i < arr.length; i++) { - var tag = arr[i] - - if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') { - tags.runtime = tag - } else if (tag === 'napi') { - tags.napi = true - } else if (tag.slice(0, 3) === 'abi') { - tags.abi = tag.slice(3) - } else if (tag.slice(0, 2) === 'uv') { - tags.uv = tag.slice(2) - } else if (tag.slice(0, 4) === 'armv') { - tags.armv = tag.slice(4) - } else if (tag === 'glibc' || tag === 'musl') { - tags.libc = tag - } else { - continue - } - - tags.specificity++ - } - - return tags -} - -function matchTags (runtime, abi) { - return function (tags) { - if (tags == null) return false - if (tags.runtime && tags.runtime !== runtime && !runtimeAgnostic(tags)) return false - if (tags.abi && tags.abi !== abi && !tags.napi) return false - if (tags.uv && tags.uv !== uv) return false - if (tags.armv && tags.armv !== armv) return false - if (tags.libc && tags.libc !== libc) return false - - return true - } -} - -function runtimeAgnostic (tags) { - return tags.runtime === 'node' && tags.napi -} - -function compareTags (runtime) { - // Precedence: non-agnostic runtime, abi over napi, then by specificity. - return function (a, b) { - if (a.runtime !== b.runtime) { - return a.runtime === runtime ? -1 : 1 - } else if (a.abi !== b.abi) { - return a.abi ? -1 : 1 - } else if (a.specificity !== b.specificity) { - return a.specificity > b.specificity ? -1 : 1 - } else { - return 0 - } - } -} - -function isNwjs () { - return !!(process.versions && process.versions.nw) -} - -function isElectron () { - if (process.versions && process.versions.electron) return true - if (process.env.ELECTRON_RUN_AS_NODE) return true - return typeof window !== 'undefined' && window.process && window.process.type === 'renderer' -} - -function isAlpine (platform) { - return platform === 'linux' && fs.existsSync('/etc/alpine-release') -} - -// Exposed for unit tests -// TODO: move to lib -load.parseTags = parseTags -load.matchTags = matchTags -load.compareTags = compareTags -load.parseTuple = parseTuple -load.matchTuple = matchTuple -load.compareTuples = compareTuples diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/optional.js b/week-5/solution/frontend/node_modules/node-gyp-build/optional.js deleted file mode 100644 index 8daa04a6f..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/optional.js +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node - -/* -I am only useful as an install script to make node-gyp not compile for purely optional native deps -*/ - -process.exit(0) diff --git a/week-5/solution/frontend/node_modules/node-gyp-build/package.json b/week-5/solution/frontend/node_modules/node-gyp-build/package.json deleted file mode 100644 index 6f6f28b50..000000000 --- a/week-5/solution/frontend/node_modules/node-gyp-build/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "node-gyp-build", - "version": "4.8.4", - "description": "Build tool and bindings loader for node-gyp that supports prebuilds", - "main": "index.js", - "imports": { - "fs": { - "bare": "builtin:fs", - "default": "fs" - }, - "path": { - "bare": "builtin:path", - "default": "path" - }, - "os": { - "bare": "builtin:os", - "default": "os" - } - }, - "devDependencies": { - "array-shuffle": "^1.0.1", - "standard": "^14.0.0", - "tape": "^5.0.0" - }, - "scripts": { - "test": "standard && node test" - }, - "bin": { - "node-gyp-build": "./bin.js", - "node-gyp-build-optional": "./optional.js", - "node-gyp-build-test": "./build-test.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/prebuild/node-gyp-build.git" - }, - "author": "Mathias Buus (@mafintosh)", - "license": "MIT", - "bugs": { - "url": "https://github.com/prebuild/node-gyp-build/issues" - }, - "homepage": "https://github.com/prebuild/node-gyp-build" -} From 7ce96eca6ad4f49a93c67ec0905e94bad84c8de8 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 16:52:11 +0530 Subject: [PATCH 08/19] add signup and login --- .../solutions/easy/example.txt | 2 +- week-5/solution/backend/routes/user.js | 4 +- week-7/server/.env.example | 4 +- week-7/server/package-lock.json | 139 ++++++++++++------ week-7/server/package.json | 9 +- week-7/server/routes/admin.js | 77 +++++++++- week-7/server/routes/user.js | 77 +++++++++- week-7/server/server.js | 8 +- 8 files changed, 258 insertions(+), 62 deletions(-) diff --git a/week-2/week-2-async-js/solutions/easy/example.txt b/week-2/week-2-async-js/solutions/easy/example.txt index a542373a3..5dd01c177 100644 --- a/week-2/week-2-async-js/solutions/easy/example.txt +++ b/week-2/week-2-async-js/solutions/easy/example.txt @@ -1 +1 @@ -Hello, world!!! \ No newline at end of file +Hello, world! \ No newline at end of file diff --git a/week-5/solution/backend/routes/user.js b/week-5/solution/backend/routes/user.js index b0d68df52..0525cace8 100644 --- a/week-5/solution/backend/routes/user.js +++ b/week-5/solution/backend/routes/user.js @@ -49,7 +49,7 @@ router.post('/signup', async (req, res) => { const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' }); res.json({ message: 'User created successfully', token }); } catch (error) { - res.status(500).json({ message: 'Error creating user', error }); + res.status(500).json({ message: 'Error creating user', error: error.message }); } }); @@ -70,7 +70,7 @@ router.post('/signin', async (req, res) => { res.json({ message: "Logged in successfully", token }); } catch (error) { - res.status(500).json({ message: "Error signing in", error }); + res.status(500).json({ message: "Error signing in", error: error.message }); } }); diff --git a/week-7/server/.env.example b/week-7/server/.env.example index b8ef1a275..b5a27fe01 100644 --- a/week-7/server/.env.example +++ b/week-7/server/.env.example @@ -1,3 +1,5 @@ JWT_SECRET="" PORT= -MONGO_URI=mongodb://localhost:27017/coursera-app \ No newline at end of file +MONGO_URI=mongodb://localhost:27017/coursera-app +// netstat -ano | findstr :3000 +// taskkill /PID 12345 /F \ No newline at end of file diff --git a/week-7/server/package-lock.json b/week-7/server/package-lock.json index 83ad2b058..6ff61a60f 100644 --- a/week-7/server/package-lock.json +++ b/week-7/server/package-lock.json @@ -9,17 +9,19 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.22.1", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.6.2" + "jsonwebtoken": "^9.0.3", + "mongoose": "^8.6.2", + "zod": "^4.1.13" } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -59,6 +61,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -84,9 +100,9 @@ } }, "node_modules/bson": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", - "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -531,12 +547,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -559,23 +575,23 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -706,25 +722,25 @@ } }, "node_modules/mongodb": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", - "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -752,24 +768,24 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.2.tgz", - "integrity": "sha512-ErbDVvuUzUfyQpXvJ6sXznmZDICD8r6wIsa0VKjJtB6/LZncqwUn5Um040G1BaNo6L3Jz+xItLSwT0wZmSmUaQ==", + "version": "8.20.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz", + "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==", "license": "MIT", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "6.8.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -848,6 +864,26 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1161,15 +1197,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/type-is": { @@ -1222,16 +1258,25 @@ } }, "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/week-7/server/package.json b/week-7/server/package.json index 5139f9dc3..8d1b3b780 100644 --- a/week-7/server/package.json +++ b/week-7/server/package.json @@ -3,17 +3,20 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node server.js", + "dev": "nodemon server.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.22.1", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.6.2" + "jsonwebtoken": "^9.0.3", + "mongoose": "^8.6.2", + "zod": "^4.1.13" } } diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js index b787b33d3..755393372 100644 --- a/week-7/server/routes/admin.js +++ b/week-7/server/routes/admin.js @@ -2,13 +2,86 @@ const { Router } = require("express"); const adminRouter = Router(); +const { Admin } = require("../db/db"); +const { z } = require('zod'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const dotenv = require("dotenv"); +dotenv.config(); +const SECRET = process.env.JWT_SECRET; -adminRouter.post('/signup', (req, res) => { +adminRouter.post('/signup', async (req, res) => { // logic to sign up admin + const requiredBody = z.object({ + email: z.email(), + password: z.string().min(3).max(30), + firstName: z.string(), + lastName: z.string() + }) + const result = requiredBody.safeParse(req.body) + + if (!result.success) { + res.json({ + message: "Incorrect format: " + result.error.issues[0].message + }) + return + } + /* + receive req.body from script.js (submit button of signup) + { + email: email (from signup-email), + password: password (from signup-password), + firstName: firstName, + lastName: lastName + } + */ + const { email, password, firstName, lastName } = req.body; + // check if email already exists + try { + const user = await Admin.findOne({ email }); + if (user) { + return res.status(403).json({ message: 'Admin already exists' }); + } + + const hashedPassword = await bcrypt.hash(password, 5); + const newAdmin = new Admin({ email, password: hashedPassword, firstName, lastName }); + await newAdmin.save(); + /* + const user = await Admin.create({ + email: email, + password: password, + firstName: firstName, + lastName, lastName + }) + */ + + const token = jwt.sign({ userId: newAdmin._id }, SECRET, { expiresIn: '1h' }); + res.json({ message: 'Admin created successfully', token }); + } catch (error) { + res.status(500).json({ message: 'Error creating user', error: error.message }); + } }); -adminRouter.post('/login', (req, res) => { +adminRouter.post('/login', async (req, res) => { // logic to log in admin + const { email, password } = req.body; + try { + const user = await Admin.findOne({ email }); + if (!user) { + return res.status(403).json({ message: "Invalid email" }); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return res.status(403).json({ message: "Invalid password" }); + } + + const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" }); + res.json({ message: "Logged in successfully", token }); + + } catch (error) { + res.status(500).json({ message: "Error signing in", error: error.message }); + } }); adminRouter.post('/courses', (req, res) => { diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js index e0cf58da7..518f9808d 100644 --- a/week-7/server/routes/user.js +++ b/week-7/server/routes/user.js @@ -2,13 +2,86 @@ const { Router } = require("express"); const userRouter = Router(); +const { User } = require("../db/db"); +const { z } = require('zod'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const dotenv = require("dotenv"); +dotenv.config(); +const SECRET = process.env.JWT_SECRET; -userRouter.post('/signup', (req, res) => { +userRouter.post('/signup', async (req, res) => { // logic to sign up user + const requiredBody = z.object({ + email: z.email(), + password: z.string().min(3).max(30), + firstName: z.string(), + lastName: z.string() + }) + const result = requiredBody.safeParse(req.body) + + if (!result.success) { + res.json({ + message: "Incorrect format: " + result.error.issues[0].message + }) + return + } + /* + receive req.body from script.js (submit button of signup) + { + email: email (from signup-email), + password: password (from signup-password), + firstName: firstName, + lastName: lastName + } + */ + const { email, password, firstName, lastName } = req.body; + // check if email already exists + try { + const user = await User.findOne({ email }); + if (user) { + return res.status(403).json({ message: 'User already exists' }); + } + + const hashedPassword = await bcrypt.hash(password, 5); + const newUser = new User({ email, password: hashedPassword, firstName, lastName }); + await newUser.save(); + /* + const user = await User.create({ + email: email, + password: password, + firstName: firstName, + lastName, lastName + }) + */ + + const token = jwt.sign({ userId: newUser._id }, SECRET, { expiresIn: '1h' }); + res.json({ message: 'User created successfully', token }); + } catch (error) { + res.status(500).json({ message: 'Error creating user', error: error.message }); + } }); -userRouter.post('/login', (req, res) => { +userRouter.post('/login', async (req, res) => { // logic to log in user + const { email, password } = req.body; + try { + const user = await User.findOne({ email }); + if (!user) { + return res.status(403).json({ message: "Invalid email" }); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return res.status(403).json({ message: "Invalid password" }); + } + + const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" }); + res.json({ message: "Logged in successfully", token }); + + } catch (error) { + res.status(500).json({ message: "Error signing in", error: error.message }); + } }); userRouter.get('/courses', (req, res) => { diff --git a/week-7/server/server.js b/week-7/server/server.js index 4c0c54fb2..a3bf86e3c 100644 --- a/week-7/server/server.js +++ b/week-7/server/server.js @@ -12,14 +12,14 @@ const app = express(); app.use(express.json()); -const secret = process.env.JWT_SECRERT; // This should be in an environment variable in a real application -const port = process.env.PORT; +const secret = process.env.JWT_SECRET; // This should be in an environment variable in a real application +const PORT = process.env.PORT || 3000; app.use("/users", userRouter) app.use("/admin", adminRouter) -app.listen(port, () => { +app.listen(PORT, () => { // Connect to MongoDB mongoose.connect(process.env.MONGO_URI); - console.log('Server is listening on port 3000'); + console.log(`Server running on port ${PORT}`); }); \ No newline at end of file From 4c756eb458e234d725006c76f46aae795123f8b1 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 20:30:34 +0530 Subject: [PATCH 09/19] admin routes --- week-7/server/.env.example | 3 +- week-7/server/middleware/admin.js | 29 ++++++++++++ week-7/server/middleware/user.js | 29 ++++++++++++ week-7/server/routes/admin.js | 73 +++++++++++++++++++++++++++++-- week-7/server/routes/user.js | 2 +- 5 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 week-7/server/middleware/admin.js create mode 100644 week-7/server/middleware/user.js diff --git a/week-7/server/.env.example b/week-7/server/.env.example index b5a27fe01..780972744 100644 --- a/week-7/server/.env.example +++ b/week-7/server/.env.example @@ -1,4 +1,5 @@ -JWT_SECRET="" +JWT_USER_SECRET="" +JWT_ADMIN_SECRET="" PORT= MONGO_URI=mongodb://localhost:27017/coursera-app // netstat -ano | findstr :3000 diff --git a/week-7/server/middleware/admin.js b/week-7/server/middleware/admin.js new file mode 100644 index 000000000..d9414f98e --- /dev/null +++ b/week-7/server/middleware/admin.js @@ -0,0 +1,29 @@ +const jwt = require('jsonwebtoken'); +const SECRET = process.env.JWT_ADMIN_SECRET || 'secret000'; + +const authenticateAdminJwt = (req, res, next) => { + /* + receive req.headers from script.js (submit button of todo) + { + Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage) + } + */ + const authHeader = req.headers.authorization; + if (authHeader) { + const token = authHeader.split(' ')[1]; + jwt.verify(token, SECRET, (err, user) => { + if (err) { + return res.status(403).json({ message: 'Forbidden: Invalid token' }); + } + req.userId = user.userId; + next(); + }); + } else { + res.status(401).json({ message: 'Unauthorized: No token provided' }); + } +}; + +module.exports = { + authenticateAdminJwt, + SECRET, +}; \ No newline at end of file diff --git a/week-7/server/middleware/user.js b/week-7/server/middleware/user.js new file mode 100644 index 000000000..7f3a5add3 --- /dev/null +++ b/week-7/server/middleware/user.js @@ -0,0 +1,29 @@ +const jwt = require('jsonwebtoken'); +const SECRET = process.env.JWT_USER_SECRET || 'secret000'; + +const authenticateUserJwt = (req, res, next) => { + /* + receive req.headers from script.js (submit button of todo) + { + Authorization: "Bearer abcdefghijklmnopqrstuvwxyz" (from local storage) + } + */ + const authHeader = req.headers.authorization; + if (authHeader) { + const token = authHeader.split(' ')[1]; + jwt.verify(token, SECRET, (err, user) => { + if (err) { + return res.status(403).json({ message: 'Forbidden: Invalid token' }); + } + req.userId = user.userId; + next(); + }); + } else { + res.status(401).json({ message: 'Unauthorized: No token provided' }); + } +}; + +module.exports = { + authenticateUserJwt, + SECRET, +}; \ No newline at end of file diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js index 755393372..313789681 100644 --- a/week-7/server/routes/admin.js +++ b/week-7/server/routes/admin.js @@ -3,12 +3,13 @@ const { Router } = require("express"); const adminRouter = Router(); const { Admin } = require("../db/db"); +const { Course } = require("../db/db"); const { z } = require('zod'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const dotenv = require("dotenv"); dotenv.config(); -const SECRET = process.env.JWT_SECRET; +const { SECRET, authenticateAdminJwt } = require("../middleware/admin") adminRouter.post('/signup', async (req, res) => { // logic to sign up admin @@ -84,16 +85,80 @@ adminRouter.post('/login', async (req, res) => { } }); -adminRouter.post('/courses', (req, res) => { +adminRouter.post('/courses', authenticateAdminJwt, async (req, res) => { // logic to create a course + const adminId = req.userId + const { title, description, imageUrl, price } = req.body + + try { + const course = await Course.findOne({ title }); + if (course) { + return res.status(403).json({ message: 'Course already exists' }); + } + + const newCourse = await Course.create({ + title: title, + description: description, + imageUrl: imageUrl, + price: price, + creatorId: adminId + }) + + res.json({ message: 'Course created successfully', courseId: newCourse._id }); + } catch (error) { + res.status(500).json({ message: 'Error creating course', error: error.message }); + } }); -adminRouter.put('/courses/:courseId', (req, res) => { +adminRouter.put('/courses/:courseId', authenticateAdminJwt, async (req, res) => { // logic to edit a course + const adminId = req.userId + const courseId = req.params.courseId + const { title, description, imageUrl, price } = req.body + + const course = await Course.findOne({ + _id: courseId + }); + + if (!course) { + return res.status(404).json({ + message: "Course not found" + }); + } + + const updatedCourse = await Course.findOneAndUpdate({ + _id: courseId, + creatorId: adminId + }, { + title: title, + description: description, + imageUrl: imageUrl, + price: price + }) + + if (!updatedCourse) { + return res.status(404).json({ + message: "Course not found or not owned by admin" + }); + } + + res.json({ + message: "Course updated", + courseId: updatedCourse._id + }) }); -adminRouter.get('/courses', (req, res) => { +adminRouter.get('/courses', authenticateAdminJwt, async (req, res) => { // logic to get all courses + const adminId = req.userId + + const courses = await Course.find({ + creatorId: adminId + }) + + res.json({ + courses: courses + }) }); module.exports = { diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js index 518f9808d..4cb024b5e 100644 --- a/week-7/server/routes/user.js +++ b/week-7/server/routes/user.js @@ -8,7 +8,7 @@ const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const dotenv = require("dotenv"); dotenv.config(); -const SECRET = process.env.JWT_SECRET; +const { SECRET, authenticateUserJwt } = require("../middleware/user") userRouter.post('/signup', async (req, res) => { // logic to sign up user From 536afa595d76a25ba98726565405d3eaf343fe8b Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 21:18:34 +0530 Subject: [PATCH 10/19] user routes --- week-7/README.md | 327 +++++++++++++++++------------- week-7/client-easy/index.html | 20 +- week-7/client-easy/script.js | 2 +- week-7/client-easy/style.css | 2 +- week-7/server/db/db.js | 50 ++--- week-7/server/middleware/admin.js | 12 +- week-7/server/middleware/user.js | 12 +- week-7/server/routes/admin.js | 206 ++++++++++--------- week-7/server/routes/user.js | 47 ++++- week-7/server/server.js | 14 +- 10 files changed, 399 insertions(+), 293 deletions(-) diff --git a/week-7/README.md b/week-7/README.md index 9c17ab31e..377b7d583 100644 --- a/week-7/README.md +++ b/week-7/README.md @@ -1,136 +1,163 @@ # Coursify + **Your task is to create a course-selling website where admin can publish/create courses and user can purchase courses.** # Frontend **There are two Client folders** + - if you are **not** familiar with **React**, pick `client-easy`. - if you are familiar with **React**, pick `client`. **Tips**: you can try cloning the UI of **app.100xdevs.com**. + # Backend # Admin Routes Structure -### POST /admin/signup +### POST /admin/signup + **Description**: Creates a new admin account. -**Input**: +**Input**: + ```json -{ - "username": "admin", - "password": "pass" +{ + "username": "admin", + "password": "pass" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Admin created successfully", - "token": "jwt_token_here" +{ + "message": "Admin created successfully", + "token": "jwt_token_here" } ``` --- -### POST /admin/login +### POST /admin/login + **Description**: Authenticates an admin. It requires the admin to send username and password in the headers. **Input**: -**Headers**: +**Headers**: + ```json -{ - "username": "admin", - "password": "pass" +{ + "username": "admin", + "password": "pass" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Logged in successfully", - "token": "jwt_token_here" +{ + "message": "Logged in successfully", + "token": "jwt_token_here" } ``` --- -### POST /admin/courses +### POST /admin/courses + **Description**: Creates a new course. **Input**: -**Headers**: +**Headers**: + ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` -**Body**: +``` + +**Body**: + ```json -{ - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true +{ + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Course created successfully", - "courseId": 1 +{ + "message": "Course created successfully", + "courseId": 1 } ``` --- -### PUT /admin/courses/:courseId +### PUT /admin/courses/:courseId + **Description**: Edits an existing course. `courseId` in the URL path should be replaced with the ID of the course to be edited. **Input**: -**Headers**: +**Headers**: + ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` -**Body**: +``` + +**Body**: + ```json -{ - "title": "updated course title", - "description": "updated course description", - "price": 100, - "imageLink": "https://updatedlinktoimage.com", - "published": false +{ + "title": "updated course title", + "description": "updated course description", + "price": 100, + "imageLink": "https://updatedlinktoimage.com", + "published": false } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Course updated successfully" +{ + "message": "Course updated successfully" } ``` --- -### GET /admin/courses +### GET /admin/courses + **Description**: Returns all the courses. **Input**: -**Headers**: +**Headers**: + ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "courses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] +{ + "courses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] } ``` @@ -138,113 +165,133 @@ --- -### POST /users/signup +### POST /users/signup + **Description**: Creates a new user account. -**Input**: +**Input**: + ```json -{ - "username": "user", - "password": "pass" +{ + "username": "user", + "password": "pass" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "User created successfully", - "token": "jwt_token_here" +{ + "message": "User created successfully", + "token": "jwt_token_here" } ``` --- -### POST /users/login +### POST /users/login + **Description**: Authenticates a user. It requires the user to send username and password in the headers. **Input**: -**Headers**: +**Headers**: + ```json -{ - "username": "user", - "password": "pass" +{ + "username": "user", + "password": "pass" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Logged in successfully", - "token": "jwt_token_here" +{ + "message": "Logged in successfully", + "token": "jwt_token_here" } ``` --- -### GET /users/courses +### GET /users/courses + **Description**: Lists all the courses. **Input**: -**Headers**: +**Headers**: + ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "courses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] +{ + "courses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] } ``` --- -### POST /users/courses/:courseId +### POST /users/courses/:courseId + **Description**: Purchases a course. `courseId` in the URL path should be replaced with the ID of the course to be purchased. **Input**: -**Headers**: +**Headers**: + ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` -**Output**: +``` + +**Output**: + ```json -{ - "message": "Course purchased successfully" +{ + "message": "Course purchased successfully" } ``` --- -### GET /users/purchasedCourses +### GET /users/purchasedCourses + **Description**: Lists all the courses purchased by the user. **Input**: -**Headers**: -```json -{ - "Authorization": "Bearer jwt_token_here" -} -``` -**Output**: -```json -{ - "purchasedCourses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] -} -``` \ No newline at end of file +**Headers**: + +```json +{ + "Authorization": "Bearer jwt_token_here" +} +``` + +**Output**: + +```json +{ + "purchasedCourses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] +} +``` diff --git a/week-7/client-easy/index.html b/week-7/client-easy/index.html index a5f795c6a..5fdc2bad2 100644 --- a/week-7/client-easy/index.html +++ b/week-7/client-easy/index.html @@ -1,14 +1,14 @@ - + - - - + + + Document - - - + + + - + - - \ No newline at end of file + + diff --git a/week-7/client-easy/script.js b/week-7/client-easy/script.js index 3337a847f..86a542dcf 100644 --- a/week-7/client-easy/script.js +++ b/week-7/client-easy/script.js @@ -1 +1 @@ -// write your code here \ No newline at end of file +// write your code here diff --git a/week-7/client-easy/style.css b/week-7/client-easy/style.css index 9f4550921..c440824c6 100644 --- a/week-7/client-easy/style.css +++ b/week-7/client-easy/style.css @@ -1 +1 @@ -/* write your code here */ \ No newline at end of file +/* write your code here */ diff --git a/week-7/server/db/db.js b/week-7/server/db/db.js index 7aa301b87..428d628a1 100644 --- a/week-7/server/db/db.js +++ b/week-7/server/db/db.js @@ -1,51 +1,51 @@ // Define mongoose schemas -const mongoose = require('mongoose'); -const ObjectId = mongoose.Types.ObjectId +const mongoose = require("mongoose"); +const ObjectId = mongoose.Types.ObjectId; const userSchema = new mongoose.Schema({ // userSchema here - email: { type: String, unique: true}, + email: { type: String, unique: true }, password: String, firstName: String, - lastName: String + lastName: String, }); const adminSchema = new mongoose.Schema({ -// adminSchema here - email: { type: String, unique: true}, + // adminSchema here + email: { type: String, unique: true }, password: String, firstName: String, - lastName: String + lastName: String, }); const courseSchema = new mongoose.Schema({ -// courseSchema here - title: String, - description: String, - price: Number, - imageUrl: String, - creatorId: ObjectId + // courseSchema here + title: String, + description: String, + price: Number, + imageUrl: String, + creatorId: ObjectId, }); const purchaseSchema = new mongoose.Schema({ - userId: ObjectId, - courseId: ObjectId + userId: ObjectId, + courseId: ObjectId, }); // Define mongoose models -const User = mongoose.model('User', userSchema); -const Admin = mongoose.model('Admin', adminSchema); -const Course = mongoose.model('Course', courseSchema); -const Purchase = mongoose.model('Purchase', purchaseSchema); +const User = mongoose.model("User", userSchema); +const Admin = mongoose.model("Admin", adminSchema); +const Course = mongoose.model("Course", courseSchema); +const Purchase = mongoose.model("Purchase", purchaseSchema); const authMiddleware = (req, res, next) => { -// authMiddleware logic here + // authMiddleware logic here }; module.exports = { - User, - Admin, - Course, - Purchase -} \ No newline at end of file + User, + Admin, + Course, + Purchase, +}; diff --git a/week-7/server/middleware/admin.js b/week-7/server/middleware/admin.js index d9414f98e..e050af04b 100644 --- a/week-7/server/middleware/admin.js +++ b/week-7/server/middleware/admin.js @@ -1,5 +1,5 @@ -const jwt = require('jsonwebtoken'); -const SECRET = process.env.JWT_ADMIN_SECRET || 'secret000'; +const jwt = require("jsonwebtoken"); +const SECRET = process.env.JWT_ADMIN_SECRET || "secret000"; const authenticateAdminJwt = (req, res, next) => { /* @@ -10,20 +10,20 @@ const authenticateAdminJwt = (req, res, next) => { */ const authHeader = req.headers.authorization; if (authHeader) { - const token = authHeader.split(' ')[1]; + const token = authHeader.split(" ")[1]; jwt.verify(token, SECRET, (err, user) => { if (err) { - return res.status(403).json({ message: 'Forbidden: Invalid token' }); + return res.status(403).json({ message: "Forbidden: Invalid token" }); } req.userId = user.userId; next(); }); } else { - res.status(401).json({ message: 'Unauthorized: No token provided' }); + res.status(401).json({ message: "Unauthorized: No token provided" }); } }; module.exports = { authenticateAdminJwt, SECRET, -}; \ No newline at end of file +}; diff --git a/week-7/server/middleware/user.js b/week-7/server/middleware/user.js index 7f3a5add3..2fc546aa5 100644 --- a/week-7/server/middleware/user.js +++ b/week-7/server/middleware/user.js @@ -1,5 +1,5 @@ -const jwt = require('jsonwebtoken'); -const SECRET = process.env.JWT_USER_SECRET || 'secret000'; +const jwt = require("jsonwebtoken"); +const SECRET = process.env.JWT_USER_SECRET || "secret000"; const authenticateUserJwt = (req, res, next) => { /* @@ -10,20 +10,20 @@ const authenticateUserJwt = (req, res, next) => { */ const authHeader = req.headers.authorization; if (authHeader) { - const token = authHeader.split(' ')[1]; + const token = authHeader.split(" ")[1]; jwt.verify(token, SECRET, (err, user) => { if (err) { - return res.status(403).json({ message: 'Forbidden: Invalid token' }); + return res.status(403).json({ message: "Forbidden: Invalid token" }); } req.userId = user.userId; next(); }); } else { - res.status(401).json({ message: 'Unauthorized: No token provided' }); + res.status(401).json({ message: "Unauthorized: No token provided" }); } }; module.exports = { authenticateUserJwt, SECRET, -}; \ No newline at end of file +}; diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js index 313789681..f157ea73e 100644 --- a/week-7/server/routes/admin.js +++ b/week-7/server/routes/admin.js @@ -4,30 +4,30 @@ const { Router } = require("express"); const adminRouter = Router(); const { Admin } = require("../db/db"); const { Course } = require("../db/db"); -const { z } = require('zod'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); +const { z } = require("zod"); +const bcrypt = require("bcrypt"); +const jwt = require("jsonwebtoken"); const dotenv = require("dotenv"); dotenv.config(); -const { SECRET, authenticateAdminJwt } = require("../middleware/admin") - -adminRouter.post('/signup', async (req, res) => { - // logic to sign up admin - const requiredBody = z.object({ - email: z.email(), - password: z.string().min(3).max(30), - firstName: z.string(), - lastName: z.string() - }) - const result = requiredBody.safeParse(req.body) - - if (!result.success) { - res.json({ - message: "Incorrect format: " + result.error.issues[0].message - }) - return - } - /* +const { SECRET, authenticateAdminJwt } = require("../middleware/admin"); + +adminRouter.post("/signup", async (req, res) => { + // logic to sign up admin + const requiredBody = z.object({ + email: z.email(), + password: z.string().min(3).max(30), + firstName: z.string(), + lastName: z.string(), + }); + const result = requiredBody.safeParse(req.body); + + if (!result.success) { + res.json({ + message: "Incorrect format: " + result.error.issues[0].message, + }); + return; + } + /* receive req.body from script.js (submit button of signup) { email: email (from signup-email), @@ -36,18 +36,23 @@ adminRouter.post('/signup', async (req, res) => { lastName: lastName } */ - const { email, password, firstName, lastName } = req.body; - // check if email already exists - try { - const user = await Admin.findOne({ email }); - if (user) { - return res.status(403).json({ message: 'Admin already exists' }); - } + const { email, password, firstName, lastName } = req.body; + // check if email already exists + try { + const user = await Admin.findOne({ email }); + if (user) { + return res.status(403).json({ message: "Admin already exists" }); + } - const hashedPassword = await bcrypt.hash(password, 5); - const newAdmin = new Admin({ email, password: hashedPassword, firstName, lastName }); - await newAdmin.save(); - /* + const hashedPassword = await bcrypt.hash(password, 5); + const newAdmin = new Admin({ + email, + password: hashedPassword, + firstName, + lastName, + }); + await newAdmin.save(); + /* const user = await Admin.create({ email: email, password: password, @@ -56,16 +61,20 @@ adminRouter.post('/signup', async (req, res) => { }) */ - const token = jwt.sign({ userId: newAdmin._id }, SECRET, { expiresIn: '1h' }); - res.json({ message: 'Admin created successfully', token }); - } catch (error) { - res.status(500).json({ message: 'Error creating user', error: error.message }); - } + const token = jwt.sign({ userId: newAdmin._id }, SECRET, { + expiresIn: "1h", + }); + res.json({ message: "Admin created successfully", token }); + } catch (error) { + res + .status(500) + .json({ message: "Error creating user", error: error.message }); + } }); -adminRouter.post('/login', async (req, res) => { - // logic to log in admin - const { email, password } = req.body; +adminRouter.post("/login", async (req, res) => { + // logic to log in admin + const { email, password } = req.body; try { const user = await Admin.findOne({ email }); if (!user) { @@ -79,88 +88,99 @@ adminRouter.post('/login', async (req, res) => { const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" }); res.json({ message: "Logged in successfully", token }); - } catch (error) { res.status(500).json({ message: "Error signing in", error: error.message }); } }); -adminRouter.post('/courses', authenticateAdminJwt, async (req, res) => { - // logic to create a course - const adminId = req.userId - const { title, description, imageUrl, price } = req.body +adminRouter.post("/courses", authenticateAdminJwt, async (req, res) => { + // logic to create a course + const adminId = req.userId; + const { title, description, imageUrl, price } = req.body; - try { - const course = await Course.findOne({ title }); - if (course) { - return res.status(403).json({ message: 'Course already exists' }); - } + try { + const course = await Course.findOne({ title }); + if (course) { + return res.status(403).json({ message: "Course already exists" }); + } - const newCourse = await Course.create({ - title: title, - description: description, - imageUrl: imageUrl, - price: price, - creatorId: adminId - }) + const newCourse = await Course.create({ + title: title, + description: description, + imageUrl: imageUrl, + price: price, + creatorId: adminId, + }); - res.json({ message: 'Course created successfully', courseId: newCourse._id }); - } catch (error) { - res.status(500).json({ message: 'Error creating course', error: error.message }); - } + res.json({ + message: "Course created successfully", + courseId: newCourse._id, + }); + } catch (error) { + res + .status(500) + .json({ message: "Error creating course", error: error.message }); + } }); -adminRouter.put('/courses/:courseId', authenticateAdminJwt, async (req, res) => { +adminRouter.put( + "/courses/:courseId", + authenticateAdminJwt, + async (req, res) => { // logic to edit a course - const adminId = req.userId - const courseId = req.params.courseId - const { title, description, imageUrl, price } = req.body + const adminId = req.userId; + const courseId = req.params.courseId; + const { title, description, imageUrl, price } = req.body; const course = await Course.findOne({ - _id: courseId + _id: courseId, }); if (!course) { - return res.status(404).json({ - message: "Course not found" - }); + return res.status(404).json({ + message: "Course not found", + }); } - const updatedCourse = await Course.findOneAndUpdate({ + const updatedCourse = await Course.findOneAndUpdate( + { _id: courseId, - creatorId: adminId - }, { + creatorId: adminId, + }, + { title: title, description: description, imageUrl: imageUrl, - price: price - }) + price: price, + }, + ); if (!updatedCourse) { - return res.status(404).json({ - message: "Course not found or not owned by admin" - }); + return res.status(404).json({ + message: "Course not found or not owned by admin", + }); } res.json({ - message: "Course updated", - courseId: updatedCourse._id - }) -}); + message: "Course updated", + courseId: updatedCourse._id, + }); + }, +); -adminRouter.get('/courses', authenticateAdminJwt, async (req, res) => { - // logic to get all courses - const adminId = req.userId +adminRouter.get("/courses", authenticateAdminJwt, async (req, res) => { + // logic to get all courses + const adminId = req.userId; - const courses = await Course.find({ - creatorId: adminId - }) + const courses = await Course.find({ + creatorId: adminId, + }); - res.json({ - courses: courses - }) + res.json({ + courses: courses, + }); }); module.exports = { - adminRouter: adminRouter -} \ No newline at end of file + adminRouter: adminRouter, +}; diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js index 4cb024b5e..c0a54df22 100644 --- a/week-7/server/routes/user.js +++ b/week-7/server/routes/user.js @@ -3,6 +3,8 @@ const { Router } = require("express"); const userRouter = Router(); const { User } = require("../db/db"); +const { Purchase } = require("../db/db"); +const { Course } = require("../db/db"); const { z } = require('zod'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); @@ -84,17 +86,54 @@ userRouter.post('/login', async (req, res) => { } }); -userRouter.get('/courses', (req, res) => { +userRouter.get('/courses', async (req, res) => { // logic to list all courses - res.json("hello") + const courses = await Course.find({}); + + res.json({ + courses: courses + }) }); -userRouter.post('/courses/:courseId', (req, res) => { +userRouter.post('/courses/:courseId', authenticateUserJwt, async (req, res) => { // logic to purchase a course + const userId = req.userId + const courseId = req.params.courseId + + const course = await Course.findOne({ + _id: courseId + }); + + if (!course) { + return res.status(404).json({ + message: "Course not found" + }); + } + + try { + const newPurchase = await Purchase.create({ + userId: userId, + courseId: courseId + }) + + res.json({ message: 'Course purchased successfully', courseId: newPurchase._id }); + } catch (error) { + res.status(500).json({ message: 'Error creating course', error: error.message }); + } + }); -userRouter.get('/purchasedCourses', (req, res) => { +userRouter.get('/purchasedCourses', authenticateUserJwt, async (req, res) => { // logic to view purchased courses + const userId = req.userId + + const purchasedCourses = await Purchase.find({ + userId: userId, + }); + + res.json({ + courses: purchasedCourses, + }); }); module.exports = { diff --git a/week-7/server/server.js b/week-7/server/server.js index a3bf86e3c..1acabf314 100644 --- a/week-7/server/server.js +++ b/week-7/server/server.js @@ -12,14 +12,14 @@ const app = express(); app.use(express.json()); -const secret = process.env.JWT_SECRET; // This should be in an environment variable in a real application +const secret = process.env.JWT_SECRET; // This should be in an environment variable in a real application const PORT = process.env.PORT || 3000; -app.use("/users", userRouter) -app.use("/admin", adminRouter) +app.use("/users", userRouter); +app.use("/admin", adminRouter); app.listen(PORT, () => { - // Connect to MongoDB - mongoose.connect(process.env.MONGO_URI); - console.log(`Server running on port ${PORT}`); -}); \ No newline at end of file + // Connect to MongoDB + mongoose.connect(process.env.MONGO_URI); + console.log(`Server running on port ${PORT}`); +}); From 67f2a99b39b844581ba174582e5474dfaa97a233 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Mon, 8 Dec 2025 21:38:21 +0530 Subject: [PATCH 11/19] nit fixes --- week-7/README.md | 327 +++++++++++++++------------------- week-7/client-easy/index.html | 20 +-- week-7/client-easy/script.js | 2 +- week-7/client-easy/style.css | 2 +- week-7/server/routes/user.js | 9 +- 5 files changed, 158 insertions(+), 202 deletions(-) diff --git a/week-7/README.md b/week-7/README.md index 377b7d583..9c17ab31e 100644 --- a/week-7/README.md +++ b/week-7/README.md @@ -1,163 +1,136 @@ # Coursify - **Your task is to create a course-selling website where admin can publish/create courses and user can purchase courses.** # Frontend **There are two Client folders** - - if you are **not** familiar with **React**, pick `client-easy`. - if you are familiar with **React**, pick `client`. **Tips**: you can try cloning the UI of **app.100xdevs.com**. - # Backend # Admin Routes Structure -### POST /admin/signup - +### POST /admin/signup **Description**: Creates a new admin account. -**Input**: - +**Input**: ```json -{ - "username": "admin", - "password": "pass" +{ + "username": "admin", + "password": "pass" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Admin created successfully", - "token": "jwt_token_here" +{ + "message": "Admin created successfully", + "token": "jwt_token_here" } ``` --- -### POST /admin/login - +### POST /admin/login **Description**: Authenticates an admin. It requires the admin to send username and password in the headers. **Input**: -**Headers**: - +**Headers**: ```json -{ - "username": "admin", - "password": "pass" +{ + "username": "admin", + "password": "pass" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Logged in successfully", - "token": "jwt_token_here" +{ + "message": "Logged in successfully", + "token": "jwt_token_here" } ``` --- -### POST /admin/courses - +### POST /admin/courses **Description**: Creates a new course. **Input**: -**Headers**: - +**Headers**: ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` - -**Body**: - +``` +**Body**: ```json -{ - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true +{ + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Course created successfully", - "courseId": 1 +{ + "message": "Course created successfully", + "courseId": 1 } ``` --- -### PUT /admin/courses/:courseId - +### PUT /admin/courses/:courseId **Description**: Edits an existing course. `courseId` in the URL path should be replaced with the ID of the course to be edited. **Input**: -**Headers**: - +**Headers**: ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` - -**Body**: - +``` +**Body**: ```json -{ - "title": "updated course title", - "description": "updated course description", - "price": 100, - "imageLink": "https://updatedlinktoimage.com", - "published": false +{ + "title": "updated course title", + "description": "updated course description", + "price": 100, + "imageLink": "https://updatedlinktoimage.com", + "published": false } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Course updated successfully" +{ + "message": "Course updated successfully" } ``` --- -### GET /admin/courses - +### GET /admin/courses **Description**: Returns all the courses. **Input**: -**Headers**: - +**Headers**: ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "courses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] +{ + "courses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] } ``` @@ -165,133 +138,113 @@ --- -### POST /users/signup - +### POST /users/signup **Description**: Creates a new user account. -**Input**: - +**Input**: ```json -{ - "username": "user", - "password": "pass" +{ + "username": "user", + "password": "pass" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "User created successfully", - "token": "jwt_token_here" +{ + "message": "User created successfully", + "token": "jwt_token_here" } ``` --- -### POST /users/login - +### POST /users/login **Description**: Authenticates a user. It requires the user to send username and password in the headers. **Input**: -**Headers**: - +**Headers**: ```json -{ - "username": "user", - "password": "pass" +{ + "username": "user", + "password": "pass" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Logged in successfully", - "token": "jwt_token_here" +{ + "message": "Logged in successfully", + "token": "jwt_token_here" } ``` --- -### GET /users/courses - +### GET /users/courses **Description**: Lists all the courses. **Input**: -**Headers**: - +**Headers**: ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "courses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] +{ + "courses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] } ``` --- -### POST /users/courses/:courseId - +### POST /users/courses/:courseId **Description**: Purchases a course. `courseId` in the URL path should be replaced with the ID of the course to be purchased. **Input**: -**Headers**: - +**Headers**: ```json -{ - "Authorization": "Bearer jwt_token_here" +{ + "Authorization": "Bearer jwt_token_here" } -``` - -**Output**: - +``` +**Output**: ```json -{ - "message": "Course purchased successfully" +{ + "message": "Course purchased successfully" } ``` --- -### GET /users/purchasedCourses - +### GET /users/purchasedCourses **Description**: Lists all the courses purchased by the user. **Input**: -**Headers**: - -```json -{ - "Authorization": "Bearer jwt_token_here" -} -``` - -**Output**: - -```json -{ - "purchasedCourses": [ - { - "id": 1, - "title": "course title", - "description": "course description", - "price": 100, - "imageLink": "https://linktoimage.com", - "published": true - }, - ... - ] -} -``` +**Headers**: +```json +{ + "Authorization": "Bearer jwt_token_here" +} +``` +**Output**: +```json +{ + "purchasedCourses": [ + { + "id": 1, + "title": "course title", + "description": "course description", + "price": 100, + "imageLink": "https://linktoimage.com", + "published": true + }, + ... + ] +} +``` \ No newline at end of file diff --git a/week-7/client-easy/index.html b/week-7/client-easy/index.html index 5fdc2bad2..a5f795c6a 100644 --- a/week-7/client-easy/index.html +++ b/week-7/client-easy/index.html @@ -1,14 +1,14 @@ - + - - - + + + Document - - - + + + - + - - + + \ No newline at end of file diff --git a/week-7/client-easy/script.js b/week-7/client-easy/script.js index 86a542dcf..3337a847f 100644 --- a/week-7/client-easy/script.js +++ b/week-7/client-easy/script.js @@ -1 +1 @@ -// write your code here +// write your code here \ No newline at end of file diff --git a/week-7/client-easy/style.css b/week-7/client-easy/style.css index c440824c6..9f4550921 100644 --- a/week-7/client-easy/style.css +++ b/week-7/client-easy/style.css @@ -1 +1 @@ -/* write your code here */ +/* write your code here */ \ No newline at end of file diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js index c0a54df22..9696e1740 100644 --- a/week-7/server/routes/user.js +++ b/week-7/server/routes/user.js @@ -2,9 +2,7 @@ const { Router } = require("express"); const userRouter = Router(); -const { User } = require("../db/db"); -const { Purchase } = require("../db/db"); -const { Course } = require("../db/db"); +const { User, Purchase, Course } = require("../db/db"); const { z } = require('zod'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); @@ -131,8 +129,13 @@ userRouter.get('/purchasedCourses', authenticateUserJwt, async (req, res) => { userId: userId, }); + const coursesData = await Course.find({ + _id: { $in: purchasedCourses.map(x => x.courseId)} + }) + res.json({ courses: purchasedCourses, + coursesData: coursesData }); }); From b898212d99af80a8d58c56da016c40cc5b1c3bc7 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 21:54:53 +0530 Subject: [PATCH 12/19] add register and login jsx --- week-7/client/src/App.jsx | 3 +- week-7/client/src/components/Login.jsx | 95 +++++++++++++++--- week-7/client/src/components/Register.jsx | 113 +++++++++++++++++++--- week-7/client/src/pages/Home.jsx | 74 +++++++++++--- week-7/server/routes/admin.js | 3 +- week-7/server/routes/user.js | 1 + week-7/server/server.js | 2 + 7 files changed, 253 insertions(+), 38 deletions(-) diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx index 8cc387045..a9d1d4f8d 100644 --- a/week-7/client/src/App.jsx +++ b/week-7/client/src/App.jsx @@ -1,7 +1,6 @@ // firstly, Don't get overwhelmed and if you are then go with client-easy. import Home from "./pages/Home" function App() { - return ( <> {/* start writing from here */} @@ -10,4 +9,4 @@ function App() { ) } -export default App +export default App \ No newline at end of file diff --git a/week-7/client/src/components/Login.jsx b/week-7/client/src/components/Login.jsx index 0948cf6e0..8852c8158 100644 --- a/week-7/client/src/components/Login.jsx +++ b/week-7/client/src/components/Login.jsx @@ -1,15 +1,88 @@ -// login code here -import axios from 'axios'; -import React from 'react' +import React, { useState } from "react"; +import axios from "axios"; const Login = () => { - // call the functions onClick of button. - async function handleLogin() { - const resposne = await axios.post(); // // if you don't know about axios, give it a read https://axios-http.com/docs/intro + const [formData, setFormData] = useState({ + email: "", + password: "" + }); + + const [message, setMessage] = useState(""); + + function handleChange(e) { + setFormData({ ...formData, [e.target.name]: e.target.value }); + } + + async function handleLogin() { + try { + const response = await axios.post( + "http://localhost:3000/users/login", + formData + ); + + setMessage(response.data.message); + + if (response.data.token) { + localStorage.setItem("token", response.data.token); + } + } catch (error) { + setMessage(error.response?.data?.message || "Login failed"); } - return ( -
    Login
    - ) -} + } + + return ( +
    +

    Login

    + + + + + + + + {message &&

    {message}

    } +
    + ); +}; + +const styles = { + container: { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + marginTop: "10px", + }, + input: { + padding: "10px", + border: "1px solid #ccc", + borderRadius: "5px", + }, + button: { + padding: "10px", + background: "#2196F3", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + fontWeight: "bold", + } +}; -export default Login \ No newline at end of file +export default Login; \ No newline at end of file diff --git a/week-7/client/src/components/Register.jsx b/week-7/client/src/components/Register.jsx index c143c3f6e..75a3f364d 100644 --- a/week-7/client/src/components/Register.jsx +++ b/week-7/client/src/components/Register.jsx @@ -1,16 +1,105 @@ -// register code here -import React from 'react' -import axios from "axios" - +import React, { useState } from "react"; +import axios from "axios"; const Register = () => { - // call the functions onClick of button. - async function handleRegister() { - const resposne = await axios.post(); // if you don't know about axios, give it a read https://axios-http.com/docs/intro + const [formData, setFormData] = useState({ + email: "", + password: "", + firstName: "", + lastName: "", + }); + + const [message, setMessage] = useState(""); + + function handleChange(e) { + setFormData({ ...formData, [e.target.name]: e.target.value }); + } + + async function handleRegister() { + try { + const response = await axios.post("http://localhost:3000/users/signup", formData); + + setMessage(response.data.message); + + if (response.data.token) { + localStorage.setItem("token", response.data.token); + } + } catch (error) { + setMessage(error.response?.data?.message || "Error occurred"); } - return ( -
    Register
    - ) -} + } + + return ( +
    +

    Create Account

    + + + + + + + + + + + + {message &&

    {message}

    } +
    + ); +}; + +const styles = { + container: { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + marginTop: "10px", + }, + input: { + padding: "10px", + border: "1px solid #ccc", + borderRadius: "5px", + }, + button: { + padding: "10px", + background: "#4CAF50", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + fontWeight: "bold", + }, +}; -export default Register \ No newline at end of file +export default Register; \ No newline at end of file diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx index 94691be96..7a798ba11 100644 --- a/week-7/client/src/pages/Home.jsx +++ b/week-7/client/src/pages/Home.jsx @@ -1,16 +1,68 @@ -// implement the home page UI here. -import React from 'react' +// implement the home page UI here. +import React from "react"; -// compoents imports -import Login from '../components/Login' -import Register from '../components/Register' -import Courses from '../components/Courses' +// components imports +import Login from "../components/Login"; +import Register from "../components/Register"; +import Courses from "../components/Courses"; const Home = () => { return ( - // write home page UI code here -
    Home
    - ) -} +
    +

    Welcome to the Learning Platform

    -export default Home \ No newline at end of file +
    +
    +

    Login

    + +
    + +
    +

    Register

    + +
    +
    + +
    +

    Available Courses

    + +
    +
    + ); +}; + +const styles = { + container: { + width: "80%", + margin: "0 auto", + paddingTop: "40px", + textAlign: "center", + }, + + title: { + fontSize: "32px", + marginBottom: "30px", + }, + + cardContainer: { + display: "flex", + justifyContent: "space-around", + marginBottom: "40px", + flexWrap: "wrap", + }, + + card: { + width: "350px", + padding: "20px", + borderRadius: "10px", + background: "#f7f7f7", + boxShadow: "0px 2px 8px rgba(0,0,0,0.1)", + marginBottom: "20px", + }, + + coursesSection: { + marginTop: "30px", + }, +}; + +export default Home; \ No newline at end of file diff --git a/week-7/server/routes/admin.js b/week-7/server/routes/admin.js index f157ea73e..5ffcf0cd4 100644 --- a/week-7/server/routes/admin.js +++ b/week-7/server/routes/admin.js @@ -2,8 +2,7 @@ const { Router } = require("express"); const adminRouter = Router(); -const { Admin } = require("../db/db"); -const { Course } = require("../db/db"); +const { Admin, Course } = require("../db/db"); const { z } = require("zod"); const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); diff --git a/week-7/server/routes/user.js b/week-7/server/routes/user.js index 9696e1740..fb75e91cb 100644 --- a/week-7/server/routes/user.js +++ b/week-7/server/routes/user.js @@ -77,6 +77,7 @@ userRouter.post('/login', async (req, res) => { } const token = jwt.sign({ userId: user._id }, SECRET, { expiresIn: "1h" }); + // Inspect -> Application -> Local Storage res.json({ message: "Logged in successfully", token }); } catch (error) { diff --git a/week-7/server/server.js b/week-7/server/server.js index 1acabf314..1bc052317 100644 --- a/week-7/server/server.js +++ b/week-7/server/server.js @@ -3,6 +3,7 @@ const express = require('express'); const jwt = require('jsonwebtoken'); const mongoose = require('mongoose'); const dotenv = require("dotenv"); +const cors = require("cors"); const { userRouter } = require('./routes/user'); const { adminRouter } = require('./routes/admin'); const { connectToDatabase } = require("./db/db"); @@ -10,6 +11,7 @@ dotenv.config(); // Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env const app = express(); +app.use(cors()); app.use(express.json()); const secret = process.env.JWT_SECRET; // This should be in an environment variable in a real application From 67f7846d7f1f78867018925dbb4b9c7f90738aec Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 22:09:45 +0530 Subject: [PATCH 13/19] add courses jsx --- week-7/client/src/components/Courses.jsx | 121 ++++++++++++++++++++-- week-7/client/src/components/Login.jsx | 5 +- week-7/client/src/components/Register.jsx | 5 +- week-7/client/src/pages/Home.jsx | 83 +++++++-------- 4 files changed, 156 insertions(+), 58 deletions(-) diff --git a/week-7/client/src/components/Courses.jsx b/week-7/client/src/components/Courses.jsx index cfbbaee98..a15d5a1f6 100644 --- a/week-7/client/src/components/Courses.jsx +++ b/week-7/client/src/components/Courses.jsx @@ -1,11 +1,118 @@ -// courses code here -import React from 'react' +import React, { useEffect, useState } from "react"; +import axios from "axios"; -// use axios here, similar to register and login const Courses = () => { + const [courses, setCourses] = useState([]); + const [purchased, setPurchased] = useState([]); + const [message, setMessage] = useState(""); + + // get token from localStorage + const token = localStorage.getItem("token"); + + // fetch all courses + const fetchCourses = async () => { + try { + const res = await axios.get("http://localhost:3000/users/courses"); + setCourses(res.data.courses); + } catch (err) { + console.error("Error fetching courses:", err); + } + }; + + // fetch purchased courses + const fetchPurchasedCourses = async () => { + if (!token) return; + try { + const res = await axios.get("http://localhost:3000/users/purchasedCourses", { + headers: { Authorization: `Bearer ${token}` }, + }); + setPurchased(res.data.coursesData.map(c => c._id)); + } catch (err) { + console.error("Error fetching purchased courses:", err); + } + }; + + useEffect(() => { + fetchCourses(); + fetchPurchasedCourses(); + }, []); + + // purchase a course + const purchaseCourse = async (courseId) => { + if (!token) { + setMessage("Please login to purchase courses"); + return; + } + + try { + const res = await axios.post( + `http://localhost:3000/users/courses/${courseId}`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + setMessage(res.data.message); + setPurchased([...purchased, courseId]); + } catch (err) { + setMessage(err.response?.data?.message || "Purchase failed"); + } + }; + return ( -
    Courses
    - ) -} +
    +

    Available Courses

    + {message &&

    {message}

    } +
    + {courses.map(course => ( +
    +

    {course.title}

    +

    {course.description}

    +

    Price: ${course.price}

    + {purchased.includes(course._id) ? ( + + ) : ( + + )} +
    + ))} +
    +
    + ); +}; + +const styles = { + courseContainer: { + display: "flex", + gap: "20px", + flexWrap: "wrap", + }, + courseCard: { + border: "1px solid #ccc", + borderRadius: "10px", + padding: "15px", + width: "250px", + }, + purchaseButton: { + background: "#4CAF50", + color: "white", + border: "none", + padding: "8px 12px", + borderRadius: "5px", + cursor: "pointer", + }, + purchasedButton: { + background: "#aaa", + color: "white", + border: "none", + padding: "8px 12px", + borderRadius: "5px", + }, +}; -export default Courses \ No newline at end of file +export default Courses; \ No newline at end of file diff --git a/week-7/client/src/components/Login.jsx b/week-7/client/src/components/Login.jsx index 8852c8158..08804aaad 100644 --- a/week-7/client/src/components/Login.jsx +++ b/week-7/client/src/components/Login.jsx @@ -22,9 +22,10 @@ const Login = () => { setMessage(response.data.message); - if (response.data.token) { + if (response.data.token) { localStorage.setItem("token", response.data.token); - } + window.location.reload(); // simple way to refresh Home.jsx state + } } catch (error) { setMessage(error.response?.data?.message || "Login failed"); } diff --git a/week-7/client/src/components/Register.jsx b/week-7/client/src/components/Register.jsx index 75a3f364d..f5528ac97 100644 --- a/week-7/client/src/components/Register.jsx +++ b/week-7/client/src/components/Register.jsx @@ -21,9 +21,10 @@ const Register = () => { setMessage(response.data.message); - if (response.data.token) { + if (response.data.token) { localStorage.setItem("token", response.data.token); - } + window.location.reload(); // simple way to refresh Home.jsx state + } } catch (error) { setMessage(error.response?.data?.message || "Error occurred"); } diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx index 7a798ba11..88b7a402a 100644 --- a/week-7/client/src/pages/Home.jsx +++ b/week-7/client/src/pages/Home.jsx @@ -1,68 +1,57 @@ -// implement the home page UI here. -import React from "react"; - -// components imports +import React, { useState, useEffect } from "react"; import Login from "../components/Login"; import Register from "../components/Register"; import Courses from "../components/Courses"; const Home = () => { - return ( -
    -

    Welcome to the Learning Platform

    + const [isLoggedIn, setIsLoggedIn] = useState(false); -
    -
    -

    Login

    - -
    + useEffect(() => { + const token = localStorage.getItem("token"); + setIsLoggedIn(!!token); + }, []); -
    -

    Register

    + const handleLogout = () => { + localStorage.removeItem("token"); + setIsLoggedIn(false); + }; + + return ( +
    +

    Welcome to Online Courses

    + + {isLoggedIn ? ( +
    + + +
    + ) : ( +
    +
    -
    - -
    -

    Available Courses

    - -
    + )}
    ); }; const styles = { - container: { - width: "80%", - margin: "0 auto", - paddingTop: "40px", - textAlign: "center", - }, - - title: { - fontSize: "32px", - marginBottom: "30px", - }, - - cardContainer: { + authContainer: { display: "flex", - justifyContent: "space-around", - marginBottom: "40px", - flexWrap: "wrap", + gap: "50px", + marginTop: "20px", }, - - card: { - width: "350px", - padding: "20px", - borderRadius: "10px", - background: "#f7f7f7", - boxShadow: "0px 2px 8px rgba(0,0,0,0.1)", + logoutButton: { + padding: "10px 15px", + background: "#f44336", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", marginBottom: "20px", }, - - coursesSection: { - marginTop: "30px", - }, }; export default Home; \ No newline at end of file From 0107428064e34be3a1ced4aed59bd9675a20bd08 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 22:32:07 +0530 Subject: [PATCH 14/19] add admin jsx --- week-7/client/src/App.jsx | 25 +++-- week-7/client/src/components/AdminCourses.jsx | 92 +++++++++++++++++++ week-7/client/src/components/AdminLogin.jsx | 44 +++++++++ .../client/src/components/AdminRegister.jsx | 51 ++++++++++ week-7/client/src/pages/AdminHome.jsx | 53 +++++++++++ week-7/client/src/pages/Home.jsx | 14 +-- 6 files changed, 262 insertions(+), 17 deletions(-) create mode 100644 week-7/client/src/components/AdminCourses.jsx create mode 100644 week-7/client/src/components/AdminLogin.jsx create mode 100644 week-7/client/src/components/AdminRegister.jsx create mode 100644 week-7/client/src/pages/AdminHome.jsx diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx index a9d1d4f8d..7c8beba49 100644 --- a/week-7/client/src/App.jsx +++ b/week-7/client/src/App.jsx @@ -1,12 +1,21 @@ -// firstly, Don't get overwhelmed and if you are then go with client-easy. -import Home from "./pages/Home" +import React from "react"; +import Home from "./pages/Home"; +import AdminHome from "./pages/AdminHome"; + function App() { return ( - <> - {/* start writing from here */} - - - ) +
    +

    Online Courses Platform

    +
    +
    + {/* Regular user view */} +
    +
    + {/* Admin view */} +
    +
    +
    + ); } -export default App \ No newline at end of file +export default App; \ No newline at end of file diff --git a/week-7/client/src/components/AdminCourses.jsx b/week-7/client/src/components/AdminCourses.jsx new file mode 100644 index 000000000..de8268d82 --- /dev/null +++ b/week-7/client/src/components/AdminCourses.jsx @@ -0,0 +1,92 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; + +const AdminCourses = () => { + const [courses, setCourses] = useState([]); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [imageUrl, setImageUrl] = useState(""); + const [price, setPrice] = useState(""); + const [message, setMessage] = useState(""); + + const token = localStorage.getItem("adminToken"); + + useEffect(() => { + fetchCourses(); + }, []); + + const fetchCourses = async () => { + if (!token) return; + try { + const res = await axios.get("http://localhost:3000/admin/courses", { + headers: { Authorization: `Bearer ${token}` }, + }); + setCourses(res.data.courses); + } catch (err) { + console.error(err); + } + }; + + const createCourse = async () => { + if (!token) { + setMessage("Please login as admin"); + return; + } + try { + const res = await axios.post( + "http://localhost:3000/admin/courses", + { title, description, imageUrl, price }, + { headers: { Authorization: `Bearer ${token}` } } + ); + setMessage(res.data.message); + setTitle(""); + setDescription(""); + setImageUrl(""); + setPrice(""); + fetchCourses(); + } catch (err) { + setMessage(err.response?.data?.message || "Error creating course"); + } + }; + + return ( +
    +

    Admin Dashboard

    + {message &&

    {message}

    } + +

    Create Course

    + setTitle(e.target.value)} + /> + setDescription(e.target.value)} + /> + setImageUrl(e.target.value)} + /> + setPrice(e.target.value)} + /> + + +

    Your Courses

    +
      + {courses.map((c) => ( +
    • + {c.title} - ${c.price} +
    • + ))} +
    +
    + ); +}; + +export default AdminCourses; \ No newline at end of file diff --git a/week-7/client/src/components/AdminLogin.jsx b/week-7/client/src/components/AdminLogin.jsx new file mode 100644 index 000000000..b301a791a --- /dev/null +++ b/week-7/client/src/components/AdminLogin.jsx @@ -0,0 +1,44 @@ +import React, { useState } from "react"; +import axios from "axios"; + +const AdminLogin = () => { + const [formData, setFormData] = useState({ email: "", password: "" }); + const [message, setMessage] = useState(""); + + function handleChange(e) { + setFormData({ ...formData, [e.target.name]: e.target.value }); + } + + async function handleLogin() { + try { + const response = await axios.post( + "http://localhost:3000/admin/login", + formData + ); + setMessage(response.data.message); + if (response.data.token) { + localStorage.setItem("adminToken", response.data.token); + window.location.reload(); // refresh to show admin dashboard + } + } catch (err) { + setMessage(err.response?.data?.message || "Login failed"); + } + } + + return ( +
    +

    Admin Login

    + + + + {message &&

    {message}

    } +
    + ); +}; + +export default AdminLogin; \ No newline at end of file diff --git a/week-7/client/src/components/AdminRegister.jsx b/week-7/client/src/components/AdminRegister.jsx new file mode 100644 index 000000000..8a1acad0a --- /dev/null +++ b/week-7/client/src/components/AdminRegister.jsx @@ -0,0 +1,51 @@ +import React, { useState } from "react"; +import axios from "axios"; + +const AdminRegister = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + firstName: "", + lastName: "", + }); + const [message, setMessage] = useState(""); + + function handleChange(e) { + setFormData({ ...formData, [e.target.name]: e.target.value }); + } + + async function handleRegister() { + try { + const response = await axios.post( + "http://localhost:3000/admin/signup", + formData + ); + setMessage(response.data.message); + if (response.data.token) { + localStorage.setItem("adminToken", response.data.token); + window.location.reload(); // refresh to show admin dashboard + } + } catch (err) { + setMessage(err.response?.data?.message || "Error registering"); + } + } + + return ( +
    +

    Admin Register

    + + + + + + {message &&

    {message}

    } +
    + ); +}; + +export default AdminRegister; \ No newline at end of file diff --git a/week-7/client/src/pages/AdminHome.jsx b/week-7/client/src/pages/AdminHome.jsx new file mode 100644 index 000000000..dd07c57f2 --- /dev/null +++ b/week-7/client/src/pages/AdminHome.jsx @@ -0,0 +1,53 @@ +import React, { useState, useEffect } from "react"; +import AdminLogin from "../components/AdminLogin"; +import AdminRegister from "../components/AdminRegister"; +import AdminCourses from "../components/AdminCourses"; + +const AdminHome = () => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + const token = localStorage.getItem("adminToken"); + setIsLoggedIn(!!token); + }, []); + + const handleLogout = () => { + localStorage.removeItem("adminToken"); + setIsLoggedIn(false); + }; + + return ( +
    +

    Admin Dashboard

    + + {isLoggedIn ? ( +
    + + +
    + ) : ( +
    + + +
    + )} +
    + ); +}; + +const styles = { + authContainer: { display: "flex", gap: "20px" }, + logoutButton: { + padding: "8px 12px", + background: "#f44336", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + marginBottom: "15px", + }, +}; + +export default AdminHome; \ No newline at end of file diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx index 88b7a402a..894c7b7da 100644 --- a/week-7/client/src/pages/Home.jsx +++ b/week-7/client/src/pages/Home.jsx @@ -17,8 +17,8 @@ const Home = () => { }; return ( -
    -

    Welcome to Online Courses

    +
    +

    User Dashboard

    {isLoggedIn ? (
    @@ -38,19 +38,15 @@ const Home = () => { }; const styles = { - authContainer: { - display: "flex", - gap: "50px", - marginTop: "20px", - }, + authContainer: { display: "flex", gap: "20px" }, logoutButton: { - padding: "10px 15px", + padding: "8px 12px", background: "#f44336", color: "white", border: "none", borderRadius: "5px", cursor: "pointer", - marginBottom: "20px", + marginBottom: "15px", }, }; From 4b6b559a1db396b94f856677dc3ae6f4cf6603a4 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 22:34:02 +0530 Subject: [PATCH 15/19] add edit functionality --- week-7/client/src/components/AdminCourses.jsx | 87 ++++++++++++++----- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/week-7/client/src/components/AdminCourses.jsx b/week-7/client/src/components/AdminCourses.jsx index de8268d82..527433645 100644 --- a/week-7/client/src/components/AdminCourses.jsx +++ b/week-7/client/src/components/AdminCourses.jsx @@ -3,12 +3,17 @@ import axios from "axios"; const AdminCourses = () => { const [courses, setCourses] = useState([]); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [imageUrl, setImageUrl] = useState(""); - const [price, setPrice] = useState(""); const [message, setMessage] = useState(""); + const [form, setForm] = useState({ + title: "", + description: "", + imageUrl: "", + price: "", + }); + + const [editingCourseId, setEditingCourseId] = useState(null); + const token = localStorage.getItem("adminToken"); useEffect(() => { @@ -27,61 +32,95 @@ const AdminCourses = () => { } }; + const handleChange = (e) => { + setForm({ ...form, [e.target.name]: e.target.value }); + }; + const createCourse = async () => { - if (!token) { - setMessage("Please login as admin"); - return; - } + if (!token) return setMessage("Please login as admin"); try { const res = await axios.post( "http://localhost:3000/admin/courses", - { title, description, imageUrl, price }, + form, { headers: { Authorization: `Bearer ${token}` } } ); setMessage(res.data.message); - setTitle(""); - setDescription(""); - setImageUrl(""); - setPrice(""); + setForm({ title: "", description: "", imageUrl: "", price: "" }); fetchCourses(); } catch (err) { setMessage(err.response?.data?.message || "Error creating course"); } }; + const startEdit = (course) => { + setEditingCourseId(course._id); + setForm({ + title: course.title, + description: course.description, + imageUrl: course.imageUrl, + price: course.price, + }); + }; + + const updateCourse = async () => { + if (!token || !editingCourseId) return; + try { + const res = await axios.put( + `http://localhost:3000/admin/courses/${editingCourseId}`, + form, + { headers: { Authorization: `Bearer ${token}` } } + ); + setMessage(res.data.message); + setEditingCourseId(null); + setForm({ title: "", description: "", imageUrl: "", price: "" }); + fetchCourses(); + } catch (err) { + setMessage(err.response?.data?.message || "Error updating course"); + } + }; + return (

    Admin Dashboard

    {message &&

    {message}

    } -

    Create Course

    +

    {editingCourseId ? "Edit Course" : "Create Course"}

    setTitle(e.target.value)} + value={form.title} + onChange={handleChange} /> setDescription(e.target.value)} + value={form.description} + onChange={handleChange} /> setImageUrl(e.target.value)} + value={form.imageUrl} + onChange={handleChange} /> setPrice(e.target.value)} + value={form.price} + onChange={handleChange} /> - + {editingCourseId ? ( + + ) : ( + + )}

    Your Courses

      {courses.map((c) => (
    • - {c.title} - ${c.price} + {c.title} - ${c.price}{" "} +
    • ))}
    From 526c3d396a5d73338d20bb11c0a2e3790ec5065f Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 23:03:47 +0530 Subject: [PATCH 16/19] add comments --- week-7/client/src/App.jsx | 4 ++-- week-7/client/src/pages/AdminHome.jsx | 18 ++++++++++++++++++ week-7/client/src/pages/Home.jsx | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx index 7c8beba49..262de6b9b 100644 --- a/week-7/client/src/App.jsx +++ b/week-7/client/src/App.jsx @@ -6,8 +6,8 @@ function App() { return (

    Online Courses Platform

    -
    -
    +
    {/* display: "flex" arranges the child
    elements side by side horizontally. */} +
    {/* flex: 1 means both columns take equal width in the flex container. */} {/* Regular user view */}
    diff --git a/week-7/client/src/pages/AdminHome.jsx b/week-7/client/src/pages/AdminHome.jsx index dd07c57f2..d10ce15d1 100644 --- a/week-7/client/src/pages/AdminHome.jsx +++ b/week-7/client/src/pages/AdminHome.jsx @@ -10,12 +10,30 @@ const AdminHome = () => { const token = localStorage.getItem("adminToken"); setIsLoggedIn(!!token); }, []); + /* + Runs once when the component loads. + Looks for adminToken in localStorage. + If it exists → isLoggedIn = true + If not → isLoggedIn = false + */ const handleLogout = () => { localStorage.removeItem("adminToken"); setIsLoggedIn(false); }; + /* + Removes the admin token from local storage. + Updates isLoggedIn to false, which re-renders the component and shows login/register forms again. + */ + /* + Admin logged in (isLoggedIn === true): + Logout button + AdminCourses component (to manage or view courses) + + Admin not logged in (isLoggedIn === false): + AdminRegister and AdminLogin forms side by side + */ return (

    Admin Dashboard

    diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx index 894c7b7da..0f737db4e 100644 --- a/week-7/client/src/pages/Home.jsx +++ b/week-7/client/src/pages/Home.jsx @@ -5,17 +5,37 @@ import Courses from "../components/Courses"; const Home = () => { const [isLoggedIn, setIsLoggedIn] = useState(false); + // isLoggedIn state keeps track of whether the user is logged in. + // Initially, it is false (user is not logged in). useEffect(() => { const token = localStorage.getItem("token"); setIsLoggedIn(!!token); }, []); + /* + useEffect runs once when the component mounts ([] dependency array). + Checks localStorage for a token: + If a token exists → user is considered logged in → isLoggedIn becomes true + If no token → isLoggedIn remains false + */ const handleLogout = () => { localStorage.removeItem("token"); setIsLoggedIn(false); }; + /* + Removes the token from local storage. + Sets isLoggedIn to false, which will re-render the component and show the login/register forms again. + */ + /* + Logged in (isLoggedIn === true): + Shows a Logout button + Shows the component (user can view courses) + + Not logged in (isLoggedIn === false): + Shows Register and Login forms side by side (styled with flex) + */ return (

    User Dashboard

    From a8115a7fa2a6754cd34ffb92da219343f158dad3 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 23:14:27 +0530 Subject: [PATCH 17/19] uniform styles --- week-7/client/src/components/AdminCourses.jsx | 157 +++++++++++++----- week-7/client/src/components/AdminLogin.jsx | 67 ++++++-- .../client/src/components/AdminRegister.jsx | 87 ++++++++-- 3 files changed, 244 insertions(+), 67 deletions(-) diff --git a/week-7/client/src/components/AdminCourses.jsx b/week-7/client/src/components/AdminCourses.jsx index 527433645..3fe8ac127 100644 --- a/week-7/client/src/components/AdminCourses.jsx +++ b/week-7/client/src/components/AdminCourses.jsx @@ -4,14 +4,12 @@ import axios from "axios"; const AdminCourses = () => { const [courses, setCourses] = useState([]); const [message, setMessage] = useState(""); - const [form, setForm] = useState({ title: "", description: "", imageUrl: "", price: "", }); - const [editingCourseId, setEditingCourseId] = useState(null); const token = localStorage.getItem("adminToken"); @@ -80,47 +78,63 @@ const AdminCourses = () => { }; return ( -
    -

    Admin Dashboard

    - {message &&

    {message}

    } +
    +

    Admin Dashboard

    + {message &&

    {message}

    } -

    {editingCourseId ? "Edit Course" : "Create Course"}

    - - - - - {editingCourseId ? ( - - ) : ( - - )} +

    + {editingCourseId ? "Edit Course" : "Create Course"} +

    +
    + + + + + {editingCourseId ? ( + + ) : ( + + )} +
    -

    Your Courses

    -
      +

      Your Courses

      +
        {courses.map((c) => ( -
      • - {c.title} - ${c.price}{" "} - +
      • + + {c.title} - ${c.price} + +
      • ))}
      @@ -128,4 +142,67 @@ const AdminCourses = () => { ); }; +const styles = { + container: { + border: "1px solid #ccc", + padding: "20px", + borderRadius: "8px", + maxWidth: "600px", + margin: "0 auto", + backgroundColor: "#f9f9f9", + }, + heading: { + textAlign: "center", + color: "#333", + }, + subHeading: { + marginTop: "20px", + color: "#555", + }, + message: { + color: "green", + fontWeight: "bold", + }, + form: { + display: "flex", + flexDirection: "column", + gap: "10px", + marginBottom: "20px", + }, + input: { + padding: "8px", + borderRadius: "5px", + border: "1px solid #ccc", + fontSize: "14px", + }, + button: { + padding: "10px", + backgroundColor: "#4caf50", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + fontWeight: "bold", + }, + courseList: { + listStyleType: "none", + padding: 0, + }, + courseItem: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: "8px 0", + borderBottom: "1px solid #ddd", + }, + editButton: { + padding: "5px 10px", + backgroundColor: "#2196f3", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + }, +}; + export default AdminCourses; \ No newline at end of file diff --git a/week-7/client/src/components/AdminLogin.jsx b/week-7/client/src/components/AdminLogin.jsx index b301a791a..94bde31be 100644 --- a/week-7/client/src/components/AdminLogin.jsx +++ b/week-7/client/src/components/AdminLogin.jsx @@ -2,7 +2,11 @@ import React, { useState } from "react"; import axios from "axios"; const AdminLogin = () => { - const [formData, setFormData] = useState({ email: "", password: "" }); + const [formData, setFormData] = useState({ + email: "", + password: "" + }); + const [message, setMessage] = useState(""); function handleChange(e) { @@ -15,30 +19,71 @@ const AdminLogin = () => { "http://localhost:3000/admin/login", formData ); + setMessage(response.data.message); - if (response.data.token) { + + if (response.data.token) { localStorage.setItem("adminToken", response.data.token); - window.location.reload(); // refresh to show admin dashboard - } - } catch (err) { - setMessage(err.response?.data?.message || "Login failed"); + window.location.reload(); // simple way to refresh Home.jsx state + } + } catch (error) { + setMessage(error.response?.data?.message || "Login failed"); } } return ( -
      -

      Admin Login

      - +
      +

      Login

      + + + - + + + {message &&

      {message}

      }
      ); }; +const styles = { + container: { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + marginTop: "10px", + }, + input: { + padding: "10px", + border: "1px solid #ccc", + borderRadius: "5px", + }, + button: { + padding: "10px", + background: "#2196F3", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + fontWeight: "bold", + } +}; + export default AdminLogin; \ No newline at end of file diff --git a/week-7/client/src/components/AdminRegister.jsx b/week-7/client/src/components/AdminRegister.jsx index 8a1acad0a..a7a7cee2d 100644 --- a/week-7/client/src/components/AdminRegister.jsx +++ b/week-7/client/src/components/AdminRegister.jsx @@ -8,6 +8,7 @@ const AdminRegister = () => { firstName: "", lastName: "", }); + const [message, setMessage] = useState(""); function handleChange(e) { @@ -16,36 +17,90 @@ const AdminRegister = () => { async function handleRegister() { try { - const response = await axios.post( - "http://localhost:3000/admin/signup", - formData - ); + const response = await axios.post("http://localhost:3000/admin/signup", formData); + setMessage(response.data.message); - if (response.data.token) { + + if (response.data.token) { localStorage.setItem("adminToken", response.data.token); - window.location.reload(); // refresh to show admin dashboard - } - } catch (err) { - setMessage(err.response?.data?.message || "Error registering"); + window.location.reload(); // simple way to refresh Home.jsx state + } + } catch (error) { + setMessage(error.response?.data?.message || "Error occurred"); } } return ( -
      -

      Admin Register

      - +
      +

      Create Account

      + + + + + + + - - - + + + {message &&

      {message}

      }
      ); }; +const styles = { + container: { + display: "flex", + flexDirection: "column", + gap: "10px", + width: "100%", + marginTop: "10px", + }, + input: { + padding: "10px", + border: "1px solid #ccc", + borderRadius: "5px", + }, + button: { + padding: "10px", + background: "#4CAF50", + color: "white", + border: "none", + borderRadius: "5px", + cursor: "pointer", + fontWeight: "bold", + }, +}; + export default AdminRegister; \ No newline at end of file From 20145b9799da40eb9e5231824ed6dbe91689c9c9 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Tue, 9 Dec 2025 23:34:09 +0530 Subject: [PATCH 18/19] add public courses view --- week-7/client/src/App.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx index 262de6b9b..9d9253279 100644 --- a/week-7/client/src/App.jsx +++ b/week-7/client/src/App.jsx @@ -1,6 +1,7 @@ import React from "react"; import Home from "./pages/Home"; import AdminHome from "./pages/AdminHome"; +import Courses from "./components/Courses"; function App() { return ( @@ -14,6 +15,10 @@ function App() { {/* Admin view */}
      +
      + {/* Public Courses view */} + {/* fetchCourses() inside Courses will load courses from /users/courses */} +
    ); } From 4db2478850f04092afaf22634797be48f7195a25 Mon Sep 17 00:00:00 2001 From: tavishiseth Date: Wed, 10 Dec 2025 00:00:14 +0530 Subject: [PATCH 19/19] fix admin view --- week-7/client/src/App.jsx | 52 ++++++++++++++++++++++++-------- week-7/client/src/pages/Home.jsx | 14 ++++++--- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/week-7/client/src/App.jsx b/week-7/client/src/App.jsx index 9d9253279..9a24d9666 100644 --- a/week-7/client/src/App.jsx +++ b/week-7/client/src/App.jsx @@ -1,23 +1,49 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import Home from "./pages/Home"; import AdminHome from "./pages/AdminHome"; -import Courses from "./components/Courses"; function App() { + const [isAdminView, setIsAdminView] = useState(false); + + // Automatically set view based on login state + useEffect(() => { + const adminToken = localStorage.getItem("adminToken"); + const userToken = localStorage.getItem("token"); + + if (adminToken) { + setIsAdminView(true); // admin logged in → show admin view + } else if (userToken) { + setIsAdminView(false); // user logged in → show user view + } + }, []); // runs only once when app loads + + const toggleView = () => { + setIsAdminView(prev => !prev); + }; + return ( -
    +

    Online Courses Platform

    -
    {/* display: "flex" arranges the child
    elements side by side horizontally. */} -
    {/* flex: 1 means both columns take equal width in the flex container. */} - {/* Regular user view */} -
    -
    - {/* Admin view */} -
    -
    + + {/* Toggle button */} + + + {/* Render view */}
    - {/* Public Courses view */} - {/* fetchCourses() inside Courses will load courses from /users/courses */} + {isAdminView ? : }
    ); diff --git a/week-7/client/src/pages/Home.jsx b/week-7/client/src/pages/Home.jsx index 0f737db4e..8e7624364 100644 --- a/week-7/client/src/pages/Home.jsx +++ b/week-7/client/src/pages/Home.jsx @@ -48,10 +48,16 @@ const Home = () => {
    ) : ( -
    - - -
    + <> +
    + + +
    +
    + {/* Public Courses view */} + {/* fetchCourses() inside Courses will load courses from /users/courses */} +
    + )}
    );