Skip to content

Commit

Permalink
feat(all): Add build process, release browser platform as a UMD library
Browse files Browse the repository at this point in the history
The new build process makes it possible to bundle the entire browser platform implementation as a
library which can be used without cordova-browser. This is particularly valuable for projects which
bundle apps with Electron or NW.js and do not use the Cordova Browser platform, but still need an
API compatible implementation for desktop. The build process also makes it possible for the Web
Worker code to be bundled with the plugin code, the adapter.js and qrcode-reader dependencies to be
fully managed by NPM, and all released code to be minified. This change also adds a simple manual
test for the library and some documentation.

Fixes bitpay#30, removes some unused code in the browser implementation.
  • Loading branch information
bitjson committed Sep 28, 2016
1 parent 6be215b commit 052b8d3
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -1 +1,6 @@
node_modules/
dist/

# generated files
src/browser/plugin.min.js
www/www.min.js
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -2,3 +2,4 @@ language: node_js
sudo: false
node_js:
- "4"
- "6"
46 changes: 46 additions & 0 deletions gulpfile.js
@@ -0,0 +1,46 @@
'use strict';

const gulp = require('gulp');
const insert = require('gulp-insert');
const fs= require('fs');

const remap = fs.readFileSync('src/common/src/cordova-remap.js', 'utf-8');

function webpack(config, callback){
const exec = require('child_process').exec;
exec('node_modules/.bin/webpack --config ' + config, (error, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
callback(error);
});
}

gulp.task('prepack', function(cb){
webpack('webpack.prepack.config.js', cb);
});

gulp.task('webpack-cordova', ['prepack'], function(cb){
webpack('webpack.cordova.config.js', cb);
});

gulp.task('dist', ['prepack'], function(cb){
webpack('webpack.library.config.js', cb);
});

gulp.task('remap', ['webpack-cordova'], function () {
return gulp.src(['dist/plugin.min.js', 'dist/www.min.js'])
.pipe(insert.prepend(remap))
.pipe(gulp.dest('dist'));
});

gulp.task('plugin', ['remap'], function () {
return gulp.src(['dist/plugin.min.js'])
.pipe(gulp.dest('src/browser'));
});

gulp.task('www', ['remap'], function () {
return gulp.src(['dist/www.min.js'])
.pipe(gulp.dest('www'));
});

