* style(privilege): Fix typo 'excusive' -> 'exclusive' * style(privilege): remove unused imports * style(privilege): Fix typo 'initizalized$' -> 'initialized$' * feat(log): Add application wide logger and error handler It is help to debug electron install app on different device * feat(privilege): Add windows USB driver installation * build: I need the windows installer to test the app on windows * fix(privilege): change wdi-simpler installer to zadic * feat(log): change log level to debug in renderer process * chore: Add author in package.json * feat(privilege): Add privilege setter file as extraResource * fix(log): Allowed transport level change only in main process * fix(privilege): Fix app path calculation * fix(privilege): Take the scriptPath between double quote * build: revert the appveyor settings * refactor(privilege): Extract vendor ID, product ID and MAX_PAYLOAD_SIZE into constants file * refactor(privilege): Add both 32 and 64 bit zadics to extraResource of the installer * feat(device): Add HID API communication protocol * build: Fix npm install process * build: Fix npm install process v2 * ci: Add libudev-dev as travis apt dependencies * ci: Merge travis apt packages * ci: remove node-hid from build:usb * ci: try to fix linux build * ci: node-hid use git repo * ci: Add libusb-1.0-0-dev to travis apt dependency * feat(device): Use logging service when communicate with the device * build: create test build * build: PUBLISH_FOR_PULL_REQUEST override * build: revert TEST_BUILD to false * build: node-hid use package version instead of git repo * refactor: remove unused device store files from PR * ci: Manage test build from environment variable * fix(privilege): Set rules files dir base on dev or prod mode * fix(log): Extract nested properties of the logged object * feat(log): use util.inspect in logger service * build: upgrade @types/node-hid -> 0.5.2 * fix(device): Add extra logging when try to open device. * fix(device): log device description and not the device * fix(device): add win specific write * fix(device): add report id as first byte * style(privilege): Reformat else and comment in privilege-checker component * fix(privilege): Comment out windows branch
This commit is contained in:
committed by
László Monda
parent
84b13d3219
commit
2df8f2ea54
@@ -28,6 +28,9 @@ addons:
|
||||
- libgnome-keyring-dev
|
||||
- libsecret-1-dev
|
||||
- icnsutils
|
||||
- libudev-dev
|
||||
- build-essential
|
||||
- libusb-1.0-0-dev
|
||||
|
||||
install:
|
||||
- nvm install
|
||||
@@ -45,9 +48,3 @@ script:
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- build-essential
|
||||
- libudev-dev
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@@ -75,7 +75,9 @@ import { SafeStylePipe } from './shared/pipes';
|
||||
import { CaptureService } from './shared/services/capture.service';
|
||||
import { MapperService } from './shared/services/mapper.service';
|
||||
import { SvgModuleProviderService } from './shared/services/svg-module-provider.service';
|
||||
import { UhkDeviceService } from './services/uhk-device.service';
|
||||
import { UhkLibUsbApiService } from './services/uhk-lib-usb-api.service';
|
||||
import { UhkHidApiService } from './services/uhk-hid-api.service';
|
||||
import { uhkDeviceProvider } from './services/uhk-device-provider';
|
||||
|
||||
import { AutoUpdateSettingsEffects, KeymapEffects, MacroEffects, UserConfigEffects } from './shared/store/effects';
|
||||
import { ApplicationEffect, AppUpdateEffect } from './store/effects';
|
||||
@@ -90,6 +92,9 @@ import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized
|
||||
import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service';
|
||||
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service';
|
||||
import { ElectronLogService } from './services/electron-log.service';
|
||||
import { LOG_SERVICE } from '../../shared/src/services/logger.service';
|
||||
import { ElectronErrorHandlerService } from './services/electron-error-handler.service';
|
||||
import { AppUpdateRendererService } from './services/app-update-renderer.service';
|
||||
import { reducer } from './store';
|
||||
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
|
||||
@@ -185,10 +190,15 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
|
||||
KeymapEditGuard,
|
||||
MacroNotFoundGuard,
|
||||
CaptureService,
|
||||
UhkDeviceService,
|
||||
{ provide: DATA_STORAGE_REPOSITORY, useClass: ElectronDataStorageRepositoryService },
|
||||
DefaultUserConfigurationService,
|
||||
AppUpdateRendererService
|
||||
{ provide: LOG_SERVICE, useClass: ElectronLogService },
|
||||
{ provide: ErrorHandler, useClass: ElectronErrorHandlerService },
|
||||
AppUpdateRendererService,
|
||||
UhkHidApiService,
|
||||
UhkLibUsbApiService,
|
||||
uhkDeviceProvider()
|
||||
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/ignoreElements';
|
||||
import 'rxjs/add/operator/takeWhile';
|
||||
|
||||
import { UhkDeviceService } from './../../services/uhk-device.service';
|
||||
import { UhkDeviceService } from '../../services/uhk-device.service';
|
||||
|
||||
@Component({
|
||||
selector: 'missing-device',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// <reference path="../../custom_types/sudo-prompt.d.ts"/>
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/observable/of';
|
||||
@@ -15,6 +15,7 @@ import * as path from 'path';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
|
||||
import { UhkDeviceService } from '../../services/uhk-device.service';
|
||||
import { ILogService, LOG_SERVICE } from '../../../../shared/src/services/logger.service';
|
||||
|
||||
@Component({
|
||||
selector: 'privilege-checker',
|
||||
@@ -23,7 +24,18 @@ import { UhkDeviceService } from '../../services/uhk-device.service';
|
||||
})
|
||||
export class PrivilegeCheckerComponent {
|
||||
|
||||
constructor(private router: Router, private uhkDevice: UhkDeviceService) {
|
||||
private rootDir: string;
|
||||
|
||||
constructor(private router: Router,
|
||||
private uhkDevice: UhkDeviceService,
|
||||
@Inject(LOG_SERVICE) private logService: ILogService) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.resolve(path.join(remote.process.cwd(), remote.process.argv[1]), '..');
|
||||
} else {
|
||||
this.rootDir = path.dirname(remote.app.getAppPath());
|
||||
}
|
||||
this.logService.info('App root dir: ', this.rootDir);
|
||||
|
||||
uhkDevice.isConnected()
|
||||
.distinctUntilChanged()
|
||||
.takeWhile(connected => connected)
|
||||
@@ -50,14 +62,21 @@ export class PrivilegeCheckerComponent {
|
||||
case 'linux':
|
||||
permissionSetter = this.setUpPermissionsOnLinux();
|
||||
break;
|
||||
// HID API shouldn't need privilege escalation on Windows
|
||||
// TODO: If all HID API test success then delete this branch and setUpPermissionsOnWin() method
|
||||
// case 'win32':
|
||||
// permissionSetter = this.setUpPermissionsOnWin();
|
||||
// break;
|
||||
default:
|
||||
permissionSetter = Observable.throw('Permissions couldn\'t be set. Invalid platform: ' + process.platform);
|
||||
break;
|
||||
}
|
||||
permissionSetter.subscribe({
|
||||
error: e => console.error(e),
|
||||
error: e => {
|
||||
console.log(e);
|
||||
},
|
||||
complete: () => {
|
||||
console.log('Permissions has been successfully set');
|
||||
this.logService.info('Permissions has been successfully set');
|
||||
this.uhkDevice.initialize();
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
@@ -66,12 +85,13 @@ export class PrivilegeCheckerComponent {
|
||||
|
||||
private setUpPermissionsOnLinux(): Observable<void> {
|
||||
const subject = new ReplaySubject<void>();
|
||||
const rootDir = path.resolve(path.join(remote.process.cwd(), remote.process.argv[1]), '..');
|
||||
const scriptPath = path.resolve(rootDir, 'rules/setup-rules.sh');
|
||||
const scriptPath = path.resolve(this.rootDir, 'rules/setup-rules.sh');
|
||||
const options = {
|
||||
name: 'Setting UHK access rules'
|
||||
};
|
||||
sudo.exec(`sh ${scriptPath}`, options, (error: any) => {
|
||||
const command = `sh ${scriptPath}`;
|
||||
console.log(command);
|
||||
sudo.exec(command, options, (error: any) => {
|
||||
if (error) {
|
||||
subject.error(error);
|
||||
} else {
|
||||
@@ -82,4 +102,34 @@ export class PrivilegeCheckerComponent {
|
||||
return subject.asObservable();
|
||||
}
|
||||
|
||||
// TODO: If all HID API test success then delete this method.
|
||||
// and remove zadic-${process.arch}.exe files from windows installer resources
|
||||
// private setUpPermissionsOnWin(): Observable<void> {
|
||||
// const subject = new ReplaySubject<void>();
|
||||
//
|
||||
// // source code: https://github.com/pbatard/libwdi
|
||||
// const scriptPath = path.resolve(this.rootDir, `rules/zadic-${process.arch}.exe`);
|
||||
// const options = {
|
||||
// name: 'Setting UHK access rules'
|
||||
// };
|
||||
// const params = [
|
||||
// `--vid $\{Constants.VENDOR_ID}`,
|
||||
// `--pid $\{Constants.PRODUCT_ID}`,
|
||||
// '--iface 0', // interface ID
|
||||
// '--usealldevices', // if the device has installed USB driver than overwrite it
|
||||
// '--noprompt' // return at the end of the installation and not waiting for any user command
|
||||
// ];
|
||||
// const paramsString = params.join(' ');
|
||||
// const command = `"${scriptPath}" ${paramsString}`;
|
||||
//
|
||||
// sudo.exec(command, options, (error: any) => {
|
||||
// if (error) {
|
||||
// subject.error(error);
|
||||
// } else {
|
||||
// subject.complete();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return subject.asObservable();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IpcEvents } from './shared/util';
|
||||
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
|
||||
// import './dev-extension';
|
||||
// require('electron-debug')({ showDevTools: true, enabled: true });
|
||||
require('electron-debug')({ showDevTools: false, enabled: true });
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<!--<script src="vendor/usb/usb.js"></script>-->
|
||||
<script>
|
||||
usb = require('usb');
|
||||
nodeHid = require('node-hid');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
6
electron/src/models/sender-message.ts
Normal file
6
electron/src/models/sender-message.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
|
||||
export interface SenderMessage {
|
||||
buffer: Buffer;
|
||||
observer: Observer<any>;
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"npm": ">=3.10.7 <4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hid": "0.5.4",
|
||||
"usb": "git+https://github.com/aktary/node-usb.git"
|
||||
}
|
||||
}
|
||||
|
||||
10
electron/src/services/electron-error-handler.service.ts
Normal file
10
electron/src/services/electron-error-handler.service.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ErrorHandler, Inject } from '@angular/core';
|
||||
import { ILogService, LOG_SERVICE } from '../../../shared/src/services/logger.service';
|
||||
|
||||
export class ElectronErrorHandlerService implements ErrorHandler {
|
||||
constructor(@Inject(LOG_SERVICE)private logService: ILogService) {}
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
29
electron/src/services/electron-log.service.ts
Normal file
29
electron/src/services/electron-log.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as log from 'electron-log';
|
||||
import * as util from 'util';
|
||||
|
||||
import { ILogService } from '../../../shared/src/services/logger.service';
|
||||
|
||||
/**
|
||||
* This service use the electron-log package to write log in file.
|
||||
* The logger usable in main and renderer process.
|
||||
* The location of the log files:
|
||||
* - on Linux: ~/.config/<app name>/log.log
|
||||
* - on OS X: ~/Library/Logs/<app name>/log.log
|
||||
* - on Windows: %USERPROFILE%\AppData\Roaming\<app name>\log.log
|
||||
* The app name: UHK Agent. The up to date value in the scripts/release.js file.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ElectronLogService implements ILogService {
|
||||
private static getErrorText(args: any) {
|
||||
return util.inspect(args);
|
||||
}
|
||||
|
||||
error(...args: any[]): void {
|
||||
log.error(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
|
||||
info(...args: any[]): void {
|
||||
log.info(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
}
|
||||
18
electron/src/services/uhk-device-provider.ts
Normal file
18
electron/src/services/uhk-device-provider.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Provider } from '@angular/core';
|
||||
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
import { UhkHidApiService } from './uhk-hid-api.service';
|
||||
|
||||
export function uhkDeviceProvider(): Provider {
|
||||
// HID API officially support MAC, WIN and linux x64 platform
|
||||
// https://github.com/node-hid/node-hid#platform-support
|
||||
if (process.platform === 'darwin' ||
|
||||
process.platform === 'win32' ||
|
||||
(process.platform === 'linux' && process.arch === 'x64')) {
|
||||
return { provide: UhkDeviceService, useClass: UhkHidApiService };
|
||||
}
|
||||
|
||||
// On other platform use libUsb, but we try to test on all platform
|
||||
// return { provide: UhkDeviceService, useClass: UhkLibUsbApiService };
|
||||
return { provide: UhkDeviceService, useClass: UhkHidApiService };
|
||||
}
|
||||
@@ -1,195 +1,49 @@
|
||||
import { Injectable, OnDestroy, NgZone } from '@angular/core';
|
||||
|
||||
import { Inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/from';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/combineLatest';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
|
||||
import { Device, Interface, InEndpoint, OutEndpoint, findByIds, on } from 'usb';
|
||||
|
||||
import { Layer } from '../shared/config-serializer/config-items/layer';
|
||||
import { UhkBuffer } from '../shared/config-serializer/uhk-buffer';
|
||||
|
||||
const vendorId = 0x1d50;
|
||||
const productId = 0x6122;
|
||||
const MAX_PAYLOAD_SIZE = 64;
|
||||
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service';
|
||||
import { SenderMessage } from '../models/sender-message';
|
||||
import { Constants } from '../shared/util/constants';
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
}
|
||||
|
||||
interface SenderMessage {
|
||||
buffer: Buffer;
|
||||
observer: Observer<any>;
|
||||
}
|
||||
export abstract class UhkDeviceService {
|
||||
protected connected$: BehaviorSubject<boolean>;
|
||||
protected initialized$: BehaviorSubject<boolean>;
|
||||
protected deviceOpened$: BehaviorSubject<boolean>;
|
||||
protected outSubscription: Subscription;
|
||||
|
||||
@Injectable()
|
||||
export class UhkDeviceService implements OnDestroy {
|
||||
protected messageIn$: Observable<Buffer>;
|
||||
protected messageOut$: Subject<SenderMessage>;
|
||||
|
||||
private device: Device;
|
||||
private deviceOpened$: BehaviorSubject<boolean>;
|
||||
private connected$: BehaviorSubject<boolean>;
|
||||
private initizalized$: BehaviorSubject<boolean>;
|
||||
|
||||
private messageIn$: Observable<Buffer>;
|
||||
private messageOut$: Subject<SenderMessage>;
|
||||
|
||||
private outSubscription: Subscription;
|
||||
|
||||
constructor(zone: NgZone) {
|
||||
constructor(@Inject(LOG_SERVICE) protected logService: ILogService) {
|
||||
this.messageOut$ = new Subject<SenderMessage>();
|
||||
this.initizalized$ = new BehaviorSubject(false);
|
||||
this.initialized$ = new BehaviorSubject(false);
|
||||
this.connected$ = new BehaviorSubject(false);
|
||||
this.deviceOpened$ = new BehaviorSubject(false);
|
||||
this.outSubscription = Subscription.EMPTY;
|
||||
|
||||
this.initialize();
|
||||
|
||||
// The change detection doesn't work properly if the callbacks are called outside Angular Zone
|
||||
on('attach', (device: Device) => zone.run(() => this.onDeviceAttach(device)));
|
||||
on('detach', (device: Device) => zone.run(() => this.onDeviceDetach(device)));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.disconnect();
|
||||
this.initizalized$.unsubscribe();
|
||||
this.initialized$.unsubscribe();
|
||||
this.connected$.unsubscribe();
|
||||
this.deviceOpened$.unsubscribe();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.initizalized$.getValue()) {
|
||||
return;
|
||||
}
|
||||
this.device = findByIds(vendorId, productId);
|
||||
this.connected$.next(!!this.device);
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
this.deviceOpened$.next(true);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const usbInterface: Interface = this.device.interface(0);
|
||||
// https://github.com/tessel/node-usb/issues/147
|
||||
// The function 'isKernelDriverActive' is not available on Windows and not even needed.
|
||||
if (process.platform !== 'win32' && usbInterface.isKernelDriverActive()) {
|
||||
usbInterface.detachKernelDriver();
|
||||
}
|
||||
|
||||
// https://github.com/tessel/node-usb/issues/30
|
||||
// Mac is not allow excusive right to use USB
|
||||
if (process.platform !== 'darwin') {
|
||||
usbInterface.claim();
|
||||
}
|
||||
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
const inEndPoint: InEndpoint = <InEndpoint>usbInterface.endpoints[0];
|
||||
console.log('Try to read');
|
||||
inEndPoint.transfer(MAX_PAYLOAD_SIZE, (error: string, receivedBuffer: Buffer) => {
|
||||
if (error) {
|
||||
console.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
console.log('read data', receivedBuffer);
|
||||
subscriber.next(receivedBuffer);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outEndPoint: OutEndpoint = <OutEndpoint>usbInterface.endpoints[1];
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
console.log('transfering', senderPackage.buffer);
|
||||
outEndPoint.transfer(senderPackage.buffer, error => {
|
||||
if (error) {
|
||||
console.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
console.log('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
})).concat(this.messageIn$)
|
||||
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
|
||||
.catch((error: string) => {
|
||||
senderPackage.observer.error(error);
|
||||
return Observable.empty<void>();
|
||||
});
|
||||
}).publish();
|
||||
this.outSubscription = outSending.connect();
|
||||
|
||||
this.initizalized$.next(true);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.outSubscription.unsubscribe();
|
||||
this.messageIn$ = undefined;
|
||||
this.initizalized$.next(false);
|
||||
this.deviceOpened$.next(false);
|
||||
this.connected$.next(false);
|
||||
}
|
||||
|
||||
isInitialized(): Observable<boolean> {
|
||||
return this.initizalized$.asObservable();
|
||||
}
|
||||
|
||||
isConnected(): Observable<boolean> {
|
||||
return this.connected$.asObservable();
|
||||
}
|
||||
|
||||
hasPermissions(): Observable<boolean> {
|
||||
return this.isConnected()
|
||||
.combineLatest(this.deviceOpened$)
|
||||
.map((latest: boolean[]) => {
|
||||
const connected = latest[0];
|
||||
const opened = latest[1];
|
||||
if (!connected) {
|
||||
return false;
|
||||
} else if (opened) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
this.device.close();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
isOpened(): Observable<boolean> {
|
||||
return this.deviceOpened$.asObservable();
|
||||
}
|
||||
|
||||
sendConfig(configBuffer: Buffer): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
console.log('Sending...', configBuffer);
|
||||
this.logService.info('Sending...', configBuffer);
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE - 4;
|
||||
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
|
||||
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
|
||||
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
|
||||
? MAX_SENDING_PAYLOAD_SIZE
|
||||
@@ -206,7 +60,7 @@ export class UhkDeviceService implements OnDestroy {
|
||||
if (buffers.length === fragments.length) {
|
||||
subscriber.next(Buffer.concat(buffers));
|
||||
subscriber.complete();
|
||||
console.log('Sending finished');
|
||||
this.logService.info('Sending finished');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -219,7 +73,7 @@ export class UhkDeviceService implements OnDestroy {
|
||||
|
||||
applyConfig(): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
console.log('Applying configuration');
|
||||
this.logService.info('Applying configuration');
|
||||
this.messageOut$.next({
|
||||
buffer: new Buffer([Command.ApplyConfig]),
|
||||
observer: subscriber
|
||||
@@ -227,21 +81,27 @@ export class UhkDeviceService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
onDeviceAttach(device: Device) {
|
||||
if (device.deviceDescriptor.idVendor !== vendorId || device.deviceDescriptor.idProduct !== productId) {
|
||||
return;
|
||||
}
|
||||
// Ugly hack: device is not openable (on Windows) right after the attach
|
||||
Observable.timer(100)
|
||||
.first()
|
||||
.subscribe(() => this.initialize());
|
||||
isInitialized(): Observable<boolean> {
|
||||
return this.initialized$.asObservable();
|
||||
}
|
||||
|
||||
onDeviceDetach(device: Device) {
|
||||
if (device.deviceDescriptor.idVendor !== vendorId || device.deviceDescriptor.idProduct !== productId) {
|
||||
return;
|
||||
}
|
||||
this.disconnect();
|
||||
isConnected(): Observable<boolean> {
|
||||
return this.connected$.asObservable();
|
||||
}
|
||||
|
||||
isOpened(): Observable<boolean> {
|
||||
return this.deviceOpened$.asObservable();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.outSubscription.unsubscribe();
|
||||
this.messageIn$ = undefined;
|
||||
this.initialized$.next(false);
|
||||
this.deviceOpened$.next(false);
|
||||
this.connected$.next(false);
|
||||
}
|
||||
|
||||
abstract initialize(): void;
|
||||
|
||||
abstract hasPermissions(): Observable<boolean>;
|
||||
}
|
||||
|
||||
164
electron/src/services/uhk-hid-api.service.ts
Normal file
164
electron/src/services/uhk-hid-api.service.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/combineLatest';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service';
|
||||
import { Constants } from '../shared/util';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
|
||||
private device: HID;
|
||||
|
||||
private pollTimer$: Subscription;
|
||||
|
||||
constructor(@Inject(LOG_SERVICE) protected logService: ILogService) {
|
||||
super(logService);
|
||||
|
||||
this.pollUhkDevice();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.pollTimer$.unsubscribe();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.initialized$.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.device = this.getDevice();
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deviceOpened$.next(true);
|
||||
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Try to read');
|
||||
this.device.read((error: any, data: any = []) => {
|
||||
if (error) {
|
||||
this.logService.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('read data', data);
|
||||
subscriber.next(data);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
this.logService.info('transfering', senderPackage.buffer);
|
||||
const data = Array.prototype.slice.call(senderPackage.buffer, 0);
|
||||
// if data start with 0 need to add additional leading zero because HID API remove it.
|
||||
// https://github.com/node-hid/node-hid/issues/187
|
||||
if (data.length > 0 && data[0] === 0) {
|
||||
data.unshift(0);
|
||||
}
|
||||
|
||||
// From HID API documentation:
|
||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||
// The first byte of data[] must contain the Report ID.
|
||||
// For devices which only support a single report, this must be set to 0x0.
|
||||
data.unshift(0);
|
||||
|
||||
try {
|
||||
this.device.write(data);
|
||||
this.logService.info('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
}
|
||||
})).concat(this.messageIn$)
|
||||
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
|
||||
.catch((error: string) => {
|
||||
senderPackage.observer.error(error);
|
||||
return Observable.empty<void>();
|
||||
});
|
||||
}).publish();
|
||||
this.outSubscription = outSending.connect();
|
||||
|
||||
this.initialized$.next(true);
|
||||
}
|
||||
|
||||
hasPermissions(): Observable<boolean> {
|
||||
return this.isConnected()
|
||||
.combineLatest(this.deviceOpened$)
|
||||
.map((latest: boolean[]) => {
|
||||
const connected = latest[0];
|
||||
const opened = latest[1];
|
||||
if (!connected) {
|
||||
return false;
|
||||
} else if (opened) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 0 interface of the keyboard.
|
||||
* @returns {HID}
|
||||
*/
|
||||
getDevice(): HID {
|
||||
try {
|
||||
const devs = devices();
|
||||
this.logService.info('Available devices:', devs);
|
||||
|
||||
const dev = devs.find((x: Device) =>
|
||||
x.vendorId === Constants.VENDOR_ID &&
|
||||
x.productId === Constants.PRODUCT_ID &&
|
||||
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
|
||||
|
||||
const device = new HID(dev.path);
|
||||
this.logService.info('Used device:', dev);
|
||||
return device;
|
||||
}
|
||||
catch (err) {
|
||||
this.logService.error('Can not create device:', err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* HID API not support device attached and detached event.
|
||||
* This method check the keyboard is attached to the computer or not.
|
||||
* Every second check the HID device list.
|
||||
*/
|
||||
private pollUhkDevice() {
|
||||
this.pollTimer$ = Observable.timer(0, 1000)
|
||||
.map(() => {
|
||||
return devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
||||
dev.productId === Constants.PRODUCT_ID);
|
||||
})
|
||||
.distinctUntilChanged()
|
||||
.do((connected: boolean) => {
|
||||
this.connected$.next(connected);
|
||||
if (connected) {
|
||||
this.initialize();
|
||||
} else {
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
142
electron/src/services/uhk-lib-usb-api.service.ts
Normal file
142
electron/src/services/uhk-lib-usb-api.service.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Inject, Injectable, NgZone, OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/combineLatest';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { Device, findByIds, InEndpoint, Interface, on, OutEndpoint } from 'usb';
|
||||
|
||||
import { ILogService, LOG_SERVICE } from '../shared/services/logger.service';
|
||||
import { Constants } from '../shared/util';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkLibUsbApiService extends UhkDeviceService implements OnDestroy {
|
||||
private device: Device;
|
||||
|
||||
static isUhkDevice(device: Device) {
|
||||
return device.deviceDescriptor.idVendor === Constants.VENDOR_ID &&
|
||||
device.deviceDescriptor.idProduct === Constants.PRODUCT_ID;
|
||||
}
|
||||
|
||||
constructor(zone: NgZone,
|
||||
@Inject(LOG_SERVICE) protected logService: ILogService) {
|
||||
super(logService);
|
||||
|
||||
this.initialize();
|
||||
|
||||
// The change detection doesn't work properly if the callbacks are called outside Angular Zone
|
||||
on('attach', (device: Device) => zone.run(() => this.onDeviceAttach(device)));
|
||||
on('detach', (device: Device) => zone.run(() => this.onDeviceDetach(device)));
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.initialized$.getValue()) {
|
||||
return;
|
||||
}
|
||||
this.device = findByIds(Constants.VENDOR_ID, Constants.PRODUCT_ID);
|
||||
this.connected$.next(!!this.device);
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
this.deviceOpened$.next(true);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const usbInterface: Interface = this.device.interface(0);
|
||||
// https://github.com/tessel/node-usb/issues/147
|
||||
// The function 'isKernelDriverActive' is not available on Windows and not even needed.
|
||||
if (usbInterface.isKernelDriverActive()) {
|
||||
usbInterface.detachKernelDriver();
|
||||
}
|
||||
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
const inEndPoint: InEndpoint = <InEndpoint>usbInterface.endpoints[0];
|
||||
this.logService.info('Try to read');
|
||||
inEndPoint.transfer(Constants.MAX_PAYLOAD_SIZE, (error: string, receivedBuffer: Buffer) => {
|
||||
if (error) {
|
||||
this.logService.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('read data', receivedBuffer);
|
||||
subscriber.next(receivedBuffer);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outEndPoint: OutEndpoint = <OutEndpoint>usbInterface.endpoints[1];
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
this.logService.info('transfering', senderPackage.buffer);
|
||||
outEndPoint.transfer(senderPackage.buffer, error => {
|
||||
if (error) {
|
||||
this.logService.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
})).concat(this.messageIn$)
|
||||
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
|
||||
.catch((error: string) => {
|
||||
senderPackage.observer.error(error);
|
||||
return Observable.empty<void>();
|
||||
});
|
||||
}).publish();
|
||||
this.outSubscription = outSending.connect();
|
||||
|
||||
this.initialized$.next(true);
|
||||
}
|
||||
|
||||
hasPermissions(): Observable<boolean> {
|
||||
return this.isConnected()
|
||||
.combineLatest(this.deviceOpened$)
|
||||
.map((latest: boolean[]) => {
|
||||
const connected = latest[0];
|
||||
const opened = latest[1];
|
||||
if (!connected) {
|
||||
return false;
|
||||
} else if (opened) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
this.device.close();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
onDeviceAttach(device: Device) {
|
||||
if (!UhkLibUsbApiService.isUhkDevice(device)) {
|
||||
return;
|
||||
}
|
||||
// Ugly hack: device is not openable (on Windows) right after the attach
|
||||
Observable.timer(100)
|
||||
.first()
|
||||
.subscribe(() => this.initialize());
|
||||
}
|
||||
|
||||
onDeviceDetach(device: Device) {
|
||||
if (!UhkLibUsbApiService.isUhkDevice(device)) {
|
||||
return;
|
||||
}
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ module.exports = {
|
||||
},
|
||||
target: 'electron-renderer',
|
||||
externals: {
|
||||
usb: 'usb'
|
||||
usb: 'usb',
|
||||
'node-hid':'nodeHid'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
@@ -40,7 +41,8 @@ module.exports = {
|
||||
use: ['raw-loader', 'sass-loader']
|
||||
},
|
||||
{ test: /jquery/, loader: 'expose-loader?$!expose-loader?jQuery' },
|
||||
{ test: require.resolve("usb"), loader: "expose-loader?usb" }
|
||||
{ test: require.resolve("usb"), loader: "expose-loader?usb" },
|
||||
{ test: require.resolve("node-hid"), loader: "expose-loader?node-hid" }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
|
||||
15
package.json
15
package.json
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "uhk-agent",
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"@types/file-saver": "0.0.1",
|
||||
"@types/jquery": "3.2.1",
|
||||
"@types/node": "^6.0.78",
|
||||
"@types/node-hid": "^0.5.2",
|
||||
"@types/usb": "^1.1.3",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
@@ -37,6 +39,7 @@
|
||||
"npm-run-all": "4.0.2",
|
||||
"path": "^0.12.7",
|
||||
"raw-loader": "^0.5.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"sass-loader": "^6.0.3",
|
||||
"standard-version": "^4.0.0",
|
||||
"stylelint": "^7.10.1",
|
||||
@@ -75,6 +78,7 @@
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
"ngrx-store-freeze": "^0.1.9",
|
||||
"node-hid": "0.5.4",
|
||||
"reselect": "3.0.1",
|
||||
"rxjs": "^5.4.1",
|
||||
"select2": "^4.0.3",
|
||||
@@ -85,17 +89,17 @@
|
||||
"zone.js": "0.8.12"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "run-p build:usb \"symlink -- -i\" ",
|
||||
"postinstall": "run-p \"symlink -- -i\" ",
|
||||
"test": "cd ./test-serializer && node ./test-serializer.js",
|
||||
"lint": "run-s -scn lint:ts lint:style",
|
||||
"lint:ts": "tslint \"electron/src/**/*.ts\" \"web/src/**/*.ts\" \"shared/**/*.ts\" \"test-serializer/**/*.ts\"",
|
||||
"lint:style": "stylelint \"electron/**/*.scss\" \"web/**/*.scss\" \"shared/**/*.scss\" --syntax scss",
|
||||
"build": "run-p build:web build:electron",
|
||||
"build:web": "webpack --config \"web/src/webpack.config.js\"",
|
||||
"build:electron": "run-s -scn build:electron:main build:electron:app",
|
||||
"build:electron": "run-s -sn build:electron:main build:electron:app install:build-deps",
|
||||
"build:electron:main": "webpack --config \"electron/src/webpack.config.electron-main.js\"",
|
||||
"build:electron:app": "webpack --config \"electron/src/webpack.config.js\"",
|
||||
"build:usb": "electron-rebuild -w usb -p",
|
||||
"build:usb": "electron-rebuild -w usb,node-hid -p -m electron/dist",
|
||||
"build:test": "webpack --config \"test-serializer/webpack.config.js\"",
|
||||
"server:web": "webpack-dev-server --config \"web/src/webpack.config.js\" --content-base \"./web/dist\"",
|
||||
"server:electron": "webpack --config \"electron/src/webpack.config.js\" --watch",
|
||||
@@ -103,7 +107,8 @@
|
||||
"symlink": "node ./tools/symlinker",
|
||||
"standard-version": "standard-version",
|
||||
"pack": "node ./scripts/release.js",
|
||||
"install:build-deps": "cd electron/dist && npm i",
|
||||
"release": "npm run install:build-deps && node ./scripts/release.js"
|
||||
"install:build-deps": "cd electron/dist && npm i && cd .. && npm run build:usb",
|
||||
"release": "node ./scripts/release.js",
|
||||
"clean": "rimraf ./node_modules ./electron/dist"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
rules/zadic-ia32.exe
Normal file
BIN
rules/zadic-ia32.exe
Normal file
Binary file not shown.
BIN
rules/zadic-x64.exe
Normal file
BIN
rules/zadic-x64.exe
Normal file
Binary file not shown.
@@ -1,7 +1,11 @@
|
||||
'use strict';
|
||||
const jsonfile = require('jsonfile');
|
||||
|
||||
const TEST_BUILD = false; // set true if you would like to test on your local machince
|
||||
const TEST_BUILD = process.env.TEST_BUILD; // set true if you would like to test on your local machince
|
||||
|
||||
// electron-builder security override.
|
||||
// Need if wanna create test release build from PR
|
||||
process.env.PUBLISH_FOR_PULL_REQUEST = TEST_BUILD;
|
||||
|
||||
if (!process.env.CI && !TEST_BUILD) {
|
||||
console.error('Create release only on CI server');
|
||||
@@ -51,16 +55,23 @@ if (process.env.TRAVIS) {
|
||||
|
||||
let target = '';
|
||||
let artifactName = 'UHK.Agent-${version}-${os}';
|
||||
let extraResources = [];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
target = Platform.MAC.createTarget();
|
||||
artifactName += '.${ext}';
|
||||
} else if (process.platform === 'win32') {
|
||||
target = Platform.WINDOWS.createTarget();
|
||||
// TODO: If all HID API test success then remove zadic extra resources
|
||||
extraResources.push(`rules/zadic-ia32.exe`);
|
||||
extraResources.push(`rules/zadic-x64.exe`);
|
||||
artifactName += '-${arch}.${ext}';
|
||||
extraResources.push(`rules/zadic-${process.arch}.exe`);
|
||||
} else if (process.platform === 'linux') {
|
||||
target = Platform.LINUX.createTarget();
|
||||
artifactName += '.${ext}';
|
||||
extraResources.push('rules/setup-rules.sh');
|
||||
extraResources.push('rules/50-uhk60.rules');
|
||||
} else {
|
||||
console.error(`I dunno how to publish a release for ${process.platform} :(`);
|
||||
process.exit(1);
|
||||
@@ -78,7 +89,7 @@ if (TEST_BUILD || gitTag) {
|
||||
updateVersionNumberIn2rndPackageJson(jsonVersion);
|
||||
|
||||
builder.build({
|
||||
dir: true,
|
||||
dir: TEST_BUILD,
|
||||
targets: target,
|
||||
appMetadata: {
|
||||
main: 'electron-main.js',
|
||||
@@ -97,6 +108,12 @@ if (TEST_BUILD || gitTag) {
|
||||
mac: {
|
||||
category: 'public.app-category.utilities'
|
||||
},
|
||||
win: {
|
||||
extraResources
|
||||
},
|
||||
linux: {
|
||||
extraResources
|
||||
},
|
||||
publish: 'github',
|
||||
artifactName,
|
||||
files: [
|
||||
|
||||
20
shared/src/services/logger.service.ts
Normal file
20
shared/src/services/logger.service.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {Injectable, InjectionToken} from '@angular/core';
|
||||
|
||||
export interface ILogService {
|
||||
|
||||
error(...args: any[]): void;
|
||||
info(...args: any[]): void;
|
||||
}
|
||||
|
||||
export let LOG_SERVICE = new InjectionToken('logger-service');
|
||||
|
||||
@Injectable()
|
||||
export class ConsoleLogService implements ILogService {
|
||||
error(...args: any[]): void {
|
||||
console.error(args);
|
||||
}
|
||||
|
||||
info(...args: any[]): void {
|
||||
console.info(args);
|
||||
}
|
||||
}
|
||||
5
shared/src/util/constants.ts
Normal file
5
shared/src/util/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export namespace Constants {
|
||||
export const VENDOR_ID = 0x1D50;
|
||||
export const PRODUCT_ID = 0x6122;
|
||||
export const MAX_PAYLOAD_SIZE = 64;
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Constants } from './constants';
|
||||
|
||||
export { Constants };
|
||||
|
||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||
export function camelCaseToSentence(camelCasedText: string): string {
|
||||
return camelCasedText.replace(/^[a-z]|[A-Z]/g, function (v, i) {
|
||||
|
||||
@@ -76,7 +76,8 @@ import { MacroNotFoundGuard } from './shared/components/macro/not-found';
|
||||
import { DATA_STORAGE_REPOSITORY } from './shared/services/datastorage-repository.service';
|
||||
import { LocalDataStorageRepositoryService } from './shared/services/local-datastorage-repository.service';
|
||||
import { DefaultUserConfigurationService } from './shared/services/default-user-configuration.service';
|
||||
import { reducer } from './shared/store/reducers/index';
|
||||
import { reducer } from '../../shared/src/store/reducers/index';
|
||||
import { ConsoleLogService, LOG_SERVICE } from '../../shared/src/services/logger.service';
|
||||
import { AutoUpdateSettings } from './shared/components/auto-update-settings/auto-update-settings';
|
||||
|
||||
@NgModule({
|
||||
@@ -160,9 +161,10 @@ import { AutoUpdateSettings } from './shared/components/auto-update-settings/aut
|
||||
MacroNotFoundGuard,
|
||||
CaptureService,
|
||||
{provide: DATA_STORAGE_REPOSITORY, useClass: LocalDataStorageRepositoryService},
|
||||
DefaultUserConfigurationService,
|
||||
{ provide: LOG_SERVICE, useClass: ConsoleLogService },
|
||||
DefaultUserConfigurationService
|
||||
],
|
||||
bootstrap: [MainAppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
export class AppModule { }
|
||||
|
||||
Reference in New Issue
Block a user