Compare commits

...

34 Commits

Author SHA1 Message Date
yugasun cfd924beab 0.3.4 1 year ago
yugasun 8fb5b98ea2 fix: add decode code result type 1 year ago
yugasun 9fa47676df 0.3.3 3 years ago
dyoshikawa 0b7edb39df
fix:type def of decodeFromImage() param (#23) 3 years ago
yugasun 8afbcdad33 chore: fix test ci 3 years ago
yugasun 0f2e84268c chore: add pnpm version 3 years ago
yugasun 33f115036f chore: update readme & ci 3 years ago
yugasun 17ced673e4 0.3.2 3 years ago
yugasun c236e43f21 chore: add type definition 3 years ago
yugasun e6fb08352f docs: update readme 3 years ago
yugasun b9d89e0545 0.3.1 3 years ago
yugasun aeee33ee6c chore: add unpkg browser field 3 years ago
yugasun a5cd17b951 0.3.0 3 years ago
yugasun 615081cc95 fix: support customize camera size 3 years ago
yugasun 7d77236fc0 fix: try to use rear camera by default 3 years ago
yugasun 8c80e312b3 0.2.2 4 years ago
yugasun 6ab5e80028 fix: remove unuse file 4 years ago
yugasun 05b1c0d310 0.2.1 4 years ago
Lőrik Levente 4161b77272
fix: re add module property (#16)
* Fix typo

* Fix require

* Remove deprecated nextjs

* Update package.json

* Update package.json
4 years ago
yugasun be1f6a980b chore: fix tsconfig 4 years ago
yugasun 44737ddfe7 0.2.0 4 years ago
yugasun 032f9ba16a chore: update deploy to pages 4 years ago
yugasun d1a5b9de6b chore: update demo 4 years ago
yugasun fd6daa86a1 chore: fix deps 4 years ago
yugasun b8d25e8969 docs: update readme 4 years ago
yugasun 455b0deb25 0.1.3 4 years ago
yugasun d77b666da2 fix: crossOrigin on IOS, close #14 4 years ago
yugasun 32c08b7599 chore: update package version 4 years ago
yugasun 900b451788 0.1.2 4 years ago
Lőrik Levente 2b19762d21
fix: typo & require (#13)
* Fix typo

* Fix require

* Remove deprecated nextjs
4 years ago
yugasun d55e4ddcef feat: refactor with ts 5 years ago
dependabot[bot] 2737d1970b build(deps): bump elliptic from 6.5.1 to 6.5.3
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.1 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.1...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
5 years ago
yugasun dfccc2f09a fix: add vconsole for mobile debug 6 years ago
yugasun 0996ab57ad fix: yarn.lock dependencies version 6 years ago

@ -1,22 +1,71 @@
{ {
"presets": [ "presets": [
["env", [
{ "@babel/env",
"targets": { {
"browsers": "last 2 versions, > 1%, ie >= 11", "modules": false,
"node": "8.0" "targets": {
}, "browsers": [
"modules": false, ">0.25%"
"loose": false ]
}], },
"stage-2" "useBuiltIns": "usage"
}
],
"@babel/typescript"
], ],
"env": {
"development": {
"plugins": [
"@babel/transform-runtime"
]
},
"production": {
"plugins": [
"@babel/transform-runtime",
"transform-remove-console"
]
},
"test": {
"presets": [
[
"@babel/env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
],
"@babel/typescript"
]
}
},
"plugins": [ "plugins": [
["transform-runtime", { "@babel/plugin-syntax-dynamic-import",
"helpers": false, "@babel/plugin-syntax-import-meta",
"polyfill": false, "@babel/plugin-proposal-class-properties",
"regenerator": false, "@babel/plugin-proposal-json-strings",
"moduleName": "babel-runtime" [
}] "@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-proposal-optional-chaining",
[
"@babel/plugin-proposal-pipeline-operator",
{
"proposal": "minimal"
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions"
] ]
} }

@ -1,12 +1,7 @@
root = true root = true
[*]
[{*.js,*.css,*.html}]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
insert_final_newline = true insert_final_newline = true
[*.{js,ts}]
[{package.json,.*rc,*.yml}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
charset = utf-8

@ -1,45 +0,0 @@
module.exports = {
root: true,
parserOptions: {
sourceType: 'module',
},
env: {
browser: true,
es6: true,
node: true,
},
extends: ['airbnb-base'],
rules: {
'no-shadow': [
'error',
{
allow: ['state'],
},
],
// 'import/extensions': 'off',
'import/extensions': [
'error',
'always',
{
js: 'never',
},
],
'import/no-unresolved': 'off',
'no-param-reassign': 'off',
'consistent-return': 'off',
'global-require': 'off',
'import/no-dynamic-require': 'off',
'import/no-extraneous-dependencies': 'off',
// 4 行空格缩进
indent: ['error', 4, { SwitchCase: 1 }],
'max-len': ['error', { code: 150 }],
'operator-linebreak': 0,
},
};

@ -0,0 +1,108 @@
name: CI
env:
NODE_OPTIONS: --max-old-space-size=6144
# install playwright binary manually (because pnpm only runs install script once)
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
on:
push:
branches:
- main
- master
- release/*
- feat/*
- fix/*
- perf/*
- v1
- v2
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
cancel-in-progress: true
jobs:
build:
timeout-minutes: 20
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node_version: [16]
fail-fast: false
name: "Build&Test: node-${{ matrix.node_version }}, ${{ matrix.os }}"
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
- name: Set node version to ${{ matrix.node_version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
cache: "pnpm"
- name: Install deps
run: pnpm install
- name: Build
run: pnpm run build
test:
timeout-minutes: 10
runs-on: ubuntu-latest
name: "Lint: node-16, ubuntu-latest"
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
- name: Set node version to 16
uses: actions/setup-node@v3
with:
node-version: 16
cache: "pnpm"
- name: Install deps
run: pnpm install
- name: Build
run: pnpm run build
- name: Test
run: pnpm run test
lint:
timeout-minutes: 10
runs-on: ubuntu-latest
name: "Lint: node-16, ubuntu-latest"
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
- name: Set node version to 16
uses: actions/setup-node@v3
with:
node-version: 16
cache: "pnpm"
- name: Install deps
run: pnpm install
- name: Build
run: pnpm run build
- name: Check formatting
run: pnpm run prettier

12
.gitignore vendored

@ -1,3 +1,13 @@
node_modules node_modules
.DS_STORE
lerna-debug.log
yarn-error.log
dist dist
.DS_Store includes
tsconfig.tsbuildinfo
.env
coverage
yarn.lock
.rpt2_cache
demo/lib/index.min.js

@ -1,11 +1,29 @@
node_modules node_modules
config *.map
demo tests
src
test test
.babelrc __tests__
__test__
.github
.env*
jest.config.js
.eslintrc.js
.eslintignore
.prettierignore
prettier.config.js
release.config.js
commitlint.config.js
.editorconfig .editorconfig
.travis.yml lerna.json
CHANGELOG.md tsconfig.tsbuildinfo
package-lock.json tsconfig.json
TODO.md tsconfig.base.json
.rpt2_cache
.vscode
/demo
/doc
/scripts
tslint.json
yarn-error.log
.babelrc
yarn.lock

@ -0,0 +1,3 @@
shamefully-hoist=true
strict-peer-dependencies=true
auto-install-peers=true

@ -0,0 +1,9 @@
# Development folders and files #
#################################
.tmp/
node_modules/
package.json
.travis.yml
dist

@ -0,0 +1,14 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"overrides": [
{
"files": "*.json",
"options": {
"parser": "json"
}
}
]
}

@ -1,3 +0,0 @@
language: node_js
node_js:
- "8"

@ -1 +0,0 @@
# Change Log

@ -1,8 +1,8 @@
# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder) # [qrcode-decoder](https://github.com/yugasun/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE) [![npm](https://img.shields.io/npm/v/qrcode-decoder)](http://www.npmtrends.com/qrcode-decoder)
[![Build Status](https://travis-ci.org/yugasun/qrcode-decoder.svg?branch=master)](https://travis-ci.org/yugasun/qrcode-decoder)
[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder) [![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE)
[简体中文](./README.zh-CN.md) | English [简体中文](./README.zh-CN.md) | English
@ -23,10 +23,10 @@ A tool for decoding qrcode.
## Guide ## Guide
Use `npm` to install. Use `pnpm` to install.
```bash ```bash
$ npm install --save qrcode-decoder $ pnpm install --save qrcode-decoder
``` ```
Using in webpack: Using in webpack:
@ -38,7 +38,7 @@ import QrcodeDecoder from 'qrcode-decoder';
Using in browser: Using in browser:
```html ```html
<script src="node_modules/qrcode-decoder/dist/index.aio.js"></script> <script src="https://unpkg.com/qrcode-decoder@0.3.1/dist/index.min.js"></script>
``` ```
## Demo ## Demo
@ -57,7 +57,7 @@ Decodes an image from url or an `<img>` element with a `src` attribute set.
```javascript ```javascript
qr.decodeFromImage(img).then((res) => { qr.decodeFromImage(img).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -69,7 +69,7 @@ Decodes directly from a video with a well specified `src` attribute
```javascript ```javascript
qr.decodeFromVideo(videoElement).then((res) => { qr.decodeFromVideo(videoElement).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -81,7 +81,7 @@ Decodes from a videoElement.
```javascript ```javascript
qr.decodeFromCamera(videoElem).then((res) => { qr.decodeFromCamera(videoElem).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -96,31 +96,31 @@ Stops the current `qr` from searching for a QRCode.
Install dependencies: Install dependencies:
```bash ```bash
$ npm install $ pnpm install
``` ```
Build code: Build code:
```bash ```bash
$ npm run build $ pnpm run build
``` ```
Run unit test: Run unit test:
```bash ```bash
$ npm test $ pnpm test
``` ```
Modify version in `package.json`, run `release` script: Modify version in `package.json`, run `release` script:
```bash ```bash
$ npm run release $ pnpm run release
``` ```
Publish Publish
```bash ```bash
$ npm publish $ pnpm publish
``` ```
## License ## License

@ -1,8 +1,8 @@
# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder) # [qrcode-decoder](https://github.com/yugasun/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE) [![npm](https://img.shields.io/npm/v/qrcode-decoder)](http://www.npmtrends.com/qrcode-decoder)
[![Build Status](https://travis-ci.org/yugasun/qrcode-decoder.svg?branch=master)](https://travis-ci.org/yugasun/qrcode-decoder)
[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder) [![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE)
简体中文 | [English](./README.md) 简体中文 | [English](./README.md)
@ -23,10 +23,10 @@
## 使用者指南 ## 使用者指南
通过 npm 下载安装代码 通过 pnpm 下载安装代码
```bash ```bash
$ npm install --save qrcode-decoder $ pnpm install --save qrcode-decoder
``` ```
如果你是 webpack 等环境 如果你是 webpack 等环境
@ -38,7 +38,7 @@ import QrcodeDecoder from 'qrcode-decoder';
如果你是浏览器环境 如果你是浏览器环境
```html ```html
<script src="node_modules/qrcode-decoder/dist/index.aio.js"></script> <script src="https://unpkg.com/qrcode-decoder@0.3.1/dist/index.min.js"></script>
``` ```
## 示例 ## 示例
@ -57,7 +57,7 @@ var qr = new QrcodeDecoder();
```javascript ```javascript
qr.decodeFromImage(img).then((res) => { qr.decodeFromImage(img).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -69,7 +69,7 @@ qr.decodeFromImage(img).then((res) => {
```javascript ```javascript
qr.decodeFromVideo(videoElement).then((res) => { qr.decodeFromVideo(videoElement).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -81,7 +81,7 @@ qr.decodeFromVideo(videoElement).then((res) => {
```javascript ```javascript
qr.decodeFromCamera(videoElem).then((res) => { qr.decodeFromCamera(videoElem).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -96,31 +96,31 @@ qr.decodeFromCamera(videoElem).then((res) => {
首次运行需要先安装依赖 首次运行需要先安装依赖
```bash ```bash
$ npm install $ pnpm install
``` ```
一键打包生成生产代码 一键打包生成生产代码
```bash ```bash
$ npm run build $ pnpm run build
``` ```
运行单元测试,浏览器环境需要手动测试,位于`test/browser` 运行单元测试,浏览器环境需要手动测试,位于`test/browser`
```bash ```bash
$ npm test $ pnpm test
``` ```
修改 package.json 中的版本号,修改 README.md 中的版本号,修改 CHANGELOG.md然后发布新版 修改 package.json 中的版本号,修改 README.md 中的版本号,修改 CHANGELOG.md然后发布新版
```bash ```bash
$ npm run release $ pnpm run release
``` ```
将新版本发布到 npm 将新版本发布到 pnpm
```bash ```bash
$ npm publish $ pnpm publish
``` ```
## License ## License

@ -2,4 +2,4 @@
Rewrite unit test. Rewrite unit test.
- [] Rewrite unit test. - [x] Rewrite unit test.

@ -0,0 +1,26 @@
import QrcodeDecoder from '../';
describe('QrcodeDecoder', () => {
test('use', async () => {
const res = await import('../dist');
expect(res.default).toEqual(QrcodeDecoder);
});
test('new QrcodeDecoder()', async () => {
const qr = new QrcodeDecoder();
expect(qr).toBeDefined();
expect(qr.videoConstraints).toEqual({
video: {
width: { min: 360, ideal: 720, max: 1080 },
height: { min: 360, ideal: 720, max: 1080 },
facingMode: { exact: 'environment' },
},
audio: false,
});
expect(qr.decodeFromImage).toBeDefined();
expect(qr.decodeFromCamera).toBeDefined();
expect(qr.decodeFromVideo).toBeDefined();
});
});

@ -1,52 +0,0 @@
// rollup.config.js
import babel from 'rollup-plugin-babel';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import { uglify } from 'rollup-plugin-uglify';
import common from './rollup';
const prod = process.env.NODE_ENV;
export default {
input: 'src/index.js',
output: {
file: prod ? 'dist/index.aio.min.js' : 'dist/index.aio.js',
format: 'umd',
// 如果不同时使用 export 与 export default 可打开legacy
// legacy: true,
// name: common.name,
name: 'QrcodeDecoder',
banner: common.banner,
},
plugins: [
nodeResolve({
main: true,
}),
commonjs({
include: 'node_modules/**',
}),
babel({
runtimeHelpers: true,
exclude: 'node_modules/**',
}),
prod &&
uglify({
compress: {
drop_debugger: true,
drop_console: true,
},
output: {
comments: (node, comment) => {
if (comment.type === 'comment2') {
// multiline comment
return /@preserve|@license|@cc_on/i.test(
comment.value,
);
}
return false;
},
},
}),
],
};

@ -1,7 +0,0 @@
import config from './rollup.config';
// ES output
config.output.format = 'es';
config.output.file = 'dist/index.esm.js';
export default config;

@ -1,21 +0,0 @@
// rollup.config.js
import babel from 'rollup-plugin-babel';
import common from './rollup';
export default {
input: 'src/index.js',
output: {
file: 'dist/index.js',
format: 'cjs',
// 如果不同时使用 export 与 export default 可打开legacy
// legacy: true,
banner: common.banner,
},
plugins: [
babel({
runtimeHelpers: true,
exclude: 'node_modules/**',
}),
],
};

@ -1,18 +0,0 @@
const pkg = require('../package.json');
// 兼容 qrcode-decoder 和 @yugasun/qrcode-decoder
const name = pkg.name.split('/').pop();
const { version } = pkg;
const banner = `/* @preserve
* qrcode-decoder ${version} (https://github.com/yugasun/qrcode-decoder)
* API https://github.com/yugasun/qrcode-decoder/blob/master/doc/api.md
* Copyright 2017-${new Date().getFullYear()} yugasun. All Rights Reserved
* Licensed under MIT (https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE)
*/
`;
export default {
name,
banner,
};

@ -1,39 +1,54 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<title>QrcodeDecoder - Camera</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<hr />
<video id="video" autoplay></video>
<script src="./index.aio.min.js"></script> <head>
<script type="text/javascript"> <meta charset="UTF-8" />
function main() { <meta name="viewport" content="width=device-width, initial-scale=1.0" />
var qr = new QrcodeDecoder(); <meta http-equiv="X-UA-Compatible" content="ie=edge" />
var video = document.querySelector('#video'); <title>QrcodeDecoder - Camera</title>
var start = document.querySelector('#start'); </head>
var stop = document.querySelector('#stop');
var result = document.querySelector('#result');
async function startScan() {
if (!qr.isCanvasSupported()) {
alert("Your browser doesn't match the required specs.");
throw new Error('Canvas and getUserMedia are required');
}
let code = await qr.decodeFromCamera(video); <body>
console.log('code', code); <button id="start">Start</button> <button id="stop">Stop</button><br />
result.innerText = "Result: " + code.data; <span id="result">Click start to scan qrcode.</span><br />
} <hr />
start.onclick = startScan; <video id="video" autoplay></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
console.log('Hello world');
function main() {
var qr = new QrcodeDecoder.default();
var video = document.querySelector('#video');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var result = document.querySelector('#result');
async function startScan() {
if (!qr.isCanvasSupported()) {
alert("Your browser doesn't match the required specs.");
throw new Error('Canvas and getUserMedia are required');
}
let code = await qr.decodeFromCamera(video,
// you can customize your camera size like below
// {
// width: 400,
// height: 400,
// }
);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
start.onclick = startScan;
stop.onclick = function () {
qr.stop();
};
}
main();
</script>
</body>
stop.onclick = function() {
qr.stop();
};
}
main();
</script>
</body>
</html> </html>

@ -1,52 +1,66 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<title>QrcodeDecoder - Image</title>
</head>
<body>
<section>
<h3>Same domain image</h3>
<img src="./assets/qrcode.png" alt="qr code" /><br>
<button id="decode1">Decode!</button><br>
<span id="result1"></span><br>
</section>
<hr/>
<section>
<h3>Different domain image</h3>
<button id="decode2">Decode!</button><br>
<span id="result2"></span><br>
</section>
<script src="./index.aio.min.js"></script>
<script type="module">
function main() {
var qr = new QrcodeDecoder();
var btn1 = document.querySelector('button#decode1'); <head>
var btn2 = document.querySelector('button#decode2'); <meta charset="UTF-8" />
var result1 = document.querySelector('#result1'); <meta name="viewport" content="width=device-width, initial-scale=1.0" />
var result2 = document.querySelector('#result2'); <meta http-equiv="X-UA-Compatible" content="ie=edge" />
var img = document.querySelector('img'); <title>QrcodeDecoder - Image</title>
</head>
btn1.onclick = async () => { <body>
// you can also decode from image path <section>
// const code = await qr.decodeFromImage('./assets/qrcode.png'); <h3>Same domain image</h3>
const code = await qr.decodeFromImage(img); <img id="img1" src="./assets/qrcode.png" alt="qr code" /><br />
console.log(code); <button id="decode1">Decode!</button><br />
result1.innerText = code.data; <span id="result1"></span><br />
}; </section>
<hr />
<section>
<h3>Different domain image</h3>
<input
id="img2"
value="https://yugasun.com/static/wechat.jpg"
style="width: 400px" /><br />
<button id="decode2">Decode!</button><br />
<span id="result2"></span><br />
</section>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="module">
var vConsole = new VConsole();
function main() {
var qr = new QrcodeDecoder.default();
var btn1 = document.querySelector('button#decode1');
var btn2 = document.querySelector('button#decode2');
var result1 = document.querySelector('#result1');
var result2 = document.querySelector('#result2');
var img1 = document.querySelector('#img1');
var img2 = document.querySelector('#img2');
btn1.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img1);
console.log(code);
result1.innerText = code.data;
};
btn2.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img2.value, {
crossOrigin: 'anonymous',
});
console.log(code);
result2.innerText = code.data;
};
}
window.onload = () => {
main();
};
</script>
</body>
btn2.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage('https://yugasun.com/static/wechat.jpg', {
crossOrigin: 'anonymous',
});
console.log(code);
result2.innerText = code.data;
};
}
main();
</script>
</body>
</html> </html>

File diff suppressed because one or more lines are too long

@ -1,18 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>QrcodeDecoder</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<body> <title>QrcodeDecoder</title>
</head>
<h1>QrcodeDecoder</h1> <body>
<h2>Examples</h2> <h1>QrcodeDecoder</h1>
<ul> <h2>Examples</h2>
<li><a href="image.html">Image</a></li> <ul>
<li><a href="video.html">Video</a></li> <li><a href="image.html">Image</a></li>
<li><a href="camera.html">Camera</a></li> <li><a href="video.html">Video</a></li>
</ul> <li><a href="camera.html">Camera</a></li>
</ul>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

@ -1,39 +1,45 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<title>QrcodeDecoder - Video</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<video src="./assets/qrcode-video.mp4"></video> <head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Video</title>
</head>
<script src="./index.aio.min.js"></script> <body>
<script type="text/javascript"> <button id="start">Start</button> <button id="stop">Stop</button><br />
function main() { <span id="result">Click start to scan qrcode.</span><br />
var video = document.querySelector('video');
var result = document.querySelector('#result');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var qr = new QrcodeDecoder();
start.onclick = startScan; <video src="./assets/qrcode-video.mp4"></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
function main() {
var video = document.querySelector('video');
var result = document.querySelector('#result');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var qr = new QrcodeDecoder.default();
stop.onclick = function() { start.onclick = startScan;
qr.stop();
video.pause(); stop.onclick = function () {
}; qr.stop();
video.pause();
};
async function startScan() {
video.play();
const code = await qr.decodeFromVideo(video);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
}
main();
</script>
</body>
async function startScan() {
video.play();
const code = await qr.decodeFromVideo(video);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
}
main();
</script>
</body>
</html> </html>

@ -14,7 +14,7 @@ Decodes an image from url or an `<img>` element with a `src` attribute set.
```javascript ```javascript
qr.decodeFromImage(img).then((res) => { qr.decodeFromImage(img).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -26,7 +26,7 @@ Decodes directly from a video with a well specified `src` attribute
```javascript ```javascript
qr.decodeFromVideo(videoElement).then((res) => { qr.decodeFromVideo(videoElement).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -38,7 +38,7 @@ Decodes from a videoElement.
```javascript ```javascript
qr.decodeFromCamera(videoElem).then((res) => { qr.decodeFromCamera(videoElem).then((res) => {
console.log(res); console.log(res);
}); });
``` ```

@ -14,7 +14,7 @@ var qr = new QrcodeDecoder();
```javascript ```javascript
qr.decodeFromImage(img).then((res) => { qr.decodeFromImage(img).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -26,7 +26,7 @@ qr.decodeFromImage(img).then((res) => {
```javascript ```javascript
qr.decodeFromVideo(videoElement).then((res) => { qr.decodeFromVideo(videoElement).then((res) => {
console.log(res); console.log(res);
}); });
``` ```
@ -38,7 +38,7 @@ qr.decodeFromVideo(videoElement).then((res) => {
```javascript ```javascript
qr.decodeFromCamera(videoElem).then((res) => { qr.decodeFromCamera(videoElem).then((res) => {
console.log(res); console.log(res);
}); });
``` ```

@ -0,0 +1,16 @@
const isDebug = process.env.DEBUG === 'true';
module.exports = {
verbose: true,
silent: !isDebug,
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testTimeout: 60000,
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*\\.(test|spec))\\.tsx?$',
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/demo/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

@ -1,59 +1,100 @@
{ {
"name": "qrcode-decoder", "name": "qrcode-decoder",
"version": "0.1.2", "version": "0.3.4",
"description": "Tool for decoding qrcode", "description": "Tool for decoding qrcode",
"main": "dist/index.js", "main": "dist/index.js",
"jsnext:main": "dist/index.esm.js", "browser": "dist/index.min.js",
"unpkg": "dist/index.min.js",
"module": "dist/index.esm.js", "module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"sideEffects": false, "sideEffects": false,
"packageManager": "pnpm@7.11.0",
"scripts": { "scripts": {
"clean": "rimraf ./dist", "build": "ts-node -P scripts/tsconfig.json scripts/bundle.ts commonjs,esm,aio && pnpm run dts && pnpm run copy-file",
"lint": "eslint src", "dts": "tsc src/index.ts --declaration --emitDeclarationOnly --outDir './dist'",
"build:self": "rollup -c config/rollup.config.js", "prettier": "prettier --check '**/*.{ts,tsx,md}' --config .prettierrc",
"build:esm": "rollup -c config/rollup.config.esm.js", "prettier:fix": "prettier --write '**/*.{ts,tsx,md}' --config .prettierrc",
"build:aio": "rollup -c config/rollup.config.aio.js", "test": "jest",
"build:aio.min": "NODE_ENV=production npm run build:aio", "codecov": "jest --coverage && codecov",
"build": "npm run clean && npm run build:self && npm run build:esm && npm run build:aio && npm run build:aio.min && npm run copy-file", "copy-file": "cp dist/index.min.js demo/lib",
"copy-file": "cp dist/index.aio.min.js demo/",
"test": "npm run lint && npm run build",
"zuul-test": "zuul --local 8080 --ui mocha-bdd -- test/test.js",
"release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags", "release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags",
"deploy": "gh-pages -d demo --remote origin" "pages": "gh-pages -d demo --remote origin",
"demo": "http-server demo -c-1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx,md}": [
"pnpm run prettier:fix",
"git add ."
]
},
"devDependencies": {
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/plugin-proposal-decorators": "^7.7.0",
"@babel/plugin-proposal-do-expressions": "^7.6.0",
"@babel/plugin-proposal-export-default-from": "^7.5.2",
"@babel/plugin-proposal-export-namespace-from": "^7.5.2",
"@babel/plugin-proposal-function-sent": "^7.7.0",
"@babel/plugin-proposal-json-strings": "^7.2.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
"@babel/plugin-proposal-pipeline-operator": "^7.5.0",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@babel/preset-typescript": "^7.7.2",
"@babel/runtime": "^7.7.2",
"@types/jest": "^24.9.0",
"@types/node": "^13.1.8",
"chalk": "^3.0.0",
"codecov": "^3.8.1",
"cross-env": "^6.0.3",
"fancy-log": "^1.3.3",
"gh-pages": "^3.1.0",
"http-server": "^14.1.0",
"husky": "^3.0.9",
"jest": "^24.9.0",
"lint-staged": "^9.5.0",
"prettier": "^1.18.2",
"rollup": "^2.38.1",
"rollup-plugin-commonjs": "^8.3.0",
"rollup-plugin-json": "^3.1.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-typescript2": "0.17.1",
"rollup-plugin-uglify": "^6.0.4",
"ts-jest": "^24.3.0",
"ts-node": "^8.4.1",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.4.3"
}, },
"author": "yugasun",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/yugasun/qrcode-decoder.git" "url": "git+https://github.com/yugasun/qrcode-decoder.git"
}, },
"keywords": [
"node-utils"
],
"author": "yugasun",
"bugs": { "bugs": {
"url": "https://github.com/yugasun/qrcode-decoder/issues" "url": "https://github.com/yugasun/qrcode-decoder/issues"
}, },
"devDependencies": { "homepage": "https://github.com/yugasun/qrcode-decoder#readme",
"babel-core": "6.26.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-preset-env": "1.6.1",
"babel-preset-stage-2": "^6.24.1",
"cdkit": "1.1.0",
"es5-shim": "4.5.10",
"eslint": "4.18.2",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.14.0",
"expect.js": "0.3.1",
"gh-pages": "^2.0.1",
"jsdom": "^13.0.0",
"mocha": "3.5.3",
"mocha-jsdom": "^2.0.0",
"rimraf": "2.6.2",
"rollup": "^0.67.3",
"rollup-plugin-babel": "3.0.3",
"rollup-plugin-commonjs": "8.3.0",
"rollup-plugin-node-resolve": "3.0.3",
"rollup-plugin-uglify": "^6.0.0",
"zuul": "^3.12.0"
},
"dependencies": { "dependencies": {
"babel-polyfill": "^6.26.0", "jsqr": "^1.4.0"
"jsqr": "^1.1.1"
} }
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,144 @@
import * as path from 'path';
import * as rollup from 'rollup';
import resolve from 'rollup-plugin-node-resolve';
import typescript2 from 'rollup-plugin-typescript2';
import { uglify } from 'rollup-plugin-uglify';
import globals from 'rollup-plugin-node-globals';
import commonjs from 'rollup-plugin-commonjs';
import ts from 'typescript';
import { createLogger } from './logger';
const logBundle = createLogger('bundle');
const plugins: rollup.Plugin[] = [
resolve({
browser: true,
jsnext: true,
preferBuiltins: true,
}),
commonjs(),
typescript2({
tsconfig: path.join(__dirname, '..', 'tsconfig.json'),
}),
];
async function bundleEsm() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [...plugins, globals()],
external: ['jsqr'],
});
await bundle.write({
file: 'dist/index.esm.js',
name: 'QrcodeDecoder',
format: 'esm',
sourcemap: false,
globals: {
jsqr: 'jsqr',
tslib: 'tslib',
},
});
}
async function bundleUmd() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [...plugins],
external: ['jsqr'],
});
await bundle.write({
file: 'dist/index.js',
exports: 'named',
name: 'QrcodeDecoder',
format: 'umd',
sourcemap: false,
globals: {
jsqr: 'jsqr',
tslib: 'tslib',
},
});
}
async function bundleCommonjs() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [...plugins],
external: ['jsqr'],
});
await bundle.write({
file: 'dist/index.js',
exports: 'named',
name: 'QrcodeDecoder',
format: 'commonjs',
sourcemap: false,
globals: {
jsqr: 'jsqr',
tslib: 'tslib',
},
});
}
async function bundleAio() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [
...plugins,
uglify({
compress: {
drop_debugger: true,
drop_console: false,
},
}),
],
});
await bundle.write({
file: 'dist/index.min.js',
name: 'QrcodeDecoder',
format: 'iife',
sourcemap: true,
});
}
async function bundle() {
try {
const outputs = process.argv.slice(2)[0].split(',');
logBundle(`Creating bundle`);
if (outputs.indexOf('esm') === -1) {
logBundle(`Skipping esm`);
} else {
logBundle(`Creating esm`);
await bundleEsm();
}
if (outputs.indexOf('umd') === -1) {
logBundle(`Skipping umd`);
} else {
logBundle(`Creating umd`);
await bundleUmd();
}
if (outputs.indexOf('commonjs') === -1) {
logBundle(`Skipping commonjs`);
} else {
logBundle(`Creating commonjs`);
await bundleCommonjs();
}
if (outputs.indexOf('aio') === -1) {
logBundle(`Skipping umd aio`);
} else {
logBundle(`Creating umd aio`);
await bundleAio();
}
} catch (err) {
logBundle('Failed to bundle:');
logBundle(err);
process.exit(1);
}
}
bundle();

@ -0,0 +1,16 @@
import * as l from 'fancy-log';
const log = <typeof import('fancy-log')>(<any>(<any>l).default || l);
import * as c from 'chalk';
const chalk = <import('chalk').Chalk>(c.default || c);
export function createLogger(name: string): typeof log {
const prefix = `> ${chalk.green(name)} `;
const logger = <typeof log>log.bind(log, prefix);
logger.info = log.info.bind(log, prefix);
logger.dir = log.dir.bind(log, prefix);
logger.warn = log.warn.bind(log, prefix);
logger.error = log.error.bind(log, prefix);
return logger;
}
export { chalk as c };

@ -0,0 +1,8 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "commonjs",
"target": "es2015",
"resolveJsonModule": true
}
}

@ -1,376 +0,0 @@
import 'babel-polyfill';
import jsQR from 'jsqr';
/* eslint-disable no-underscore-dangle */
class QrcodeDecoder {
/**
* Constructor for QrcodeDecoder
*/
constructor() {
this.timerCapture = null;
this.canvasElem = null;
this.gCtx = null;
this.stream = null;
this.videoElem = null;
this.getUserMediaHandler = null;
this.videoConstraints = { video: true, audio: false };
this.defaultOption = { inversionAttempts: 'attemptBoth' };
}
/**
* Verifies if canvas element is supported.
*/
/* eslint-disable class-methods-use-this */
isCanvasSupported() {
const elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
/**
* draw lint
*
* @param {object} begin line begin point
* @param {object} end line end point
* @param {string} color color string
*/
// _drawLine(begin, end, color) {
// this.gCtx.beginPath();
// this.gCtx.moveTo(begin.x, begin.y);
// this.gCtx.lineTo(end.x, end.y);
// this.gCtx.lineWidth = 4;
// this.gCtx.strokeStyle = color;
// this.gCtx.stroke();
// }
/**
* create qrcode marker
*
* @param {object} code jsqr parse code object
*/
// _createQrcodeMark(code) {
// this._drawLine(
// code.location.topLeftCorner,
// code.location.topRightCorner,
// '#FF3B58',
// );
// this._drawLine(
// code.location.topRightCorner,
// code.location.bottomRightCorner,
// '#FF3B58',
// );
// this._drawLine(
// code.location.bottomRightCorner,
// code.location.bottomLeftCorner,
// '#FF3B58',
// );
// this._drawLine(
// code.location.bottomLeftCorner,
// code.location.topLeftCorner,
// '#FF3B58',
// );
// }
_createImageData(target, width, height) {
if (!this.canvasElem) {
this._prepareCanvas(width, height);
}
this.gCtx.clearRect(0, 0, width, height);
this.gCtx.drawImage(target, 0, 0, width, height);
const imageData = this.gCtx.getImageData(
0,
0,
this.canvasElem.width,
this.canvasElem.height,
);
return imageData;
}
/**
* Prepares the canvas element (which will
* receive the image from the camera and provide
* what the algorithm needs for checking for a
* QRCode and then decoding it.)
*
*
* @param {DOMElement} canvasElem the canvas
* element
* @param {number} width The width that
* the canvas element
* should have
* @param {number} height The height that
* the canvas element
* should have
* @return {DOMElement} the canvas
* after the resize if width and height
* provided.
*/
_prepareCanvas(width, height) {
if (!this.canvasElem) {
this.canvasElem = document.createElement('canvas');
this.canvasElem.style.width = `${width}px`;
this.canvasElem.style.height = `${height}px`;
this.canvasElem.width = width;
this.canvasElem.height = height;
}
this.gCtx = this.canvasElem.getContext('2d');
}
/**
* Based on the video dimensions and the canvas
* that was previously generated captures the
* video/image source and then paints into the
* canvas so that the decoder is able to work as
* it expects.
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async _captureToCanvas(videoElem, options) {
if (this.timerCapture) {
clearTimeout(this.timerCapture);
}
const proms = () => {
const p = new Promise(async (resolve) => {
let code;
if (videoElem.videoWidth && videoElem.videoHeight) {
const imageData = this._createImageData(
videoElem,
videoElem.videoWidth,
videoElem.videoHeight,
);
code = jsQR(
imageData.data,
imageData.width,
imageData.height,
options,
);
if (code) {
resolve(code);
} else {
this.timerCapture = setTimeout(async () => {
code = await this._captureToCanvas(
videoElem,
options,
);
resolve(code);
}, 500);
}
} else {
this.timerCapture = setTimeout(async () => {
code = await this._captureToCanvas(videoElem, options);
resolve(code);
}, 500);
}
});
return p;
};
const result = await proms();
return result;
}
/**
* Prepares the video element for receiving
* camera's input. Releases a stream if there
* was any (resets).
*
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromCamera(videoElem, options = {}) {
const opts = {
...this.defaultOption,
...options,
};
this.stop();
if (!navigator.mediaDevices.getUserMedia) {
throw new Error("Couldn't get video from camera");
}
try {
const stream = await navigator.mediaDevices.getUserMedia(
this.videoConstraints,
);
videoElem.srcObject = stream;
// videoElem.src = window.URL.createObjectURL(stream);
this.videoElem = videoElem;
this.stream = stream;
this.videoDimensions = false;
const code = await this.decodeFromVideo(videoElem, opts);
return code;
} catch (e) {
throw e;
}
}
/**
* Prepares the video element for video file.
*
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromVideo(videoElem, options = {}) {
const opts = {
...this.defaultOption,
...options,
};
try {
this.videoElem = videoElem;
const code = await this._captureToCanvas(videoElem, opts);
return code;
} catch (e) {
throw e;
}
}
/**
* Decodes an image from its src.
* @param {DOMElement} imageElem
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromImage(img, options = {}) {
let imgDom;
const opts = {
...this.defaultOption,
...options,
};
if (+img.nodeType > 0) {
if (!img.src) {
throw new Error('The ImageElement must contain a src');
}
imgDom = img;
} else if (typeof img === 'string') {
imgDom = document.createElement('img');
imgDom.src = img;
if (options.crossOrigin) {
imgDom.crossOrigin = options.crossOrigin;
}
const proms = () => new Promise((resolve) => {
imgDom.onload = () => resolve(true);
});
await proms();
}
let code = false;
if (imgDom) {
code = this._decodeFromImageElm(imgDom, opts);
}
return code;
}
_decodeFromImageElm(imgObj, options = {}) {
const opts = {
...this.defaultOption,
...options,
};
const imageData = this._createImageData(
imgObj,
imgObj.width,
imgObj.height,
);
const code = jsQR(
imageData.data,
imageData.width,
imageData.height,
opts,
);
if (code) {
return code;
}
return false;
}
/**
* Releases a video stream that was being
* captured by prepareToVideo
*/
stop() {
if (this.stream) {
const track = this.stream.getTracks()[0];
track.stop();
this.stream = undefined;
// fix: clear black bg after camera capture
this.videoElem.srcObject = null;
}
if (this.timerCapture) {
clearTimeout(this.timerCapture);
this.timerCapture = undefined;
}
return this;
}
/**
* Sets the sourceId for the camera to use.
*
* The sourceId can be found using the
* getVideoSources function on a browser that
* supports it (currently only Chrome).
*
* @param {String} sourceId The id of the
* video source you want to use (or false to use
* the current default)
*/
setSourceId(sourceId) {
if (sourceId) {
this.videoConstraints.video = {
optional: [{ sourceId }],
};
} else {
this.videoConstraints.video = true;
}
return this;
}
/**
* Gets a list of all available video sources on
* the current device.
* @param {Function} cb callback to be resolved
* with error (first param) ou results (second
* param) - a list containing all of the sources
* that are of the 'video' kind.
*/
getVideoSources() {
const sources = [];
if (!(MediaStreamTrack && MediaStreamTrack.getSources)) {
throw new Error(
'Current browser doest not support MediaStreamTrack.getSources',
);
}
MediaStreamTrack.getSources((sourceInfos) => {
sourceInfos.forEach((sourceInfo) => {
if (sourceInfo.kind === 'video') {
sources.push(sourceInfo);
}
});
});
return sources;
}
}
export default QrcodeDecoder;

@ -0,0 +1,356 @@
import jsQR, { QRCode, Options } from 'jsqr';
export type CodeResult = QRCode | null;
const videoSize = {
width: { min: 360, ideal: 720, max: 1080 },
height: { min: 360, ideal: 720, max: 1080 },
};
export class QrcodeDecoder {
timerCapture: null | NodeJS.Timeout;
canvasElem: null | HTMLCanvasElement;
gCtx: null | CanvasRenderingContext2D;
stream: null | MediaStream;
videoElem: null | HTMLVideoElement;
getUserMediaHandler: null;
videoConstraints: MediaStreamConstraints;
defaultOption: Options;
constructor() {
this.timerCapture = null;
this.canvasElem = null;
this.gCtx = null;
this.stream = null;
this.videoElem = null;
this.getUserMediaHandler = null;
this.videoConstraints = {
// default use rear camera
video: { ...videoSize, facingMode: { exact: 'environment' } },
audio: false,
};
this.defaultOption = { inversionAttempts: 'attemptBoth' };
}
/**
* Verifies if canvas element is supported.
*/
isCanvasSupported() {
const elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
_createImageData(target: CanvasImageSource, width: number, height: number) {
if (!this.canvasElem) {
this._prepareCanvas(width, height);
}
this.gCtx!.clearRect(0, 0, width, height);
this.gCtx!.drawImage(target, 0, 0, width, height);
const imageData = this.gCtx!.getImageData(
0,
0,
this.canvasElem!.width,
this.canvasElem!.height,
);
return imageData;
}
/**
* Prepares the canvas element (which will
* receive the image from the camera and provide
* what the algorithm needs for checking for a
* QRCode and then decoding it.)
*
*
* @param {DOMElement} canvasElem the canvas
* element
* @param {number} width The width that
* the canvas element
* should have
* @param {number} height The height that
* the canvas element
* should have
* @return {DOMElement} the canvas
* after the resize if width and height
* provided.
*/
_prepareCanvas(width: number, height: number) {
if (!this.canvasElem) {
this.canvasElem = document.createElement('canvas');
this.canvasElem.style.width = `${width}px`;
this.canvasElem.style.height = `${height}px`;
this.canvasElem.width = width;
this.canvasElem.height = height;
}
this.gCtx = this.canvasElem.getContext('2d');
}
/**
* Based on the video dimensions and the canvas
* that was previously generated captures the
* video/image source and then paints into the
* canvas so that the decoder is able to work as
* it expects.
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async _captureToCanvas(
videoElem: HTMLVideoElement,
options: Options,
): Promise<CodeResult> {
if (this.timerCapture) {
clearTimeout(this.timerCapture);
}
const proms = () => {
const p = new Promise(async (resolve) => {
let code;
if (videoElem.videoWidth && videoElem.videoHeight) {
const imageData = this._createImageData(
videoElem,
videoElem.videoWidth,
videoElem.videoHeight,
);
code = jsQR(
imageData.data,
imageData.width,
imageData.height,
options,
);
if (code) {
resolve(code);
} else {
this.timerCapture = setTimeout(async () => {
code = await this._captureToCanvas(videoElem, options);
resolve(code);
}, 500);
}
} else {
this.timerCapture = setTimeout(async () => {
code = await this._captureToCanvas(videoElem, options);
resolve(code);
}, 500);
}
});
return p;
};
const result = await proms();
return result as CodeResult;
}
/**
* Prepares the video element for receiving
* camera's input. Releases a stream if there
* was any (resets).
*
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromCamera(
videoElem: HTMLVideoElement,
options: any = {},
): Promise<CodeResult> {
const opts = {
...this.defaultOption,
...options,
};
this.stop();
if (!navigator.mediaDevices.getUserMedia) {
throw new Error("Couldn't get video from camera");
}
let stream: MediaStream;
try {
stream = await navigator.mediaDevices.getUserMedia(this.videoConstraints);
} catch (e) {
if ((e as OverconstrainedError).name === 'OverconstrainedError') {
console.log('[OverconstrainedError] Can not use rear camera.');
stream = await navigator.mediaDevices.getUserMedia({
video: {
...videoSize,
...{
width: opts.width,
height: opts.height,
},
},
audio: false,
});
} else {
throw e;
}
}
if (stream) {
videoElem.srcObject = stream;
// videoElem.src = window.URL.createObjectURL(stream);
this.videoElem = videoElem;
this.stream = stream;
const code = await this.decodeFromVideo(videoElem, opts);
return code;
}
return null;
}
/**
* Prepares the video element for video file.
*
* @param {DOMElement} videoElem <video> dom element
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromVideo(
videoElem: HTMLVideoElement,
options = {},
): Promise<CodeResult> {
const opts = {
...this.defaultOption,
...options,
};
try {
this.videoElem = videoElem;
const code = await this._captureToCanvas(videoElem, opts);
return code;
} catch (e) {
throw e;
}
}
/**
* Decodes an image from its src.
* @param {DOMElement} imageElem
* @param {Object} options options (optional) - Additional options.
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
* refer to jsqr options: https://github.com/cozmo/jsQR
*/
async decodeFromImage(
img: HTMLImageElement | string,
options: { crossOrigin?: string } = {},
): Promise<CodeResult> {
let imgDom: HTMLImageElement | null = null;
const opts = {
...this.defaultOption,
...options,
};
if (typeof img === 'string') {
imgDom = document.createElement('img');
if (options.crossOrigin) {
imgDom.crossOrigin = options.crossOrigin;
}
imgDom.src = img;
const proms = () =>
new Promise((resolve) => {
imgDom!.onload = () => resolve(true);
});
await proms();
} else if (+img.nodeType > 0) {
if (!img.src) {
throw new Error('The ImageElement must contain a src');
}
imgDom = img;
}
let code = null;
if (imgDom) {
code = this._decodeFromImageElm(imgDom, opts);
}
return code;
}
_decodeFromImageElm(imgObj: HTMLImageElement, options = {}) {
const opts: Options = {
...this.defaultOption,
...options,
};
const imageData = this._createImageData(
imgObj,
imgObj.width,
imgObj.height,
);
const code = jsQR(imageData.data, imageData.width, imageData.height, opts);
if (code) {
return code;
}
return null;
}
/**
* Releases a video stream that was being
* captured by prepareToVideo
*/
stop() {
if (this.stream) {
const track = this.stream.getTracks()[0];
track.stop();
this.stream = null;
// fix: clear black bg after camera capture
this.videoElem!.srcObject = null;
}
if (this.timerCapture) {
clearTimeout(this.timerCapture);
this.timerCapture = null;
}
return this;
}
/**
* Sets the sourceId for the camera to use.
*/
setGroupId(groupId: string) {
if (groupId) {
this.videoConstraints.video = {
advanced: [{ groupId }],
};
} else {
this.videoConstraints.video = true;
}
return this;
}
/**
* Gets a list of all available video sources on
* the current device.
*/
async getVideoDevices() {
if (navigator.mediaDevices.enumerateDevices) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter((item: MediaDeviceInfo) => {
if (item.kind === 'videoinput') {
return true;
}
return false;
});
} else {
throw new Error(
'Current browser doest not support MediaStreamTrack.getSources',
);
}
}
}
export default QrcodeDecoder;

@ -1 +0,0 @@
make test for your own private key

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="../../node_modules/es5-shim/es5-shim.js"></script>
<script src="../../node_modules/es5-shim/es5-sham.js"></script>
<script src="../../node_modules/mocha/mocha.js"></script>
<script src="../../node_modules/expect.js/index.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="../../dist/index.aio.js"></script>
<script>
var libs = {
'expect.js': expect,
'../dist/index.js': window['qrcode-decoder']
};
var require = function(path) {
return libs[path];
}
</script>
<script src="../test.js"></script>
<script>
mocha.run();
</script>
</body>
</html>

@ -1,57 +0,0 @@
const expect = require('expect.js');
const QrcodeDecoder = require('../dist/index.js');
describe('QrcodeDecoder', function () {
// global.mocha.checkLeaks = false;
this.timeout(180000);
it('be defined', function () {
expect(QrcodeDecoder).to.be.ok();
});
var qr;
beforeEach(function () {
qr = new QrcodeDecoder();
});
describe('decodeFromImage', function () {
it('decode image from img element', function (done) {
const img = document.createElement('img');
img.src = 'demo/assets/qrcode.png';
img.onload = async function () {
const result = await qr.decodeFromImage(img);
expect(result.data).to.equal('192.168.1.13:3000');
done();
}
});
it('decode image from img url', async function () {
const result = await qr.decodeFromImage('demo/assets/qrcode.png');
expect(result.data).to.equal('192.168.1.13:3000');
});
it('throw if no src in image element', async function (done) {
var img = document.createElement('img');
try {
await qr.decodeFromImage(img);
} catch(e) {
expect(e.message).to.equal('The ImageElement must contain a src')
done();
}
});
});
describe('decodeFromCamera', function () {
it('decode from a video with qrcode', async function () {
const video = document.createElement('video');
video.setAttribute('autoplay', true);
video.setAttribute('src', 'demo/assets/qrcode-video.mp4');
const result = await qr.decodeFromVideo(video);
expect(result.data).to.equal('192.168.1.13:3000');
});
});
});

@ -0,0 +1,26 @@
{
"compilerOptions": {
"outDir": "dist",
"baseUrl": ".",
"composite": true,
"declaration": true,
"declarationMap": true,
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"preserveConstEnums": true,
"importHelpers": false,
"lib": [
"esnext",
"dom"
],
"module": "ESNext",
"moduleResolution": "node",
"sourceMap": false,
"resolveJsonModule": true,
"noUnusedLocals": true,
"strict": true,
"target": "es5"
}
}

@ -0,0 +1,23 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"interface-name": [true, "never-prefix"],
"member-access": false,
"no-angle-bracket-type-assertion": false,
"no-bitwise": false,
"no-console": false,
"no-default-export": true,
"no-empty-interface": false,
"no-implicit-dependencies": false,
"no-submodule-imports": false,
"ordered-imports": [false],
"object-literal-sort-keys": false,
"object-literal-key-quotes": [true, "as-needed"],
"quotemark": [true, "single"],
"semicolon": [true, "always", "ignore-bound-class-methods"],
"jsx-boolean-value": false
},
"linterOptions": {
"exclude": ["config/**/*.js", "node_modules/**/*.ts"]
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save