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
110
packages/uhk-agent/src/services/app-update.service.ts
Normal file
110
packages/uhk-agent/src/services/app-update.service.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { ProgressInfo } from 'electron-builder-http/out/ProgressCallbackTransform';
|
||||
import { VersionInfo } from 'electron-builder-http/out/publishOptions';
|
||||
import * as settings from 'electron-settings';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
|
||||
import { IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
|
||||
export class AppUpdateService extends MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow,
|
||||
private app: Electron.App) {
|
||||
super(logService, win);
|
||||
|
||||
this.initListeners();
|
||||
logService.info('AppUpdateService init success');
|
||||
}
|
||||
|
||||
saveFirtsRun() {
|
||||
settings.set('firstRunVersion', this.app.getVersion());
|
||||
}
|
||||
|
||||
private initListeners() {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (ev: any, info: VersionInfo) => {
|
||||
autoUpdater.downloadUpdate();
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (ev: any, info: VersionInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (ev: any, err: string) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, err.substr(0, 100));
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj: ProgressInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloadProgress, progressObj);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev: any, info: VersionInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.updateAndRestart, () => autoUpdater.quitAndInstall(true));
|
||||
|
||||
ipcMain.on(IpcEvents.app.appStarted, () => {
|
||||
if (this.checkForUpdateAtStartup()) {
|
||||
this.checkForUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => this.checkForUpdate());
|
||||
}
|
||||
|
||||
private checkForUpdate() {
|
||||
if (isDev) {
|
||||
const msg = 'Application update is not working in dev mode.';
|
||||
this.logService.info(msg);
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFirstRun()) {
|
||||
const msg = 'Application update is skipping at first run.';
|
||||
this.logService.info(msg);
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.allowPrerelease = this.allowPreRelease();
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
private isFirstRun() {
|
||||
if (!settings.has('firstRunVersion')) {
|
||||
return true;
|
||||
}
|
||||
const firstRunVersion = settings.get('firstRunVersion');
|
||||
this.logService.info(`firstRunVersion: ${firstRunVersion}`);
|
||||
this.logService.info(`package.version: ${this.app.getVersion()}`);
|
||||
|
||||
return firstRunVersion !== this.app.getVersion();
|
||||
}
|
||||
|
||||
private allowPreRelease() {
|
||||
const autoUpdateSettings = this.getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.usePreReleaseUpdate;
|
||||
}
|
||||
|
||||
private checkForUpdateAtStartup() {
|
||||
const autoUpdateSettings = this.getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.checkForUpdateOnStartUp;
|
||||
}
|
||||
|
||||
private getAutoUpdateSettings() {
|
||||
// const storageService = new ElectronDataStorageRepositoryService();
|
||||
// return storageService.getAutoUpdateSettings();
|
||||
return { checkForUpdateOnStartUp: false, usePreReleaseUpdate: false };
|
||||
}
|
||||
|
||||
}
|
||||
28
packages/uhk-agent/src/services/app.service.ts
Normal file
28
packages/uhk-agent/src/services/app.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
|
||||
import { CommandLineArgs, IpcEvents, AppStartInfo, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
import { DeviceService } from './device.service';
|
||||
|
||||
export class AppService extends MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow,
|
||||
private deviceService: DeviceService,
|
||||
private options: CommandLineArgs) {
|
||||
super(logService, win);
|
||||
|
||||
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
|
||||
logService.info('AppService init success');
|
||||
}
|
||||
|
||||
private handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('getStartInfo');
|
||||
const response: AppStartInfo = {
|
||||
commandLineArgs: this.options,
|
||||
deviceConnected: this.deviceService.isConnected,
|
||||
hasPermission: this.deviceService.hasPermission()
|
||||
};
|
||||
this.logService.info('getStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
}
|
||||
}
|
||||
167
packages/uhk-agent/src/services/device.service.ts
Normal file
167
packages/uhk-agent/src/services/device.service.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
|
||||
import { Constants, IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
}
|
||||
|
||||
export class DeviceService {
|
||||
private static convertBufferToIntArray(buffer: Buffer): number[] {
|
||||
return Array.prototype.slice.call(buffer, 0);
|
||||
}
|
||||
|
||||
private pollTimer$: Subscription;
|
||||
private connected: boolean = false;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private win: Electron.BrowserWindow) {
|
||||
this.pollUhkDevice();
|
||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
|
||||
logService.info('DeviceService init success');
|
||||
}
|
||||
|
||||
public get isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
public hasPermission(): boolean {
|
||||
try {
|
||||
const devs = devices();
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logService.error('[DeviceService] hasPermission', err);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
this.pollTimer$ = Observable.interval(1000)
|
||||
.startWith(0)
|
||||
.map(() => {
|
||||
return devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
||||
dev.productId === Constants.PRODUCT_ID);
|
||||
})
|
||||
.distinctUntilChanged()
|
||||
.do((connected: boolean) => {
|
||||
this.connected = connected;
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, connected);
|
||||
this.logService.info(`Device connection state changed to: ${connected}`);
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private saveUserConfiguration(event: Electron.Event, json: string): void {
|
||||
const response = new IpcResponse();
|
||||
|
||||
try {
|
||||
const buffer: Buffer = new Buffer(JSON.parse(json).data);
|
||||
const fragments = this.getTransferBuffers(buffer);
|
||||
const device = this.getDevice();
|
||||
device.read((err, data) => {
|
||||
if (err) {
|
||||
this.logService.error('Send data to device err:', err);
|
||||
}
|
||||
this.logService.debug('send data to device response:', data.toString());
|
||||
});
|
||||
|
||||
for (const fragment of fragments) {
|
||||
const transferData = this.getTransferData(fragment);
|
||||
this.logService.debug('Fragment: ', JSON.stringify(transferData));
|
||||
device.write(transferData);
|
||||
}
|
||||
|
||||
const applyBuffer = new Buffer([Command.ApplyConfig]);
|
||||
const applyTransferData = this.getTransferData(applyBuffer);
|
||||
this.logService.debug('Fragment: ', JSON.stringify(applyTransferData));
|
||||
device.write(applyTransferData);
|
||||
|
||||
response.success = true;
|
||||
this.logService.info('transferring finished');
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('transferring error', error);
|
||||
response.error = { message: error.message };
|
||||
}
|
||||
|
||||
event.sender.send(IpcEvents.device.saveUserConfigurationReply, response);
|
||||
}
|
||||
|
||||
private getTransferData(buffer: Buffer): number[] {
|
||||
const data = DeviceService.convertBufferToIntArray(buffer);
|
||||
// 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);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private getTransferBuffers(configBuffer: Buffer): Buffer[] {
|
||||
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)]));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 0 interface of the keyboard.
|
||||
* @returns {HID}
|
||||
*/
|
||||
private getDevice(): HID {
|
||||
try {
|
||||
const devs = devices();
|
||||
this.logService.silly('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));
|
||||
|
||||
if (!dev) {
|
||||
this.logService.info('[DeviceService] UHK Device not found:');
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
4
packages/uhk-agent/src/services/logger.service.ts
Normal file
4
packages/uhk-agent/src/services/logger.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as log from 'electron-log';
|
||||
log.transports.file.level = 'debug';
|
||||
|
||||
export const logger = log;
|
||||
16
packages/uhk-agent/src/services/main-service-base.ts
Normal file
16
packages/uhk-agent/src/services/main-service-base.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
export class MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow) {}
|
||||
|
||||
protected sendIpcToWindow(message: string, arg?: any) {
|
||||
this.logService.info('sendIpcToWindow:', message, arg);
|
||||
if (!this.win || this.win.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.win.webContents.send(message, arg);
|
||||
}
|
||||
|
||||
}
|
||||
57
packages/uhk-agent/src/services/sudo.service.ts
Normal file
57
packages/uhk-agent/src/services/sudo.service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ipcMain, app } from 'electron';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import * as path from 'path';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
|
||||
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
|
||||
export class SudoService {
|
||||
private rootDir: string;
|
||||
|
||||
constructor(private logService: LogService) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../..');
|
||||
} else {
|
||||
this.rootDir = path.dirname(app.getAppPath());
|
||||
}
|
||||
this.logService.info('[SudoService] App root dir: ', this.rootDir);
|
||||
ipcMain.on(IpcEvents.device.setPrivilegeOnLinux, this.setPrivilege.bind(this));
|
||||
}
|
||||
|
||||
private setPrivilege(event: Electron.Event) {
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
this.setPrivilegeOnLinux(event);
|
||||
break;
|
||||
default:
|
||||
const response: IpcResponse = {
|
||||
success: false,
|
||||
error: { message: 'Permissions couldn\'t be set. Invalid platform: ' + process.platform }
|
||||
};
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setPrivilegeOnLinux(event: Electron.Event) {
|
||||
const scriptPath = path.join(this.rootDir, 'rules/setup-rules.sh');
|
||||
const options = {
|
||||
name: 'Setting UHK access rules'
|
||||
};
|
||||
const command = `sh ${scriptPath}`;
|
||||
console.log(command);
|
||||
sudo.exec(command, options, (error: any) => {
|
||||
const response = new IpcResponse();
|
||||
|
||||
if (error) {
|
||||
response.success = false;
|
||||
response.error = error;
|
||||
} else {
|
||||
response.success = true;
|
||||
}
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user