diff --git a/README.md b/README.md
index d8a8901a..6c9710aa 100644
--- a/README.md
+++ b/README.md
@@ -216,8 +216,7 @@ The wasm binary won't be fetched or instantiated unless a [read](#readbarcodesfr
import { getZXingModule } from "zxing-wasm";
/**
- * This function will trigger the download and
- * instantiation of the wasm binary immediately
+ * This function will trigger the download and instantiation of the wasm binary immediately
*/
const zxingModulePromise1 = getZXingModule();
diff --git a/biome.json b/biome.json
index a6a63c25..b0c8c6a5 100644
--- a/biome.json
+++ b/biome.json
@@ -33,6 +33,12 @@
"allowTrailingCommas": true
}
}
+ },
+ {
+ "include": ["react/components"],
+ "formatter": {
+ "lineWidth": 120
+ }
}
]
}
diff --git a/copy-files-from-to.json b/copy-files-from-to.json
index a33a23ac..f83dbdb5 100644
--- a/copy-files-from-to.json
+++ b/copy-files-from-to.json
@@ -11,6 +11,10 @@
{
"from": "./src/full/*.wasm",
"to": "./dist/full/"
+ },
+ {
+ "from": "./src/stream/media-track-shims.d.ts",
+ "to": "./dist/"
}
],
"copyFilesSettings": {
diff --git a/index.html b/index.html
index f0464cdf..23d81393 100644
--- a/index.html
+++ b/index.html
@@ -1,10 +1,13 @@
-
+
- ZXing Reader Demo
+
+
+
+ ZXing WASM React
-
-
+
+
diff --git a/main.ts b/main.ts
deleted file mode 100644
index d4750662..00000000
--- a/main.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import {
- readBarcodesFromImageFile,
- writeBarcodeToImageFile,
-} from "./src/full/index";
-
-// import { readBarcodesFromImageFile } from "./src/reader/index";
-
-// import { writeBarcodeToImageFile } from "./src/writer/index";
-
-const text = "Hello World!";
-const barcodeImage = (await writeBarcodeToImageFile(text)).image;
-if (barcodeImage) {
- const readResults = await readBarcodesFromImageFile(barcodeImage, {
- formats: ["QRCode"],
- });
- console.log(readResults);
- console.log(readResults[0].text === text);
-}
diff --git a/main.tsx b/main.tsx
new file mode 100644
index 00000000..920b3fc5
--- /dev/null
+++ b/main.tsx
@@ -0,0 +1,45 @@
+///
+
+import React, { useState } from "react";
+import ReactDOM from "react-dom/client";
+import { StreamBarcodeDetector } from "./src/react/components/StreamBarcodeDetector.js";
+
+import type { InitConstraints } from "./src/stream/index.js";
+
+const App = () => {
+ const [initConstraints] = useState({
+ video: {
+ aspectRatio: undefined,
+ },
+ });
+
+ const [videoConstraints] = useState({
+ advanced: [
+ {
+ exposureMode: "continuous",
+ },
+ ],
+ });
+
+ return (
+ {
+ console.log(r);
+ }}
+ onStreamInspect={(c) => {
+ console.log(c);
+ }}
+ initConstraints={initConstraints}
+ videoConstraints={videoConstraints}
+ scanThrottle={0}
+ negativeDebounce={0}
+ formats={["QRCode"]}
+ />
+ );
+};
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/package-lock.json b/package-lock.json
index ddaa112f..5469d88e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,12 @@
"version": "1.2.6",
"license": "MIT",
"dependencies": {
- "@types/emscripten": "^1.39.10"
+ "@types/emscripten": "^1.39.10",
+ "just-compare": "^2.3.0",
+ "sha1-uint8array": "^0.10.7",
+ "type-fest": "^4.10.2",
+ "webrtc-adapter": "^8.2.3",
+ "zustand": "^4.5.1"
},
"devDependencies": {
"@babel/core": "^7.24.0",
@@ -18,12 +23,18 @@
"@changesets/cli": "^2.27.1",
"@types/babel__core": "^7.20.5",
"@types/node": "^20.11.28",
+ "@types/react": "^18.2.56",
+ "@types/react-dom": "^18.2.19",
+ "@vanilla-extract/css": "^1.14.1",
+ "@vanilla-extract/vite-plugin": "^4.0.4",
"concurrently": "^8.2.2",
"copy-files-from-to": "^3.9.1",
"lint-staged": "^15.2.2",
"npm-check-updates": "^16.14.17",
"prettier": "^3.2.5",
"pretty-quick": "^4.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"rimraf": "^5.0.5",
"simple-git-hooks": "^2.11.0",
"tsx": "^4.7.1",
@@ -33,6 +44,33 @@
"vite-plugin-babel": "^1.2.0"
}
},
+ "../user-media-stream": {
+ "version": "0.1.1",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "just-compare": "^2.3.0",
+ "webrtc-adapter": "^8.2.3",
+ "zustand": "^4.5.0"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^1.5.3",
+ "@changesets/cli": "^2.27.1",
+ "@commitlint/cli": "^18.6.0",
+ "@commitlint/config-conventional": "^18.6.0",
+ "concurrently": "^8.2.2",
+ "copy-files-from-to": "^3.9.1",
+ "lint-staged": "^15.2.1",
+ "npm-check-updates": "^16.14.14",
+ "prettier": "^3.2.4",
+ "prettier-plugin-jsdoc": "^1.3.0",
+ "rimraf": "^5.0.5",
+ "simple-git-hooks": "^2.9.0",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.12"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
@@ -298,6 +336,15 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-simple-access": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
@@ -460,6 +507,21 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz",
+ "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
@@ -1282,6 +1344,28 @@
"node": ">=0.1.90"
}
},
+ "node_modules/@emotion/hash": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==",
+ "dev": true
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz",
+ "integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@esbuild/linux-x64": {
"version": "0.19.10",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz",
@@ -2049,12 +2133,123 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true
},
+ "node_modules/@types/prop-types": {
+ "version": "15.7.11",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
+ "devOptional": true
+ },
+ "node_modules/@types/react": {
+ "version": "18.2.56",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz",
+ "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==",
+ "devOptional": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.2.19",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
+ "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
+ "devOptional": true
+ },
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
+ "node_modules/@vanilla-extract/babel-plugin-debug-ids": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@vanilla-extract/babel-plugin-debug-ids/-/babel-plugin-debug-ids-1.0.4.tgz",
+ "integrity": "sha512-mevYcVMwsT6960xnXRw/Rr2K7SOEwzwVBApg/2SJ3eg2KGsHfj1rN0oQ12WdoTT3RzThq+0551bVQKPvQnjeaA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.20.7"
+ }
+ },
+ "node_modules/@vanilla-extract/css": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.14.1.tgz",
+ "integrity": "sha512-V4JUuHNjZgl64NGfkDJePqizkNgiSpphODtZEs4cCPuxLAzwOUJYATGpejwimJr1n529kq4DEKWexW22LMBokw==",
+ "dev": true,
+ "dependencies": {
+ "@emotion/hash": "^0.9.0",
+ "@vanilla-extract/private": "^1.0.3",
+ "chalk": "^4.1.1",
+ "css-what": "^6.1.0",
+ "cssesc": "^3.0.0",
+ "csstype": "^3.0.7",
+ "deep-object-diff": "^1.1.9",
+ "deepmerge": "^4.2.2",
+ "media-query-parser": "^2.0.2",
+ "modern-ahocorasick": "^1.0.0",
+ "outdent": "^0.8.0"
+ }
+ },
+ "node_modules/@vanilla-extract/css/node_modules/outdent": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz",
+ "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==",
+ "dev": true
+ },
+ "node_modules/@vanilla-extract/integration": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-7.1.0.tgz",
+ "integrity": "sha512-kCFn2IfnCHf4PCP538zBs5g6JJvqybJ4lU+ww2CeV/B2roze8drF7jVu2hDQUTtfiXgNe0Q3WpUfXdX27KFLsw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.20.7",
+ "@babel/plugin-syntax-typescript": "^7.20.0",
+ "@vanilla-extract/babel-plugin-debug-ids": "^1.0.4",
+ "@vanilla-extract/css": "^1.14.0",
+ "esbuild": "npm:esbuild@~0.17.6 || ~0.18.0 || ~0.19.0",
+ "eval": "0.1.8",
+ "find-up": "^5.0.0",
+ "javascript-stringify": "^2.0.1",
+ "lodash": "^4.17.21",
+ "mlly": "^1.4.2",
+ "outdent": "^0.8.0",
+ "vite": "^5.0.11",
+ "vite-node": "^1.2.0"
+ }
+ },
+ "node_modules/@vanilla-extract/integration/node_modules/outdent": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz",
+ "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==",
+ "dev": true
+ },
+ "node_modules/@vanilla-extract/private": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.3.tgz",
+ "integrity": "sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ==",
+ "dev": true
+ },
+ "node_modules/@vanilla-extract/vite-plugin": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@vanilla-extract/vite-plugin/-/vite-plugin-4.0.4.tgz",
+ "integrity": "sha512-cfg4GK274xzwbVFh8YWvQXNnsCMemvMMwej7V93TTBP2O8qzyTgsx5VJuiAPov3oUU8JWGboaTs16Vnoe5bZ9w==",
+ "dev": true,
+ "dependencies": {
+ "@vanilla-extract/integration": "^7.1.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.3 || ^5.0.0"
+ }
+ },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -2062,9 +2257,9 @@
"dev": true
},
"node_modules/acorn": {
- "version": "8.11.2",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
- "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -2143,6 +2338,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
+ "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2495,6 +2702,15 @@
"semver": "^7.0.0"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/cacache": {
"version": "17.1.4",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
@@ -2616,9 +2832,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001571",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz",
- "integrity": "sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==",
+ "version": "1.0.30001583",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz",
+ "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==",
"dev": true,
"funding": [
{
@@ -3084,6 +3300,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/css-what": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "devOptional": true
+ },
"node_modules/csv": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz",
@@ -3220,6 +3466,21 @@
"node": ">=4.0.0"
}
},
+ "node_modules/deep-object-diff": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz",
+ "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==",
+ "dev": true
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -3296,6 +3557,20 @@
"node": ">=8"
}
},
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3519,6 +3794,342 @@
"@esbuild/win32-x64": "0.19.10"
}
},
+ "node_modules/esbuild/node_modules/@esbuild/android-arm": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz",
+ "integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-arm64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz",
+ "integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/android-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz",
+ "integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz",
+ "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz",
+ "integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz",
+ "integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz",
+ "integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-arm": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz",
+ "integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz",
+ "integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz",
+ "integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz",
+ "integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz",
+ "integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz",
+ "integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz",
+ "integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz",
+ "integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz",
+ "integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz",
+ "integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz",
+ "integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz",
+ "integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz",
+ "integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-x64": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz",
+ "integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3553,6 +4164,19 @@
"node": ">=4"
}
},
+ "node_modules/eval": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz",
+ "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "require-like": ">= 0.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
@@ -4905,6 +5529,12 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/javascript-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
+ "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
+ "dev": true
+ },
"node_modules/jju": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
@@ -4914,8 +5544,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -5007,6 +5636,11 @@
"node >= 0.2.0"
]
},
+ "node_modules/just-compare": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/just-compare/-/just-compare-2.3.0.tgz",
+ "integrity": "sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg=="
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -5049,6 +5683,224 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz",
+ "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.23.0",
+ "lightningcss-darwin-x64": "1.23.0",
+ "lightningcss-freebsd-x64": "1.23.0",
+ "lightningcss-linux-arm-gnueabihf": "1.23.0",
+ "lightningcss-linux-arm64-gnu": "1.23.0",
+ "lightningcss-linux-arm64-musl": "1.23.0",
+ "lightningcss-linux-x64-gnu": "1.23.0",
+ "lightningcss-linux-x64-musl": "1.23.0",
+ "lightningcss-win32-x64-msvc": "1.23.0"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz",
+ "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz",
+ "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz",
+ "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz",
+ "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz",
+ "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz",
+ "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz",
+ "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz",
+ "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz",
+ "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/lilconfig": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
@@ -5401,6 +6253,17 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
"node_modules/lowercase-keys": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -5489,6 +6352,15 @@
"is-buffer": "~1.1.6"
}
},
+ "node_modules/media-query-parser": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz",
+ "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ }
+ },
"node_modules/meow": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz",
@@ -5894,6 +6766,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/mlly": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz",
+ "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.3",
+ "pathe": "^1.1.2",
+ "pkg-types": "^1.0.3",
+ "ufo": "^1.3.2"
+ }
+ },
+ "node_modules/modern-ahocorasick": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.0.1.tgz",
+ "integrity": "sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==",
+ "dev": true
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -6859,6 +7749,12 @@
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true
+ },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -6962,6 +7858,17 @@
"node": ">=8"
}
},
+ "node_modules/pkg-types": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
+ "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+ "dev": true,
+ "dependencies": {
+ "jsonc-parser": "^3.2.0",
+ "mlly": "^1.2.0",
+ "pathe": "^1.1.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
@@ -7308,6 +8215,30 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+ "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
"node_modules/read-package-json": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz",
@@ -7612,6 +8543,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-like": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz",
+ "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@@ -7875,6 +8815,20 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "node_modules/scheduler": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
+ "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/sdp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
+ "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
+ },
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@@ -7959,6 +8913,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/sha1-uint8array": {
+ "version": "0.10.7",
+ "resolved": "https://registry.npmjs.org/sha1-uint8array/-/sha1-uint8array-0.10.7.tgz",
+ "integrity": "sha512-COJRCUOuTgEEPyhcRncHlf3Z2/Nik0PGZ60/tA9Ni2jlwYJ2g/WgP8TV19gbllmZDs/DGV5YklZxreyMHFX8ww=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8864,12 +9823,11 @@
}
},
"node_modules/type-fest": {
- "version": "3.13.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
- "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
- "dev": true,
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
+ "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
"engines": {
- "node": ">=14.16"
+ "node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -9007,6 +9965,12 @@
"node": ">=14.17"
}
},
+ "node_modules/ufo": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
+ "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
+ "dev": true
+ },
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -9167,6 +10131,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -9250,6 +10222,28 @@
}
}
},
+ "node_modules/vite-node": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.0.tgz",
+ "integrity": "sha512-D/oiDVBw75XMnjAXne/4feCkCEwcbr2SU1bjAhCcfI5Bq3VoOHji8/wCPAfUkDIeohJ5nSZ39fNxM3dNZ6OBOA==",
+ "dev": true,
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/vite-plugin-babel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/vite-plugin-babel/-/vite-plugin-babel-1.2.0.tgz",
@@ -9281,6 +10275,18 @@
"defaults": "^1.0.3"
}
},
+ "node_modules/webrtc-adapter": {
+ "version": "8.2.3",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
+ "integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
+ "dependencies": {
+ "sdp": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9619,6 +10625,33 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.1.tgz",
+ "integrity": "sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg==",
+ "dependencies": {
+ "use-sync-external-store": "1.2.0"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index 8b500839..4d2ba003 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,35 @@
},
"./reader/zxing_reader.wasm": "./dist/reader/zxing_reader.wasm",
"./writer/zxing_writer.wasm": "./dist/writer/zxing_writer.wasm",
- "./full/zxing_full.wasm": "./dist/full/zxing_full.wasm"
+ "./full/zxing_full.wasm": "./dist/full/zxing_full.wasm",
+ "./scanner": {
+ "import": "./dist/es/scanner/index.js",
+ "require": "./dist/cjs/scanner/index.js",
+ "default": "./dist/es/scanner/index.js"
+ },
+ "./stream": {
+ "import": "./dist/es/stream/index.js",
+ "require": "./dist/cjs/stream/index.js",
+ "default": "./dist/es/stream/index.js"
+ },
+ "./media-track-shims": {
+ "types": "./dist/media-track-shims.d.ts"
+ },
+ "./react": {
+ "import": "./dist/es/react/index.js",
+ "require": "./dist/cjs/react/index.js",
+ "default": "./dist/es/react/index.js"
+ },
+ "./react/components": {
+ "import": "./dist/es/react/components/index.js",
+ "require": "./dist/cjs/react/components/index.js",
+ "default": "./dist/es/react/components/index.js"
+ },
+ "./react/hooks": {
+ "import": "./dist/es/react/hooks/index.js",
+ "require": "./dist/cjs/react/hooks/index.js",
+ "default": "./dist/es/react/hooks/index.js"
+ }
},
"repository": {
"type": "git",
@@ -66,7 +94,7 @@
"submodule:update": "git submodule update --remote",
"cmake": "emcmake cmake -S src/cpp -B build",
"build:wasm": "cmake --build build -j$(($(nproc) - 1))",
- "copy:wasm": "copy-files-from-to",
+ "copy": "copy-files-from-to",
"docs:dev": "conc \"npm:docs:preview\" \"typedoc --watch --excludeInternal\"",
"docs:build": "typedoc --excludeInternal",
"docs:preview": "vite preview --outDir ./docs",
@@ -84,9 +112,9 @@
"build:cjs": "tsx ./scripts/build-cjs.ts",
"build:iife": "tsx ./scripts/build-iife.ts",
"build": "conc \"npm:build:es\" \"npm:build:cjs\" \"npm:build:iife\"",
- "postbuild:es": "tsc --declarationDir ./dist/es",
- "postbuild:cjs": "tsc --declarationDir ./dist/cjs",
- "postbuild": "conc \"npm:copy:wasm\" \"npm:docs:build\"",
+ "postbuild:es": "tsc --project ./tsconfig.production.json --declarationDir ./dist/es",
+ "postbuild:cjs": "tsc --project ./tsconfig.production.json --declarationDir ./dist/cjs",
+ "postbuild": "conc \"npm:copy\" \"npm:docs:build\"",
"build:all": "npm run submodule:init && npm run cmake && npm run build:wasm && npm run build",
"preview": "vite preview",
"prepublishOnly": "npm run build:all",
@@ -102,12 +130,18 @@
"@changesets/cli": "^2.27.1",
"@types/babel__core": "^7.20.5",
"@types/node": "^20.11.28",
+ "@types/react": "^18.2.56",
+ "@types/react-dom": "^18.2.19",
+ "@vanilla-extract/css": "^1.14.1",
+ "@vanilla-extract/vite-plugin": "^4.0.4",
"concurrently": "^8.2.2",
"copy-files-from-to": "^3.9.1",
"lint-staged": "^15.2.2",
"npm-check-updates": "^16.14.17",
"prettier": "^3.2.5",
"pretty-quick": "^4.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"rimraf": "^5.0.5",
"simple-git-hooks": "^2.11.0",
"tsx": "^4.7.1",
@@ -117,7 +151,12 @@
"vite-plugin-babel": "^1.2.0"
},
"dependencies": {
- "@types/emscripten": "^1.39.10"
+ "@types/emscripten": "^1.39.10",
+ "just-compare": "^2.3.0",
+ "sha1-uint8array": "^0.10.7",
+ "type-fest": "^4.10.2",
+ "webrtc-adapter": "^8.2.3",
+ "zustand": "^4.5.1"
},
"overrides": {
"typedoc": {
diff --git a/scripts/build-iife.ts b/scripts/build-iife.ts
index ab28abe7..6ce39d0d 100644
--- a/scripts/build-iife.ts
+++ b/scripts/build-iife.ts
@@ -1,12 +1,15 @@
import { rimraf } from "rimraf";
import { type LibraryOptions, build } from "vite";
import viteConfig from "../vite.config.js";
+import { emscriptenBun } from "./vite-plugin-emscripten-bun.js";
async function buildIife() {
await rimraf("dist/iife");
await Promise.all(
- Object.entries((viteConfig.build?.lib as LibraryOptions).entry).map(
- ([entryAlias, entryPath]) => {
+ Object.entries((viteConfig.build?.lib as LibraryOptions).entry)
+ // TODO: pay attention to the order
+ .slice(0, 3)
+ .map(([entryAlias, entryPath]) => {
return build({
...viteConfig,
build: {
@@ -19,14 +22,21 @@ async function buildIife() {
formats: ["iife"],
name: "ZXingWASM",
},
- rollupOptions: undefined,
+ rollupOptions: {
+ external: ["react"],
+ output: {
+ globals: {
+ react: "React",
+ },
+ },
+ },
outDir: "dist/iife",
emptyOutDir: false,
},
configFile: false,
+ plugins: [emscriptenBun()],
});
- },
- ),
+ }),
);
}
diff --git a/scripts/vite-plugin-emscripten-bun.ts b/scripts/vite-plugin-emscripten-bun.ts
new file mode 100644
index 00000000..b0b5b88e
--- /dev/null
+++ b/scripts/vite-plugin-emscripten-bun.ts
@@ -0,0 +1,68 @@
+import type { PluginItem } from "@babel/core";
+import {
+ binaryExpression,
+ identifier,
+ logicalExpression,
+ stringLiteral,
+ unaryExpression,
+ variableDeclaration,
+ variableDeclarator,
+} from "@babel/types";
+import babel from "vite-plugin-babel";
+
+function emscriptenBunBabel(): PluginItem {
+ return {
+ visitor: {
+ VariableDeclaration(path) {
+ if (
+ path.node.kind === "var" &&
+ path.node.declarations[0]?.id.type === "Identifier" &&
+ path.node.declarations[0]?.id.name === "ENVIRONMENT_IS_WEB"
+ ) {
+ path.insertAfter([
+ variableDeclaration("var", [
+ variableDeclarator(
+ identifier("ENVIRONMENT_IS_BUN"),
+ binaryExpression(
+ "!==",
+ unaryExpression("typeof", identifier("Bun")),
+ stringLiteral("undefined"),
+ ),
+ ),
+ ]),
+ ]);
+ }
+ },
+ LogicalExpression(path) {
+ if (
+ path.node.operator === "||" &&
+ path.node.left.type === "Identifier" &&
+ (path.node.left.name === "ENVIRONMENT_IS_WEB" ||
+ path.node.left.name === "ENVIRONMENT_IS_WORKER") &&
+ path.node.right.type === "Identifier" &&
+ (path.node.right.name === "ENVIRONMENT_IS_WORKER" ||
+ path.node.right.name === "ENVIRONMENT_IS_WEB")
+ ) {
+ path.replaceWith(
+ logicalExpression(
+ "||",
+ path.node,
+ identifier("ENVIRONMENT_IS_BUN"),
+ ),
+ );
+ path.skip();
+ }
+ },
+ },
+ };
+}
+
+export function emscriptenBun() {
+ return babel({
+ babelConfig: {
+ plugins: [emscriptenBunBabel()],
+ },
+ filter: /zxing_(reader|writer|full)\.js$/,
+ include: /zxing_(reader|writer|full)\.js$/,
+ });
+}
diff --git a/src/bindings/barcodeFormat.ts b/src/bindings/barcodeFormat.ts
index 2e0877cf..108e2c1b 100644
--- a/src/bindings/barcodeFormat.ts
+++ b/src/bindings/barcodeFormat.ts
@@ -29,12 +29,14 @@ export const barcodeFormats = [
export type BarcodeFormat = (typeof barcodeFormats)[number];
/**
- * Barcode formats that can be used in {@link ReaderOptions.formats | `ReaderOptions.formats`} to read barcodes.
+ * Barcode formats that can be used in {@link ReaderOptions.formats | `ReaderOptions.formats`} to
+ * read barcodes.
*/
export type ReadInputBarcodeFormat = Exclude;
/**
- * Barcode formats that can be used in {@link WriterOptions.format | `WriterOptions.format`} to write barcodes.
+ * Barcode formats that can be used in {@link WriterOptions.format | `WriterOptions.format`} to write
+ * barcodes.
*/
export type WriteInputBarcodeFormat = Exclude<
BarcodeFormat,
@@ -73,7 +75,7 @@ export function formatFromString(format: string): BarcodeFormat {
let end = barcodeFormats.length - 1;
while (start <= end) {
const mid = Math.floor((start + end) / 2);
- const midElement = barcodeFormats[mid];
+ const midElement = barcodeFormats[mid]!;
const normalizedMidElement = normalizeFormatString(midElement);
if (normalizedMidElement === normalizedTarget) {
return midElement;
diff --git a/src/bindings/binarizer.ts b/src/bindings/binarizer.ts
index 63f4d320..202bf81f 100644
--- a/src/bindings/binarizer.ts
+++ b/src/bindings/binarizer.ts
@@ -23,5 +23,5 @@ export function binarizerToZXingEnum(
}
export function zxingEnumToBinarizer(zxingEnum: ZXingEnum): Binarizer {
- return binarizers[zxingEnum.value];
+ return binarizers[zxingEnum.value]!;
}
diff --git a/src/bindings/characterSet.ts b/src/bindings/characterSet.ts
index 8b0dae48..877a42b3 100644
--- a/src/bindings/characterSet.ts
+++ b/src/bindings/characterSet.ts
@@ -53,5 +53,5 @@ export function characterSetToZXingEnum(
}
export function zxingEnumToCharacterSet(zxingEnum: ZXingEnum): CharacterSet {
- return characterSets[zxingEnum.value];
+ return characterSets[zxingEnum.value]!;
}
diff --git a/src/bindings/contentType.ts b/src/bindings/contentType.ts
index 0a634f0f..d96a0d3b 100644
--- a/src/bindings/contentType.ts
+++ b/src/bindings/contentType.ts
@@ -25,5 +25,5 @@ export function contentTypeToZXingEnum(
}
export function zxingEnumToContentType(zxingEnum: ZXingEnum): ContentType {
- return contentTypes[zxingEnum.value];
+ return contentTypes[zxingEnum.value]!;
}
diff --git a/src/bindings/eanAddOnSymbol.ts b/src/bindings/eanAddOnSymbol.ts
index 7856ebea..87382d2f 100644
--- a/src/bindings/eanAddOnSymbol.ts
+++ b/src/bindings/eanAddOnSymbol.ts
@@ -20,5 +20,5 @@ export function eanAddOnSymbolToZXingEnum(
export function zxingEnumToEanAddOnSymbol(
zxingEnum: ZXingEnum,
): EanAddOnSymbol {
- return eanAddOnSymbols[zxingEnum.value];
+ return eanAddOnSymbols[zxingEnum.value]!;
}
diff --git a/src/bindings/exposedReaderBindings.ts b/src/bindings/exposedReaderBindings.ts
index d3c03aaf..de3346ad 100644
--- a/src/bindings/exposedReaderBindings.ts
+++ b/src/bindings/exposedReaderBindings.ts
@@ -1,57 +1,61 @@
-import { type ReaderOptions, defaultReaderOptions as ro } from "./index.js";
+import {
+ type ReaderOptions,
+ type ResolvedReaderOptions,
+ defaultReaderOptions as ro,
+} from "./index.js";
-export const defaultReaderOptions: Required = {
+export const defaultReaderOptions: ReaderOptions = {
...ro,
formats: [...ro.formats],
-};
+} satisfies ResolvedReaderOptions;
export {
barcodeFormats,
+ binarizers,
+ characterSets,
+ contentTypes,
+ eanAddOnSymbols,
+ readOutputEccLevels,
+ textModes,
type BarcodeFormat,
+ type Binarizer,
+ type CharacterSet,
+ type ContentType,
+ type EanAddOnSymbol,
+ type Point,
+ type Position,
type ReadInputBarcodeFormat,
type ReadOutputBarcodeFormat,
- binarizers,
+ type ReadOutputEccLevel,
+ type ReadResult,
+ type ReaderOptions,
+ type TextMode,
type ZXingBinarizer,
- type Binarizer,
- characterSets,
type ZXingCharacterSet,
- type CharacterSet,
- contentTypes,
type ZXingContentType,
- type ContentType,
- type ZXingReaderOptions,
- type ReaderOptions,
- eanAddOnSymbols,
type ZXingEanAddOnSymbol,
- type EanAddOnSymbol,
- readOutputEccLevels,
- type ReadOutputEccLevel,
type ZXingEnum,
type ZXingPoint,
type ZXingPosition,
- type Point,
- type Position,
type ZXingReadResult,
- type ReadResult,
- textModes,
+ type ZXingReaderOptions,
type ZXingTextMode,
- type TextMode,
type ZXingVector,
} from "./index.js";
export {
/**
- * @deprecated renamed as `defaultReaderOptions`
+ * @deprecated Renamed as `ReaderOptions`
*/
- defaultReaderOptions as defaultDecodeHints,
-};
-export {
+ type ReaderOptions as DecodeHints,
/**
- * @deprecated renamed as `ZXingReaderOptions`
+ * @deprecated Renamed as `ZXingReaderOptions`
*/
type ZXingReaderOptions as ZXingDecodeHints,
+} from "./index.js";
+export {
/**
- * @deprecated renamed as `ReaderOptions`
+ * @deprecated Renamed as `defaultReaderOptions`
*/
- type ReaderOptions as DecodeHints,
-} from "./index.js";
+ defaultReaderOptions as defaultDecodeHints,
+};
diff --git a/src/bindings/exposedWriterBindings.ts b/src/bindings/exposedWriterBindings.ts
index 3daebdc8..8188afa9 100644
--- a/src/bindings/exposedWriterBindings.ts
+++ b/src/bindings/exposedWriterBindings.ts
@@ -1,36 +1,42 @@
-import { type WriterOptions, defaultWriterOptions as wo } from "./index.js";
+import {
+ type ResolvedWriterOptions,
+ type WriterOptions,
+ defaultWriterOptions as wo,
+} from "./index.js";
-export const defaultWriterOptions: Required = { ...wo };
+export const defaultWriterOptions: WriterOptions = {
+ ...wo,
+} satisfies ResolvedWriterOptions;
export {
barcodeFormats,
- type BarcodeFormat,
- type WriteInputBarcodeFormat,
characterSets,
- type ZXingCharacterSet,
- type CharacterSet,
writeInputEccLevels,
+ type BarcodeFormat,
+ type CharacterSet,
+ type WriteInputBarcodeFormat,
type WriteInputEccLevel,
- type ZXingWriterOptions,
+ type WriteResult,
type WriterOptions,
+ type ZXingCharacterSet,
type ZXingEnum,
type ZXingWriteResult,
- type WriteResult,
+ type ZXingWriterOptions,
} from "./index.js";
export {
/**
- * @deprecated renamed as `defaultWriterOptions`
+ * @deprecated Renamed as `WriterOptions`
*/
- defaultWriterOptions as defaultEncodeHints,
-};
-export {
+ type WriterOptions as EncodeHints,
/**
- * @deprecated renamed as `ZXingWriterOptions`
+ * @deprecated Renamed as `ZXingWriterOptions`
*/
type ZXingWriterOptions as ZXingEncodeHints,
+} from "./index.js";
+export {
/**
- * @deprecated renamed as `WriterOptions`
+ * @deprecated Renamed as `defaultWriterOptions`
*/
- type WriterOptions as EncodeHints,
-} from "./index.js";
+ defaultWriterOptions as defaultEncodeHints,
+};
diff --git a/src/bindings/index.ts b/src/bindings/index.ts
index 0081c441..64de3f45 100644
--- a/src/bindings/index.ts
+++ b/src/bindings/index.ts
@@ -2,13 +2,13 @@ export * from "./barcodeFormat.js";
export * from "./binarizer.js";
export * from "./characterSet.js";
export * from "./contentType.js";
-export * from "./readerOptions.js";
export * from "./eanAddOnSymbol.js";
export * from "./eccLevel.js";
-export * from "./writerOptions.js";
export * from "./enum.js";
export * from "./position.js";
export * from "./readResult.js";
+export * from "./readerOptions.js";
export * from "./textMode.js";
export * from "./vector.js";
export * from "./writeResult.js";
+export * from "./writerOptions.js";
diff --git a/src/bindings/position.ts b/src/bindings/position.ts
index b7637f65..0188399f 100644
--- a/src/bindings/position.ts
+++ b/src/bindings/position.ts
@@ -21,7 +21,6 @@ export interface ZXingPosition {
*
* @property x X coordinate.
* @property y Y coordinate.
- *
* @see {@link Position | `Position`}
*/
export interface Point extends ZXingPoint {}
diff --git a/src/bindings/readResult.ts b/src/bindings/readResult.ts
index 28bc3c92..947e567d 100644
--- a/src/bindings/readResult.ts
+++ b/src/bindings/readResult.ts
@@ -63,9 +63,9 @@ export interface ZXingReadResult {
/**
* Number of symbols in a structured append sequence.
*
- * If this is not part of a structured append sequence, the returned value is `-1`.
- * If it is a structured append symbol but the total number of symbols is unknown, the
- * returned value is `0` (see PDF417 if optional "Segment Count" not given).
+ * If this is not part of a structured append sequence, the returned value is `-1`. If it is a
+ * structured append symbol but the total number of symbols is unknown, the returned value is `0`
+ * (see PDF417 if optional "Segment Count" not given).
*/
sequenceSize: number;
/**
@@ -75,9 +75,9 @@ export interface ZXingReadResult {
/**
* ID to check if a set of symbols belongs to the same structured append sequence.
*
- * If the symbology does not support this feature, the returned value is empty (see MaxiCode).
- * For QR Code, this is the parity integer converted to a string.
- * For PDF417 and DataMatrix, this is the `"fileId"`.
+ * If the symbology does not support this feature, the returned value is empty (see MaxiCode). For
+ * QR Code, this is the parity integer converted to a string. For PDF417 and DataMatrix, this is
+ * the `"fileId"`.
*/
sequenceId: string;
/**
@@ -104,14 +104,13 @@ export interface ReadResult
"format" | "eccLevel" | "contentType" | "position"
> {
/**
- * Format of the barcode, should be one of {@link ReadOutputBarcodeFormat | `ReadOutputBarcodeFormat`}.
+ * Format of the barcode, should be one of
+ * {@link ReadOutputBarcodeFormat | `ReadOutputBarcodeFormat`}.
*
- * Possible values are:
- * `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`,
- * `"DataBar"`, `"DataBarExpanded"`, `"DataMatrix"`, `"DXFilmEdge"`,
- * `"EAN-13"`, `"EAN-8"`, `"ITF"`,
- * `"MaxiCode"`, `"MicroQRCode"`, `"None"`,
- * `"PDF417"`, `"QRCode"`, `"rMQRCode"`, `"UPC-A"`, `"UPC-E"`
+ * Possible values are: `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`, `"DataBar"`,
+ * `"DataBarExpanded"`, `"DataMatrix"`, `"DXFilmEdge"`, `"EAN-13"`, `"EAN-8"`, `"ITF"`,
+ * `"MaxiCode"`, `"MicroQRCode"`, `"None"`, `"PDF417"`, `"QRCode"`, `"rMQRCode"`, `"UPC-A"`,
+ * `"UPC-E"`
*/
format: ReadOutputBarcodeFormat;
/**
diff --git a/src/bindings/readerOptions.ts b/src/bindings/readerOptions.ts
index 042c25b6..d49cb8b9 100644
--- a/src/bindings/readerOptions.ts
+++ b/src/bindings/readerOptions.ts
@@ -44,26 +44,27 @@ export interface ZXingReaderOptions {
tryDownscale: boolean;
binarizer: ZXingEnum;
/**
- * Set to `true` if the input contains nothing but a single perfectly aligned barcode (usually generated images).
+ * Set to `true` if the input contains nothing but a single perfectly aligned barcode (usually
+ * generated images).
*
* @defaultValue `false`
*/
isPure: boolean;
/**
- * Image size ( min(width, height) ) threshold at which to start downscaled scanning
- * **WARNING**: this API is experimental and may change / disappear
+ * Image size ( min(width, height) ) threshold at which to start downscaled scanning **WARNING**:
+ * this API is experimental and may change / disappear
*
- * @experimental
* @defaultValue `500`
+ * @experimental
* @see {@link tryDownscale | `tryDownscale`} {@link downscaleFactor | `downscaleFactor`}
*/
downscaleThreshold: number;
/**
- * Scale factor to use during downscaling, meaningful values are `2`, `3` and `4`.
- * **WARNING**: this API is experimental and may change / disappear
+ * Scale factor to use during downscaling, meaningful values are `2`, `3` and `4`. **WARNING**:
+ * this API is experimental and may change / disappear
*
- * @experimental
* @defaultValue `3`
+ * @experimental
* @see {@link tryDownscale | `tryDownscale`} {@link downscaleThreshold | `downscaleThreshold`}
*/
downscaleFactor: number;
@@ -74,8 +75,8 @@ export interface ZXingReaderOptions {
*/
minLineCount: number;
/**
- * The maximum number of symbols / barcodes to detect / look for in the image.
- * The upper limit of this number is 255.
+ * The maximum number of symbols / barcodes to detect / look for in the image. The upper limit of
+ * this number is 255.
*
* @defaultValue `255`
*/
@@ -132,32 +133,27 @@ export interface ReaderOptions
* A set of {@link ReadInputBarcodeFormat | `ReadInputBarcodeFormat`}s that should be searched for.
* An empty list `[]` indicates all supported formats.
*
- * Supported values in this list are:
- * `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`,
- * `"DataBar"`, `"DataBarExpanded"`, `"DataMatrix"`, `"DXFilmEdge"`,
- * `"EAN-13"`, `"EAN-8"`, `"ITF"`, `"Linear-Codes"`, `"Matrix-Codes"`,
- * `"MaxiCode"`, `"MicroQRCode"`, `"PDF417"`, `"QRCode"`, `"rMQRCode"`, `"UPC-A"`, `"UPC-E"`
+ * Supported values in this list are: `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`,
+ * `"DataBar"`, `"DataBarExpanded"`, `"DataMatrix"`, `"DXFilmEdge"`, `"EAN-13"`, `"EAN-8"`,
+ * `"ITF"`, `"Linear-Codes"`, `"Matrix-Codes"`, `"MaxiCode"`, `"MicroQRCode"`, `"PDF417"`,
+ * `"QRCode"`, `"rMQRCode"`, `"UPC-A"`, `"UPC-E"`
*
* @defaultValue `[]`
*/
formats?: ReadInputBarcodeFormat[];
/**
- * Algorithm to use for the grayscale to binary transformation.
- * The difference is how to get to a threshold value T
- * which results in a bit value R = L <= T.
+ * Algorithm to use for the grayscale to binary transformation. The difference is how to get to a
+ * threshold value T which results in a bit value R = L <= T.
*
* - `"LocalAverage"`
*
* T = average of neighboring pixels for matrix and GlobalHistogram for linear
- *
* - `"GlobalHistogram"`
*
* T = valley between the 2 largest peaks in the histogram (per line in linear case)
- *
* - `"FixedThreshold"`
*
* T = 127
- *
* - `"BoolCast"`
*
* T = 0, fastest possible
@@ -166,16 +162,15 @@ export interface ReaderOptions
*/
binarizer?: Binarizer;
/**
- * Specify whether to ignore, read or require EAN-2 / 5 add-on symbols while scanning EAN / UPC codes.
+ * Specify whether to ignore, read or require EAN-2 / 5 add-on symbols while scanning EAN / UPC
+ * codes.
*
* - `"Ignore"`
*
* Ignore any Add-On symbol during read / scan
- *
* - `"Read"`
*
* Read EAN-2 / EAN-5 Add-On symbol if found
- *
* - `"Require"`
*
* Require EAN-2 / EAN-5 Add-On symbol to be present
@@ -184,41 +179,43 @@ export interface ReaderOptions
*/
eanAddOnSymbol?: EanAddOnSymbol;
/**
- * Specifies the `TextMode` that controls the result of {@link ReadResult.text | `ReadResult.text`}.
+ * Specifies the `TextMode` that controls the result of
+ * {@link ReadResult.text | `ReadResult.text`}.
*
* - `"Plain"`
*
- * {@link ReadResult.bytes | `ReadResult.bytes`} transcoded to unicode based on ECI info or guessed character set
- *
+ * {@link ReadResult.bytes | `ReadResult.bytes`} transcoded to unicode based on ECI info or guessed
+ * character set
* - `"ECI"`
*
- * Standard content following the ECI protocol with every character set ECI segment transcoded to unicode
- *
+ * Standard content following the ECI protocol with every character set ECI segment transcoded to
+ * unicode
* - `"HRI"`
*
* Human Readable Interpretation (dependent on the ContentType)
- *
* - `"Hex"`
*
* {@link ReadResult.bytes | `ReadResult.bytes`} transcoded to ASCII string of HEX values
- *
* - `"Escaped"`
*
- * Escape non-graphical characters in angle brackets (e.g. ASCII `29` will be transcoded to `""`)
+ * Escape non-graphical characters in angle brackets (e.g. ASCII `29` will be transcoded to
+ * `""`)
*
* @defaultValue `"Plain"`
*/
textMode?: TextMode;
/**
- * Character set to use (when applicable).
- * If this is set to `"Unknown"`, auto-detecting will be used.
+ * Character set to use (when applicable). If this is set to `"Unknown"`, auto-detecting will be
+ * used.
*
* @defaultValue `"Unknown"`
*/
characterSet?: CharacterSet;
}
-export const defaultReaderOptions: Required = {
+export type ResolvedReaderOptions = Required;
+
+export const defaultReaderOptions: ResolvedReaderOptions = {
formats: [],
tryHarder: true,
tryRotate: true,
@@ -240,12 +237,55 @@ export const defaultReaderOptions: Required = {
characterSet: "Unknown",
};
+export function resolveReaderOptions(
+ readerOptions?: ReaderOptions,
+): ResolvedReaderOptions {
+ return {
+ formats: readerOptions?.formats ?? defaultReaderOptions.formats,
+ tryHarder: readerOptions?.tryHarder ?? defaultReaderOptions.tryHarder,
+ tryRotate: readerOptions?.tryRotate ?? defaultReaderOptions.tryRotate,
+ tryInvert: readerOptions?.tryInvert ?? defaultReaderOptions.tryInvert,
+ tryDownscale:
+ readerOptions?.tryDownscale ?? defaultReaderOptions.tryDownscale,
+ binarizer: readerOptions?.binarizer ?? defaultReaderOptions.binarizer,
+ isPure: readerOptions?.isPure ?? defaultReaderOptions.isPure,
+ downscaleFactor:
+ readerOptions?.downscaleFactor ?? defaultReaderOptions.downscaleFactor,
+ downscaleThreshold:
+ readerOptions?.downscaleThreshold ??
+ defaultReaderOptions.downscaleThreshold,
+ minLineCount:
+ readerOptions?.minLineCount ?? defaultReaderOptions.minLineCount,
+ maxNumberOfSymbols:
+ readerOptions?.maxNumberOfSymbols ??
+ defaultReaderOptions.maxNumberOfSymbols,
+ tryCode39ExtendedMode:
+ readerOptions?.tryCode39ExtendedMode ??
+ defaultReaderOptions.tryCode39ExtendedMode,
+ validateCode39CheckSum:
+ readerOptions?.validateCode39CheckSum ??
+ defaultReaderOptions.validateCode39CheckSum,
+ validateITFCheckSum:
+ readerOptions?.validateITFCheckSum ??
+ defaultReaderOptions.validateITFCheckSum,
+ returnCodabarStartEnd:
+ readerOptions?.returnCodabarStartEnd ??
+ defaultReaderOptions.returnCodabarStartEnd,
+ returnErrors:
+ readerOptions?.returnErrors ?? defaultReaderOptions.returnErrors,
+ eanAddOnSymbol:
+ readerOptions?.eanAddOnSymbol ?? defaultReaderOptions.eanAddOnSymbol,
+ textMode: readerOptions?.textMode ?? defaultReaderOptions.textMode,
+ characterSet:
+ readerOptions?.characterSet ?? defaultReaderOptions.characterSet,
+ };
+}
+
export function readerOptionsToZXingReaderOptions(
zxingModule: ZXingModule,
- readerOptions: Required,
+ readerOptions: ResolvedReaderOptions,
): ZXingReaderOptions {
- return {
- ...readerOptions,
+ return Object.assign(readerOptions, {
formats: formatsToString(readerOptions.formats),
binarizer: binarizerToZXingEnum(zxingModule, readerOptions.binarizer),
eanAddOnSymbol: eanAddOnSymbolToZXingEnum(
@@ -257,5 +297,5 @@ export function readerOptionsToZXingReaderOptions(
zxingModule,
readerOptions.characterSet,
),
- };
+ });
}
diff --git a/src/bindings/textMode.ts b/src/bindings/textMode.ts
index 0fb2606b..edfcd3c5 100644
--- a/src/bindings/textMode.ts
+++ b/src/bindings/textMode.ts
@@ -18,5 +18,5 @@ export function textModeToZXingEnum(
}
export function zxingEnumToTextMode(zxingEnum: ZXingEnum): TextMode {
- return textModes[zxingEnum.value];
+ return textModes[zxingEnum.value]!;
}
diff --git a/src/bindings/writeResult.ts b/src/bindings/writeResult.ts
index 3386294e..652a90df 100644
--- a/src/bindings/writeResult.ts
+++ b/src/bindings/writeResult.ts
@@ -4,8 +4,7 @@
export interface ZXingWriteResult {
image: Uint8Array;
/**
- * Encoding error.
- * If there's no error, this will be an empty string `""`.
+ * Encoding error. If there's no error, this will be an empty string `""`.
*
* @see {@link WriteResult.error | `WriteResult.error`}
*/
@@ -16,8 +15,7 @@ export interface ZXingWriteResult {
export interface WriteResult
extends Omit {
/**
- * The encoded barcode as an image blob.
- * If some error happens, this will be `null`.
+ * The encoded barcode as an image blob. If some error happens, this will be `null`.
*
* @see {@link WriteResult.error | `WriteResult.error`}
*/
diff --git a/src/bindings/writerOptions.ts b/src/bindings/writerOptions.ts
index 9a382852..394a4876 100644
--- a/src/bindings/writerOptions.ts
+++ b/src/bindings/writerOptions.ts
@@ -41,32 +41,29 @@ export interface WriterOptions
/**
* The format of the barcode to write.
*
- * Supported values are:
- * `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`,
- * `"DataMatrix"`, `"EAN-13"`, `"EAN-8"`, `"ITF"`,
- * `"PDF417"`, `"QRCode"`, `"UPC-A"`, `"UPC-E"`
+ * Supported values are: `"Aztec"`, `"Codabar"`, `"Code128"`, `"Code39"`, `"Code93"`,
+ * `"DataMatrix"`, `"EAN-13"`, `"EAN-8"`, `"ITF"`, `"PDF417"`, `"QRCode"`, `"UPC-A"`, `"UPC-E"`
*
* @defaultValue `"QRCode"`
*/
format?: WriteInputBarcodeFormat;
/**
- * Character set to use for encoding the text.
- * Used for Aztec, PDF417, and QRCode only.
+ * Character set to use for encoding the text. Used for Aztec, PDF417, and QRCode only.
*
* @defaultValue `"UTF8"`
*/
characterSet?: CharacterSet;
/**
- * Error correction level of the symbol.
- * Used for Aztec, PDF417, and QRCode only.
- * `-1` means auto.
+ * Error correction level of the symbol. Used for Aztec, PDF417, and QRCode only. `-1` means auto.
*
* @defaultValue `-1`
*/
eccLevel?: WriteInputEccLevel;
}
-export const defaultWriterOptions: Required = {
+export type ResolvedWriterOptions = Required;
+
+export const defaultWriterOptions: ResolvedWriterOptions = {
width: 200,
height: 200,
format: "QRCode",
@@ -75,15 +72,28 @@ export const defaultWriterOptions: Required = {
margin: 10,
};
+export function resolveWriterOptions(
+ writerOptions?: WriterOptions,
+): ResolvedWriterOptions {
+ return {
+ width: writerOptions?.width ?? defaultWriterOptions.width,
+ height: writerOptions?.height ?? defaultWriterOptions.height,
+ format: writerOptions?.format ?? defaultWriterOptions.format,
+ characterSet:
+ writerOptions?.characterSet ?? defaultWriterOptions.characterSet,
+ eccLevel: writerOptions?.eccLevel ?? defaultWriterOptions.eccLevel,
+ margin: writerOptions?.margin ?? defaultWriterOptions.margin,
+ };
+}
+
export function writerOptionsToZXingWriterOptions(
zxingModule: ZXingModule,
- writerOptions: Required,
+ writerOptions: ResolvedWriterOptions,
): ZXingWriterOptions {
- return {
- ...writerOptions,
+ return Object.assign(writerOptions, {
characterSet: characterSetToZXingEnum(
zxingModule,
writerOptions.characterSet,
),
- };
+ });
}
diff --git a/src/core.ts b/src/core.ts
index 5e816101..953d52be 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -12,9 +12,9 @@ import {
type ZXingVector,
type ZXingWriteResult,
type ZXingWriterOptions,
- defaultReaderOptions,
- defaultWriterOptions,
readerOptionsToZXingReaderOptions,
+ resolveReaderOptions,
+ resolveWriterOptions,
writerOptionsToZXingWriterOptions,
zxingReadResultToReadResult,
zxingWriteResultToWriteResult,
@@ -93,7 +93,7 @@ export type ZXingModuleFactory =
export type ZXingModuleOverrides = Partial;
-const defaultModuleOverrides: ZXingModuleOverrides = import.meta.env.PROD
+export const defaultModuleOverrides: ZXingModuleOverrides = import.meta.env.PROD
? {
locateFile: (path, prefix) => {
const match = path.match(/_(.+?)\.wasm$/);
@@ -173,12 +173,9 @@ export async function readBarcodesFromImageFileWithFactory<
>(
zxingModuleFactory: ZXingModuleFactory,
imageFile: Blob,
- readerOptions: ReaderOptions = defaultReaderOptions,
+ readerOptions?: ReaderOptions,
) {
- const requiredReaderOptions: Required = {
- ...defaultReaderOptions,
- ...readerOptions,
- };
+ const resolvedReaderOptions = resolveReaderOptions(readerOptions);
const zxingModule = await getZXingModuleWithFactory(zxingModuleFactory);
const { size } = imageFile;
const buffer = new Uint8Array(await imageFile.arrayBuffer());
@@ -187,7 +184,7 @@ export async function readBarcodesFromImageFileWithFactory<
const zxingReadResultVector = zxingModule.readBarcodesFromImage(
bufferPtr,
size,
- readerOptionsToZXingReaderOptions(zxingModule, requiredReaderOptions),
+ readerOptionsToZXingReaderOptions(zxingModule, resolvedReaderOptions),
);
zxingModule._free(bufferPtr);
const readResults: ReadResult[] = [];
@@ -204,12 +201,9 @@ export async function readBarcodesFromImageDataWithFactory<
>(
zxingModuleFactory: ZXingModuleFactory,
imageData: ImageData,
- readerOptions: ReaderOptions = defaultReaderOptions,
+ readerOptions?: ReaderOptions,
) {
- const requiredReaderOptions: Required = {
- ...defaultReaderOptions,
- ...readerOptions,
- };
+ const resolvedReaderOptions = resolveReaderOptions(readerOptions);
const zxingModule = await getZXingModuleWithFactory(zxingModuleFactory);
const {
data: buffer,
@@ -223,7 +217,7 @@ export async function readBarcodesFromImageDataWithFactory<
bufferPtr,
width,
height,
- readerOptionsToZXingReaderOptions(zxingModule, requiredReaderOptions),
+ readerOptionsToZXingReaderOptions(zxingModule, resolvedReaderOptions),
);
zxingModule._free(bufferPtr);
const readResults: ReadResult[] = [];
@@ -240,16 +234,13 @@ export async function writeBarcodeToImageFileWithFactory<
>(
zxingModuleFactory: ZXingModuleFactory,
text: string,
- writerOptions: WriterOptions = defaultWriterOptions,
+ writerOptions?: WriterOptions,
) {
- const requiredWriterOptions: Required = {
- ...defaultWriterOptions,
- ...writerOptions,
- };
+ const resolvedWriterOptions = resolveWriterOptions(writerOptions);
const zxingModule = await getZXingModuleWithFactory(zxingModuleFactory);
const zxingWriteResult = zxingModule.writeBarcodeToImage(
text,
- writerOptionsToZXingWriterOptions(zxingModule, requiredWriterOptions),
+ writerOptionsToZXingWriterOptions(zxingModule, resolvedWriterOptions),
);
return zxingWriteResultToWriteResult(zxingWriteResult);
}
diff --git a/src/index.css.ts b/src/index.css.ts
new file mode 100644
index 00000000..837ed0ed
--- /dev/null
+++ b/src/index.css.ts
@@ -0,0 +1,22 @@
+import { style } from "@vanilla-extract/css";
+
+export const wrapperClass = style({
+ display: "grid",
+ alignItems: "center",
+ justifyContent: "center",
+});
+
+const canvasClass = style({
+ gridArea: "1 / 1",
+ maxWidth: "100%",
+ maxHeight: "100%",
+});
+
+export const frameClass = canvasClass;
+
+export const overlayClass = style([
+ canvasClass,
+ {
+ pointerEvents: "none",
+ },
+]);
diff --git a/src/react/components/StreamBarcodeDetector.tsx b/src/react/components/StreamBarcodeDetector.tsx
new file mode 100644
index 00000000..3f824a36
--- /dev/null
+++ b/src/react/components/StreamBarcodeDetector.tsx
@@ -0,0 +1,186 @@
+import { type ComponentPropsWithRef, type ComponentPropsWithoutRef, memo, useEffect, useMemo, useRef } from "react";
+import { frameClass, overlayClass, wrapperClass } from "../../index.css.js";
+import type { ReaderOptions } from "../../reader/index.js";
+import {
+ type UseUserMediaStreamOptions,
+ type UseVideoScannerOptions,
+ useUserMediaStream,
+ useVideoScanner,
+} from "../hooks/index.js";
+
+export interface StreamBarcodeDetectorProps
+ extends UseUserMediaStreamOptions,
+ UseVideoScannerOptions,
+ ReaderOptions,
+ ComponentPropsWithRef<"div"> {
+ overlayCanvasProps?: ComponentPropsWithoutRef<"canvas">;
+ frameCanvasProps?: ComponentPropsWithoutRef<"canvas">;
+}
+
+export const StreamBarcodeDetector = memo(
+ ({
+ /**
+ * stream options
+ */
+ streaming = true,
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+
+ /**
+ * scanner options
+ */
+ scanning = true,
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+
+ /**
+ * reader options
+ */
+ formats,
+ tryHarder,
+ tryRotate,
+ tryInvert,
+ tryDownscale,
+ binarizer,
+ isPure,
+ downscaleThreshold,
+ downscaleFactor,
+ minLineCount,
+ maxNumberOfSymbols,
+ tryCode39ExtendedMode,
+ validateCode39CheckSum,
+ validateITFCheckSum,
+ returnCodabarStartEnd,
+ returnErrors,
+ eanAddOnSymbol,
+ textMode,
+ characterSet,
+
+ /**
+ * inner element props
+ */
+ overlayCanvasProps,
+ frameCanvasProps,
+
+ /**
+ * element options
+ */
+ children,
+ ref,
+ ...wrapperProps
+ }: StreamBarcodeDetectorProps = {}) => {
+ const resolvedReaderOptions = useMemo(
+ () => ({
+ formats: formats ?? readerOptions?.formats,
+ tryHarder: tryHarder ?? readerOptions?.tryHarder,
+ tryRotate: tryRotate ?? readerOptions?.tryRotate,
+ tryInvert: tryInvert ?? readerOptions?.tryInvert,
+ tryDownscale: tryDownscale ?? readerOptions?.tryDownscale,
+ binarizer: binarizer ?? readerOptions?.binarizer,
+ isPure: isPure ?? readerOptions?.isPure,
+ downscaleFactor: downscaleFactor ?? readerOptions?.downscaleFactor,
+ downscaleThreshold: downscaleThreshold ?? readerOptions?.downscaleThreshold,
+ minLineCount: minLineCount ?? readerOptions?.minLineCount,
+ maxNumberOfSymbols: maxNumberOfSymbols ?? readerOptions?.maxNumberOfSymbols,
+ tryCode39ExtendedMode: tryCode39ExtendedMode ?? readerOptions?.tryCode39ExtendedMode,
+ validateCode39CheckSum: validateCode39CheckSum ?? readerOptions?.validateCode39CheckSum,
+ validateITFCheckSum: validateITFCheckSum ?? readerOptions?.validateITFCheckSum,
+ returnCodabarStartEnd: returnCodabarStartEnd ?? readerOptions?.returnCodabarStartEnd,
+ returnErrors: returnErrors ?? readerOptions?.returnErrors,
+ eanAddOnSymbol: eanAddOnSymbol ?? readerOptions?.eanAddOnSymbol,
+ textMode: textMode ?? readerOptions?.textMode,
+ characterSet: characterSet ?? readerOptions?.characterSet,
+ }),
+ [
+ readerOptions,
+ formats,
+ tryHarder,
+ tryRotate,
+ tryInvert,
+ tryDownscale,
+ binarizer,
+ isPure,
+ downscaleFactor,
+ downscaleThreshold,
+ minLineCount,
+ maxNumberOfSymbols,
+ tryCode39ExtendedMode,
+ validateCode39CheckSum,
+ validateITFCheckSum,
+ returnCodabarStartEnd,
+ returnErrors,
+ eanAddOnSymbol,
+ textMode,
+ characterSet,
+ ],
+ );
+
+ const streamVideoRefCallback = useUserMediaStream({
+ streaming,
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ });
+
+ const {
+ videoRefCallback: scannerVideoRefCallback,
+ overlayCanvasElementRef,
+ frameCanvasElementRef,
+ } = useVideoScanner({
+ /**
+ * scanner options
+ */
+ scanning,
+ wasmLocation,
+ readerOptions: resolvedReaderOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ });
+
+ const videoElementRef = useRef(document.createElement("video"));
+
+ useEffect(() => {
+ streamVideoRefCallback(videoElementRef.current);
+ scannerVideoRefCallback(videoElementRef.current);
+ }, [streamVideoRefCallback, scannerVideoRefCallback]);
+
+ return (
+
+
+
+ {children}
+
+ );
+ },
+);
+
+export default StreamBarcodeDetector;
diff --git a/src/react/components/index.ts b/src/react/components/index.ts
new file mode 100644
index 00000000..4ed7de7a
--- /dev/null
+++ b/src/react/components/index.ts
@@ -0,0 +1 @@
+export * from "./StreamBarcodeDetector.js";
diff --git a/src/react/hooks/index.ts b/src/react/hooks/index.ts
new file mode 100644
index 00000000..98c45ecd
--- /dev/null
+++ b/src/react/hooks/index.ts
@@ -0,0 +1,3 @@
+export * from "./useUserMediaStream.js";
+export * from "./useHeadlessVideoScanner.js";
+export * from "./useVideoScanner.js";
diff --git a/src/react/hooks/useHeadlessVideoScanner.ts b/src/react/hooks/useHeadlessVideoScanner.ts
new file mode 100644
index 00000000..80b20206
--- /dev/null
+++ b/src/react/hooks/useHeadlessVideoScanner.ts
@@ -0,0 +1,95 @@
+import { type RefCallback, useCallback, useEffect, useRef } from "react";
+import {
+ type VideoScanner,
+ type VideoScannerOptions,
+ createVideoScanner,
+} from "../../scanner/index.js";
+
+export interface UseHeadlessVideoScannerOptions extends VideoScannerOptions {
+ /**
+ * control the activation of scanning
+ */
+ scanning?: boolean;
+}
+
+export function useHeadlessVideoScanner({
+ /**
+ * scanner options
+ */
+ scanning = true,
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+}: UseHeadlessVideoScannerOptions) {
+ const videoScannerRef = useRef(null);
+
+ useEffect(() => {
+ if (scanning) {
+ videoScannerRef.current?.start();
+ } else {
+ videoScannerRef.current?.stop();
+ }
+ }, [scanning]);
+
+ const createVideoScannerRef = useRef((videoElement: HTMLVideoElement) =>
+ createVideoScanner(videoElement, {
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ }),
+ );
+
+ const videoRefCallback = useCallback>(
+ (videoElement) => {
+ if (videoElement !== null) {
+ videoScannerRef.current = createVideoScannerRef.current(videoElement);
+ } else {
+ videoScannerRef.current?.close();
+ videoScannerRef.current = null;
+ }
+ },
+ [],
+ );
+
+ useEffect(() => {
+ videoScannerRef.current?.setOptions({
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ });
+ }, [
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ ]);
+
+ return videoRefCallback;
+}
diff --git a/src/react/hooks/useUserMediaStream.ts b/src/react/hooks/useUserMediaStream.ts
new file mode 100644
index 00000000..2b57c4a4
--- /dev/null
+++ b/src/react/hooks/useUserMediaStream.ts
@@ -0,0 +1,97 @@
+import {
+ type RefCallback,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import {
+ type UserMediaStream,
+ type UserMediaStreamOptions,
+ attachMediaStream,
+ createUserMediaStream,
+} from "../../stream/index.js";
+
+export interface UseUserMediaStreamOptions extends UserMediaStreamOptions {
+ /**
+ * control the activation of streaming
+ */
+ streaming?: boolean;
+}
+
+export function useUserMediaStream({
+ /**
+ * stream options
+ */
+ streaming = true,
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+}: UseUserMediaStreamOptions) {
+ const userMediaStreamRef = useRef(
+ createUserMediaStream({
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ }),
+ );
+
+ const [stream, setStream] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ if (streaming) {
+ const stream = await userMediaStreamRef.current.start();
+ setStream(stream);
+ userMediaStreamRef.current.inspect();
+ } else {
+ await userMediaStreamRef.current.stop();
+ setStream(null);
+ }
+ })();
+ }, [streaming]);
+
+ const videoRefCallback = useCallback>(
+ (videoElement) => {
+ if (stream !== null && videoElement !== null) {
+ attachMediaStream(videoElement, stream);
+ videoElement.play();
+ }
+ },
+ [stream],
+ );
+
+ useEffect(() => {
+ userMediaStreamRef.current.setOptions({
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ });
+ }, [
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ ]);
+
+ return videoRefCallback;
+}
diff --git a/src/react/hooks/useVideoScanner.ts b/src/react/hooks/useVideoScanner.ts
new file mode 100644
index 00000000..5319c116
--- /dev/null
+++ b/src/react/hooks/useVideoScanner.ts
@@ -0,0 +1,170 @@
+import { type RefCallback, useCallback, useRef } from "react";
+import type { ReadResult } from "../../reader/index.js";
+import {
+ type UseHeadlessVideoScannerOptions,
+ useHeadlessVideoScanner,
+} from "./useHeadlessVideoScanner.js";
+
+export interface UseVideoScannerOptions
+ extends UseHeadlessVideoScannerOptions {}
+
+export function useVideoScanner({
+ onScanUpdate,
+ onRepaint,
+ ...restUseVideoScannerOptions
+}: UseVideoScannerOptions) {
+ const videoElementRef = useRef(null);
+
+ const scannerVideoRefCallback = useCallback>(
+ (videoElement) => {
+ videoElementRef.current = videoElement;
+ },
+ [],
+ );
+
+ const overlayCanvasElementRef = useRef(null);
+
+ const handleOverlayUpdate = useCallback(
+ (() => {
+ let context: CanvasRenderingContext2D | undefined = undefined;
+ return (readResults: ReadResult[]) => {
+ // check canvas ready state
+ if (
+ overlayCanvasElementRef.current === null ||
+ videoElementRef.current === null
+ ) {
+ return;
+ }
+ // update canvas dimension
+ const { videoWidth, videoHeight } = videoElementRef.current;
+ if (context === undefined) {
+ context = overlayCanvasElementRef.current.getContext("2d")!;
+ }
+ if (context.canvas.width !== videoWidth) {
+ context.canvas.width = videoWidth;
+ }
+ if (context.canvas.height !== videoHeight) {
+ context.canvas.height = videoHeight;
+ }
+ // draw overlay
+ context.clearRect(0, 0, videoWidth, videoHeight);
+ context.beginPath();
+
+ context.moveTo(0, 0);
+ context.lineTo(videoWidth, 0);
+ context.lineTo(videoWidth, videoHeight);
+ context.lineTo(0, videoHeight);
+ context.closePath();
+
+ for (const readResult of readResults) {
+ if (readResult.isValid) {
+ context.moveTo(
+ readResult.position.topLeft.x,
+ readResult.position.topLeft.y,
+ );
+ context.lineTo(
+ readResult.position.bottomLeft.x,
+ readResult.position.bottomLeft.y,
+ );
+ context.lineTo(
+ readResult.position.bottomRight.x,
+ readResult.position.bottomRight.y,
+ );
+ context.lineTo(
+ readResult.position.topRight.x,
+ readResult.position.topRight.y,
+ );
+ context.closePath();
+ }
+ }
+
+ context.fillStyle = "rgba(0, 0, 0, 0.8)";
+ context.fill();
+ // context.clearRect(0, 0, videoWidth, videoHeight);
+ // for (const readResult of readResults) {
+ // if (readResult.isValid) {
+ // context.strokeStyle = "#f00";
+ // context.lineWidth = 1;
+ // context.beginPath();
+ // context.moveTo(
+ // readResult.position.topLeft.x,
+ // readResult.position.topLeft.y,
+ // );
+ // context.lineTo(
+ // readResult.position.topRight.x,
+ // readResult.position.topRight.y,
+ // );
+ // context.lineTo(
+ // readResult.position.bottomRight.x,
+ // readResult.position.bottomRight.y,
+ // );
+ // context.lineTo(
+ // readResult.position.bottomLeft.x,
+ // readResult.position.bottomLeft.y,
+ // );
+ // context.closePath();
+ // context.stroke();
+ // }
+ // }
+ };
+ })(),
+ [],
+ );
+
+ const handleScanUpdate = useCallback(
+ (readResults: ReadResult[]) => {
+ handleOverlayUpdate(readResults);
+ onScanUpdate?.(readResults);
+ },
+ [handleOverlayUpdate, onScanUpdate],
+ );
+
+ const frameCanvasElementRef = useRef(null);
+
+ const handleFrameUpdate = useCallback(
+ (() => {
+ let context: CanvasRenderingContext2D | undefined = undefined;
+ return () => {
+ if (
+ frameCanvasElementRef.current === null ||
+ videoElementRef.current === null
+ ) {
+ return;
+ }
+ const { videoWidth, videoHeight } = videoElementRef.current;
+ if (context === undefined) {
+ context = frameCanvasElementRef.current.getContext("2d")!;
+ }
+ if (context.canvas.width !== videoWidth) {
+ context.canvas.width = videoWidth;
+ }
+ if (context.canvas.height !== videoHeight) {
+ context.canvas.height = videoHeight;
+ }
+ context.drawImage(videoElementRef.current, 0, 0);
+ };
+ })(),
+ [],
+ );
+
+ const handleRepaint = useCallback(() => {
+ handleFrameUpdate();
+ onRepaint?.();
+ }, [handleFrameUpdate, onRepaint]);
+
+ const headlessScannerVideoRefCallback = useHeadlessVideoScanner({
+ onScanUpdate: handleScanUpdate,
+ onRepaint: handleRepaint,
+ ...restUseVideoScannerOptions,
+ });
+
+ const videoRefCallback = useCallback>(
+ (videoElement) => {
+ scannerVideoRefCallback(videoElement);
+ headlessScannerVideoRefCallback(videoElement);
+ },
+ [scannerVideoRefCallback, headlessScannerVideoRefCallback],
+ );
+
+ return { videoRefCallback, overlayCanvasElementRef, frameCanvasElementRef };
+}
diff --git a/src/react/index.ts b/src/react/index.ts
new file mode 100644
index 00000000..c67e1a1a
--- /dev/null
+++ b/src/react/index.ts
@@ -0,0 +1,2 @@
+export * from "./components/index.js";
+export * from "./hooks/index.js";
diff --git a/src/reader/index.ts b/src/reader/index.ts
index 1ffa08e6..2925907a 100644
--- a/src/reader/index.ts
+++ b/src/reader/index.ts
@@ -50,6 +50,6 @@ export async function readBarcodesFromImageData(
export * from "../bindings/exposedReaderBindings.js";
export {
purgeZXingModule,
- type ZXingReaderModule,
type ZXingModuleOverrides,
+ type ZXingReaderModule,
} from "../core.js";
diff --git a/src/scanner/index.ts b/src/scanner/index.ts
new file mode 100644
index 00000000..e7327e1b
--- /dev/null
+++ b/src/scanner/index.ts
@@ -0,0 +1 @@
+export * from "./videoScanner.js";
diff --git a/src/scanner/videoScanner.ts b/src/scanner/videoScanner.ts
new file mode 100644
index 00000000..bfa170af
--- /dev/null
+++ b/src/scanner/videoScanner.ts
@@ -0,0 +1,510 @@
+import { createHash } from "sha1-uint8array";
+import type { SetRequired } from "type-fest";
+import { subscribeWithSelector } from "zustand/middleware";
+import { createStore } from "zustand/vanilla";
+import { defaultModuleOverrides } from "../core.js";
+import {
+ type ReadResult,
+ type ReaderOptions,
+ barcodeFormats,
+ contentTypes,
+ readBarcodesFromImageData,
+ setZXingModuleOverrides,
+} from "../reader/index.js";
+
+/**
+ * The default minimum interval in milliseconds for scanning operations.
+ */
+const SCAN_THROTTLE = 40;
+
+/**
+ * The default minimum interval in milliseconds to confirm a negative result.
+ */
+const NEGATIVE_DEBOUNCE = 0;
+
+/**
+ * Symbols used as unique keys for internal state properties to avoid overriden.
+ */
+const scanSymbol = Symbol("scan");
+const closeSymbol = Symbol("close");
+
+/**
+ * Options for configuring the VideoScanner.
+ */
+export interface VideoScannerOptions {
+ /**
+ * Location of the ZXing WebAssembly (WASM) file used for barcode decoding.
+ */
+ wasmLocation?: string;
+ /**
+ * Configuration options for the ZXing barcode reader.
+ */
+ readerOptions?: ReaderOptions;
+ /**
+ * Minimum interval in milliseconds between scan operations to throttle the scan frequency.
+ */
+ scanThrottle?: number;
+ /**
+ * Minimum duration in milliseconds before a negative result is confirmed.
+ */
+ negativeDebounce?: number;
+ /**
+ * Callback function that is triggered when a new barcode is detected.
+ *
+ * @param readResults - Array of detected barcodes.
+ */
+ onScanDetect?: (readResults: ReadResult[]) => unknown;
+ /**
+ * Callback function that is triggered on each scan update, regardless of new detections.
+ *
+ * @param readResults - Array of detected barcodes.
+ */
+ onScanUpdate?: (readResults: ReadResult[]) => unknown;
+ /**
+ * Callback function that is triggered when the scanning process starts.
+ */
+ onScanStart?: () => unknown;
+ /**
+ * Callback function that is triggered when the scanning process stops.
+ */
+ onScanStop?: () => unknown;
+ /**
+ * Callback function that is triggered when the scanning process is closed.
+ */
+ onScanClose?: () => unknown;
+ /**
+ * Callback function that is triggered when the browser repaints.
+ */
+ onRepaint?: () => unknown;
+}
+
+/**
+ * Internal state and options for VideoScanner.
+ */
+interface VideoScannerState extends ResolvedVideoScannerOptions {
+ /**
+ * Indicates whether the scanner is currently scanning.
+ */
+ [scanSymbol]: boolean;
+ /**
+ * Indicates whether the scanner has been closed.
+ */
+ [closeSymbol]: boolean;
+}
+
+/**
+ * Resolved options for the VideoScanner with default values provided.
+ */
+type ResolvedVideoScannerOptions = SetRequired<
+ VideoScannerOptions,
+ "scanThrottle" | "negativeDebounce"
+>;
+
+/**
+ * Resolves and merges the provided VideoScannerOptions with the default values for unspecified options.
+ *
+ * @param videoScannerOptions - The user-provided configuration options for the VideoScanner.
+ * @returns The resolved configuration options with default values for unspecified options.
+ */
+function resolveVideoScannerOptions(
+ videoScannerOptions: VideoScannerOptions,
+): ResolvedVideoScannerOptions {
+ return {
+ wasmLocation:
+ "wasmLocation" in videoScannerOptions
+ ? videoScannerOptions.wasmLocation
+ : undefined,
+ readerOptions:
+ "readerOptions" in videoScannerOptions
+ ? videoScannerOptions.readerOptions
+ : undefined,
+ scanThrottle: videoScannerOptions.scanThrottle ?? SCAN_THROTTLE,
+ negativeDebounce: videoScannerOptions.negativeDebounce ?? NEGATIVE_DEBOUNCE,
+ onScanDetect:
+ "onScanDetect" in videoScannerOptions
+ ? videoScannerOptions.onScanDetect
+ : undefined,
+ onScanUpdate:
+ "onScanUpdate" in videoScannerOptions
+ ? videoScannerOptions.onScanUpdate
+ : undefined,
+ onScanStart:
+ "onScanStart" in videoScannerOptions
+ ? videoScannerOptions.onScanStart
+ : undefined,
+ onScanStop:
+ "onScanStop" in videoScannerOptions
+ ? videoScannerOptions.onScanStop
+ : undefined,
+ onScanClose:
+ "onScanClose" in videoScannerOptions
+ ? videoScannerOptions.onScanClose
+ : undefined,
+ onRepaint:
+ "onRepaint" in videoScannerOptions
+ ? videoScannerOptions.onRepaint
+ : undefined,
+ };
+}
+
+/**
+ * Represents a video scanner that can start, stop, and close the scanning process, and update its configuration.
+ */
+export interface VideoScanner {
+ /**
+ * Starts the scanning process.
+ */
+ start: () => void;
+ /**
+ * Stops the scanning process.
+ */
+ stop: () => void;
+ /**
+ * Closes the scanner and performs cleanup operations.
+ */
+ close: () => void;
+ /**
+ * Update the configuration options of the VideoScanner.
+ *
+ * @param videoScannerOptions - New configuration options.
+ */
+ setOptions: (videoScannerOptions: VideoScannerOptions) => void;
+}
+
+/**
+ * Creates and returns a VideoScanner object.
+ *
+ * Initializes a VideoScanner with the specified HTMLVideoElement and configuration options, providing control methods
+ * for starting, stopping, and closing the video scanner, along with updating its options.
+ *
+ * @param videoElement - The HTMLVideoElement used for scanning.
+ * @param videoScannerOptions - Configuration options for the VideoScanner.
+ * @returns A VideoScanner object providing control methods for video scanning.
+ */
+export function createVideoScanner(
+ videoElement: HTMLVideoElement,
+ videoScannerOptions: VideoScannerOptions,
+): VideoScanner {
+ const {
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ } = resolveVideoScannerOptions(videoScannerOptions);
+
+ // request animation frame id
+ let requestAnimationFrameId: number;
+
+ // create a state store
+ const videoScannerStore = createStore()(
+ subscribeWithSelector(() => ({
+ [scanSymbol]: false,
+ [closeSymbol]: false,
+
+ wasmLocation,
+ readerOptions,
+ scanThrottle,
+ negativeDebounce,
+
+ onScanDetect,
+ onScanUpdate,
+ onScanStart,
+ onScanStop,
+ onScanClose,
+ onRepaint,
+ })),
+ );
+
+ const start = () => {
+ videoScannerStore.setState({
+ [scanSymbol]: true,
+ });
+ };
+
+ const stop = () => {
+ videoScannerStore.setState({
+ [scanSymbol]: false,
+ });
+ };
+
+ const close = () => {
+ videoScannerStore.setState({
+ [closeSymbol]: true,
+ });
+ };
+
+ // subscribe to scan start and stop actions
+ // so we can call the event handlers
+ const unsubScan = videoScannerStore.subscribe(
+ (options) => options[scanSymbol],
+ (scan) => {
+ if (scan) {
+ videoScannerStore.getState().onScanStart?.();
+ } else {
+ videoScannerStore.getState().onScanStop?.();
+ }
+ },
+ );
+
+ // subscribe to the close action
+ // so we can do some cleanups
+ const unsubClose = videoScannerStore.subscribe(
+ (options) => options[closeSymbol],
+ (close) => {
+ if (close) {
+ globalThis.cancelAnimationFrame(requestAnimationFrameId);
+ stop();
+ unsubScan();
+ unsubClose();
+ unsubWasmLocation();
+ videoScannerStore.getState().onScanClose?.();
+ }
+ },
+ {
+ fireImmediately: true,
+ },
+ );
+
+ // subscribe to wasm location change
+ const unsubWasmLocation = videoScannerStore.subscribe(
+ (options) => options.wasmLocation,
+ (wasmLocation) => {
+ if (wasmLocation === undefined) {
+ setZXingModuleOverrides(defaultModuleOverrides);
+ return;
+ }
+ setZXingModuleOverrides({
+ locateFile: (path, prefix) => {
+ if (path.endsWith(".wasm")) {
+ return wasmLocation;
+ }
+ return prefix + path;
+ },
+ });
+ },
+ {
+ fireImmediately: true,
+ },
+ );
+
+ // define the frame request callback function
+ const frameRequestCallback = (() => {
+ // keep the context values in the closure
+ let detecting = false;
+ let prevTimestamp = 0;
+
+ type SignatureMap = Map<
+ string,
+ { timestamp: number; readResult: ReadResult }
+ >;
+ let prevSignatureMap: SignatureMap = new Map();
+
+ // return the actual callback function
+ return async (currTimestamp: DOMHighResTimeStamp) => {
+ // get options snapshot
+ const {
+ [scanSymbol]: scan,
+ [closeSymbol]: close,
+ readerOptions,
+ scanThrottle,
+ onScanDetect,
+ onScanUpdate,
+ onRepaint,
+ } = videoScannerStore.getState();
+
+ // return if closed
+ if (close) {
+ return;
+ }
+
+ // call repaint event handler
+ onRepaint?.();
+
+ // skip if the following conditions are met
+ if (!scan || detecting || currTimestamp - prevTimestamp < scanThrottle) {
+ if (!scan) {
+ // clear previous signature map
+ prevSignatureMap.clear();
+ // clear previouse timestamp
+ prevTimestamp = 0;
+ }
+ // trigger next cycle
+ requestAnimationFrameId =
+ globalThis.requestAnimationFrame(frameRequestCallback);
+ return;
+ }
+
+ // set detecting status
+ detecting = true;
+
+ // detect
+ const imageData = getImageDataFromVideoElement(videoElement);
+ let readResults: ReadResult[] = [];
+ if (imageData !== null) {
+ readResults = await readBarcodesFromImageData(imageData, readerOptions);
+ }
+
+ // populate the signature map and set the flag
+ let newSymbolDetected = false;
+ const currSignatureMap: SignatureMap = new Map();
+ for (const readResult of readResults) {
+ const signature = getSignature(readResult);
+ if (!newSymbolDetected && !prevSignatureMap.has(signature)) {
+ newSymbolDetected = true;
+ }
+ currSignatureMap.set(signature, {
+ timestamp: currTimestamp,
+ readResult,
+ });
+ }
+ for (const [prevSignature, prevResult] of prevSignatureMap) {
+ if (
+ !currSignatureMap.has(prevSignature) &&
+ currTimestamp - prevResult.timestamp < negativeDebounce
+ ) {
+ currSignatureMap.set(prevSignature, prevResult);
+ readResults.push(prevResult.readResult);
+ }
+ }
+ prevSignatureMap = currSignatureMap;
+
+ // call onScanDetect event handler
+ if (newSymbolDetected) {
+ onScanDetect?.(readResults);
+ }
+
+ // call onScanUpdate event handler
+ onScanUpdate?.(readResults);
+
+ // update prevTimestamp
+ prevTimestamp = currTimestamp;
+
+ // clear detecting status
+ detecting = false;
+
+ // trigger next cycle
+ requestAnimationFrameId =
+ globalThis.requestAnimationFrame(frameRequestCallback);
+ };
+ })();
+
+ requestAnimationFrameId =
+ globalThis.requestAnimationFrame(frameRequestCallback);
+
+ return {
+ start,
+ stop,
+ close,
+ setOptions: (videoScannerOptions) =>
+ videoScannerStore.setState(
+ resolveVideoScannerOptions(videoScannerOptions),
+ ),
+ };
+}
+
+/**
+ * Retrieves image data from the provided HTMLVideoElement for processing.
+ *
+ * Ensures the video element is in a ready state and has non-zero dimensions before extracting the image data.
+ *
+ * @param videoElement - The HTMLVideoElement to extract image data from.
+ * @returns ImageData if successful, or null in case of failure or invalid state.
+ */
+export const getImageDataFromVideoElement = (() => {
+ let context:
+ | CanvasRenderingContext2D
+ | OffscreenCanvasRenderingContext2D
+ | undefined = undefined;
+ return (videoElement: HTMLVideoElement) => {
+ if (videoElement.readyState === 0 || videoElement.readyState === 1) {
+ return null;
+ }
+ const width = videoElement.videoWidth;
+ const height = videoElement.videoHeight;
+ if (width === 0 || height === 0) {
+ return null;
+ }
+ if (context === undefined) {
+ const canvas = createCanvas(width, height);
+ context = canvas.getContext("2d", { willReadFrequently: true }) as
+ | CanvasRenderingContext2D
+ | OffscreenCanvasRenderingContext2D;
+ } else {
+ if (context.canvas.width !== width) {
+ context.canvas.width = width;
+ }
+ if (context.canvas.height !== height) {
+ context.canvas.height = height;
+ }
+ }
+ context.drawImage(videoElement, 0, 0);
+ try {
+ const imageData = context.getImageData(0, 0, width, height);
+ return imageData;
+ } catch (e) {
+ return null;
+ }
+ };
+})();
+
+/**
+ * Dynamically creates a canvas element suitable for the environment.
+ *
+ * Prefers OffscreenCanvas but falls back to HTMLCanvasElement where OffscreenCanvas is not supported.
+ *
+ * @param width - The width of the canvas.
+ * @param height - The height of the canvas.
+ * @returns A canvas element, either OffscreenCanvas or HTMLCanvasElement based on environment support.
+ */
+function createCanvas(
+ width: number,
+ height: number,
+): OffscreenCanvas | HTMLCanvasElement {
+ try {
+ const canvas = new OffscreenCanvas(width, height);
+ if (
+ canvas.getContext("2d", { willReadFrequently: true }) instanceof
+ OffscreenCanvasRenderingContext2D
+ ) {
+ return canvas;
+ }
+ throw undefined;
+ } catch {
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ }
+}
+
+/**
+ * Generates a unique signature for a given ReadResult.
+ *
+ * Creates a signature string for a ReadResult object to uniquely identify a read barcode.
+ *
+ * @param readResult - The ReadResult object for which to generate the signature.
+ * @returns A unique signature string for the given ReadResult.
+ */
+function getSignature(readResult: ReadResult) {
+ return createHash()
+ .update(
+ new Uint8Array(
+ (+readResult.isValid << 0) +
+ (+readResult.isMirrored << 1) +
+ (+readResult.isInverted << 2) +
+ (+readResult.hasECI << 3) +
+ (+readResult.readerInit << 4),
+ ),
+ )
+ .update(new Uint8Array(barcodeFormats.indexOf(readResult.format) + 1))
+ .update(new Uint8Array(contentTypes.indexOf(readResult.contentType) + 1))
+ .update(readResult.symbologyIdentifier + readResult.version)
+ .update(readResult.bytes)
+ .digest("hex");
+}
diff --git a/src/stream/compatibility.d.ts b/src/stream/compatibility.d.ts
new file mode 100644
index 00000000..2e6d5684
--- /dev/null
+++ b/src/stream/compatibility.d.ts
@@ -0,0 +1,11 @@
+declare type CreateObjectURLCompat = (
+ obj: Blob | MediaSource | MediaStream,
+) => string;
+
+declare interface HTMLVideoElement {
+ mozSrcObject?: HTMLVideoElement["srcObject"];
+}
+
+declare interface MediaStreamTrack {
+ getCapabilities?: MediaStreamTrack["getCapabilities"];
+}
diff --git a/src/stream/index.ts b/src/stream/index.ts
new file mode 100644
index 00000000..0729d42f
--- /dev/null
+++ b/src/stream/index.ts
@@ -0,0 +1 @@
+export * from "./userMediaStream.js";
diff --git a/src/stream/media-track-shims.d.ts b/src/stream/media-track-shims.d.ts
new file mode 100644
index 00000000..5c0d38b1
--- /dev/null
+++ b/src/stream/media-track-shims.d.ts
@@ -0,0 +1,61 @@
+// TODO: complete constraints and capabilities
+
+declare interface MediaTrackSupportedConstraints {
+ brightness?: boolean;
+ browserWindow?: boolean;
+ colorTemperature?: boolean;
+ contrast?: boolean;
+ exposureCompensation?: boolean;
+ exposureMode?: boolean;
+ exposureTime?: boolean;
+ focusDistance?: boolean;
+ focusMode?: boolean;
+ iso?: boolean;
+ latency?: boolean;
+ mediaSource?: boolean;
+ pan?: boolean;
+ pointsOfInterest?: boolean;
+ resizeMode?: boolean;
+ saturation?: boolean;
+ scrollWithPage?: boolean;
+ sharpness?: boolean;
+ suppressLocalAudioPlayback?: boolean;
+ tilt?: boolean;
+ torch?: boolean;
+ viewPortHeight?: boolean;
+ viewPortOffsetX?: boolean;
+ viewPortOffsetY?: boolean;
+ viewportWidth?: boolean;
+ whiteBalanceMode?: boolean;
+ zoom?: boolean;
+}
+
+interface NumberRangeWithStep {
+ min: number;
+ max: number;
+ step: number;
+}
+
+declare interface MediaTrackCapabilities {
+ brightness?: NumberRangeWithStep;
+ colorTemperature?: NumberRangeWithStep;
+ contrast?: NumberRangeWithStep;
+ exposureMode?: string[];
+ exposureTime?: NumberRangeWithStep;
+ resizeMode?: string[];
+ saturation?: NumberRangeWithStep;
+ sharpness?: NumberRangeWithStep;
+ whiteBalanceMode?: string[];
+}
+
+declare interface MediaTrackConstraintSet {
+ brightness?: ConstrainULong;
+ colorTemperature?: ConstrainULong;
+ contrast?: ConstrainULong;
+ exposureMode?: ConstrainDOMString;
+ exposureTime?: ConstrainDouble;
+ resizeMode?: ConstrainDOMString;
+ saturation?: ConstrainULong;
+ sharpness?: ConstrainULong;
+ whiteBalanceMode?: ConstrainDOMString;
+}
diff --git a/src/stream/shimGetUserMedia.ts b/src/stream/shimGetUserMedia.ts
new file mode 100644
index 00000000..2212e910
--- /dev/null
+++ b/src/stream/shimGetUserMedia.ts
@@ -0,0 +1,50 @@
+import { detectBrowser } from "webrtc-adapter/dist/utils";
+
+/**
+ * Provide a shim for getUserMedia API across different browsers.
+ *
+ * This function ensures compatibility of the getUserMedia API with various browsers by applying
+ * necessary shims provided by the 'webrtc-adapter' package. It detects the browser and applies the
+ * appropriate shim. The function is designed to be called once to set up the shims and will not
+ * re-apply the shims on subsequent calls.
+ *
+ * @returns A promise that resolves once the appropriate shim (if any) has been applied. It resolves
+ * to `void` as its purpose is to configure the environment rather than return a value.
+ */
+export const shimGetUserMedia = (() => {
+ let called = false;
+ return async () => {
+ // Ensure the shim is applied only once
+ if (called) {
+ return;
+ }
+
+ // Detect the current browser
+ const browserDetails = detectBrowser(window);
+
+ // Apply browser-specific shim for getUserMedia
+ switch (browserDetails.browser) {
+ case "chrome":
+ (
+ await import("webrtc-adapter/dist/chrome/getusermedia")
+ ).shimGetUserMedia(window, browserDetails);
+ break;
+ case "firefox":
+ (
+ await import("webrtc-adapter/dist/firefox/getusermedia")
+ ).shimGetUserMedia(window, browserDetails);
+ break;
+ case "safari":
+ (
+ await import("webrtc-adapter/dist/safari/safari_shim")
+ ).shimGetUserMedia(window);
+ break;
+ default:
+ // Handle other browsers or runtimes in a non-disruptive way
+ break;
+ }
+
+ // Mark the shim as applied
+ called = true;
+ };
+})();
diff --git a/src/stream/userMediaStream.ts b/src/stream/userMediaStream.ts
new file mode 100644
index 00000000..637a7464
--- /dev/null
+++ b/src/stream/userMediaStream.ts
@@ -0,0 +1,834 @@
+import compare from "just-compare";
+import type { SetRequired } from "type-fest";
+import { subscribeWithSelector } from "zustand/middleware";
+import { shallow } from "zustand/shallow";
+import { createStore } from "zustand/vanilla";
+import { shimGetUserMedia } from "./shimGetUserMedia.js";
+
+/**
+ * The default maximum time in milliseconds to wait for getting the capabilities of a media track.
+ */
+const GET_CAPABILITIES_TIMEOUT = 500;
+
+/**
+ * Media stream constraints for initialization.
+ *
+ * This can either be a standard `MediaStreamConstraints` object or a function that returns
+ * `MediaStreamConstraints`. The function is provided with an argument,
+ * `MediaTrackSupportedConstraints`, which represents constraints supported by the **user agent**,
+ * and should return either `MediaStreamConstraints` or a promise that resolves to it.
+ *
+ * Note that `MediaTrackSupportedConstraints` doesn't reflect the constraints supported by the
+ * device. It only provides information about which constraint can be understood by the user agent.
+ *
+ * - [`MediaStreamConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#constraints)
+ * - [`MediaTrackSupportedConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSupportedConstraints)
+ */
+export type InitConstraints =
+ | MediaStreamConstraints
+ | ((
+ supportedConstraints: MediaTrackSupportedConstraints,
+ ) => MediaStreamConstraints | Promise);
+
+/**
+ * Media track constraints specific to video tracks.
+ *
+ * Can be a `MediaTrackConstraints` object or a function that returns `MediaTrackConstraints`. The
+ * function is provided with an argument, `MediaTrackCapabilities`, which reprensents the media
+ * track capabilities, and should return either `MediaTrackConstraints` or a promise that resolves
+ * to it.
+ *
+ * Unlike `MediaTrackSupportedConstraints`, `MediaTrackCapabilities` provides the accurate
+ * information of the track capabilities supported by your device.
+ *
+ * - [`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
+ * - [`MediaTrackCapabilities`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#return_value)
+ */
+export type VideoConstraints =
+ | MediaTrackConstraints
+ | undefined
+ | ((
+ capabilities: MediaTrackCapabilities,
+ ) =>
+ | MediaTrackConstraints
+ | undefined
+ | Promise);
+
+/**
+ * Media track constraints specific to audio tracks.
+ *
+ * Can be a `MediaTrackConstraints` object or a function that returns `MediaTrackConstraints`. The
+ * function is provided with an argument, `MediaTrackCapabilities`, which reprensents the media
+ * track capabilities, and should return either `MediaTrackConstraints` or a promise that resolves
+ * to it.
+ *
+ * Unlike `MediaTrackSupportedConstraints`, `MediaTrackCapabilities` provides the accurate
+ * information of the track capabilities supported by your device.
+ *
+ * - [`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
+ * - [`MediaTrackCapabilities`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#return_value)
+ */
+export type AudioConstraints =
+ | MediaTrackConstraints
+ | undefined
+ | ((
+ capabilities: MediaTrackCapabilities,
+ ) =>
+ | MediaTrackConstraints
+ | undefined
+ | Promise);
+
+/**
+ * Details the result of a "start" action, including the options used, and the resulting media stream.
+ */
+interface StartActionResult {
+ type: "start";
+ options: StartMediaStreamOptions;
+ stream: MediaStream;
+}
+
+/**
+ * Details the result of an "inspect" action, including options used, the media stream inspected,
+ * and the capabilities of video and audio tracks.
+ */
+interface InspectActionResult {
+ type: "inspect";
+ options: InspectMediaStreamOptions;
+ stream: MediaStream;
+ videoTracksCapabilities: MediaTrackCapabilities[];
+ audioTracksCapabilities: MediaTrackCapabilities[];
+}
+
+/**
+ * Details the result of a "constrain" action, including the options used and the constrained media stream.
+ */
+interface ConstrainActionResult {
+ type: "constrain";
+ options: ConstrainMediaStreamOptions;
+ stream: MediaStream;
+}
+
+/**
+ * Represents the result of a "stop" action.
+ */
+interface StopActionResult {
+ type: "stop";
+}
+
+/**
+ * Union type representing the possible outcomes of actions performed on a media stream.
+ */
+type ActionResult =
+ | StartActionResult
+ | InspectActionResult
+ | ConstrainActionResult
+ | StopActionResult;
+
+/**
+ * Configuration options for the UserMediaStream object, including constraints for initialization,
+ * video, and audio tracks, a timeout for capability retrieval, and callbacks for stream lifecycle events.
+ */
+export interface UserMediaStreamOptions {
+ /**
+ * Media stream constraints for initialization.
+ */
+ initConstraints?: InitConstraints;
+ /**
+ * Media track constraints specific to video tracks.
+ */
+ videoConstraints?: VideoConstraints;
+ /**
+ * Media track constraints specific to audio tracks.
+ */
+ audioConstraints?: AudioConstraints;
+ /**
+ * The default maximum time (in milliseconds) to wait for getting the capabilities of a media
+ * track.
+ */
+ getCapabilitiesTimeout?: number;
+ /**
+ * Callback function that is triggered when the stream starts.
+ *
+ * @param stream - Media stream
+ */
+ onStreamStart?: (stream: MediaStream) => unknown;
+ /**
+ * Callback function that is triggered when the stream stops.
+ */
+ onStreamStop?: () => unknown;
+ /**
+ * Callback function that is triggered on each application of constraints.
+ *
+ * @param stream - Media stream
+ */
+ onStreamUpdate?: (stream: MediaStream) => unknown;
+ /**
+ * Callback function that is triggered when the stream is inspected.
+ *
+ * @param streamCapablities - User media stream capabilities
+ */
+ onStreamInspect?: (
+ streamCapablities?: UserMediaStreamCapabilities,
+ ) => unknown;
+}
+
+/**
+ * Represents the capabilities of video and audio tracks within a user media stream.
+ */
+export interface UserMediaStreamCapabilities {
+ videoTracksCapabilities: MediaTrackCapabilities[];
+ audioTracksCapabilities: MediaTrackCapabilities[];
+}
+
+/**
+ * Interface for controlling and interacting with a user media stream, including starting,
+ * inspecting, stopping the stream, and updating configuration options.
+ */
+export interface UserMediaStream {
+ start: () => Promise;
+ inspect: () => Promise;
+ stop: () => Promise;
+ setOptions: (userMediaStreamOptions: UserMediaStreamOptions) => void;
+}
+
+/**
+ * Resolved options for the UserMediaStream with default values provided.
+ */
+type ResolvedUserMediaStreamOptions = SetRequired<
+ UserMediaStreamOptions,
+ "initConstraints" | "getCapabilitiesTimeout"
+>;
+
+/**
+ * Resolves and merges the provided UserMediaStreamOptions with default values for unspecified options.
+ *
+ * @param userMediaStreamOptions - The user-provided configuration options for the UserMediaStream.
+ * @returns The resolved configuration options with default values for unspecified options.
+ */
+function resolveUserMediaStreamOptions(
+ userMediaStreamOptions: UserMediaStreamOptions,
+): ResolvedUserMediaStreamOptions {
+ return {
+ initConstraints: userMediaStreamOptions.initConstraints ?? {
+ video: true,
+ audio: false,
+ },
+ videoConstraints:
+ "videoConstraints" in userMediaStreamOptions
+ ? userMediaStreamOptions.videoConstraints
+ : undefined,
+ audioConstraints:
+ "audioConstraints" in userMediaStreamOptions
+ ? userMediaStreamOptions.audioConstraints
+ : undefined,
+ getCapabilitiesTimeout:
+ userMediaStreamOptions.getCapabilitiesTimeout ?? GET_CAPABILITIES_TIMEOUT,
+ onStreamStart:
+ "onStreamStart" in userMediaStreamOptions
+ ? userMediaStreamOptions.onStreamStart
+ : undefined,
+ onStreamStop:
+ "onStreamStop" in userMediaStreamOptions
+ ? userMediaStreamOptions.onStreamStop
+ : undefined,
+ onStreamUpdate:
+ "onStreamUpdate" in userMediaStreamOptions
+ ? userMediaStreamOptions.onStreamUpdate
+ : undefined,
+ onStreamInspect:
+ "onStreamInspect" in userMediaStreamOptions
+ ? userMediaStreamOptions.onStreamInspect
+ : undefined,
+ };
+}
+
+/**
+ * Creates and returns a UserMediaStream object with the specified configuration options.
+ * Provides methods to start, inspect, and stop the media stream, as well as to update its options.
+ *
+ * @param userMediaStreamOptions - Optional configuration options for the UserMediaStream.
+ * @returns An object providing control methods for managing a user media stream.
+ */
+export function createUserMediaStream(
+ userMediaStreamOptions: UserMediaStreamOptions = {},
+): UserMediaStream {
+ const {
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ } = resolveUserMediaStreamOptions(userMediaStreamOptions);
+
+ // initialize a queue
+ let queue: Promise = Promise.resolve({
+ type: "stop",
+ });
+
+ // queueify start
+ const queuefiedStart = async (
+ startMediaStreamOptions: StartMediaStreamOptions,
+ ) => {
+ // update the queue
+ queue = queue.then((prevActionResult) => {
+ switch (prevActionResult.type) {
+ // previous action is a start action
+ case "start": {
+ // if options are the same, reuse the previous action result
+ if (compare(prevActionResult.options, startMediaStreamOptions)) {
+ return prevActionResult;
+ }
+ // otherwise stop and then start
+ return stopMediaStream(prevActionResult.stream).then(() =>
+ startMediaStream(startMediaStreamOptions),
+ );
+ }
+ // previous action is an inspect action
+ case "inspect": {
+ // stop and then start
+ return stopMediaStream(prevActionResult.stream).then(() =>
+ startMediaStream(startMediaStreamOptions),
+ );
+ }
+ // previous action is a constrain action
+ case "constrain": {
+ // stop and then start
+ return stopMediaStream(prevActionResult.stream).then(() =>
+ startMediaStream(startMediaStreamOptions),
+ );
+ }
+ // previous action is a stop action
+ case "stop": {
+ // just start
+ return startMediaStream(startMediaStreamOptions);
+ }
+ // unknown action
+ default: {
+ throw new TypeError(
+ `Unknown action result: ${prevActionResult satisfies never}`,
+ );
+ }
+ }
+ });
+ // execute the queue and get the result
+ const actionResult = (await queue) as StartActionResult;
+ // return the stream
+ return actionResult.stream;
+ };
+
+ // queueify inspect
+ const queuefiedInspect = async (
+ inspectMediaStreamOptions: InspectMediaStreamOptions,
+ ) => {
+ // update the queue
+ queue = queue.then((prevActionResult) => {
+ switch (prevActionResult.type) {
+ // previous action is a start action
+ case "start": {
+ // just inspect
+ return inspectMediaStream(
+ prevActionResult.stream,
+ inspectMediaStreamOptions,
+ );
+ }
+ // previous action is an inspect action
+ case "inspect": {
+ // if options are the same, reuse the previous action result
+ if (compare(prevActionResult.options, inspectMediaStreamOptions)) {
+ return prevActionResult;
+ }
+ // otherwise just inspect
+ return inspectMediaStream(
+ prevActionResult.stream,
+ inspectMediaStreamOptions,
+ );
+ }
+ // previous action is a constrain action
+ case "constrain": {
+ // just inspect
+ return inspectMediaStream(
+ prevActionResult.stream,
+ inspectMediaStreamOptions,
+ );
+ }
+ // previous action is a stop action
+ case "stop": {
+ // we cannot inspect a stopped stream
+ // so do nothing and reuse the previous action
+ return prevActionResult;
+ }
+ // unknown action
+ default: {
+ throw new TypeError(
+ `Unknown action result: ${prevActionResult satisfies never}`,
+ );
+ }
+ }
+ });
+ // execute the queue and get the result
+ const actionResult = await queue;
+ if (actionResult.type === "inspect") {
+ // return the stream if it is not stopped
+ return {
+ videoTracksCapabilities: actionResult.videoTracksCapabilities,
+ audioTracksCapabilities: actionResult.audioTracksCapabilities,
+ };
+ }
+ };
+
+ // queueify constrain
+ const queuefiedConstrain = async (
+ constrainMediaStreamOptions: ConstrainMediaStreamOptions,
+ ) => {
+ // update the queue
+ queue = queue.then((prevActionResult) => {
+ switch (prevActionResult.type) {
+ // previous action is a start action
+ case "start": {
+ // just apply constraints
+ return constrainMediaStream(
+ prevActionResult.stream,
+ constrainMediaStreamOptions,
+ );
+ }
+ // previous action is an inspect action
+ case "inspect": {
+ // just apply constraints
+ return constrainMediaStream(
+ prevActionResult.stream,
+ constrainMediaStreamOptions,
+ );
+ }
+ // previous action is a constrain action
+ case "constrain": {
+ // if options are the same, reuse the previous action result
+ if (compare(prevActionResult.options, constrainMediaStreamOptions)) {
+ return prevActionResult;
+ }
+ // otherwise just apply constraints
+ return constrainMediaStream(
+ prevActionResult.stream,
+ constrainMediaStreamOptions,
+ );
+ }
+ // previous action is a stop action
+ case "stop": {
+ // we cannot apply constraints to a stopped stream
+ // so do nothing and reuse the previous action
+ return prevActionResult;
+ }
+ // unknown action
+ default: {
+ throw new TypeError(
+ `Unknown action result: ${prevActionResult satisfies never}`,
+ );
+ }
+ }
+ });
+ // execute the queue and get the result
+ const actionResult = await queue;
+ if (actionResult.type === "constrain") {
+ // return the stream if it is not stopped
+ return actionResult.stream;
+ }
+ };
+
+ // queueify stop
+ const queuefiedStop = async () => {
+ queue = queue.then((prevActionResult) => {
+ switch (prevActionResult.type) {
+ // previous action is a start action
+ case "start": {
+ // just stop the stream
+ return stopMediaStream(prevActionResult.stream);
+ }
+ // previous action is an inspect action
+ case "inspect": {
+ // just stop the stream
+ return stopMediaStream(prevActionResult.stream);
+ }
+ // previous action is a constrain action
+ case "constrain": {
+ // just stop the stream
+ return stopMediaStream(prevActionResult.stream);
+ }
+ // previous action is a stop action
+ case "stop": {
+ // reuse the previous action
+ return prevActionResult;
+ }
+ // unknown action
+ default: {
+ throw new TypeError(
+ `Unknown action result: ${prevActionResult satisfies never}`,
+ );
+ }
+ }
+ });
+ // execute the queue
+ await queue;
+ };
+
+ // create a state store
+ const userMediaStreamStore = createStore()(
+ subscribeWithSelector(() => ({
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+
+ getCapabilitiesTimeout,
+
+ onStreamStart,
+ onStreamStop,
+ onStreamUpdate,
+ onStreamInspect,
+ })),
+ );
+
+ // invoke constrain when constraints update
+ userMediaStreamStore.subscribe(
+ (options) => [options.videoConstraints, options.audioConstraints] as const,
+ async ([videoConstraints, audioConstraints]) => {
+ const { getCapabilitiesTimeout, onStreamUpdate } =
+ userMediaStreamStore.getState();
+ const stream = await queuefiedConstrain({
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ });
+ stream && onStreamUpdate?.(stream);
+ return stream;
+ },
+ { equalityFn: shallow },
+ );
+
+ // the exposed control function to start a stream
+ const start = async () => {
+ const {
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ onStreamStart,
+ } = userMediaStreamStore.getState();
+ const stream = await queuefiedStart({
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ });
+ onStreamStart?.(stream);
+ return stream;
+ };
+
+ // the exposed inspect function to inspect a stream
+ const inspect = async () => {
+ const { getCapabilitiesTimeout, onStreamInspect } =
+ userMediaStreamStore.getState();
+ const streamCapablities = await queuefiedInspect({
+ getCapabilitiesTimeout,
+ });
+ onStreamInspect?.(streamCapablities ? { ...streamCapablities } : undefined);
+ return streamCapablities;
+ };
+
+ // the exposed control function to stop a stream
+ const stop = async () => {
+ const { onStreamStop } = userMediaStreamStore.getState();
+ await queuefiedStop();
+ onStreamStop?.();
+ };
+
+ // return the exposed control functions
+ return {
+ start,
+ inspect,
+ stop,
+ setOptions: (userMediaStreamOptions) =>
+ userMediaStreamStore.setState(
+ resolveUserMediaStreamOptions(userMediaStreamOptions),
+ ),
+ };
+}
+
+interface StartMediaStreamOptions {
+ initConstraints: InitConstraints;
+ videoConstraints?: VideoConstraints;
+ audioConstraints?: AudioConstraints;
+ getCapabilitiesTimeout: number;
+}
+
+/**
+ * Start a media stream with specified constraints.
+ *
+ * This function does some checks mainly for compatibility.
+ *
+ * @param startMediaStreamOptions - The constraints (initial, video, and audio) to apply along with
+ * meta configuration options.
+ * @returns A promise that resolves to an StartActionResult for later queueing.
+ */
+async function startMediaStream({
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+}: StartMediaStreamOptions): Promise {
+ // check if we are in the secure context
+ if (window.isSecureContext !== true) {
+ throw new DOMException(
+ "Cannot use navigator.mediaDevices in insecure contexts. Please use HTTPS or localhost.",
+ "NotAllowedError",
+ );
+ }
+
+ // check if we can use the getUserMedia API
+ if (navigator.mediaDevices?.getUserMedia === undefined) {
+ throw new DOMException(
+ "The method navigator.mediaDevices.getUserMedia is not defined. Does your runtime support this API?",
+ "NotSupportedError",
+ );
+ }
+
+ // shim WebRTC APIs in the client runtime
+ await shimGetUserMedia();
+
+ // resolve initial constraints
+ const resolvedInitConstraints =
+ typeof initConstraints === "function"
+ ? // callback constraints
+ await initConstraints(navigator.mediaDevices.getSupportedConstraints())
+ : initConstraints;
+
+ // apply initial constraints and get the media stream
+ const stream = await navigator.mediaDevices.getUserMedia(
+ resolvedInitConstraints,
+ );
+
+ // apply video and audio constraints
+ await constrainMediaStream(stream, {
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ });
+
+ return {
+ type: "start",
+ options: {
+ initConstraints,
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ },
+ stream: stream,
+ };
+}
+
+interface ConstrainMediaStreamOptions {
+ videoConstraints?: VideoConstraints;
+ audioConstraints?: AudioConstraints;
+ getCapabilitiesTimeout: number;
+}
+
+/**
+ * Apply video and audio constraints to a given media stream.
+ *
+ * This function processes both video and audio tracks of the media stream, applying the specified
+ * constraints to each kind of tracks.
+ *
+ * @param stream - The MediaStream to which constraints will be applied.
+ * @param constrainMediaStreamOptions - The video and audio constraints to apply along with meta
+ * configuration options.
+ * @returns A promise that resolves to a ConstrainActionResult for later queueing.
+ */
+async function constrainMediaStream(
+ stream: MediaStream,
+ {
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ }: ConstrainMediaStreamOptions,
+): Promise {
+ // get video tracks
+ const videoTracks = stream.getVideoTracks();
+
+ // get audio tracks
+ const audioTracks = stream.getAudioTracks();
+
+ // apply media track constraints
+ await Promise.all([
+ // apply video constraints
+ Promise.all(
+ videoTracks.map(async (videoTrack) => {
+ const resolvedVideoConstraints =
+ typeof videoConstraints === "function"
+ ? // callback constraints
+ await videoConstraints(
+ await getCapabilities(videoTrack, getCapabilitiesTimeout),
+ )
+ : videoConstraints;
+ await videoTrack.applyConstraints(resolvedVideoConstraints);
+ }),
+ ),
+ // apply audio constraints
+ Promise.all(
+ audioTracks.map(async (audioTrack) => {
+ const resolvedAudioConstraints =
+ typeof audioConstraints === "function"
+ ? // callback constraints
+ await audioConstraints(
+ await getCapabilities(audioTrack, getCapabilitiesTimeout),
+ )
+ : audioConstraints;
+ await audioTrack.applyConstraints(resolvedAudioConstraints);
+ }),
+ ),
+ ]);
+
+ return {
+ type: "constrain",
+ options: {
+ videoConstraints,
+ audioConstraints,
+ getCapabilitiesTimeout,
+ },
+ stream,
+ };
+}
+
+interface InspectMediaStreamOptions {
+ getCapabilitiesTimeout: number;
+}
+
+async function inspectMediaStream(
+ stream: MediaStream,
+ { getCapabilitiesTimeout }: InspectMediaStreamOptions,
+): Promise {
+ // get video tracks
+ const videoTracks = stream.getVideoTracks();
+
+ // get audio tracks
+ const audioTracks = stream.getAudioTracks();
+
+ const [videoTracksCapabilities, audioTracksCapabilities] = await Promise.all([
+ Promise.all(
+ videoTracks.map(async (videoTrack) =>
+ getCapabilities(videoTrack, getCapabilitiesTimeout),
+ ),
+ ),
+ Promise.all(
+ audioTracks.map(async (audioTrack) =>
+ getCapabilities(audioTrack, getCapabilitiesTimeout),
+ ),
+ ),
+ ]);
+
+ return {
+ type: "inspect",
+ options: {
+ getCapabilitiesTimeout,
+ },
+ stream,
+ videoTracksCapabilities,
+ audioTracksCapabilities,
+ };
+}
+
+/**
+ * Stop a given media stream.
+ *
+ * This function stops all tracks of the given media stream. It ensures that the media stream is
+ * properly terminated.
+ *
+ * @param stream - The MediaStream to be stopped.
+ * @returns A promise that resolves to a StopActionResult for later queueing.
+ */
+async function stopMediaStream(stream: MediaStream): Promise {
+ for (const track of stream.getTracks()) {
+ stream.removeTrack(track);
+ track.stop();
+ }
+
+ return {
+ type: "stop",
+ };
+}
+
+/**
+ * Attach a given media stream to a HTMLVideoElement.
+ *
+ * This function sets the source of the video element to the provided media stream. It supports
+ * different ways of attaching the stream based on browser compatibility.
+ *
+ * @param videoElement - The HTMLVideoElement to which the media stream will be attached.
+ * @param stream - The MediaStream to be attached to the video element.
+ */
+export function attachMediaStream(
+ videoElement: HTMLVideoElement,
+ stream: MediaStream,
+) {
+ // attach the stream to the video element
+ if (videoElement.srcObject !== undefined) {
+ videoElement.srcObject = stream;
+ } else if (videoElement.mozSrcObject !== undefined) {
+ videoElement.mozSrcObject = stream;
+ } else if (window.URL.createObjectURL) {
+ videoElement.src = (window.URL.createObjectURL as CreateObjectURLCompat)(
+ stream,
+ );
+ } else if (window.webkitURL) {
+ videoElement.src = (
+ window.webkitURL.createObjectURL as CreateObjectURLCompat
+ )(stream);
+ } else {
+ videoElement.src = stream.id;
+ }
+}
+
+/**
+ * Asynchronously retrieve the capabilities of a given media stream track.
+ *
+ * This function attempts to fetch the capabilities of a MediaStreamTrack. If called too early, it
+ * may return an empty object as [the capabilities might not be available
+ * immediately](https://oberhofer.co/mediastreamtrack-and-its-capabilities/#queryingcapabilities).
+ * Because capabilities are allowed to be an empty object, a timeout mechanism is implemented to
+ * ensure the function returns even if capabilities are not obtained within the specified time,
+ * preventing indefinite waiting.
+ *
+ * @param track - The MediaStreamTrack whose capabilities are to be fetched.
+ * @param timeout - The maximum time (in milliseconds) to wait for fetching capabilities.
+ * @returns A promise that resolves to the track's capabilities, which may be an empty object.
+ */
+export async function getCapabilities(
+ track: MediaStreamTrack,
+ timeout = GET_CAPABILITIES_TIMEOUT,
+): Promise {
+ return new Promise((resolve) => {
+ // timeout, return empty capabilities
+ let timeoutId: number | undefined = setTimeout(() => {
+ resolve({});
+ timeoutId = undefined;
+ return;
+ }, timeout);
+
+ // not supported, return empty capabilities
+ if (!track.getCapabilities) {
+ clearTimeout(timeoutId);
+ resolve({});
+ timeoutId = undefined;
+ return;
+ }
+
+ // poll to check capabilities
+ let capabilities: MediaTrackCapabilities = {};
+ while (Object.keys(capabilities).length === 0 && timeoutId !== undefined) {
+ capabilities = track.getCapabilities();
+ }
+ clearTimeout(timeoutId);
+ resolve(capabilities);
+ timeoutId = undefined;
+ return;
+ });
+}
diff --git a/src/stream/webrtc-adapter.d.ts b/src/stream/webrtc-adapter.d.ts
new file mode 100644
index 00000000..ad4f11e3
--- /dev/null
+++ b/src/stream/webrtc-adapter.d.ts
@@ -0,0 +1,30 @@
+/**
+ * This file declares types for the non-public exports from "webrtc-adapter". As we're only
+ * interested in the getUserMedia shims, we use this approach to avoid importing the whole library.
+ *
+ * This type declaration file is only meant to be used internally.
+ */
+
+declare module "webrtc-adapter/dist/chrome/getusermedia" {
+ function shimGetUserMedia(
+ window: Window,
+ browserDetails: import("webrtc-adapter").IAdapter["browserDetails"],
+ ): void;
+}
+
+declare module "webrtc-adapter/dist/firefox/getusermedia" {
+ function shimGetUserMedia(
+ window: Window,
+ browserDetails: import("webrtc-adapter").IAdapter["browserDetails"],
+ ): void;
+}
+
+declare module "webrtc-adapter/dist/safari/safari_shim" {
+ function shimGetUserMedia(window: Window): void;
+}
+
+declare module "webrtc-adapter/dist/utils" {
+ function detectBrowser(
+ window: Window,
+ ): import("webrtc-adapter").IAdapter["browserDetails"];
+}
diff --git a/src/writer/index.ts b/src/writer/index.ts
index 0d43db84..1b8b1955 100644
--- a/src/writer/index.ts
+++ b/src/writer/index.ts
@@ -1,6 +1,6 @@
/**
- * The writer part API of this package is subject to change a lot.
- * Please track the status of [this issue](https://github.com/zxing-cpp/zxing-cpp/issues/332).
+ * The writer part API of this package is subject to change a lot. Please track the status of [this
+ * issue](https://github.com/zxing-cpp/zxing-cpp/issues/332).
*
* @packageDocumentation
*/
@@ -45,6 +45,6 @@ export async function writeBarcodeToImageFile(
export * from "../bindings/exposedWriterBindings.js";
export {
purgeZXingModule,
- type ZXingWriterModule,
type ZXingModuleOverrides,
+ type ZXingWriterModule,
} from "../core.js";
diff --git a/tsconfig.base.json b/tsconfig.base.json
index f1a2ef8f..ffaea4a9 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1,6 +1,8 @@
{
"compilerOptions": {
"target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
"useDefineForClassFields": true,
"skipLibCheck": true,
"resolveJsonModule": true,
diff --git a/tsconfig.json b/tsconfig.json
index 07dcdbbb..05b705a4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,12 +1,12 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
- "module": "NodeNext",
+ "jsx": "react-jsx",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"types": ["emscripten"],
- "moduleResolution": "NodeNext"
+ "noUncheckedIndexedAccess": true
},
- "include": ["./src"],
+ "include": ["./src", "./main.ts", "./main.tsx"],
"references": [
{
"path": "./tsconfig.node.json"
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 2c6111d3..521555ee 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -2,9 +2,7 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"composite": true,
- "module": "ESNext",
"types": ["node"],
- "moduleResolution": "Bundler",
"resolveJsonModule": true,
"allowJs": true
},
diff --git a/tsconfig.production.json b/tsconfig.production.json
new file mode 100644
index 00000000..a16641f0
--- /dev/null
+++ b/tsconfig.production.json
@@ -0,0 +1,5 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["./src"],
+ "ignore": ["./src/index.css.ts"]
+}
diff --git a/typedoc.json b/typedoc.json
index dddddcdd..9d9a3f37 100644
--- a/typedoc.json
+++ b/typedoc.json
@@ -3,7 +3,11 @@
"entryPoints": [
"./src/full/index.ts",
"./src/reader/index.ts",
- "./src/writer/index.ts"
+ "./src/writer/index.ts",
+ "./src/scanner/index.ts",
+ "./src/stream/index.ts",
+ "./src/react/hooks/index.ts"
],
+ "sortEntryPoints": false,
"out": "docs"
}
diff --git a/vite.config.ts b/vite.config.ts
index 2cc173f9..db7e32b1 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,3 +1,4 @@
+import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import { defineConfig } from "vite";
import babel from "vite-plugin-babel";
import { version } from "./package-lock.json";
@@ -11,14 +12,35 @@ export default defineConfig({
"reader/index": "src/reader/index.ts",
"writer/index": "src/writer/index.ts",
"full/index": "src/full/index.ts",
+ "scanner/index": "src/scanner/index.ts",
+ "stream/index": "src/stream/index.ts",
+ "react/index": "src/react/index.ts",
+ "react/hooks/index": "src/react/hooks/index.ts",
+ "react/components/index": "src/react/components/index.ts",
},
formats: ["es"],
fileName: (_, entryName) => `${entryName}.js`,
},
outDir: "dist/es",
rollupOptions: {
+ external: ["react"],
output: {
manualChunks: (id) => {
+ if (/webrtc-adapter\/dist\/utils/.test(id)) {
+ return "shim-utils";
+ }
+ if (/webrtc-adapter\/dist\/chrome/.test(id)) {
+ return "shim-chrome";
+ }
+ if (/webrtc-adapter\/dist\/firefox/.test(id)) {
+ return "shim-firefox";
+ }
+ if (/webrtc-adapter\/dist\/safari/.test(id)) {
+ return "shim-safari";
+ }
+ if (/zustand/.test(id)) {
+ return "zustand";
+ }
if (
/core\.ts|exposedReaderBindings\.ts|exposedWriterBindings\.ts/.test(
id,
@@ -26,11 +48,34 @@ export default defineConfig({
) {
return "core";
}
+ if (/src\/reader\/index\.ts/.test(id)) {
+ return "reader";
+ }
+ if (/src\/writer\/index\.ts/.test(id)) {
+ return "writer";
+ }
+ if (/src\/full\/index\.ts/.test(id)) {
+ return "full";
+ }
+ if (/src\/scanner\/index\.ts/.test(id)) {
+ return "scanner";
+ }
+ if (/src\/stream\/index\.ts/.test(id)) {
+ return "stream";
+ }
+ if (/src\/react\/hooks\/index\.ts/.test(id)) {
+ return "react-hooks";
+ }
+ if (/src\/react\/components\/index\.ts/.test(id)) {
+ return "react-components";
+ }
},
+ chunkFileNames: "[name]-[hash].js",
},
},
},
plugins: [
+ vanillaExtractPlugin(),
babel({
babelConfig: {
plugins: [emscriptenPatch()],
@@ -41,5 +86,6 @@ export default defineConfig({
],
define: {
NPM_PACKAGE_VERSION: JSON.stringify(version),
+ "process.env.NODE_ENV": JSON.stringify("production"),
},
});