quadqr-js 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -26
- package/bin/quadqr.js +102 -102
- package/dist/esm/node.js +275 -275
- package/dist/esm/quadqr.js +1962 -1962
- package/dist/esm/wasm.js +98 -98
- package/dist/quadqr.js +1921 -1921
- package/docs/API.md +160 -39
- package/docs/BROWSER_CDN.md +83 -29
- package/docs/CLI.md +76 -0
- package/docs/GETTING_STARTED.md +104 -22
- package/docs/NODE.md +51 -20
- package/docs/PUBLISHING.md +12 -18
- package/docs/README.md +25 -10
- package/docs/SECURITY.md +72 -21
- package/docs/WASM.md +42 -11
- package/package.json +2 -2
- package/types/benchmark.d.ts +7 -7
- package/types/index.d.ts +139 -139
- package/types/node.d.ts +9 -9
package/README.md
CHANGED
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://akanshsirohi.github.io/QuadQR/demo/"><strong>Try the Live Demo</strong></a>
|
|
13
13
|
·
|
|
14
|
+
<a href="https://akanshsirohi.github.io/QuadQR/docs-site/"><strong>Documentation Site</strong></a>
|
|
15
|
+
·
|
|
16
|
+
<a href="https://www.npmjs.com/package/quadqr-js">npm</a>
|
|
17
|
+
·
|
|
14
18
|
<a href="#current-benchmark">Benchmark</a>
|
|
15
19
|
·
|
|
16
20
|
<a href="#getting-started">Use the Library</a>
|
|
17
21
|
·
|
|
18
|
-
<a href="docs/README.md">
|
|
22
|
+
<a href="docs/README.md">Markdown Docs</a>
|
|
19
23
|
·
|
|
20
24
|
<a href="FORMAT.md">Format Specification</a>
|
|
21
25
|
</p>
|
|
@@ -550,21 +554,21 @@ QuadQR can be used as an npm package, directly from a CDN, or from this reposito
|
|
|
550
554
|
|
|
551
555
|
### Requirements
|
|
552
556
|
|
|
553
|
-
- **Node.js 20.19+** for
|
|
554
|
-
- npm
|
|
557
|
+
- **Node.js 20.19+** for Node.js and CommonJS usage
|
|
558
|
+
- npm or another Node package manager for installation
|
|
555
559
|
- A modern browser for the demo and documentation site
|
|
556
560
|
- HTTPS or localhost for browser camera access
|
|
557
561
|
|
|
558
562
|
### Install from npm
|
|
559
563
|
|
|
560
564
|
```bash
|
|
561
|
-
npm install quadqr
|
|
565
|
+
npm install quadqr-js
|
|
562
566
|
```
|
|
563
567
|
|
|
564
568
|
Core usage:
|
|
565
569
|
|
|
566
570
|
```js
|
|
567
|
-
import { encodeText, decodeMatrix } from "quadqr";
|
|
571
|
+
import { encodeText, decodeMatrix } from "quadqr-js";
|
|
568
572
|
|
|
569
573
|
const code = encodeText("Hello from QuadQR", { ecc: "M" });
|
|
570
574
|
const result = decodeMatrix(code.matrix);
|
|
@@ -574,8 +578,8 @@ console.log(result.text);
|
|
|
574
578
|
Node PNG usage:
|
|
575
579
|
|
|
576
580
|
```js
|
|
577
|
-
import { encodeText } from "quadqr";
|
|
578
|
-
import { savePNG, scanFile } from "quadqr/node";
|
|
581
|
+
import { encodeText } from "quadqr-js";
|
|
582
|
+
import { savePNG, scanFile } from "quadqr-js/node";
|
|
579
583
|
|
|
580
584
|
const code = encodeText("Generated on Node.js");
|
|
581
585
|
await savePNG(code, "quadqr.png", { moduleSize: 12, quietZone: 4 });
|
|
@@ -586,10 +590,10 @@ console.log(result.text);
|
|
|
586
590
|
|
|
587
591
|
### CDN / script tag
|
|
588
592
|
|
|
589
|
-
|
|
593
|
+
The `quadqr-js` package can be loaded directly from npm-backed CDNs:
|
|
590
594
|
|
|
591
595
|
```html
|
|
592
|
-
<script src="https://cdn.jsdelivr.net/npm/quadqr@0.
|
|
596
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0.0/dist/quadqr.min.js"></script>
|
|
593
597
|
<script>
|
|
594
598
|
const code = QuadQR.encodeText("Hello from a script tag");
|
|
595
599
|
</script>
|
|
@@ -597,7 +601,25 @@ After the package is published to npm, the classic global build can be loaded fr
|
|
|
597
601
|
|
|
598
602
|
The same file is available through unpkg. Pin an exact version in production.
|
|
599
603
|
|
|
600
|
-
###
|
|
604
|
+
### CLI
|
|
605
|
+
|
|
606
|
+
Encode and decode directly from the terminal:
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
610
|
+
npx quadqr-js decode hello.png
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
Password-protected payloads use the same commands:
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
npx quadqr-js encode "Private data" --password "my-password" -o secure.png
|
|
617
|
+
npx quadqr-js decode secure.png --password "my-password"
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including raw 256-bit key mode.
|
|
621
|
+
|
|
622
|
+
### Run from source
|
|
601
623
|
|
|
602
624
|
```bash
|
|
603
625
|
git clone https://github.com/akanshsirohi/QuadQR.git
|
|
@@ -611,7 +633,7 @@ Run the complete test suite:
|
|
|
611
633
|
npm test
|
|
612
634
|
```
|
|
613
635
|
|
|
614
|
-
Build the
|
|
636
|
+
Build the distributable browser, Node.js, CDN, and WASM files:
|
|
615
637
|
|
|
616
638
|
```bash
|
|
617
639
|
npm run build
|
|
@@ -635,18 +657,12 @@ Run benchmarks:
|
|
|
635
657
|
npm run benchmark
|
|
636
658
|
```
|
|
637
659
|
|
|
638
|
-
Inspect exactly what npm will publish:
|
|
639
|
-
|
|
640
|
-
```bash
|
|
641
|
-
npm run pack:check
|
|
642
|
-
```
|
|
643
|
-
|
|
644
660
|
### Optional WASM acceleration
|
|
645
661
|
|
|
646
662
|
The package ships a prebuilt WASM helper but never requires it.
|
|
647
663
|
|
|
648
664
|
```js
|
|
649
|
-
import { initWasm } from "quadqr";
|
|
665
|
+
import { initWasm } from "quadqr-js";
|
|
650
666
|
|
|
651
667
|
await initWasm();
|
|
652
668
|
```
|
|
@@ -659,11 +675,11 @@ If WASM cannot load, the normal JavaScript codec remains available.
|
|
|
659
675
|
|
|
660
676
|
| Import | Purpose |
|
|
661
677
|
|---|---|
|
|
662
|
-
| `quadqr` | Runtime-neutral core, secure payloads, rendering, scanning, optional WASM |
|
|
663
|
-
| `quadqr/browser` | Browser ESM entry |
|
|
664
|
-
| `quadqr/node` | Node core plus PNG/file/buffer helpers |
|
|
665
|
-
| `quadqr/benchmark` | Capacity and codec benchmark helpers |
|
|
666
|
-
| `quadqr/quadqr.min.js` | Classic browser global/CDN bundle |
|
|
678
|
+
| `quadqr-js` | Runtime-neutral core, secure payloads, rendering, scanning, optional WASM |
|
|
679
|
+
| `quadqr-js/browser` | Browser ESM entry |
|
|
680
|
+
| `quadqr-js/node` | Node core plus PNG/file/buffer helpers |
|
|
681
|
+
| `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
|
|
682
|
+
| `quadqr-js/quadqr.min.js` | Classic browser global/CDN bundle |
|
|
667
683
|
|
|
668
684
|
The Node PNG path is dependency-free. For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application already has it installed.
|
|
669
685
|
|
|
@@ -686,7 +702,7 @@ wasm-src/
|
|
|
686
702
|
quadqr_core.c Small portable WASM accelerator source
|
|
687
703
|
|
|
688
704
|
wasm/
|
|
689
|
-
quadqr-core.wasm
|
|
705
|
+
quadqr-core.wasm Source-tree WASM build output
|
|
690
706
|
|
|
691
707
|
dist/
|
|
692
708
|
index.js ESM package entry
|
|
@@ -714,11 +730,11 @@ docs/
|
|
|
714
730
|
BROWSER_CDN.md
|
|
715
731
|
NODE.md
|
|
716
732
|
SECURITY.md
|
|
733
|
+
CLI.md
|
|
717
734
|
WASM.md
|
|
718
|
-
PUBLISHING.md
|
|
719
735
|
|
|
720
736
|
types/ TypeScript declarations for JavaScript consumers
|
|
721
|
-
bin/ `npx quadqr`
|
|
737
|
+
bin/ `quadqr` CLI (`npx quadqr-js`)
|
|
722
738
|
scripts/ Build, benchmark, and local server scripts
|
|
723
739
|
tests/ Codec and package distribution tests
|
|
724
740
|
FORMAT.md Wire-format specification
|
package/bin/quadqr.js
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
import process from "node:process";
|
|
5
|
-
import {
|
|
6
|
-
bytesToHex,
|
|
7
|
-
decryptDecoded,
|
|
8
|
-
encodeSecureText,
|
|
9
|
-
encodeText,
|
|
10
|
-
generateRaw256Key
|
|
11
|
-
} from "../dist/index.js";
|
|
12
|
-
import { savePNG, scanFile } from "../dist/node.js";
|
|
13
|
-
|
|
14
|
-
function help() {
|
|
15
|
-
console.log(`QuadQR CLI\n\nUsage:\n quadqr encode <text> [-o file.png] [--ecc M] [--version auto|1..40]\n quadqr encode <text> --password <password> [-o file.png]\n quadqr encode <text> --key <64-hex-key> [-o file.png]\n quadqr decode <file.png> [--password <password> | --key <64-hex-key>]\n quadqr keygen\n\nOptions:\n -o, --output <file> Output PNG path (default: quadqr.png)\n --ecc <L|M|Q|H> ECC profile (default: M)\n --version <auto|1..40> Symbol version (default: auto)\n --password <text> Encrypt/decrypt with password mode\n --key <hex> Encrypt/decrypt with raw 256-bit key mode\n --module-size <px> PNG module size (default: 12)\n --quiet-zone <modules> PNG quiet zone (default: 4)\n -h, --help Show help\n`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parse(argv) {
|
|
19
|
-
const args = [];
|
|
20
|
-
const flags = {};
|
|
21
|
-
for (let i = 0; i < argv.length; i++) {
|
|
22
|
-
const token = argv[i];
|
|
23
|
-
if (token === "-h" || token === "--help") flags.help = true;
|
|
24
|
-
else if (token === "-o" || token === "--output") flags.output = argv[++i];
|
|
25
|
-
else if (token === "--ecc") flags.ecc = argv[++i];
|
|
26
|
-
else if (token === "--version") flags.version = argv[++i];
|
|
27
|
-
else if (token === "--password") flags.password = argv[++i];
|
|
28
|
-
else if (token === "--key") flags.key = argv[++i];
|
|
29
|
-
else if (token === "--module-size") flags.moduleSize = Number(argv[++i]);
|
|
30
|
-
else if (token === "--quiet-zone") flags.quietZone = Number(argv[++i]);
|
|
31
|
-
else args.push(token);
|
|
32
|
-
}
|
|
33
|
-
return { args, flags };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function main() {
|
|
37
|
-
const { args, flags } = parse(process.argv.slice(2));
|
|
38
|
-
if (flags.help || !args.length) {
|
|
39
|
-
help();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const command = args.shift();
|
|
44
|
-
if (command === "keygen") {
|
|
45
|
-
console.log(bytesToHex(generateRaw256Key()));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (command === "encode") {
|
|
50
|
-
const text = args.join(" ");
|
|
51
|
-
if (!text) throw new Error("encode requires text.");
|
|
52
|
-
if (flags.password && flags.key) throw new Error("Choose password mode or raw-key mode, not both.");
|
|
53
|
-
|
|
54
|
-
const options = {
|
|
55
|
-
ecc: flags.ecc || "M",
|
|
56
|
-
...(flags.version && flags.version !== "auto" ? { version: Number(flags.version) } : {})
|
|
57
|
-
};
|
|
58
|
-
const code = flags.password
|
|
59
|
-
? await encodeSecureText(text, { ...options, security: { mode: "password", password: flags.password } })
|
|
60
|
-
: flags.key
|
|
61
|
-
? await encodeSecureText(text, { ...options, security: { mode: "raw-key", key: flags.key } })
|
|
62
|
-
: encodeText(text, options);
|
|
63
|
-
|
|
64
|
-
const output = flags.output || "quadqr.png";
|
|
65
|
-
const saved = await savePNG(code, output, {
|
|
66
|
-
moduleSize: flags.moduleSize || 12,
|
|
67
|
-
quietZone: Number.isFinite(flags.quietZone) ? flags.quietZone : 4
|
|
68
|
-
});
|
|
69
|
-
console.log(`Saved ${output} (${saved.bytes} bytes, v${code.version}, ${code.size}x${code.size}, ECC ${code.eccLevel}).`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (command === "decode") {
|
|
74
|
-
const filename = args[0];
|
|
75
|
-
if (!filename) throw new Error("decode requires an image filename.");
|
|
76
|
-
let result = await scanFile(filename);
|
|
77
|
-
if (result.secure) {
|
|
78
|
-
if (!flags.password && !flags.key) {
|
|
79
|
-
console.log(JSON.stringify({
|
|
80
|
-
secure: true,
|
|
81
|
-
mode: result.security?.mode,
|
|
82
|
-
algorithm: result.security?.algorithm,
|
|
83
|
-
keyId: result.security?.keyId || result.security?.keyIdHex || null,
|
|
84
|
-
requiresDecryption: true
|
|
85
|
-
}, null, 2));
|
|
86
|
-
process.exitCode = 2;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
result = await decryptDecoded(result, flags.password ? { password: flags.password } : { key: flags.key });
|
|
90
|
-
}
|
|
91
|
-
if (result.text != null) console.log(result.text);
|
|
92
|
-
else process.stdout.write(Buffer.from(result.payload));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
throw new Error(`Unknown command: ${command}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
main().catch((error) => {
|
|
100
|
-
console.error(`QuadQR: ${error.message}`);
|
|
101
|
-
process.exitCode = 1;
|
|
102
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import {
|
|
6
|
+
bytesToHex,
|
|
7
|
+
decryptDecoded,
|
|
8
|
+
encodeSecureText,
|
|
9
|
+
encodeText,
|
|
10
|
+
generateRaw256Key
|
|
11
|
+
} from "../dist/index.js";
|
|
12
|
+
import { savePNG, scanFile } from "../dist/node.js";
|
|
13
|
+
|
|
14
|
+
function help() {
|
|
15
|
+
console.log(`QuadQR CLI\n\nUsage:\n quadqr encode <text> [-o file.png] [--ecc M] [--version auto|1..40]\n quadqr encode <text> --password <password> [-o file.png]\n quadqr encode <text> --key <64-hex-key> [-o file.png]\n quadqr decode <file.png> [--password <password> | --key <64-hex-key>]\n quadqr keygen\n\nOptions:\n -o, --output <file> Output PNG path (default: quadqr.png)\n --ecc <L|M|Q|H> ECC profile (default: M)\n --version <auto|1..40> Symbol version (default: auto)\n --password <text> Encrypt/decrypt with password mode\n --key <hex> Encrypt/decrypt with raw 256-bit key mode\n --module-size <px> PNG module size (default: 12)\n --quiet-zone <modules> PNG quiet zone (default: 4)\n -h, --help Show help\n`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parse(argv) {
|
|
19
|
+
const args = [];
|
|
20
|
+
const flags = {};
|
|
21
|
+
for (let i = 0; i < argv.length; i++) {
|
|
22
|
+
const token = argv[i];
|
|
23
|
+
if (token === "-h" || token === "--help") flags.help = true;
|
|
24
|
+
else if (token === "-o" || token === "--output") flags.output = argv[++i];
|
|
25
|
+
else if (token === "--ecc") flags.ecc = argv[++i];
|
|
26
|
+
else if (token === "--version") flags.version = argv[++i];
|
|
27
|
+
else if (token === "--password") flags.password = argv[++i];
|
|
28
|
+
else if (token === "--key") flags.key = argv[++i];
|
|
29
|
+
else if (token === "--module-size") flags.moduleSize = Number(argv[++i]);
|
|
30
|
+
else if (token === "--quiet-zone") flags.quietZone = Number(argv[++i]);
|
|
31
|
+
else args.push(token);
|
|
32
|
+
}
|
|
33
|
+
return { args, flags };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const { args, flags } = parse(process.argv.slice(2));
|
|
38
|
+
if (flags.help || !args.length) {
|
|
39
|
+
help();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const command = args.shift();
|
|
44
|
+
if (command === "keygen") {
|
|
45
|
+
console.log(bytesToHex(generateRaw256Key()));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (command === "encode") {
|
|
50
|
+
const text = args.join(" ");
|
|
51
|
+
if (!text) throw new Error("encode requires text.");
|
|
52
|
+
if (flags.password && flags.key) throw new Error("Choose password mode or raw-key mode, not both.");
|
|
53
|
+
|
|
54
|
+
const options = {
|
|
55
|
+
ecc: flags.ecc || "M",
|
|
56
|
+
...(flags.version && flags.version !== "auto" ? { version: Number(flags.version) } : {})
|
|
57
|
+
};
|
|
58
|
+
const code = flags.password
|
|
59
|
+
? await encodeSecureText(text, { ...options, security: { mode: "password", password: flags.password } })
|
|
60
|
+
: flags.key
|
|
61
|
+
? await encodeSecureText(text, { ...options, security: { mode: "raw-key", key: flags.key } })
|
|
62
|
+
: encodeText(text, options);
|
|
63
|
+
|
|
64
|
+
const output = flags.output || "quadqr.png";
|
|
65
|
+
const saved = await savePNG(code, output, {
|
|
66
|
+
moduleSize: flags.moduleSize || 12,
|
|
67
|
+
quietZone: Number.isFinite(flags.quietZone) ? flags.quietZone : 4
|
|
68
|
+
});
|
|
69
|
+
console.log(`Saved ${output} (${saved.bytes} bytes, v${code.version}, ${code.size}x${code.size}, ECC ${code.eccLevel}).`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (command === "decode") {
|
|
74
|
+
const filename = args[0];
|
|
75
|
+
if (!filename) throw new Error("decode requires an image filename.");
|
|
76
|
+
let result = await scanFile(filename);
|
|
77
|
+
if (result.secure) {
|
|
78
|
+
if (!flags.password && !flags.key) {
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
secure: true,
|
|
81
|
+
mode: result.security?.mode,
|
|
82
|
+
algorithm: result.security?.algorithm,
|
|
83
|
+
keyId: result.security?.keyId || result.security?.keyIdHex || null,
|
|
84
|
+
requiresDecryption: true
|
|
85
|
+
}, null, 2));
|
|
86
|
+
process.exitCode = 2;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
result = await decryptDecoded(result, flags.password ? { password: flags.password } : { key: flags.key });
|
|
90
|
+
}
|
|
91
|
+
if (result.text != null) console.log(result.text);
|
|
92
|
+
else process.stdout.write(Buffer.from(result.payload));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw new Error(`Unknown command: ${command}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch((error) => {
|
|
100
|
+
console.error(`QuadQR: ${error.message}`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
});
|