Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
run: npm install --no-audit --no-fund

- name: Lint
run: npm run lint

- name: Test
run: npm test

- name: Package
run: npm run package

- uses: actions/upload-artifact@v4
with:
name: extension-zip
path: dist/*.zip
if-no-files-found: error
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vendor/
dist/
node_modules/
readme_imgs/
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
[semantic versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0]

A rewrite. The extension no longer talks to the internet.

### Changed

- **Decoding is now on-device.** v1 uploaded a screenshot of the user's tab to
`api.qrserver.com` for every scan. Decoding now uses the native Barcode Detection API where
Chrome offers it, falling back to a bundled copy of jsQR. Nothing is transmitted.
- **Permissions cut to `activeTab` and `storage`.** `<all_urls>` host permissions and the
`scripting` permission are gone; without a remote endpoint there is nothing to reach.
- **Redesigned popup** — warm paper-and-ink palette, serif headings, light/dark/system themes,
proper focus states, `prefers-reduced-motion` support and live-region status messages.
- **History rewritten.** Entries are keyed by id rather than array position, deduplicated,
timestamped with relative times, and capped at 100.
- Sources moved to `src/`, with decoding, payload parsing and persistence split into modules.

### Added

- Scan from an image file, a drag-and-drop, or the clipboard (<kbd>Ctrl</kbd>+<kbd>V</kbd>).
- Payload classification for links, email, phone, SMS, geo, Wi-Fi, contact cards and
authenticator keys, each with its own presentation.
- <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>Q</kbd> keyboard shortcut.
- Unit tests, ESLint, a Web Store packaging script, and CI running all three.
- Extension icons at every size Chrome asks for, generated from a committed SVG source.

### Fixed

- **Stored-payload injection.** Decoded text was written into the popup with `innerHTML`;
it now goes through `textContent` exclusively.
- **Unsafe navigation.** Any payload was openable, and non-URL text was silently prefixed with
`http://`. Only an allowlist of schemes is openable now, and `javascript:`, `data:`, `blob:`,
`file:` and `chrome:` payloads render as inert text.
- **Corrupt deletions.** Deleting a history entry indexed into a stale array, so removing one
row could delete a different one.
- An implicit global in `addLink` and a leftover unused `captureScreen` helper.
- The loading spinner was left visible when tab capture itself failed.

## [1.0.0]

- Initial release: capture the visible tab, decode via the qrserver.com web API, save results.
141 changes: 97 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,111 @@
# **QR Code Scanner** 📷
# QR Reader

![QR Code Scanner](https://img.shields.io/badge/QR--Scanner-v1.0-blue.svg?style=for-the-badge)
Scan QR codes directly from your browser screen with ease!
A Chrome extension that reads QR codes off your screen. Open the popup, hit **Scan this tab**,
and whatever code is visible on the page gets decoded — on your machine, with no network call.

## **Description**
QR Code Scanner is a Chrome extension that captures your current tab's visible screen, scans it for QR codes, and instantly displays the result. Perfect for quickly accessing URLs or other embedded information on QR codes without needing a mobile device.
<p>
<img alt="Manifest V3" src="https://img.shields.io/badge/Manifest-V3-a8492a?style=flat-square" />
<img alt="No network access" src="https://img.shields.io/badge/network-none-2f6a55?style=flat-square" />
<img alt="Zero runtime dependencies" src="https://img.shields.io/badge/runtime%20deps-0-2f6a55?style=flat-square" />
<img alt="MIT licence" src="https://img.shields.io/badge/licence-MIT-555?style=flat-square" />
</p>

## **Features**
- Capture the visible part of any tab in Chrome.
- Automatically scans for QR codes within the captured image.
- Displays the decoded text or URL in the popup.
- Saved scans for easy access and deletion when needed.
- Stylish, minimalistic popup design for a clean user experience.
| Light | Dark |
| -------------------------------------------------- | ------------------------------------------------ |
| ![Popup, light theme](readme_imgs/popup-light.png) | ![Popup, dark theme](readme_imgs/popup-dark.png) |

## **Screenshots**
| Feature | Screenshot |
|---------|------------|
| **Popup Interface** | ![Popup UI](https://github.com/devnsko/qrcode-reader-chrome-extension/blob/1c9f337477447606f7044d5653ba48a394995d2c/readme_imgs/screen-1.png) |
| **Scanning in Action** | ![Scanning Process](https://github.com/devnsko/qrcode-reader-chrome-extension/blob/1c9f337477447606f7044d5653ba48a394995d2c/readme_imgs/screen-2.png) |
## What it does

## **Installation**
1. Clone or download this repository.
2. Open Chrome and navigate to `chrome://extensions/`.
3. Enable **Developer mode** (top right corner).
4. Click **Load unpacked** and select the folder containing this project.
- **Scan the visible tab.** One click captures the viewport and finds the code in it.
- **Scan an image.** Drop a file on the popup, pick one with **Image…**, or press <kbd>Ctrl</kbd>+<kbd>V</kbd>
to scan whatever is on your clipboard.
- **Understands the payload.** Links, email addresses, phone numbers, SMS, geo coordinates,
Wi-Fi credentials, contact cards and authenticator keys each get their own presentation
instead of a wall of raw text.
- **Keeps a history.** The last 100 scans, newest first, each openable, copyable or removable.
- **Light, dark, or follow the system.** Toggle in the top-right corner.

Now, the extension is installed and ready to use!
## Privacy

## **Usage**
1. Open the extension by clicking the QR Code Scanner icon in your Chrome toolbar.
2. Click **Scan QR Code**.
3. Wait for the capture and decoding process.
4. Your scanned result will appear in the popup, with an option to save or delete it.
Decoding happens entirely in the extension. There is no analytics, no telemetry, no remote
endpoint — the manifest requests no host permissions at all, so the extension is not capable
of making one. A screenshot of your tab never leaves your computer.

## **Project Structure**
- **background.js**: Handles screen capture and communicates with the popup.
- **manifest.json**: Manages permissions and configuration for Chrome.
- **popup.html / popup.js**: The UI and main logic for scanning and displaying QR code results.
Two details that follow from taking that seriously:

## **Contributing**
Contributions are welcome! Feel free to submit a pull request with bug fixes, enhancements, or new features.
- History lives in `chrome.storage.local`, never `sync`, because a scan list can contain
Wi-Fi passwords.
- A `WIFI:` payload shows the network name but not the password, and an `otpauth:` payload
shows the account but not the secret. Both are still on the clipboard if you press **Copy**.

1. Fork this repository.
2. Create a new branch (`git checkout -b feature/YourFeature`).
3. Commit your changes (`git commit -m 'Add some feature'`).
4. Push to the branch (`git push origin feature/YourFeature`).
5. Open a pull request.
## Install

## **License**
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
**From a clone (developer mode)**

## **Credits**
Created by [Dev.nsko](https://github.com/devnsko).
```bash
git clone https://github.com/devnsko/qrcode-reader-chrome-extension.git
```

---
1. Open `chrome://extensions/`.
2. Turn on **Developer mode** (top right).
3. Click **Load unpacked** and choose the cloned folder.

### Enjoy seamless QR code scanning from your desktop browser!
No build step — the repository is loadable as-is. Pin the extension to your toolbar and it is
reachable with <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>Q</kbd>.

## How it works

```
manifest.json MV3 config: activeTab + storage, and nothing else
src/popup.html/.css the popup shell and its warm-paper design system
src/popup.js controller — capture, drop, paste, render
src/decoder.js two decode engines behind one function
src/payload.js payload classification and URL vetting
src/history.js chrome.storage.local persistence
vendor/jsqr.min.js jsQR 1.4.0, bundled (Apache-2.0)
```

**Decoding** tries the native [Barcode Detection API][bda] first: Chrome does the work
natively, which is fast even on a 4K screenshot. Where that API is missing or comes up empty,
`decoder.js` lazily imports jsQR and retries at several scales with inversion enabled —
downscaling rescues noisy screenshots, upscaling rescues codes that are only a few dozen
pixels across. The jsQR bundle is only fetched into memory when that fallback is actually
needed.

**Permissions** are deliberately small. `activeTab` grants a screenshot of the current tab
for the moment you click the toolbar icon, and nothing more; `storage` holds the history and
your theme choice. The v1 of this project sent every screenshot to a third-party web API and
requested `<all_urls>` to do it — both are gone.

**Untrusted input.** A QR code is something a stranger can print and tape to a wall, so
`payload.js` treats every payload as hostile: nothing reaches the DOM except through
`textContent`, and only `http`, `https`, `mailto`, `tel`, `sms` and `geo` URLs are ever
openable. A `javascript:` or `data:` payload is displayed as inert text. Those rules are what
`test/payload.test.js` exists to pin down.

[bda]: https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API

## Development

```bash
npm install # tooling only — the extension itself ships zero runtime dependencies
npm test # unit tests for payload parsing and URL vetting
npm run lint # ESLint, flat config
npm run check # lint + test, same as CI
npm run package # dist/qr-reader-<version>.zip, ready for the Web Store
npm run icons # re-render icons/icon-*.png from icons/icon.svg
```

CI runs lint, tests and the packaging step on every push and pull request.

## Contributing

Issues and pull requests are welcome. Please run `npm run check` before opening one, and add a
test to `test/payload.test.js` if you touch how payloads are parsed or vetted.

## Licence

MIT — see [LICENSE](LICENSE). Bundled jsQR is Apache-2.0; its licence is kept alongside the
bundle at [`vendor/jsqr.LICENSE.txt`](vendor/jsqr.LICENSE.txt).

Built by [Dev.nsko](https://github.com/devnsko).
8 changes: 0 additions & 8 deletions background.js

This file was deleted.

36 changes: 36 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import js from '@eslint/js';
import globals from 'globals';

export default [
{
ignores: ['vendor/**', 'dist/**', 'node_modules/**'],
},
js.configs.recommended,
{
files: ['src/**/*.js'],
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
globals: {
...globals.browser,
chrome: 'readonly',
OffscreenCanvas: 'readonly',
BarcodeDetector: 'readonly',
},
},
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
eqeqeq: ['error', 'smart'],
'no-implicit-globals': 'error',
'prefer-const': 'error',
},
},
{
files: ['scripts/**/*.mjs', 'test/**/*.js', 'eslint.config.js'],
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
globals: globals.node,
},
},
];
Binary file added icons/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/icon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/icon-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions icons/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 34 additions & 14 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
{
"manifest_version": 3,
"name": "QR Code Scanner",
"version": "1.0",
"description": "Scanning QR codes directly from the browser screen",
"permissions": ["activeTab", "scripting", "storage"],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
},
"host_permissions": [
"<all_urls>"
]
"manifest_version": 3,
"name": "QR Reader — scan codes on screen",
"short_name": "QR Reader",
"version": "2.0.0",
"description": "Scan any QR code on the page, from an image file, or from your clipboard. Decoding happens on your device — nothing is uploaded.",
"minimum_chrome_version": "116",
"permissions": ["activeTab", "storage"],
"action": {
"default_popup": "src/popup.html",
"default_title": "Scan QR code (Alt+Shift+Q)",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Alt+Shift+Q"
},
"description": "Open QR Reader and scan the current tab"
}
},
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; base-uri 'none'"
}
}
Loading
Loading