gulp.task('default', ['dist', 'plugin', 'www']);
25 changes: 16 additions & 9 deletions package.json
Expand Up @@ -9,20 +9,22 @@
]
},
"scripts": {
"build": "gulp && npm run clean-build",
"clean-build": "trash dist/plugin.min.js && trash dist/www.min.js && trash src/browser/worker.min.js",
"install": "npm run build",
"test": "npm run jshint",
"jshint": "jshint www && jshint src && jshint tests",
"update": "npm update && npm run update:qrcode-reader && npm run update:adapter.js",
"update:qrcode-reader": "ncp node_modules/qrcode-reader/dist/index.js src/browser/qrcode-reader.js",
"update:adapter.js": "ncp node_modules/webrtc-adapter/out/adapter_no_edge.js src/browser/adapter.js",
"gen-tests": "npm run clean-platform-tests && npm run mkdirp-platform-tests && npm run copy-platform-tests && npm run install-platform-tests",
"gen-tests": "npm run build && npm run clean-platform-tests && npm run mkdirp-platform-tests && npm run copy-platform-tests && npm run install-platform-tests",
"clean-platform-tests": "trash ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests",
"mkdirp-platform-tests": "mkdirp ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests",
"copy-platform-tests": "ncp tests/project ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests",
"install-platform-tests": "cd ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests && npm install",
"test:ios": "cd ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests && npm run test:ios",
"test:android": "cd ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests && npm run test:android",
"test:browser": "cd ../cordova-plugin-test-projects/cordova-plugin-qrscanner-tests && npm run test:browser",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
"test:library": "npm run build && node tests/library/test.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"prep-release": "git clean -dfx && npm install && npm run changelog"
},
"repository": {
"type": "git",
Expand All @@ -44,18 +46,23 @@
},
"homepage": "https://github.com/bitpay/cordova-plugin-qrscanner",
"dependencies": {
"qrcode-reader": "^0.2.1",
"webrtc-adapter": "^2.0.2",
"xcode": "^0.8.8"
"qrcode-reader": "^0.2.2",
"webrtc-adapter": "^2.0.3",
"xcode": "^0.8.9"
},
"devDependencies": {
"cz-conventional-changelog": "^1.1.6",
"express": "^4.14.0",
"ghooks": "^1.3.2",
"gulp": "^3.9.1",
"gulp-insert": "^0.5.0",
"jshint": "^2.9.2",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"raw-loader": "^0.5.1",
"trash-cli": "^1.3.0",
"validate-commit-msg": "^2.6.1"
"validate-commit-msg": "^2.6.1",
"webpack": "^2.1.0-beta.22"
},
"config": {
"commitizen": {
Expand Down
28 changes: 12 additions & 16 deletions plugin.xml
Expand Up @@ -9,9 +9,7 @@
<engine name="cordova" version=">=3.4.0"/>
</engines>

<asset src="www/QRScanner.js" target="js/QRScanner.js"/>

<js-module src="www/QRScanner.js" name="QRScanner">
<js-module src="www/www.min.js" name="QRScanner">
<clobbers target="QRScanner" />
</js-module>

Expand Down Expand Up @@ -39,22 +37,20 @@
</config-file>
<header-file src="src/ios/QRScanner-Bridging-Header.h" />
<source-file src="src/ios/QRScanner.swift"/>
<config-file target="*-Info.plist" parent="NSCameraUsageDescription">
<string></string>
</config-file>
</platform>

<platform name="browser">
<config-file target="config.xml" parent="/*">
<feature name="QRScanner">
<param name="browser-package" value="QRScanner" />
</feature>
</config-file>
<js-module src="src/browser/QRScannerProxy.js" name="QRScannerProxy">
<runs />
</js-module>
<source-file src="src/browser/worker.js" target-dir="platform_www/plugins/cordova-plugin-qrscanner/src/browser" />
<source-file src="src/browser/qrcode-reader.js" target-dir="platform_www/plugins/cordova-plugin-qrscanner/src/browser" />
<js-module src="src/browser/adapter.js" name="AdapterJs">
<clobbers target="window.getUserMedia" />
</js-module>
<config-file target="config.xml" parent="/*">
<feature name="QRScanner">
<param name="browser-package" value="QRScanner" />
</feature>
</config-file>
<js-module src="src/browser/plugin.min.js" name="QRScannerProxy">
<runs />
</js-module>
</platform>

</plugin>
28 changes: 20 additions & 8 deletions readme.md
Expand Up @@ -125,6 +125,24 @@ A project can only have one bridging header. If your app uses plugins other than
```
Copy the script from `cordova-plugin-qrscanner/scripts/swift-support.js` into your project (eg. into the `hooks` folder), and modify the `BRIDGING_HEADER_END` variable to point to your new bridging header. Finally, remove and re-add the ios platform to trigger the hook. See [this issue](https://github.com/eface2face/cordova-plugin-iosrtc/issues/9) for more information.

### Electron or NW.js usage without `cordova-browser`

If your app uses the Cordova Browser platform, simply adding the plugin to the Cordova project will make the `window.QRScanner` global object available once the `deviceready` event propagates. For apps not using `cordova-browser`, this plugin is also available as a simple javascript library.

The library uses the [Universal Module Definition API](https://github.com/umdjs/umd), so it can simply be required by most build systems.

```js
var QRScanner = require('QRScanner');
```

Or alternatively, the library can be included in a page as-is, and the QRScanner will be made available at `window.QRScanner`.

```html
<script src="path/to/qrscanner/library.bundle.min.js"></script>
```

On the browser platform, performance is improved by running the processing-intensive scanning operation in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). For more information about the browser platform, see [Browser Platform Specific Details](#browser).

## API
With the exception of `QRScanner.scan(callback)`, all callbacks are optional.

Expand Down Expand Up @@ -307,7 +325,7 @@ Name | Description
`lightEnabled` | A boolean value which is true if the light is enabled.
`canOpenSettings` | A boolean value which is true only if the users' operating system is able to `QRScanner.openSettings()`.
`canEnableLight` | A boolean value which is true only if the users' device can enable a light in the direction of the currentCamera.
`canChangeCamera` | A boolean value which is true only if the current device "should" have a front camera. The camera may still not be capturable, which would emit error code 3, 4, or 5 when the switch is attempted.
`canChangeCamera` | A boolean value which is true only if the current device "should" have a front camera. The camera may still not be capturable, which would emit error code 3, 4, or 5 when the switch is attempted. (On the browser platform, this value is false until the `prepare` method is called.)
`currentCamera` | A number representing the index of the currentCamera. `0` is the back camera, `1` is the front.


Expand Down Expand Up @@ -397,7 +415,7 @@ For this same reason, scanning requires the video preview to be active, and the

### Camera Selection

The browser platform attempts to select the best camera as the "back" camera (the default camera). If a "next-best" camera is available, that camera will be selected as the "front" camera. Camera switching is intended to be "togglable", so this plugin has no plans to support access to more than 2 cameras.
When the `prepare` method runs, the browser platform attempts to select the best camera as the "back" camera (the default camera). If a "next-best" camera is available, that camera will be selected as the "front" camera. Camera switching is intended to be "togglable", so this plugin has no plans to support access to more than 2 cameras.

The "back" camera is selected by the following criteria:
1. [**facingMode**](http://w3c.github.io/mediacapture-main/#dfn-facingmode) – if a camera with a facingMode of `environment` exists, we use this one.
Expand All @@ -411,12 +429,6 @@ The browser platform always returns the boolean `status.canEnableLight` as `fals

`status.canEnableLight` is camera specific, meaning it will return `false` if the camera in use does not have a flash.

### Using with Electron or NW.js

This plugin should work out-of-the box with the Cordova browser platform. As of now, there is no clear "best-way" of using the cordova browser build inside an Electron or NW.js application. This plugin attempts to provide an as-clean-as-possible source such that implementations can choose to either:
- fully implement the cordova platform (please [let us know](https://github.com/bitpay/cordova-plugin-qrscanner/issues/new) how you do it so we can add documentation!), or
- import this plugin's source into the Electron or NW.js project and re-bundle it manually.

#### Using Status.authorized

Both Electron and NW.js automatically provide authorization to access the camera (without user confirmation) to bundled applications. This difference can't be detected via an API this plugin can implement, so the `authorized` property on any returned Status objects will be `false` on startup, even when it should be `true`. You should adjust your code to assume that these platforms are always authorized. (ie: Skip "permission priming" on these platforms.)
Expand Down
7 changes: 7 additions & 0 deletions src/browser/src/cordova-plugin.js
@@ -0,0 +1,7 @@
var cordovaRequire = require('webpack/cordova/require');
var cordovaModule = require('webpack/cordova/module');

var createQRScannerInternal = require('./createQRScannerInternal.js');

cordovaModule.exports = createQRScannerInternal();
cordovaRequire('cordova/exec/proxy').add('QRScanner', cordovaModule.exports);
@@ -1,4 +1,7 @@
(function() {
require('webrtc-adapter');
var workerScript = require("raw!../worker.min.js");

module.exports = function(){

var ELEMENTS = {
preview: 'cordova-plugin-qrscanner-video-preview',
Expand Down Expand Up @@ -84,7 +87,7 @@
return searchState;
} else {
searchState.nextConstraint = next;
return navigator.mediaDevices.getUserMedia(searchState.nextConstraint).then(function(mediaStream){
return window.navigator.mediaDevices.getUserMedia(searchState.nextConstraint).then(function(mediaStream){
// We found the first working constraint object, now we can stop
// the stream and short-circuit the search.
killStream(mediaStream);
Expand Down Expand Up @@ -125,7 +128,7 @@
}

function chooseCameras(){
var devices = navigator.mediaDevices.enumerateDevices();
var devices = window.navigator.mediaDevices.enumerateDevices();
return devices.then(function(mediaDeviceInfoList){
var videoDeviceIds = mediaDeviceInfoList.filter(function(elem){
return elem.kind === 'videoinput';
Expand Down Expand Up @@ -229,7 +232,7 @@
}

function canChangeCamera(){
return backCamera !== null && frontCamera !== null;
return !!backCamera && !!frontCamera;
}

function calcStatus(){
Expand Down Expand Up @@ -262,7 +265,7 @@
function startCamera(success, error){
var currentCameraIndex = getCurrentCameraIndex();
var currentCamera = getCurrentCamera();
navigator.mediaDevices.getUserMedia({
window.navigator.mediaDevices.getUserMedia({
audio: false,
video: {
deviceId: {exact: currentCamera.deviceId},
Expand Down Expand Up @@ -307,7 +310,8 @@

function initialize(success, error){
if(scanWorker === null){
scanWorker = new Worker('/plugins/cordova-plugin-qrscanner/src/browser/worker.js');
var workerBlob = new Blob([workerScript],{type: "text/javascript"});
scanWorker = new Worker(URL.createObjectURL(workerBlob));
}
if(!getVideoPreview()){
// prepare DOM (sync)
Expand Down Expand Up @@ -418,7 +422,6 @@
}
};
thisScanCycle = function(){
window.lastData = getCurrentImageData(video);
scanWorker.postMessage(getCurrentImageData(video));
// avoid race conditions, always clear before starting a cycle
window.clearTimeout(nextScan);
Expand Down Expand Up @@ -452,7 +455,6 @@
video.pause();
var img = new Image();
img.src = captureCurrentFrame(video);
window.lastImage = img.src;
getImg().style.backgroundImage = 'url(' + img.src + ')';
bringStillToFront();
// kill the active stream to turn off the privacy light (the screenshot
Expand Down Expand Up @@ -482,13 +484,22 @@

function useCamera(success, error, array){
var requestedCamera = array[0];
var initialized = isInitialized();
if(requestedCamera !== currentCamera){
currentCamera = requestedCamera;
hide(function(status){
// Don't need this one
status = null;
});
show(success, error);
if(initialized && requestedCamera === 1 && !canChangeCamera()){
error(4); //FRONT_CAMERA_UNAVAILABLE
} else {
currentCamera = requestedCamera;
if(initialized){
hide(function(status){
// Don't need this one
status = null;
});
show(success, error);
} else {
success(calcStatus());
}
}
} else {
success(calcStatus());
}
Expand Down Expand Up @@ -524,7 +535,7 @@
success(calcStatus());
}

module.exports = {
return {
prepare: prepare,
show: show,
hide: hide,
Expand All @@ -539,6 +550,4 @@
getStatus: getStatus,
destroy: destroy
};

require('cordova/exec/proxy').add('QRScanner', module.exports);
})();
}
40 changes: 40 additions & 0 deletions src/browser/src/library.js
@@ -0,0 +1,40 @@
function QRScanner() {
var createQRScannerAdapter = require('../../common/src/createQRScannerAdapter.js');
var createQRScannerInternal = require('./createQRScannerInternal.js');

var internal = createQRScannerInternal();
var functionList = {
prepare: internal.prepare,
show: internal.show,
hide: internal.hide,
scan: internal.scan,
cancelScan: internal.cancelScan,
pausePreview: internal.pausePreview,
resumePreview: internal.resumePreview,
enableLight: internal.enableLight,
disableLight: internal.disableLight,
useCamera: internal.useCamera,
openSettings: internal.openSettings,
getStatus: internal.getStatus,
destroy: internal.destroy
}

// shim cordova's functionality for library usage
var shimCordova = {
exec: function(successCallback, errorCallback, className, functionName, inputArray){
if(className !== 'QRScanner' || !functionList[functionName]){
return errorCallback(0);
}
if(inputArray){
functionList[functionName](successCallback, errorCallback, inputArray);
} else {
functionList[functionName](successCallback, errorCallback);
}
}
}

var adapter = createQRScannerAdapter(shimCordova);
return adapter;
};

module.exports = new QRScanner();
3 changes: 1 addition & 2 deletions src/browser/worker.js → src/browser/src/worker.js
@@ -1,8 +1,7 @@
/*global module:true, importScripts:false, postMessage:false, onmessage:true*/

module = {};
importScripts('qrcode-reader.js');
var QrCode = module.exports;
var QrCode = require('qrcode-reader').default;
var qr = new QrCode();
qr.callback = function(result, err){
postMessage({result: result, err: err});
Expand Down
7 changes: 7 additions & 0 deletions src/common/src/cordova-remap.js
@@ -0,0 +1,7 @@
// This file is generated by `npm run build`.

// remap parameter names from cordova.define
// see `externals` in webpack.cordova.config.js
var cordovaRequire = require;
var cordovaExports = exports;
var cordovaModule = module;

0 comments on commit 052b8d3

Please sign in to comment.