refactor: add pwa service

This commit is contained in:
4gray authored and GitHub committed 2021-12-14 07:55:40 +00:00
1 parent 55c201b715
commit 0f1662bbe8
14 files changed
+156 -95

No files matched your search

+7 -9
View File
@@ -106,17 +106,15 @@ describe('AppComponent', () => {
describe('Test ipc listeners and commands', () => {
it('should set IPC listeners', () => {
spyOn(electronService.ipcRenderer, 'on');
spyOn(electronService, 'listenOn');
component.setRendererListeners();
expect(electronService.ipcRenderer.on).toHaveBeenCalledTimes(5);
expect(electronService.listenOn).toHaveBeenCalledTimes(5);
});
it('should remove all ipc listeners on destroy', () => {
spyOn(electronService.ipcRenderer, 'removeAllListeners');
spyOn(electronService, 'removeAllListeners');
component.ngOnDestroy();
expect(
electronService.ipcRenderer.removeAllListeners
).toHaveBeenCalledTimes(4);
expect(electronService.removeAllListeners).toHaveBeenCalledTimes(4);
});
it('should navigate to the provided route', inject(
@@ -245,7 +243,7 @@ describe('AppComponent', () => {
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(1);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(1);
expect(translateService.use).toHaveBeenCalledWith(language);
});
@@ -259,7 +257,7 @@ describe('AppComponent', () => {
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
expect(settingsService.changeTheme).toHaveBeenCalledTimes(0);
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(0);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(0);
expect(translateService.use).toHaveBeenCalledTimes(0);
});
@@ -273,7 +271,7 @@ describe('AppComponent', () => {
expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(0);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(0);
expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
});
});
+11 -19
View File
@@ -1,7 +1,5 @@
import { Component, NgZone } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Titlebar, Color } from 'custom-electron-titlebar';
import { ElectronService } from './services/electron.service';
import { Router } from '@angular/router';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { ChannelStore } from './state';
@@ -21,13 +19,7 @@ import { WhatsNewService } from './services/whats-new.service';
import * as semver from 'semver';
import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface';
import { STORE_KEY } from './shared/enums/store-keys.enum';
// create custom title bar
new Titlebar({
backgroundColor: Color.fromHex('#000'),
itemBackgroundColor: Color.fromHex('#222'),
enableMnemonics: true,
});
import { DataService } from './services/data.service';
/**
* AppComponent
@@ -69,7 +61,7 @@ export class AppComponent {
*/
constructor(
private channelStore: ChannelStore,
private electronService: ElectronService,
private electronService: DataService,
private ngZone: NgZone,
private router: Router,
private translate: TranslateService,
@@ -78,7 +70,7 @@ export class AppComponent {
private storage: StorageMap,
private whatsNewService: WhatsNewService
) {
if (
/* if (
(this.electronService.remote.process.platform === 'linux' ||
this.electronService.remote.process.platform === 'win32') &&
this.electronService.remote.process.argv.length > 2
@@ -90,12 +82,12 @@ export class AppComponent {
if (filePath) {
const filePathsArray = filePath.split('/');
const fileName = filePathsArray[filePathsArray.length - 1];
this.electronService.ipcRenderer.send('open-file', {
this.electronService.sendIpcEvent('open-file', {
filePath,
fileName,
});
}
}
} */
}
/**
@@ -114,7 +106,7 @@ export class AppComponent {
*/
setRendererListeners(): void {
this.commandsList.forEach((command) =>
this.electronService.ipcRenderer.on(command.id, () =>
this.electronService.listenOn(command.id, () =>
this.ngZone.run(() => command.callback())
)
);
@@ -131,7 +123,7 @@ export class AppComponent {
if (settings && Object.keys(settings).length > 0) {
this.translate.use(settings.language ?? this.DEFAULT_LANG);
if (settings.epgUrl) {
this.electronService.ipcRenderer.send(EPG_FETCH, {
this.electronService.sendIpcEvent(EPG_FETCH, {
url: settings.epgUrl,
});
this.snackBar.open(
@@ -229,9 +221,9 @@ export class AppComponent {
* Removes all ipc command listeners on component destroy
*/
ngOnDestroy(): void {
this.electronService.ipcRenderer.removeAllListeners(EPG_FETCH_DONE);
this.electronService.ipcRenderer.removeAllListeners(EPG_ERROR);
this.electronService.ipcRenderer.removeAllListeners(VIEW_ADD_PLAYLIST);
this.electronService.ipcRenderer.removeAllListeners(VIEW_SETTINGS);
this.electronService.removeAllListeners(EPG_FETCH_DONE);
this.electronService.removeAllListeners(EPG_ERROR);
this.electronService.removeAllListeners(VIEW_ADD_PLAYLIST);
this.electronService.removeAllListeners(VIEW_SETTINGS);
}
}
+20 -1
View File
@@ -14,12 +14,25 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AppComponent } from './app.component';
import { ElectronService } from './services/electron.service';
import { PwaService } from './services/pwa.service';
import { DataService } from './services/data.service';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
function isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
export function AuthenticationFactory() {
if (isElectron()) {
return new ElectronService();
}
return new PwaService();
}
@NgModule({
declarations: [AppComponent],
imports: [
@@ -37,6 +50,12 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
},
}),
],
providers: [
{
provide: DataService,
useFactory: AuthenticationFactory,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
+9 -9
View File
@@ -83,7 +83,7 @@ describe('HomeComponent', () => {
const playlistId = '12345';
spyOn(electronService.ipcRenderer, 'send');
component.removePlaylist(playlistId);
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
'playlist-remove-by-id',
{ id: playlistId }
);
@@ -96,7 +96,7 @@ describe('HomeComponent', () => {
} as PlaylistMeta;
spyOn(electronService.ipcRenderer, 'send');
component.refreshPlaylist(playlistMeta);
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
PLAYLIST_UPDATE,
{ id: playlistMeta._id, filePath: playlistMeta.filePath }
);
@@ -106,7 +106,7 @@ describe('HomeComponent', () => {
const playlistId = '6789';
spyOn(electronService.ipcRenderer, 'send');
component.getPlaylist(playlistId);
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
'playlist-by-id',
{
id: playlistId,
@@ -119,7 +119,7 @@ describe('HomeComponent', () => {
const playlistUrl = 'http://test.com/' + playlistTitle;
spyOn(electronService.ipcRenderer, 'send');
component.sendPlaylistsUrl(playlistUrl);
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
'parse-playlist-by-url',
{
title: playlistTitle,
@@ -141,7 +141,7 @@ describe('HomeComponent', () => {
target: { result: playlistContent },
} as unknown as Event;
component.handlePlaylist({ file, uploadEvent });
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
PLAYLIST_PARSE,
{ title, playlist: [playlistContent], path }
);
@@ -184,10 +184,10 @@ describe('HomeComponent', () => {
});
it('should remove all ipc listeners on destroy', () => {
spyOn(electronService.ipcRenderer, 'removeAllListeners');
spyOn(electronService, 'removeAllListeners');
component.ngOnDestroy();
expect(
electronService.ipcRenderer.removeAllListeners
).toHaveBeenCalledTimes(component.commandsList.length);
expect(electronService.removeAllListeners).toHaveBeenCalledTimes(
component.commandsList.length
);
});
});
+13 -15
View File
@@ -14,6 +14,7 @@ import {
PLAYLIST_UPDATE_RESPONSE,
} from '../../../shared/ipc-commands';
import { DialogService } from './../services/dialog.service';
import { DataService } from '../services/data.service';
/** Type to describe meta data of a playlist */
export type PlaylistMeta = Pick<
@@ -60,7 +61,7 @@ export class HomeComponent {
this.snackBar.open('Done! Playlist was removed.', null, {
duration: 2000,
});
this.electronService.ipcRenderer.send('playlists-all');
// this.electronService.sendIpcEvent('playlists-all');
},
},
{
@@ -90,14 +91,14 @@ export class HomeComponent {
constructor(
private channelStore: ChannelStore,
private dialogService: DialogService,
private electronService: ElectronService,
private electronService: DataService,
private ngZone: NgZone,
private router: Router,
private snackBar: MatSnackBar,
private translate: TranslateService
) {
// get all playlists
this.electronService.ipcRenderer.send('playlists-all');
this.electronService.sendIpcEvent('playlists-all');
// set all renderer listeners
this.setRendererListeners();
}
@@ -107,12 +108,9 @@ export class HomeComponent {
*/
setRendererListeners(): void {
this.commandsList.forEach((command) => {
this.electronService.ipcRenderer.on(
command.id,
(event, response) => {
this.ngZone.run(() => command.execute(response));
}
);
this.electronService.listenOn(command.id, (event, response) => {
this.ngZone.run(() => command.execute(response));
});
});
}
@@ -135,7 +133,7 @@ export class HomeComponent {
this.isLoading = true;
const result = (payload.uploadEvent.target as FileReader).result;
const array = (result as string).split('\n');
this.electronService.ipcRenderer.send(PLAYLIST_PARSE, {
this.electronService.sendIpcEvent(PLAYLIST_PARSE, {
title: payload.file.name,
playlist: array,
path: payload.file.nativeFile.path,
@@ -156,7 +154,7 @@ export class HomeComponent {
*/
sendPlaylistsUrl(playlistUrl: string): void {
this.isLoading = true;
this.electronService.ipcRenderer.send('parse-playlist-by-url', {
this.electronService.sendIpcEvent('parse-playlist-by-url', {
title: this.getLastUrlSegment(playlistUrl),
url: playlistUrl,
});
@@ -201,7 +199,7 @@ export class HomeComponent {
* @param playlistId playlist id to remove
*/
removePlaylist(playlistId: string): void {
this.electronService.ipcRenderer.send('playlist-remove-by-id', {
this.electronService.sendIpcEvent('playlist-remove-by-id', {
id: playlistId,
});
}
@@ -211,7 +209,7 @@ export class HomeComponent {
* @param item playlist to update
*/
refreshPlaylist(item: PlaylistMeta): void {
this.electronService.ipcRenderer.send(PLAYLIST_UPDATE, {
this.electronService.sendIpcEvent(PLAYLIST_UPDATE, {
id: item._id,
...(item.url ? { url: item.url } : { filePath: item.filePath }),
});
@@ -222,7 +220,7 @@ export class HomeComponent {
* @param playlistId playlist id
*/
getPlaylist(playlistId: string): void {
this.electronService.ipcRenderer.send('playlist-by-id', {
this.electronService.sendIpcEvent('playlist-by-id', {
id: playlistId,
});
}
@@ -255,7 +253,7 @@ export class HomeComponent {
*/
ngOnDestroy(): void {
this.commandsList.forEach((command) =>
this.electronService.ipcRenderer.removeAllListeners(command.id)
this.electronService.removeAllListeners(command.id)
);
}
}
@@ -57,8 +57,8 @@ describe('PlaylistInfoComponent', () => {
const playlistToSave = { _id: 'a12345', title: 'Playlist' } as Playlist;
spyOn(electronService.ipcRenderer, 'send');
component.saveChanges(playlistToSave);
expect(electronService.ipcRenderer.send).toHaveBeenCalledTimes(1);
expect(electronService.ipcRenderer.send).toHaveBeenCalledWith(
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(1);
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(
PLAYLIST_SAVE_DETAILS,
playlistToSave
);
@@ -76,6 +76,6 @@ export class PlaylistInfoComponent {
saveChanges(
data: Pick<Playlist, '_id' | 'title' | 'userAgent' | 'autoRefresh'>
): void {
this.electronService.ipcRenderer.send(PLAYLIST_SAVE_DETAILS, data);
this.electronService.sendIpcEvent(PLAYLIST_SAVE_DETAILS, data);
}
}
@@ -148,14 +148,12 @@ describe('EpgListComponent', () => {
});
it('should remove ipc listeners on destroy', () => {
spyOn(electronService.ipcRenderer, 'removeAllListeners');
spyOn(electronService, 'removeAllListeners');
component.ngOnDestroy();
expect(
electronService.ipcRenderer.removeAllListeners
).toHaveBeenCalledTimes(1);
expect(
electronService.ipcRenderer.removeAllListeners
).toHaveBeenCalledWith(EPG_GET_PROGRAM_DONE);
expect(electronService.removeAllListeners).toHaveBeenCalledTimes(1);
expect(electronService.removeAllListeners).toHaveBeenCalledWith(
EPG_GET_PROGRAM_DONE
);
});
it('should set epg program as active', () => {
@@ -61,7 +61,7 @@ export class EpgListComponent {
private electronService: ElectronService,
private ngZone: NgZone
) {
this.electronService.ipcRenderer.on(
this.electronService.listenOn(
EPG_GET_PROGRAM_DONE,
(event, response) => {
this.ngZone.run(() => this.handleEpgData(response));
@@ -183,8 +183,6 @@ export class EpgListComponent {
* Removes all ipc renderer listeners after destroy
*/
ngOnDestroy(): void {
this.electronService.ipcRenderer.removeAllListeners(
EPG_GET_PROGRAM_DONE
);
this.electronService.removeAllListeners(EPG_GET_PROGRAM_DONE);
}
}
+9
View File
@@ -0,0 +1,9 @@
export abstract class DataService {
abstract getAppVersion(): string;
abstract sendIpcEvent(type: string, payload?: unknown): void;
abstract removeAllListeners(type: string): void;
abstract listenOn(
command: string,
callback: (...args: any[]) => void
): void;
}
+4 -4
View File
@@ -11,14 +11,14 @@ export class ElectronServiceStub {
},
app: {
getVersion: jest.fn(() => '1.0.0'),
}
},
};
getAppVersion() {
return this.remote.app.getVersion();
}
sendIpcEvent() {
this.ipcRenderer.send();
sendIpcEvent() {
this.sendIpcEvent();
}
}
}
+36 -14
View File
@@ -1,33 +1,43 @@
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import { ipcRenderer, webFrame, remote } from 'electron';
import * as childProcess from 'child_process';
import * as fs from 'fs';
import { ipcRenderer, remote /* webFrame */ } from 'electron';
/* import * as childProcess from 'child_process';
import * as fs from 'fs'; */
// import { Titlebar, Color } from 'custom-electron-titlebar';
@Injectable({
providedIn: 'root',
})
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
webFrame: typeof webFrame;
remote: typeof remote;
childProcess: typeof childProcess;
fs: typeof fs;
export class ElectronService extends DataService {
private ipcRenderer: typeof ipcRenderer;
// webFrame: typeof webFrame;
private remote: typeof remote;
// childProcess: typeof childProcess;
// fs: typeof fs;
get isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
constructor() {
super();
// Conditional imports
if (this.isElectron) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.webFrame = window.require('electron').webFrame;
this.remote = window.require('electron').remote;
this.childProcess = window.require('child_process');
this.fs = window.require('fs');
/* this.webFrame = window.require('electron').webFrame */ // need to test whether remote is available
/* this.childProcess = window.require('child_process');
this.fs = window.require('fs'); */
// create custom title bar
/* new Titlebar({
backgroundColor: Color.fromHex('#000'),
itemBackgroundColor: Color.fromHex('#222'),
enableMnemonics: true,
}); */
}
}
@@ -43,7 +53,19 @@ export class ElectronService {
* @param type event type
* @param payload data payload
*/
sendIpcEvent(type: string, payload: unknown): void {
this.ipcRenderer.send(type, payload);
sendIpcEvent(type: string, payload?: unknown): void {
this.sendIpcEvent(type, payload);
}
/**
* Removes all IPC listeners
* @param type command name
*/
removeAllListeners(type: string): void {
this.ipcRenderer.removeAllListeners(type);
}
listenOn(command: string, callback: (...args: any[]) => void): void {
this.ipcRenderer.on(command, callback);
}
}
+30
View File
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
@Injectable({
providedIn: 'root',
})
export class PwaService extends DataService {
constructor() {
super();
console.log('PWA Service');
}
getAppVersion(): string {
return '1.0.0';
//throw new Error('Method not implemented.');
}
sendIpcEvent(type: string, payload?: unknown): void {
console.log(type, payload);
// throw new Error('Method not implemented.');
}
removeAllListeners(type: string) {
console.log(type + ' listeners removed');
}
listenOn(command: string, callback: (...args: any[]) => void): void {
console.log('listen on ' + command);
}
}
+7 -10
View File
@@ -48,7 +48,7 @@ export class ChannelStore extends EntityStore<ChannelState> {
} else {
favorites = [...store.favorites, channel.id];
}
this.electronService.ipcRenderer.send('update-favorites', {
this.electronService.sendIpcEvent('update-favorites', {
id: store.playlistId,
favorites,
});
@@ -63,17 +63,14 @@ export class ChannelStore extends EntityStore<ChannelState> {
setActiveChannel(channel: Channel): void {
this.update((store) => {
if (store.epgAvailable) {
this.electronService.ipcRenderer.send(EPG_GET_PROGRAM, {
this.electronService.sendIpcEvent(EPG_GET_PROGRAM, {
channelName: channel.name,
});
if (channel.http['user-agent']) {
this.electronService.ipcRenderer.send(
CHANNEL_SET_USER_AGENT,
{
referer: channel.http.referrer,
userAgent: channel.http['user-agent'],
}
);
this.electronService.sendIpcEvent(CHANNEL_SET_USER_AGENT, {
referer: channel.http.referrer,
userAgent: channel.http['user-agent'],
});
}
}
return {
@@ -117,7 +114,7 @@ export class ChannelStore extends EntityStore<ChannelState> {
setEpgAvailableFlag(value: boolean): void {
this.update((store) => {
if (store.active && store.active.name) {
this.electronService.ipcRenderer.send(EPG_GET_PROGRAM, {
this.electronService.sendIpcEvent(EPG_GET_PROGRAM, {
channelName: store.active.name,
});
}