refactore: create feature modules (#387)
* add @angular/cli to the project * increase nodejs version -> 8.2.1 * add lerna * merge web and shared module * move electron module into packages as uhk-agent Electron agent functionality is not working * delete symlinker * convert private properties to public of component if used in html * revert uhk-message.component * fix component path * fix the correct name of the uhk-message.component.scss * building web and electron module * delete uhk-renderer package * handle device connect disconnect state * add privilege detection * fix set privilege functionality * turn back download keymap functionality * add bootstrap, select2 js and fix null pointer exception * turn back upload data to keyboard * fix send keymap * fix test-serializer * add missing package.json * merging * fix appveyor build * fix linting * turn back electron storage service * commit the missing electron-datastorage-repository * update node to 8.3.0 in .nvmrc and log node version in appveyor build * set exact version number in appveyor build * vertical align privilege and missing device components * set back node version to 8 in appveyor * move node-usb dependency from usb dir to root maybe it is fix the appveyor build * revert usb to root * fix electron builder script * fix electron builder script * turn off electron devtools * remove CTRL+U functionality * fix CTRL+o * fix lint error * turnoff store freeze * start process when got `Error: EPERM: operation not permitted` error * move files from root usb dir -> packages/usb
This commit is contained in:
committed by
László Monda
parent
97770f67c0
commit
0f558e4132
@@ -0,0 +1,42 @@
|
||||
import * as storage from 'electron-settings';
|
||||
|
||||
import { UserConfiguration } from '../../app/config-serializer/config-items/user-configuration';
|
||||
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
|
||||
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';
|
||||
|
||||
export class ElectronDataStorageRepositoryService implements DataStorageRepositoryService {
|
||||
static getValue(key: string): any {
|
||||
const value = storage.get(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(<string>value);
|
||||
}
|
||||
|
||||
static saveValue(key: string, value: any) {
|
||||
storage.set(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
// TODO: Throw error when read user config from electron datastore
|
||||
// Agent-electron should always read the configuration from the UHK over USB which will be implemented later.
|
||||
// If implemented the feature should have to throw an error to prevent unwanted side effects.
|
||||
getConfig(): UserConfiguration {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Throw error when save user config from electron-datastore
|
||||
// Agent-electron should always read the configuration from the UHK over USB which will be implemented later.
|
||||
// If implemented the feature should have to throw an error to prevent unwanted side effects.
|
||||
saveConfig(config: UserConfiguration): void {
|
||||
|
||||
}
|
||||
|
||||
getAutoUpdateSettings(): AutoUpdateSettings {
|
||||
return ElectronDataStorageRepositoryService.getValue('auto-update-settings');
|
||||
}
|
||||
|
||||
saveAutoUpdateSettings(settings: AutoUpdateSettings): void {
|
||||
ElectronDataStorageRepositoryService.saveValue('auto-update-settings', settings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
@Injectable()
|
||||
export class ElectronErrorHandlerService implements ErrorHandler {
|
||||
constructor(private logService: LogService) { }
|
||||
|
||||
handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as log from 'electron-log';
|
||||
import * as util from 'util';
|
||||
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
/**
|
||||
* 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 LogService {
|
||||
private static getErrorText(args: any) {
|
||||
return util.inspect(args);
|
||||
}
|
||||
|
||||
error(...args: any[]): void {
|
||||
log.error(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
|
||||
debug(...args: any[]): void {
|
||||
log.debug(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
|
||||
silly(...args: any[]): void {
|
||||
log.silly(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
|
||||
info(...args: any[]): void {
|
||||
log.info(ElectronLogService.getErrorText(args));
|
||||
}
|
||||
}
|
||||
15
packages/uhk-web/src/renderer/services/ipc-uhk-renderer.ts
Normal file
15
packages/uhk-web/src/renderer/services/ipc-uhk-renderer.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { IpcCommonRenderer } from '../../app/services/ipc-common-renderer';
|
||||
|
||||
export class IpcUhkRenderer implements IpcCommonRenderer {
|
||||
public send(channel: string, ...args: any[]): void {
|
||||
ipcRenderer.send(channel, args);
|
||||
}
|
||||
|
||||
public on(channel: string, listener: Function): this {
|
||||
ipcRenderer.on(channel, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
import { UhkHidApiService } from './uhk-hid-api.service';
|
||||
|
||||
export function uhkDeviceFactory(logService: LogService): UhkDeviceService {
|
||||
// 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 new UhkHidApiService(logService);
|
||||
}
|
||||
|
||||
// On other platform use libUsb, but we try to test on all platform
|
||||
// return new UhkLibUsbApiService(logService);
|
||||
return new UhkHidApiService(logService);
|
||||
}
|
||||
106
packages/uhk-web/src/renderer/services/uhk-device.service.ts
Normal file
106
packages/uhk-web/src/renderer/services/uhk-device.service.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
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 { Constants, LogService } from 'uhk-common';
|
||||
import { SenderMessage } from '../models/sender-message';
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
}
|
||||
|
||||
export abstract class UhkDeviceService implements OnDestroy {
|
||||
protected connected$: BehaviorSubject<boolean>;
|
||||
protected initialized$: BehaviorSubject<boolean>;
|
||||
protected deviceOpened$: BehaviorSubject<boolean>;
|
||||
protected outSubscription: Subscription;
|
||||
|
||||
protected messageIn$: Observable<Buffer>;
|
||||
protected messageOut$: Subject<SenderMessage>;
|
||||
|
||||
constructor(protected logService: LogService) {
|
||||
this.messageOut$ = new Subject<SenderMessage>();
|
||||
this.initialized$ = new BehaviorSubject(false);
|
||||
this.connected$ = new BehaviorSubject(false);
|
||||
this.deviceOpened$ = new BehaviorSubject(false);
|
||||
this.outSubscription = Subscription.EMPTY;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.disconnect();
|
||||
this.initialized$.unsubscribe();
|
||||
this.connected$.unsubscribe();
|
||||
this.deviceOpened$.unsubscribe();
|
||||
}
|
||||
|
||||
sendConfig(configBuffer: Buffer): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Sending...', configBuffer);
|
||||
const fragments: Buffer[] = [];
|
||||
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
|
||||
: configBuffer.length - offset;
|
||||
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
|
||||
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
|
||||
}
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
const observer: Observer<Buffer> = {
|
||||
next: (buffer: Buffer) => buffers.push(buffer),
|
||||
error: error => subscriber.error(error),
|
||||
complete: () => {
|
||||
if (buffers.length === fragments.length) {
|
||||
subscriber.next(Buffer.concat(buffers));
|
||||
subscriber.complete();
|
||||
this.logService.info('Sending finished');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fragments
|
||||
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
|
||||
.forEach(senderPackage => this.messageOut$.next(senderPackage));
|
||||
});
|
||||
}
|
||||
|
||||
applyConfig(): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Applying configuration');
|
||||
this.messageOut$.next({
|
||||
buffer: new Buffer([Command.ApplyConfig]),
|
||||
observer: subscriber
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isInitialized(): Observable<boolean> {
|
||||
return this.initialized$.asObservable();
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
136
packages/uhk-web/src/renderer/services/uhk-hid-api.service.ts
Normal file
136
packages/uhk-web/src/renderer/services/uhk-hid-api.service.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/interval';
|
||||
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 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { Constants, LogService } from 'uhk-common';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
|
||||
private device: HID;
|
||||
|
||||
constructor(protected logService: LogService) {
|
||||
super(logService);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user