copay-crown
Version:
A Secure Crown Wallet
300 lines (272 loc) • 9.28 kB
text/typescript
import { Injectable } from '@angular/core';
import { QRScanner } from '@ionic-native/qr-scanner';
import { Events } from 'ionic-angular';
import { Logger } from '../../providers/logger/logger';
import { PlatformProvider } from '../platform/platform';
export class ScanProvider {
public text: string;
public scannerVisible: boolean;
public lightEnabled: boolean;
public frontCameraEnabled: boolean;
public isDesktop = !this.platform.isCordova;
public isAvailable: boolean = true;
public hasPermission: boolean = false;
public isDenied: boolean = false;
public isRestricted: boolean = false;
public canEnableLight: boolean = false;
public canChangeCamera: boolean = false;
public canOpenSettings: boolean = false;
public backCamera: boolean = true;
public initializeStarted: boolean = false;
public initializeCompleted: boolean = false;
constructor(
private qrScanner: QRScanner,
private platform: PlatformProvider,
private logger: Logger,
private events: Events
) {
this.scannerVisible = false;
this.lightEnabled = false;
this.frontCameraEnabled = false;
}
private checkCapabilities(status) {
this.logger.debug('scannerService is reviewing platform capabilities...');
// Permission can be assumed on the desktop builds
this.hasPermission = (this.isDesktop || status.authorized) ? true : false;
this.isDenied = status.denied ? true : false;
this.isRestricted = status.restricted ? true : false;
this.canEnableLight = status.canEnableLight ? true : false;
this.canChangeCamera = status.canChangeCamera ? true : false;
this.canOpenSettings = status.canOpenSettings ? true : false;
this.logCapabilities();
}
private orIsNot(bool: boolean): string {
return bool ? '' : 'not ';
}
private logCapabilities() {
this.logger.debug('A camera is ' + this.orIsNot(this.isAvailable) + 'available to this app.');
var access = 'not authorized';
if (this.hasPermission) access = 'authorized';
if (this.isDenied) access = 'denied';
if (this.isRestricted) access = 'restricted';
this.logger.debug('Camera access is ' + access + '.');
this.logger.debug('Support for opening device settings is ' + this.orIsNot(this.canOpenSettings) + 'available on this platform.');
this.logger.debug('A light is ' + this.orIsNot(this.canEnableLight) + 'available on this platform.');
this.logger.debug('A second camera is ' + this.orIsNot(this.canChangeCamera) + 'available on this platform.');
}
/**
* Immediately return known capabilities of the current platform.
*/
public getCapabilities(): any {
return {
isAvailable: this.isAvailable,
hasPermission: this.hasPermission,
isDenied: this.isDenied,
isRestricted: this.isRestricted,
canEnableLight: this.canEnableLight,
canChangeCamera: this.canChangeCamera,
canOpenSettings: this.canOpenSettings
};
};
/**
* If camera access has been granted, pre-initialize the QRScanner. This method
* can be safely called before the scanner is visible to improve perceived
* scanner loading times.
*
* The `status` of QRScanner is returned to the callback.
*/
public gentleInitialize(): Promise<any> {
return new Promise((resolve, reject) => {
if (this.initializeStarted && !this.isDesktop) {
this.qrScanner.getStatus().then((status) => {
this.completeInitialization(status);
});
return resolve();
}
this.initializeStarted = true;
this.logger.debug('Trying to pre-initialize QRScanner.');
if (!this.isDesktop) {
this.qrScanner.getStatus().then((status) => {
this.checkCapabilities(status);
if (status.authorized) {
this.logger.debug('Camera permission already granted.');
this.initialize().then(() => {
return resolve();
});
} else {
this.logger.debug('QRScanner not authorized, waiting to initalize.');
this.completeInitialization(status);
return resolve();
}
});
} else {
this.logger.debug('To avoid flashing the privacy light, we do not pre-initialize the camera on desktop.');
return resolve();
}
});
}
public reinitialize(): void {
this.initializeCompleted = false;
this.qrScanner.destroy();
this.initialize();
};
public initialize(): Promise<any> {
return new Promise((resolve, reject) => {
this.logger.debug('Initializing scanner...');
this.qrScanner.prepare().then((status: any) => {
this.completeInitialization(status);
return resolve();
}).catch((err) => {
this.isAvailable = false;
this.logger.error(err);
// does not return `status` if there is an error
this.qrScanner.getStatus().then((status) => {
this.completeInitialization(status);
return resolve();
});
});
});
}
private completeInitialization(status: any): void {
this.checkCapabilities(status);
this.initializeCompleted = true;
this.events.publish('scannerServiceInitialized');
}
public isInitialized(): boolean {
return this.initializeCompleted;
};
public isInitializeStarted(): boolean {
return this.initializeStarted;
};
/**
* (Re)activate the QRScanner, and cancel the timeouts if present.
*
* The `status` of QRScanner is passed to the callback when activation
* is complete.
*/
public activate(): Promise<any> {
return new Promise((resolve, reject) => {
this.logger.debug('Activating scanner...');
this.qrScanner.show().then((status) => {
this.initializeCompleted = true;
this.checkCapabilities(status);
return resolve();
});
});
}
/**
* Start a new scan.
*/
public scan(): Promise<any> {
return new Promise((resolve, reject) => {
this.logger.debug('Scanning...');
let scanSub = this.qrScanner.scan().subscribe((text: string) => {
this.logger.debug('Scanned something', text);
this.qrScanner.hide(); // hide camera preview
scanSub.unsubscribe(); // stop scanning
return resolve(text);
});
});
}
public pausePreview(): void {
this.qrScanner.pausePreview();
}
public resumePreview(): void {
this.qrScanner.resumePreview();
}
/**
* Deactivate the QRScanner. To balance user-perceived performance and power
* consumption, this kicks off a countdown which will "sleep" the scanner
* after a certain amount of time.
*
* The `status` of QRScanner is passed to the callback when deactivation
* is complete.
*/
public deactivate(): void {
this.logger.debug('Deactivating scanner...');
if (this.lightEnabled) {
this.qrScanner.disableLight();
this.lightEnabled = false;
}
this.hide();
this.destroy();
}
// Natively hide the QRScanner's preview
// On mobile platforms, this can reduce GPU/power usage
// On desktop, this fully turns off the camera (and any associated privacy lights)
private hide() {
this.qrScanner.hide();
}
// Reduce QRScanner power/processing consumption by the maximum amount
private destroy() {
this.qrScanner.destroy();
}
/**
* Toggle the device light (if available).
*
* The callback receives a boolean which is `true` if the light is enabled.
*/
public toggleLight(): Promise<any> {
return new Promise((resolve, reject) => {
this.logger.info('Toggling light...');
if (this.lightEnabled) {
this.qrScanner.disableLight()
.then(resp => {
this.lightEnabled = false;
return resolve(this.lightEnabled);
})
.catch(err => {
this.logger.warn("Error: ", err);
return reject(err);
});
} else {
this.qrScanner.enableLight()
.then(resp => {
this.lightEnabled = true;
return resolve(this.lightEnabled);
})
.catch(err => {
this.logger.warn("Error: ", err);
return reject(err);
});
}
});
};
/**
* Switch cameras (if a second camera is available).
*
* The `status` of QRScanner is passed to the callback when activation
* is complete.
*/
public toggleCamera(): Promise<any> {
return new Promise((resolve, reject) => {
this.logger.info('Toggling camera...');
if (this.frontCameraEnabled) {
this.qrScanner.useBackCamera()
.then(resp => {
this.frontCameraEnabled = false;
return resolve(this.frontCameraEnabled);
})
.catch(err => {
this.logger.warn("Error: ", err);
return reject(err);
});
} else {
this.qrScanner.useFrontCamera()
.then(resp => {
this.frontCameraEnabled = true;
return resolve(this.frontCameraEnabled);
})
.catch(err => {
this.logger.warn("Error: ", err);
return reject(err);
});
}
});
}
public openSettings(): void {
this.logger.debug('Attempting to open device settings...');
this.qrScanner.openSettings();
}
}