Skip to content

Commit 25a42f9

Browse files
committed
adding README docs, fixing a variety of bugs and poor API design decisions
1 parent 368f86b commit 25a42f9

File tree

6 files changed

+314
-115
lines changed

6 files changed

+314
-115
lines changed

README.md

Lines changed: 169 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,195 @@
11
# QR Data Sync
22

3-
[![npm Module](https://badge.fury.io/@lofi%2fqr-data-sync.svg)](https://www.npmjs.org/package/@lofi/qr-data-sync)
3+
[![npm Module](https://badge.fury.io/js/@lo-fi%2fqr-data-sync.svg)](https://www.npmjs.org/package/@lo-fi/qr-data-sync)
44
[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)
55

6-
**QR Data Sync** is a browser library with utils for sharing/synchronizing data via "animated" QR codes.
6+
**QR-Data-Sync** is a browser library with utils for sharing/synchronizing data via "animated" QR codes.
77

88
----
99

1010
[Demo/Tests](https://mylofi.github.io/qr-data-sync/)
1111

1212
----
1313

14+
Any set of data that can be serialized into a string -- such as an object that's JSON-serializable -- can be broken up into a series of QR codes (of equal length, with padding for visual consistency), which can then be displayed in rapid succession, as if the code is "animated".
15+
16+
A camera on another device, with a scanning library designed to read multiple QR codes in succession, can then read these animated QR code "frames", and re-assemble the original data set on the other device.
17+
18+
This is what **QR Data Sync** library supports.
19+
1420
## Deployment / Import
1521

1622
```cmd
17-
npm install @lofi/qr-data-sync
23+
npm install @lo-fi/qr-data-sync
1824
```
1925

20-
The [**qr-data-sync** npm package](https://npmjs.com/package/@lofi/webauthn-local-client) includes a `dist/` directory with all files you need to deploy **qr-data-sync** (and its dependencies) into your application/project.
26+
The [**@lo-fi/qr-data-sync** npm package](https://npmjs.com/package/@lo-fi/webauthn-local-client) includes a `dist/` directory with all files you need to deploy **qr-data-sync** (and its dependencies) into your application/project.
2127

2228
If you obtain this library via git instead of npm, you'll need to [build `dist/` manually](#re-building-dist) before deployment.
2329

2430
* **USING A WEB BUNDLER?** (Vite, Webpack, etc) Use the `dist/bundlers/*` files and see [Bundler Deployment](BUNDLERS.md) for instructions.
2531

2632
* Otherwise, use the `dist/auto/*` files and see [Non-Bundler Deployment](NON-BUNDLERS.md) for instructions.
2733

34+
## Sending Data (generating "animated" QR codes)
35+
36+
To generate an "animated" QR code (cycle of frames) that *sends* data, use `send()`:
37+
38+
```js
39+
import { send } from "...";
40+
41+
var sendData = { /* ... */ };
42+
var qrCodeIDOrElement = /* ... */;
43+
var sendOptions = { /* ... */ };
44+
45+
var sendStarted = await send(
46+
sendData,
47+
qrCodeIDOrElement,
48+
sendOptions
49+
);
50+
```
51+
52+
The `send()` call returns a promise that will resolve to `true` if the QR code generation starts successfully (rendering the first frame). Otherwise, it will be rejected (`await` will throw) if sending fails to start, or if the `signal` option cancels the sending before it starts.
53+
54+
### Send Configuration
55+
56+
To configure the sending operation, the first two arguments are *required*:
57+
58+
* `sendData` (any): Any JSON-compatible primitive value (string, number, boolean), or an object/array that can be properly serialized to JSON
59+
60+
* `qrCodeIDOrElement` (string or DOM element): Either a string representing the ID of a DOM element, or the DOM element itself, where the QR codes can be rendered (into the element, as child elements).
61+
62+
This DOM element should be styled (via CSS, etc) to ensure that it's properly sized for visibility (as big as is practical for the screen), and should be square (same width and height).
63+
64+
For example, depending on the layout of the page, it may be useful to set the `aspect-ratio` on the element like this:
65+
66+
```css
67+
#qr-codes {
68+
width: 100%;
69+
aspect-ratio: 1 / 1;
70+
}
71+
```
72+
73+
Optional configuration can be passed in as properties of an object, as the third argument (`sendOptions` above):
74+
75+
* `onFrameRendered` (function): a callback to receive information for each render of a QR code frame. This is strongly suggested UX-wise for displaying a frame counter (e.g., "Frame 3 / 11").
76+
77+
Arguments to the callback are:
78+
79+
- `frameIndex` (integer): The zero-based QR code frame index in the current frame-set
80+
81+
- `frameCount` (integer): The length of the current frame-set
82+
83+
- `frameTextChunk` (string): The chunk of string text (from the data being sent) in the current QR frame.
84+
85+
**Note:** This value does *not* include the header data included in the QR code itself (i.e., `:{frameEncodingVersion}:{dataSetID}:{frameIndex}:{frameCount}:{frameTextChunk}` -- the raw frame encoding format is subject to change in future versions of the library).
86+
87+
- `dataSetID` (string): A 5-character (hexadecimal) ID that uniquely identifies the frame-set currently being displayed. If the another `send()` is invoked (canceling a previous frameset rendering), this value will change, and any stored data associated with a previous ID should be discarded.
88+
89+
* `signal` (AbortSignal): an [`AbortController.signal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) instance to stop/cancel the sending "animation".
90+
91+
```js
92+
import { send } from "...";
93+
94+
var cancelToken = new AbortController();
95+
var sendStarted = await send( /* .. */, { signal: cancelToken.signal });
96+
97+
// later, to stop the QR code animation rendering:
98+
cancelToken.abort("Done sending!");
99+
```
100+
101+
**Note:** While `signal` is technically *optional*, it really should be passed in, as it's the only way to cleanly (without JS exceptions) stop the internal QR code generation/animation processing.
102+
103+
* `maxFramesPerSecond` (integer): Controls the attempted time interval for animating QR code frames. The default is `7`, and the allowed range is `2` - `13`.
104+
105+
**Note:** This setting is only a target maximum, but actual animation rates may be lower depending on CPU processing of QR code generation, etc; it really should not be changed unless there are issues with receiving QR code frames.
106+
107+
* `frameTextChunkSize` (integer): Length of each frame text chunk (with space padding to ensure visual consistency between each QR code). The default is `50`, and the allowed range is `25` - `150`.
108+
109+
**Note:** This setting should really not be changed unless there are issues with receiving QR code frames (i.e., too small, cameras resolution missing frames, etc).
110+
111+
* `qrCodeSize` (integer): the size (in logical pixels) to render the QR code width/height. The minimum value, if specified, is `150`.
112+
113+
**Note:** This setting is not responsive (CSS wise), so it really shouldn't be used *unless* the CSS styling of the QR code element is not possible/sufficient.
114+
115+
## Receiving Data (scanning "animated" QR codes)
116+
117+
To scan an "animated" QR code (cycle of frames) that *receives* data, use `receive()`:
118+
119+
```js
120+
import { receive } from "...";
121+
122+
var videoIDorElement = /* ... */;
123+
var receiveOptions = { /* ... */ };
124+
125+
var receiveResult = await receive(
126+
videoIDorElement,
127+
receiveOptions
128+
);
129+
```
130+
131+
The `receive()` call returns a promise that will resolve to an object ([`receiveResult` above](#receive-result)) if QR code scanning completes successfully. Otherwise, it will be rejected (`await` will throw) if scanning experiences an issue, or if the `signal` option cancels the receiving before it completes.
132+
133+
### Receive Configuration
134+
135+
To configure the receiving operation, the first arguments is *required*:
136+
137+
* `videoIDorElement` (string or DOM element): Either a string representing the ID of a `<video>` DOM element, or the DOM element itself, where the camera scanner can be rendered for the user to see (for QR code alignment).
138+
139+
This DOM element should be styled (via CSS, etc) to ensure that it's properly sized for visibility (as big as is practical for the screen).
140+
141+
Optional configuration can be passed in as properties of an object, as the second argument (`receiveOptions` above):
142+
143+
* `onFrameReceived` (function): a callback to receive information for each scanned QR code frame. This is strongly suggested UX-wise for displaying a frame-read counter (e.g., "Frames Read 3 / 11").
144+
145+
Arguments to the callback are:
146+
147+
- `framesRead` (integer): How many frames have been read from the current frame-set
148+
149+
- `frameCount` (integer): The length of the current frame-set
150+
151+
- `frameIndex` (integer): The zero-based frame index in the current frame-set
152+
153+
- `frameTextChunk` (string): The chunk of string text (from the data being received) in the current QR frame.
154+
155+
**Note:** This value does *not* include the header data included in the QR code itself (i.e., `:{frameEncodingVersion}:{dataSetID}:{frameIndex}:{frameCount}:{frameTextChunk}` -- the raw frame encoding format is subject to change in future versions of the library).
156+
157+
- `dataSetID` (string): A 5-character (hexadecimal) ID that uniquely identifies the frame-set currently being received. If this value changes changes while receiving, it means the sent data changed, and any stored data associated with a previous ID should be discarded.
158+
159+
* `signal` (AbortSignal): an [`AbortController.signal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) instance to stop/cancel the receive scanning.
160+
161+
```js
162+
import { receive } from "...";
163+
164+
var cancelToken = new AbortController();
165+
var receiveResult = await receive( /* .. */, { signal: cancelToken.signal });
166+
167+
// later, to stop the QR code scanning:
168+
cancelToken.abort("Cancel scanning!");
169+
```
170+
171+
**Note:** While `signal` is technically *optional*, it really should be passed in, as it's the only way to cleanly (without JS exceptions) stop the QR code scanning and release the device's camera.
172+
173+
* `maxScansPerSecond` (integer): Controls how many QR code frames will be attempted to scan per second. The default is `10`, and the allowed range is `2` - `13`.
174+
175+
**Note:** This setting is only a target maximum, but actual scanning rates may be lower depending on CPU processing of QR code decoding, etc; it really should not be changed unless there are issues with receiving QR code frames.
176+
177+
* `preferredCamera` (string): Controls which camera (if multiple on a device) is preferred/requested. Defaults to `"environment"` (e.g., the outward facing camera on the back of a phone), but [can have other values](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/facingMode#videofacingmodeenum) such as `"user"` (e.g., the forward facing camera on a phone or laptop).
178+
179+
* `highlightScanRegion` (boolean): Controls whether the yellow/orange brackets are rendered over the camera feed, to hint the user on where to align the QR codes for scanning. Defaults to `true`.
180+
181+
### Receive Result
182+
183+
`receive()` returns a promise that's resolved once a full frame-set is successfully read. Otherwise, it will be rejected (`await` will throw) if a scan error occurs, or the receiving operation is canceled (with `signal`).
184+
185+
If `receive()` completes completes successfully, the return value (`receiveResult` above) will be an object that includes these properties:
186+
187+
* `data` (any): The received data, assumed to be a JSON-compatible value that's automatically `JSON.parse()`d. If parsing fails, the full raw string received will be returned.
188+
189+
* `frameCount` (integer): How many frames were read in the frame-set
190+
191+
* `dataSetID` (string): A 5-character (hexadecimal) ID that uniquely identifies the frame-set that was read.
192+
28193
## Re-building `dist/*`
29194

30195
If you need to rebuild the `dist/*` files for any reason, run:

bundler-plugins/vite.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import fs from "node:fs";
33
import fsp from "node:fs/promises";
44

55

6+
// ********************************
7+
68
export default QRDS;
79

810

@@ -21,7 +23,7 @@ function QRDS() {
2123

2224
async configResolved(cfg) {
2325
config = cfg;
24-
var bundlersDir = path.join(config.root,"node_modules","qr-data-sync","dist","bundlers");
26+
var bundlersDir = path.join(config.root,"node_modules","@lo-fi","qr-data-sync","dist","bundlers");
2527
qrdsSrcPath = path.join(bundlersDir,"qrds.js");
2628
externalBundleSrcPath = path.join(bundlersDir,"qrds-external-bundle.js");
2729
externalBundleDestPath = (

bundler-plugins/webpack.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
// References:
2+
// https://github.com/principalstudio/html-webpack-inject-preload
3+
// https://github.com/icelam/html-inline-script-webpack-plugin
4+
15
import path from "node:path";
26
import fs from "node:fs";
37
import fsp from "node:fs/promises";
48

59

10+
// ********************************
11+
612
export default QRDS;
713

814

915
// ********************************
1016

11-
// References:
12-
// https://github.com/principalstudio/html-webpack-inject-preload
13-
// https://github.com/icelam/html-inline-script-webpack-plugin
14-
1517
function WALC() {
1618
var options;
1719
var qrdsSrcPath;
@@ -25,7 +27,7 @@ function WALC() {
2527
compiler.hooks.beforeRun.tap(pluginName,compiler => {
2628
options = compiler.options;
2729

28-
var bundlersDir = path.join(options.context,"node_modules","qr-data-sync","dist","bundlers");
30+
var bundlersDir = path.join(options.context,"node_modules","@lo-fi","qr-data-sync","dist","bundlers");
2931
qrdsSrcPath = path.join(bundlersDir,"qrds.js");
3032
externalBundleSrcPath = path.join(bundlersDir,"qrds-external-bundle.js");
3133
externalBundleDestPath = path.join(options.output.path,path.basename(externalBundleSrcPath));

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "qr-data-sync",
33
"description": "Browser-only utils for sharing/synchronizing data using 'animated' QR codes",
4-
"version": "0.0.0-a",
4+
"version": "0.999.0",
55
"exports": {
66
".": "./dist/bundlers/qrds.mjs",
77
"./bundlers/vite": "./bundler-plugins/vite.mjs",

0 commit comments

Comments
 (0)