Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@lo-fi/qr-data-sync
Advanced tools
Browser-only utils for sharing/synchronizing data using 'animated' QR codes
QR-Data-Sync is a browser library with utils for sharing/synchronizing data via "animated" QR codes.
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".
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.
This is what QR Data Sync library supports.
npm install @lo-fi/qr-data-sync
The @lo-fi/qr-data-sync npm package includes a dist/
directory with all files you need to deploy qr-data-sync (and its dependencies) into your application/project.
If you obtain this library via git instead of npm, you'll need to build dist/
manually before deployment.
USING A WEB BUNDLER? (Astro, Vite, Webpack, etc) Use the dist/bundlers/*
files and see Bundler Deployment for instructions.
Otherwise, use the dist/auto/*
files and see Non-Bundler Deployment for instructions.
To generate an "animated" QR code (cycle of frames) that sends data, use send()
:
import { send } from "..";
var sendData = { /* ... */ };
var qrCodeIDOrElement = /* ... */;
var sendOptions = { /* ... */ };
var sendStarted = await send(
sendData,
qrCodeIDOrElement,
sendOptions
);
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.
To configure the sending operation, the first two arguments are required:
sendData
(any): Any JSON-compatible primitive value (string, number, boolean), or an object/array that can be properly serialized to JSON
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).
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).
For example, depending on the layout of the page, it may be useful to set the aspect-ratio
on the element like this:
#qr-codes {
width: 100%;
aspect-ratio: 1 / 1;
}
Optional configuration can be passed in as properties of an object, as the third argument (sendOptions
above):
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").
Arguments to the callback are:
frameIndex
(integer): The zero-based QR code frame index in the current frame-set
frameCount
(integer): The length of the current frame-set
frameTextChunk
(string): The chunk of string text (from the data being sent) in the current QR frame.
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).
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.
signal
(AbortSignal): an AbortController.signal
instance to stop/cancel the sending "animation".
import { send } from "..";
var cancelToken = new AbortController();
var sendStarted = await send( /* .. */, { signal: cancelToken.signal });
// later, to stop the QR code animation rendering:
cancelToken.abort("Done sending!");
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.
maxFramesPerSecond
(integer): Controls the attempted time interval for animating QR code frames. The default is 7
, and the allowed range is 2
- 13
.
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.
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
.
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).
qrCodeSize
(integer): the size (in logical pixels) to render the QR code width/height. The minimum value, if specified, is 150
.
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.
To scan an "animated" QR code (cycle of frames) that receives data, use receive()
:
import { receive } from "..";
var videoIDorElement = /* ... */;
var receiveOptions = { /* ... */ };
var receiveResult = await receive(
videoIDorElement,
receiveOptions
);
The receive()
call returns a promise that will resolve to an object (receiveResult
above) 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.
To configure the receiving operation, the first arguments is required:
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).
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).
Optional configuration can be passed in as properties of an object, as the second argument (receiveOptions
above):
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").
Arguments to the callback are:
framesRead
(integer): How many frames have been read from the current frame-set
frameCount
(integer): The length of the current frame-set
frameIndex
(integer): The zero-based frame index in the current frame-set
frameTextChunk
(string): The chunk of string text (from the data being received) in the current QR frame.
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).
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.
signal
(AbortSignal): an AbortController.signal
instance to stop/cancel the receive scanning.
import { receive } from "..";
var cancelToken = new AbortController();
var receiveResult = await receive( /* .. */, { signal: cancelToken.signal });
// later, to stop the QR code scanning:
cancelToken.abort("Cancel scanning!");
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.
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
.
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.
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 such as "user"
(e.g., the forward facing camera on a phone or laptop).
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
.
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
).
If receive()
completes completes successfully, the return value (receiveResult
above) will be an object that includes these properties:
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.
frameCount
(integer): How many frames were read in the frame-set
dataSetID
(string): A 5-character (hexadecimal) ID that uniquely identifies the frame-set that was read.
dist/*
If you need to rebuild the dist/*
files for any reason, run:
# only needed one time
npm install
npm run build:all
Since the library involves non-automatable behaviors (requiring user intervention on two devices at once), an automated unit-test suite is not included. Instead, a simple interactive browser test page is provided, to run on both devices.
Visit https://mylofi.github.io/qr-data-sync/
on each device, and follow instructions in-page from there to perform the interactive tests.
Note: You will need two devices to test, and at least one needs a camera.
To locally run the tests, start the simple static server (no server-side logic):
# only needed one time
npm install
npm run test:start
Then visit http://localhost:8080/
in a browser.
All code and documentation are (c) 2024 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.
FAQs
Browser-only utils for sharing/synchronizing data using 'animated' QR codes
We found that @lo-fi/qr-data-sync demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.