From c3a5b5174612971a725ec612667c3011d4f201b0 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 13:17:48 -0500 Subject: [PATCH 01/11] Use the new fingerprint endpoint --- .../addon-providers/curse-addon-provider.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 352fe074..13b31b46 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -26,6 +26,7 @@ import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-re import * as CircuitBreaker from "opossum"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; +const HUB_API_URL = "https://hub.dev.wowup.io"; export class CurseAddonProvider implements AddonProvider { private readonly _circuitBreaker: CircuitBreaker< @@ -125,15 +126,15 @@ export class CurseAddonProvider implements AddonProvider { return; } - scanResults.forEach(result => { + scanResults.forEach((result) => { console.debug(result.folderName, result.fingerprint); }); - const fingerprintResponse = await this.getAddonsByFingerprints( + const fingerprintResponse = await this.getAddonsByFingerprintsW( scanResults.map((result) => result.fingerprint) ); - console.log('fingerprintResponse', fingerprintResponse); + console.log("fingerprintResponse", fingerprintResponse); for (let scanResult of scanResults) { // Curse can deliver the wrong result sometimes, ensure the result matches the client type @@ -146,7 +147,7 @@ export class CurseAddonProvider implements AddonProvider { ); // If the addon does not have an exact match, check the partial matches. - if (!scanResult.exactMatch) { + if (!scanResult.exactMatch && fingerprintResponse.partialMatches) { scanResult.exactMatch = fingerprintResponse.partialMatches.find( (partialMatch) => partialMatch.file?.modules?.some( @@ -173,6 +174,25 @@ export class CurseAddonProvider implements AddonProvider { return gameVersionFlavor === this.getGameVersionFlavor(clientType); } + private async getAddonsByFingerprintsW(fingerprints: number[]) { + const url = `${HUB_API_URL}/curseforge/addons/fingerprint`; + + console.log(`Wowup Fetching fingerprints`, JSON.stringify(fingerprints)); + + return await this._httpClient + .post(url, { + fingerprints, + }) + .toPromise(); + + return await this.getCircuitBreaker().fire( + async () => + await this._httpClient + .post(url, fingerprints) + .toPromise() + ); + } + private async getAddonsByFingerprints( fingerprints: number[] ): Promise { From 15709e3c2839f1b4a0f04770e9a242717d037142 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 13:22:18 -0500 Subject: [PATCH 02/11] Version bump --- wowup-electron/package.json | 2 +- wowup-electron/src/assets/changelog.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wowup-electron/package.json b/wowup-electron/package.json index beb5407c..ca557672 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -1,7 +1,7 @@ { "name": "wowup", "productName": "WowUp", - "version": "2.0.0-alpha.9", + "version": "2.0.0-alpha.10", "description": "Word of Warcraft addon updater", "homepage": "https://github.com/maximegris/angular-electron", "author": { diff --git a/wowup-electron/src/assets/changelog.json b/wowup-electron/src/assets/changelog.json index 7fe0122d..5c28c804 100644 --- a/wowup-electron/src/assets/changelog.json +++ b/wowup-electron/src/assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "2.0.0-alpha.10", + "Description": "Temporary CF fingerprint endpoint patch." + }, { "Version": "2.0.0-alpha.9", "Description": "Update Russian locale.\nTry to fix some font blurriness.\nRemove debug error in WowInterface provider." From 6e4fcada58d8b328e8985b82f1438cec70386215 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 15:40:14 -0500 Subject: [PATCH 03/11] Fix some GitHub related errors. --- .../src/app/addon-providers/github-addon-provider.ts | 7 +++---- wowup-electron/src/app/services/addons/addon.service.ts | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/github-addon-provider.ts b/wowup-electron/src/app/addon-providers/github-addon-provider.ts index c9cfd7f5..5e9dc5a0 100644 --- a/wowup-electron/src/app/addon-providers/github-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/github-addon-provider.ts @@ -31,7 +31,7 @@ export class GitHubAddonProvider implements AddonProvider { async getAll(clientType: WowClientType, addonIds: string[]): Promise { var searchResults: AddonSearchResult[] = [] - for (let addonId in addonIds) { + for (let addonId of addonIds) { var result = await this.getById(addonId, clientType).toPromise(); if (result == null) { continue; @@ -74,7 +74,7 @@ export class GitHubAddonProvider implements AddonProvider { author: author, downloadCount: asset.download_count, externalId: repoPath, - externalUrl: latestRelease.url, + externalUrl: repository.html_url, name: repository.name, providerName: this.name, thumbnailUrl: authorImageUrl @@ -124,7 +124,7 @@ export class GitHubAddonProvider implements AddonProvider { var searchResult: AddonSearchResult = { author: author, externalId: addonId, - externalUrl: asset.url, + externalUrl: repository.html_url, files: [searchResultFile], name: addonName, providerName: this.name, @@ -203,7 +203,6 @@ export class GitHubAddonProvider implements AddonProvider { private getReleases(repositoryPath: string): Observable { const url = `${API_URL}${repositoryPath}/releases`; - return this._httpClient.get(url.toString()); } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 294e685f..1ffbeb9c 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -93,6 +93,7 @@ export class AddonService { clientType ).toPromise(); this._addonStorage.set(addon.id, addon); + await this.installAddon(addon.id, onUpdate); } @@ -166,7 +167,6 @@ export class AddonService { let downloadedFilePath = ""; let unzippedDirectory = ""; - let downloadedThumbnail = ""; try { downloadedFilePath = await this._downloadService.downloadZipFile( addon.downloadUrl, @@ -174,6 +174,7 @@ export class AddonService { ); onUpdate?.call(this, AddonInstallState.Installing, 75); + this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, From e69e55f774b77271f8fb263ea42690f0a9926587 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 16:07:45 -0500 Subject: [PATCH 04/11] Rework most of the IPC handlers --- wowup-electron/ipc-events.ts | 80 +++++- wowup-electron/main.ts | 235 ++++++------------ .../src/app/services/addons/addon.service.ts | 3 +- .../app/services/download/download.service.ts | 148 ++++++----- .../src/app/services/files/file.service.ts | 101 +++++--- .../common/models/copy-directory-request.ts | 10 +- .../src/common/models/copy-file-request.ts | 10 +- .../common/models/delete-directory-request.ts | 8 +- .../src/common/models/download-request.ts | 10 +- .../src/common/models/read-file-request.ts | 8 +- .../src/common/models/unzip-request.ts | 10 +- 11 files changed, 348 insertions(+), 275 deletions(-) diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index fa80a978..a261dfdc 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -1,8 +1,11 @@ import { ipcMain, shell } from "electron"; import * as fs from "fs"; import * as async from "async"; +import * as admZip from "adm-zip"; +import { ncp } from "ncp"; +import * as rimraf from "rimraf"; -import { readDirRecursive } from "./file.utils"; +import { readDirRecursive, readFile } from "./file.utils"; import { CURSE_HASH_FILE_CHANNEL, LIST_DIRECTORIES_CHANNEL, @@ -11,6 +14,12 @@ import { PATH_EXISTS_CHANNEL, CURSE_GET_SCAN_RESULTS, WOWUP_GET_SCAN_RESULTS, + UNZIP_FILE_CHANNEL, + COPY_FILE_CHANNEL, + COPY_DIRECTORY_CHANNEL, + DELETE_DIRECTORY_CHANNEL, + RENAME_DIRECTORY_CHANNEL, + READ_FILE_CHANNEL, } from "./src/common/constants"; import { CurseGetScanResultsRequest } from "./src/common/curse/curse-get-scan-results-request"; import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; @@ -28,6 +37,14 @@ import { WowUpGetScanResultsRequest } from "./src/common/wowup/wowup-get-scan-re import { WowUpGetScanResultsResponse } from "./src/common/wowup/wowup-get-scan-results-response"; import { WowUpFolderScanner } from "./src/common/wowup/wowup-folder-scanner"; import { WowUpScanResult } from "./src/common/wowup/wowup-scan-result"; +import { UnzipRequest } from "./src/common/models/unzip-request"; +import { UnzipStatus } from "./src/common/models/unzip-status"; +import { UnzipStatusType } from "./src/common/models/unzip-status-type"; +import { CopyFileRequest } from "./src/common/models/copy-file-request"; +import { CopyDirectoryRequest } from "./src/common/models/copy-directory-request"; +import { DeleteDirectoryRequest } from "./src/common/models/delete-directory-request"; +import { ReadFileRequest } from "./src/common/models/read-file-request"; +import { ReadFileResponse } from "./src/common/models/read-file-response"; const nativeAddon = require("./build/Release/addon.node"); @@ -84,7 +101,7 @@ ipcMain.on(LIST_FILES_CHANNEL, async (evt, arg: ListFilesRequest) => { response.error = err; } - evt.reply(arg.sourcePath, response); + evt.reply(arg.responseKey, response); }); ipcMain.on(LIST_DIRECTORIES_CHANNEL, (evt, arg: ValueRequest) => { @@ -175,3 +192,62 @@ ipcMain.on( evt.reply(arg.responseKey, response); } ); + +ipcMain.on(UNZIP_FILE_CHANNEL, (evt, arg: UnzipRequest) => { + const zipFilePath = arg.zipFilePath; + const outputFolder = arg.outputFolder; + + const zip = new admZip(zipFilePath); + zip.extractAllToAsync(outputFolder, true, (err) => { + const status: UnzipStatus = { + type: UnzipStatusType.Complete, + outputFolder, + }; + + if (err) { + status.type = UnzipStatusType.Error; + status.error = err; + } + + evt.reply(arg.responseKey, status); + }); +}); + +ipcMain.on(COPY_FILE_CHANNEL, (evt, arg: CopyFileRequest) => { + console.log("Copy File", arg); + fs.copyFile(arg.sourceFilePath, arg.destinationFilePath, (err) => { + evt.reply(arg.responseKey, { error: err }); + }); +}); + +ipcMain.on(COPY_DIRECTORY_CHANNEL, (evt, arg: CopyDirectoryRequest) => { + console.log("Copy Dir", arg); + ncp(arg.sourcePath, arg.destinationPath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(DELETE_DIRECTORY_CHANNEL, (evt, arg: DeleteDirectoryRequest) => { + console.log("Delete Dir", arg); + rimraf(arg.sourcePath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(RENAME_DIRECTORY_CHANNEL, (evt, arg: CopyDirectoryRequest) => { + console.log("Rename Dir", arg); + fs.rename(arg.sourcePath, arg.destinationPath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(READ_FILE_CHANNEL, async (evt, arg: ReadFileRequest) => { + // console.log('Read File', arg); + const response: ReadFileResponse = { data: "" }; + try { + response.data = await readFile(arg.sourcePath); + } catch (err) { + response.error = err; + } + evt.reply(arg.responseKey, response); +}); diff --git a/wowup-electron/main.ts b/wowup-electron/main.ts index 05818d0c..998f1c6b 100644 --- a/wowup-electron/main.ts +++ b/wowup-electron/main.ts @@ -12,41 +12,19 @@ import { } from "electron"; import * as path from "path"; import * as url from "url"; -import * as fs from "fs"; import { release, arch } from "os"; import * as electronDl from "electron-dl"; -import * as admZip from "adm-zip"; import { DownloadRequest } from "./src/common/models/download-request"; import { DownloadStatus } from "./src/common/models/download-status"; import { DownloadStatusType } from "./src/common/models/download-status-type"; -import { UnzipStatus } from "./src/common/models/unzip-status"; -import { - DOWNLOAD_FILE_CHANNEL, - UNZIP_FILE_CHANNEL, - COPY_FILE_CHANNEL, - COPY_DIRECTORY_CHANNEL, - DELETE_DIRECTORY_CHANNEL, - RENAME_DIRECTORY_CHANNEL, - READ_FILE_CHANNEL, -} from "./src/common/constants"; -import { UnzipStatusType } from "./src/common/models/unzip-status-type"; -import { UnzipRequest } from "./src/common/models/unzip-request"; -import { CopyFileRequest } from "./src/common/models/copy-file-request"; -import { CopyDirectoryRequest } from "./src/common/models/copy-directory-request"; -import { DeleteDirectoryRequest } from "./src/common/models/delete-directory-request"; -import { ReadFileRequest } from "./src/common/models/read-file-request"; -import { ReadFileResponse } from "./src/common/models/read-file-response"; +import { DOWNLOAD_FILE_CHANNEL } from "./src/common/constants"; import "./ipc-events"; -import { ncp } from "ncp"; -import * as rimraf from "rimraf"; import * as log from "electron-log"; import { autoUpdater } from "electron-updater"; import * as Store from "electron-store"; -import { readFile } from "./file.utils"; -import { WindowState } from './src/common/models/window-state'; -import { isBetween } from './src/common/utils/number.utils'; -import { Subject } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; +import { WindowState } from "./src/common/models/window-state"; +import { Subject } from "rxjs"; +import { debounceTime } from "rxjs/operators"; const isMac = process.platform === "darwin"; const isWin = process.platform === "win32"; @@ -68,37 +46,37 @@ autoUpdater.on("update-downloaded", () => { const appMenuTemplate: Array = isMac ? [ - { - label: app.name, - submenu: [{ role: "quit" }], - }, - { - label: "Edit", - submenu: [ - { role: "undo" }, - { role: "redo" }, - { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - { role: "selectAll" }, - ], - }, - { - label: "View", - submenu: [ - { role: "reload" }, - { role: "forceReload" }, - { role: "toggleDevTools" }, - { type: "separator" }, - { role: "resetZoom" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { type: "separator" }, - { role: "togglefullscreen" }, - ], - }, - ] + { + label: app.name, + submenu: [{ role: "quit" }], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + ] : []; const appMenu = Menu.buildFromTemplate(appMenuTemplate); @@ -161,43 +139,50 @@ function createTray() { tray.setContextMenu(contextMenu); } -function windowStateManager(windowName: string, { width, height }: { width: number, height: number }) { +function windowStateManager( + windowName: string, + { width, height }: { width: number; height: number } +) { let window: BrowserWindow; let windowState: WindowState; const saveState$ = new Subject(); function setState() { let setDefaults = false; - windowState = preferenceStore.get(`${windowName}-window-state`) as WindowState; + windowState = preferenceStore.get( + `${windowName}-window-state` + ) as WindowState; if (!windowState) { setDefaults = true; } else { - log.info('found window state:', windowState); + log.info("found window state:", windowState); - const valid = screen.getAllDisplays().some(display => { + const valid = screen.getAllDisplays().some((display) => { return ( windowState.x >= display.bounds.x && windowState.y >= display.bounds.y && - windowState.x + windowState.width <= display.bounds.x + display.bounds.width && - windowState.y + windowState.height <= display.bounds.y + display.bounds.height + windowState.x + windowState.width <= + display.bounds.x + display.bounds.width && + windowState.y + windowState.height <= + display.bounds.y + display.bounds.height ); - }) + }); if (!valid) { - log.info('reset window state, bounds are outside displays'); + log.info("reset window state, bounds are outside displays"); setDefaults = true; } } if (setDefaults) { - log.info('setting window defaults'); + log.info("setting window defaults"); windowState = { width, height }; } } function saveState() { - log.info('saving window state'); + log.info("saving window state"); if (!window.isMaximized() && !window.isFullScreen()) { windowState = { ...windowState, ...window.getBounds() }; } @@ -209,37 +194,38 @@ function windowStateManager(windowName: string, { width, height }: { width: numb function monitorState(win: BrowserWindow) { window = win; - win.on('close', saveState); - win.on('resize', () => saveState$.next()); - win.on('move', () => saveState$.next()); - win.on('closed', () => saveState$.unsubscribe()); + win.on("close", saveState); + win.on("resize", () => saveState$.next()); + win.on("move", () => saveState$.next()); + win.on("closed", () => saveState$.unsubscribe()); } - saveState$ - .pipe(debounceTime(500)) - .subscribe(() => saveState()); + saveState$.pipe(debounceTime(500)).subscribe(() => saveState()); setState(); - return ({ + return { ...windowState, monitorState, - }); + }; } function createWindow(): BrowserWindow { // Main object for managing window state // Initialize with a window name and default size - const mainWindowManager = windowStateManager('main', { width: 900, height: 600 }); + const mainWindowManager = windowStateManager("main", { + width: 900, + height: 600, + }); const windowOptions: BrowserWindowConstructorOptions = { width: mainWindowManager.width, height: mainWindowManager.height, x: mainWindowManager.x, y: mainWindowManager.y, - backgroundColor: '#444444', - title: 'WowUp', - titleBarStyle: 'hidden', + backgroundColor: "#444444", + title: "WowUp", + titleBarStyle: "hidden", webPreferences: { preload: path.join(__dirname, "preload.js"), nodeIntegration: true, @@ -264,21 +250,20 @@ function createWindow(): BrowserWindow { win.webContents.userAgent = USER_AGENT; - win.once('ready-to-show', () => { + win.once("ready-to-show", () => { win.show(); - autoUpdater.checkForUpdatesAndNotify() - .then((result) => { - console.log('UPDATE', result) - }) + autoUpdater.checkForUpdatesAndNotify().then((result) => { + console.log("UPDATE", result); + }); }); - win.once('show', () => { + win.once("show", () => { if (mainWindowManager.isFullScreen) { win.setFullScreen(true); } else if (mainWindowManager.isMaximized) { win.maximize(); } - }) + }); if (isMac) { win.on("close", (e) => { @@ -383,84 +368,26 @@ ipcMain.on(DOWNLOAD_FILE_CHANNEL, async (evt, arg: DownloadRequest) => { const download = await electronDl.download(win, arg.url, { directory: arg.outputFolder, onProgress: (progress) => { - win.webContents.send(arg.url, { + const progressStatus: DownloadStatus = { type: DownloadStatusType.Progress, progress: parseFloat((progress.percent * 100.0).toFixed(2)), - } as DownloadStatus); + }; + + win.webContents.send(arg.responseKey, progressStatus); }, }); - win.webContents.send(arg.url, { + const status: DownloadStatus = { type: DownloadStatusType.Complete, savePath: download.getSavePath(), - } as DownloadStatus); + }; + win.webContents.send(arg.responseKey, status); } catch (err) { console.error(err); - win.webContents.send(arg.url, { + const status: DownloadStatus = { type: DownloadStatusType.Error, error: err, - } as DownloadStatus); - } -}); - -ipcMain.on(UNZIP_FILE_CHANNEL, async (evt, arg: UnzipRequest) => { - const zipFilePath = arg.zipFilePath; - const outputFolder = arg.outputFolder; - - const zip = new admZip(zipFilePath); - zip.extractAllToAsync(outputFolder, true, (err) => { - const status: UnzipStatus = { - type: UnzipStatusType.Complete, - outputFolder, }; - - if (err) { - status.type = UnzipStatusType.Error; - status.error = err; - } - - win.webContents.send(zipFilePath, status); - }); -}); - -ipcMain.on(COPY_FILE_CHANNEL, async (evt, arg: CopyFileRequest) => { - console.log("Copy File", arg); - fs.copyFile(arg.sourceFilePath, arg.destinationFilePath, (err) => { - win.webContents.send(arg.destinationFilePath, { error: err }); - }); -}); - -ipcMain.on(COPY_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log("Copy Dir", arg); - ncp(arg.sourcePath, arg.destinationPath, (err) => { - win.webContents.send(arg.destinationPath, err); - }); -}); - -ipcMain.on( - DELETE_DIRECTORY_CHANNEL, - async (evt, arg: DeleteDirectoryRequest) => { - console.log("Delete Dir", arg); - rimraf(arg.sourcePath, (err) => { - win.webContents.send(arg.sourcePath, err); - }); + win.webContents.send(arg.responseKey, status); } -); - -ipcMain.on(RENAME_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log("Rename Dir", arg); - fs.rename(arg.sourcePath, arg.destinationPath, (err) => { - win.webContents.send(arg.destinationPath, err); - }); -}); - -ipcMain.on(READ_FILE_CHANNEL, async (evt, arg: ReadFileRequest) => { - // console.log('Read File', arg); - const response: ReadFileResponse = { data: "" }; - try { - response.data = await readFile(arg.sourcePath); - } catch (err) { - response.error = err; - } - win.webContents.send(arg.sourcePath, response); }); diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 1ffbeb9c..5ef96e21 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -174,7 +174,7 @@ export class AddonService { ); onUpdate?.call(this, AddonInstallState.Installing, 75); - + this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, @@ -185,6 +185,7 @@ export class AddonService { this._wowUpService.applicationDownloadsFolderPath, uuidv4() ); + unzippedDirectory = await this._downloadService.unzipFile( downloadedFilePath, unzipPath diff --git a/wowup-electron/src/app/services/download/download.service.ts b/wowup-electron/src/app/services/download/download.service.ts index e56df6c0..100c4844 100644 --- a/wowup-electron/src/app/services/download/download.service.ts +++ b/wowup-electron/src/app/services/download/download.service.ts @@ -1,5 +1,10 @@ import { Injectable } from "@angular/core"; -import { COPY_FILE_CHANNEL, DOWNLOAD_FILE_CHANNEL, UNZIP_FILE_CHANNEL } from "common/constants"; +import { + COPY_FILE_CHANNEL, + DOWNLOAD_FILE_CHANNEL, + UNZIP_FILE_CHANNEL, +} from "common/constants"; +import { v4 as uuidv4 } from "uuid"; import { DownloadRequest } from "common/models/download-request"; import { DownloadStatus } from "common/models/download-status"; import { DownloadStatusType } from "common/models/download-status-type"; @@ -10,78 +15,93 @@ import { ElectronService } from "../electron/electron.service"; import { CopyFileRequest } from "common/models/copy-file-request"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class DownloadSevice { + constructor(private _electronService: ElectronService) {} - constructor( - private _electronService: ElectronService - ) { } + public downloadZipFile( + url: string, + outputFolder: string, + onProgress?: (progress: number) => void + ): Promise { + return new Promise((resolve, reject) => { + const request: DownloadRequest = { + url, + outputFolder, + responseKey: uuidv4(), + }; - public downloadZipFile(url: string, outputFolder: string, onProgress?: (progress: number) => void): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: DownloadStatus) => { - switch (arg.type) { - case DownloadStatusType.Complete: - resolve(arg.savePath); - this._electronService.ipcRenderer.off(url, eventHandler); - break; - case DownloadStatusType.Error: - reject(arg.error); - this._electronService.ipcRenderer.off(url, eventHandler); - break; - case DownloadStatusType.Progress: - onProgress?.call(null, arg.progress); - break; - default: - break; - } - }; + const eventHandler = (_evt: any, arg: DownloadStatus) => { + switch (arg.type) { + case DownloadStatusType.Complete: + this._electronService.ipcRenderer.off( + request.responseKey, + eventHandler + ); + resolve(arg.savePath); + break; + case DownloadStatusType.Error: + this._electronService.ipcRenderer.off( + request.responseKey, + eventHandler + ); + reject(arg.error); + break; + case DownloadStatusType.Progress: + onProgress?.call(null, arg.progress); + break; + default: + break; + } + }; - this._electronService.ipcRenderer.on(url, eventHandler); - this._electronService.ipcRenderer.send(DOWNLOAD_FILE_CHANNEL, { url, outputFolder } as DownloadRequest); - }) - } + this._electronService.ipcRenderer.on(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(DOWNLOAD_FILE_CHANNEL, request); + }); + } - public unzipFile(zipFilePath: string, outputFolder: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: UnzipStatus) => { - this._electronService.ipcRenderer.off(zipFilePath, eventHandler); + public unzipFile(zipFilePath: string, outputFolder: string): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: UnzipStatus) => { + if (arg.type === UnzipStatusType.Error) { + return reject(arg.error); + } + resolve(arg.outputFolder); + }; - if (arg.type === UnzipStatusType.Error) { - return reject(arg.error); - } - resolve(arg.outputFolder); - } + const request: UnzipRequest = { + outputFolder, + zipFilePath, + responseKey: uuidv4(), + }; - const request: UnzipRequest = { - outputFolder, - zipFilePath - }; + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(UNZIP_FILE_CHANNEL, request); + }); + } - this._electronService.ipcRenderer.on(zipFilePath, eventHandler); - this._electronService.ipcRenderer.send(UNZIP_FILE_CHANNEL, request); - }); - } + public copyFile( + sourceFilePath: string, + destinationFilePath: string + ): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: { error?: Error }) => { + if (arg.error) { + return reject(arg.error); + } - public copyFile(sourceFilePath: string, destinationFilePath: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: { error?: Error }) => { - this._electronService.ipcRenderer.off(destinationFilePath, eventHandler); - if (arg.error) { - return reject(arg.error); - } + resolve(destinationFilePath); + }; - resolve(destinationFilePath); - }; + const request: CopyFileRequest = { + destinationFilePath, + sourceFilePath, + responseKey: uuidv4(), + }; - const request: CopyFileRequest = { - destinationFilePath, - sourceFilePath - }; - - this._electronService.ipcRenderer.on(destinationFilePath, eventHandler); - this._electronService.ipcRenderer.send(COPY_FILE_CHANNEL, request); - }) - } -} \ No newline at end of file + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(COPY_FILE_CHANNEL, request); + }); + } +} diff --git a/wowup-electron/src/app/services/files/file.service.ts b/wowup-electron/src/app/services/files/file.service.ts index 2ca47e46..49ef0a68 100644 --- a/wowup-electron/src/app/services/files/file.service.ts +++ b/wowup-electron/src/app/services/files/file.service.ts @@ -1,27 +1,33 @@ import { Injectable } from "@angular/core"; -import { COPY_DIRECTORY_CHANNEL, DELETE_DIRECTORY_CHANNEL, LIST_DIRECTORIES_CHANNEL, LIST_FILES_CHANNEL, PATH_EXISTS_CHANNEL, READ_FILE_CHANNEL, RENAME_DIRECTORY_CHANNEL, SHOW_DIRECTORY } from "common/constants"; +import { + COPY_DIRECTORY_CHANNEL, + DELETE_DIRECTORY_CHANNEL, + LIST_DIRECTORIES_CHANNEL, + LIST_FILES_CHANNEL, + PATH_EXISTS_CHANNEL, + READ_FILE_CHANNEL, + RENAME_DIRECTORY_CHANNEL, + SHOW_DIRECTORY, +} from "common/constants"; import { CopyDirectoryRequest } from "common/models/copy-directory-request"; import { DeleteDirectoryRequest } from "common/models/delete-directory-request"; import { ElectronService } from "../electron/electron.service"; -import * as fs from 'fs'; -import * as globrex from 'globrex'; +import * as fs from "fs"; +import * as globrex from "globrex"; import { ReadFileResponse } from "common/models/read-file-response"; import { ReadFileRequest } from "common/models/read-file-request"; import { ListFilesResponse } from "common/models/list-files-response"; import { ListFilesRequest } from "common/models/list-files-request"; import { ShowDirectoryRequest } from "common/models/show-directory-request"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; import { ValueRequest } from "common/models/value-request"; import { ValueResponse } from "common/models/value-response"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class FileService { - - constructor( - private _electronService: ElectronService - ) { } + constructor(private _electronService: ElectronService) {} public showDirectory(sourceDir: string) { return new Promise((resolve, reject) => { @@ -29,11 +35,14 @@ export class FileService { resolve(arg); }; - const request: ShowDirectoryRequest = { sourceDir, responseKey: uuidv4() }; + const request: ShowDirectoryRequest = { + sourceDir, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(SHOW_DIRECTORY, request); - }) + }); } public pathExists(sourcePath: string) { @@ -45,11 +54,14 @@ export class FileService { resolve(arg.value); }; - const request: ValueRequest = { value: sourcePath, responseKey: uuidv4() }; + const request: ValueRequest = { + value: sourcePath, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(PATH_EXISTS_CHANNEL, request); - }) + }); } public deleteDirectory(sourcePath: string) { @@ -61,11 +73,14 @@ export class FileService { resolve(sourcePath); }; - const request: DeleteDirectoryRequest = { sourcePath }; + const request: DeleteDirectoryRequest = { + sourcePath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(DELETE_DIRECTORY_CHANNEL, request); - }) + }); } public copyDirectory(sourcePath: string, destinationPath: string) { @@ -78,11 +93,15 @@ export class FileService { resolve(destinationPath); }; - const request: CopyDirectoryRequest = { sourcePath, destinationPath }; + const request: CopyDirectoryRequest = { + sourcePath, + destinationPath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(destinationPath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(COPY_DIRECTORY_CHANNEL, request); - }) + }); } public renameDirectory(sourcePath: string, destinationPath: string) { @@ -95,9 +114,13 @@ export class FileService { resolve(destinationPath); }; - const request: CopyDirectoryRequest = { sourcePath, destinationPath }; + const request: CopyDirectoryRequest = { + sourcePath, + destinationPath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(destinationPath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(RENAME_DIRECTORY_CHANNEL, request); }); } @@ -112,11 +135,14 @@ export class FileService { resolve(arg.data); }; - const request: ReadFileRequest = { sourcePath }; + const request: ReadFileRequest = { + sourcePath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(READ_FILE_CHANNEL, request); - }) + }); } public listDirectories(sourcePath: string): Promise { @@ -129,7 +155,10 @@ export class FileService { resolve(arg.value); }; - const request: ValueRequest = { value: sourcePath, responseKey: uuidv4() }; + const request: ValueRequest = { + value: sourcePath, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(LIST_DIRECTORIES_CHANNEL, request); @@ -139,12 +168,16 @@ export class FileService { public listFiles(sourcePath: string, filter: string) { const globFilter = globrex(filter); - return fs.readdirSync(sourcePath, { withFileTypes: true }) - .filter(entry => !!globFilter.regex.test(entry.name)) - .map(entry => entry.name); + return fs + .readdirSync(sourcePath, { withFileTypes: true }) + .filter((entry) => !!globFilter.regex.test(entry.name)) + .map((entry) => entry.name); } - public listAllFiles(sourcePath: string, recursive: boolean = true): Promise { + public listAllFiles( + sourcePath: string, + recursive: boolean = true + ): Promise { return new Promise((resolve, reject) => { const eventHandler = (_evt: any, arg: ListFilesResponse) => { if (arg.error) { @@ -154,10 +187,14 @@ export class FileService { resolve(arg.files); }; - const request: ListFilesRequest = { sourcePath, recursive, responseKey: uuidv4() }; + const request: ListFilesRequest = { + sourcePath, + recursive, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(LIST_FILES_CHANNEL, request); }); } -} \ No newline at end of file +} diff --git a/wowup-electron/src/common/models/copy-directory-request.ts b/wowup-electron/src/common/models/copy-directory-request.ts index 1daebc25..58af48a5 100644 --- a/wowup-electron/src/common/models/copy-directory-request.ts +++ b/wowup-electron/src/common/models/copy-directory-request.ts @@ -1,4 +1,6 @@ -export interface CopyDirectoryRequest { - sourcePath: string; - destinationPath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface CopyDirectoryRequest extends IpcRequest { + sourcePath: string; + destinationPath: string; +} diff --git a/wowup-electron/src/common/models/copy-file-request.ts b/wowup-electron/src/common/models/copy-file-request.ts index ba372693..3b2392d8 100644 --- a/wowup-electron/src/common/models/copy-file-request.ts +++ b/wowup-electron/src/common/models/copy-file-request.ts @@ -1,4 +1,6 @@ -export interface CopyFileRequest { - sourceFilePath: string; - destinationFilePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface CopyFileRequest extends IpcRequest { + sourceFilePath: string; + destinationFilePath: string; +} diff --git a/wowup-electron/src/common/models/delete-directory-request.ts b/wowup-electron/src/common/models/delete-directory-request.ts index 27fcc9b1..809fadec 100644 --- a/wowup-electron/src/common/models/delete-directory-request.ts +++ b/wowup-electron/src/common/models/delete-directory-request.ts @@ -1,3 +1,5 @@ -export interface DeleteDirectoryRequest { - sourcePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface DeleteDirectoryRequest extends IpcRequest { + sourcePath: string; +} diff --git a/wowup-electron/src/common/models/download-request.ts b/wowup-electron/src/common/models/download-request.ts index 7998414a..d44ef73e 100644 --- a/wowup-electron/src/common/models/download-request.ts +++ b/wowup-electron/src/common/models/download-request.ts @@ -1,4 +1,6 @@ -export interface DownloadRequest { - url: string; - outputFolder: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface DownloadRequest extends IpcRequest { + url: string; + outputFolder: string; +} diff --git a/wowup-electron/src/common/models/read-file-request.ts b/wowup-electron/src/common/models/read-file-request.ts index d12eb617..bdfd2e94 100644 --- a/wowup-electron/src/common/models/read-file-request.ts +++ b/wowup-electron/src/common/models/read-file-request.ts @@ -1,3 +1,5 @@ -export interface ReadFileRequest { - sourcePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface ReadFileRequest extends IpcRequest { + sourcePath: string; +} diff --git a/wowup-electron/src/common/models/unzip-request.ts b/wowup-electron/src/common/models/unzip-request.ts index dc04a261..8bb375f6 100644 --- a/wowup-electron/src/common/models/unzip-request.ts +++ b/wowup-electron/src/common/models/unzip-request.ts @@ -1,4 +1,6 @@ -export interface UnzipRequest { - zipFilePath: string; - outputFolder: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface UnzipRequest extends IpcRequest { + zipFilePath: string; + outputFolder: string; +} From 5c17e64bb8a6e07915d685076136dd3b1808f0b7 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 16:10:09 -0500 Subject: [PATCH 05/11] Fix bug with uninstalling addons with no installed folders --- wowup-electron/src/app/services/addons/addon.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5ef96e21..b152cbee 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -185,7 +185,7 @@ export class AddonService { this._wowUpService.applicationDownloadsFolderPath, uuidv4() ); - + unzippedDirectory = await this._downloadService.unzipFile( downloadedFilePath, unzipPath @@ -364,7 +364,7 @@ export class AddonService { } public async removeAddon(addon: Addon) { - const installedDirectories = addon.installedFolders.split(","); + const installedDirectories = addon.installedFolders?.split(",") ?? []; const addonFolderPath = this._warcraftService.getAddonFolderPath( addon.clientType From 67dea624ceff0f97a9484cf955c29be6f0aac54f Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 20:34:23 -0500 Subject: [PATCH 06/11] Start analytics --- .../services/analytics/analytics.service.ts | 121 +++++++++++++----- .../src/environments/environment.dev.ts | 6 +- .../src/environments/environment.prod.ts | 2 + .../src/environments/environment.ts | 2 + .../src/environments/environment.web.ts | 4 +- 5 files changed, 100 insertions(+), 35 deletions(-) diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 1b07674f..4cb6df67 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -1,47 +1,104 @@ import { ErrorHandler, Injectable } from "@angular/core"; -import * as Rollbar from 'rollbar'; +import * as Rollbar from "rollbar"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { telemetryEnabledKey } from "../../../constants"; +import { AppConfig } from "environments/environment"; +import { HttpClient, HttpParams } from "@angular/common/http"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class AnalyticsService implements ErrorHandler { - private rollbarConfig = { - accessToken: 'd01c11314a064572b11acee18d880650', - captureUncaught: true, - captureUnhandledRejections: true, - }; + private readonly analyticsUrl = "https://www.google-analytics.com"; + private readonly installIdPreferenceKey = "install_id"; + private readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; + private readonly telemetryEnabledKey = "telemetry_enabled"; - private _rollbar: Rollbar; - private get rollbar() { - if (!this.telemetryEnabled) { - return undefined; - } + private readonly rollbarConfig = { + accessToken: AppConfig.rollbarAccessKey, + captureUncaught: true, + captureUnhandledRejections: true, + }; - if (!this._rollbar) { - this._rollbar = new Rollbar(this.rollbarConfig); - } - return this._rollbar; + private _rollbar: Rollbar; + private get rollbar() { + if (!this.telemetryEnabled) { + return undefined; } - private get telemetryEnabled() { - return this._preferenceStorageService.get(telemetryEnabledKey) === true.toString(); + if (!this._rollbar) { + this._rollbar = new Rollbar(this.rollbarConfig); + } + return this._rollbar; + } + + private get telemetryEnabled() { + return ( + this._preferenceStorageService.get(telemetryEnabledKey) === + true.toString() + ); + } + + public get shouldPromptTelemetry() { + return ( + this._preferenceStorageService.get(telemetryEnabledKey) === undefined + ); + } + + constructor( + private _preferenceStorageService: PreferenceStorageService, + private _httpClient: HttpClient + ) {} + + public async TrackStartup() { + await Track((request) => { + request.SetQueryParam("t", "pageview").SetQueryParam("dp", "app/startup"); + }); + + await TrackAppCenter("AppStartup"); + } + + // ErrorHandler + handleError(error: any): void { + console.error("Caught error", error); + + this.rollbar?.error(error.originalError || error); + } + + private async track(action: (params: HttpParams) => void = undefined) { + if (!this.telemetryEnabled) { + return; } + var url = `${this.analyticsUrl}/collect`; - public get shouldPromptTelemetry() { - return this._preferenceStorageService.get(telemetryEnabledKey) === undefined; + try { + let params = new HttpParams(); + params.set('v', '1'); + params.set('tid', AppConfig.googleAnalyticsId); + params.set('cid', AppConfig.googleAnalyticsId); + + action?.call(params); + + const response = await this._httpClient + .post( + url, + {}, + { + params, + } + ) + .toPromise(); + var response = await request + .SetQueryParam("v", "1") + .SetQueryParam("tid", "") + .SetQueryParam("cid", InstallId) + .SetQueryParam("ua", HttpUtilities.UserAgent) + .SetQueryParam("an", "WowUp Client") + .SetQueryParam("av", AppUtilities.CurrentVersionString) + .PostJsonAsync(new {}()); + } catch (Exception) { + // eat } - - constructor( - private _preferenceStorageService: PreferenceStorageService - ) { } - - // ErrorHandler - handleError(error: any): void { - console.error('Caught error', error); - - this.rollbar?.error(error.originalError || error); - } -} \ No newline at end of file + } +} diff --git a/wowup-electron/src/environments/environment.dev.ts b/wowup-electron/src/environments/environment.dev.ts index 42fd2388..b0d3deaa 100644 --- a/wowup-electron/src/environments/environment.dev.ts +++ b/wowup-electron/src/environments/environment.dev.ts @@ -5,7 +5,9 @@ export const AppConfig = { production: false, - environment: 'DEV', - wowUpApiUrl: 'https://api.dev.wowup.io', + environment: "DEV", + wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.prod.ts b/wowup-electron/src/environments/environment.prod.ts index a8e45d57..cd8a50b7 100644 --- a/wowup-electron/src/environments/environment.prod.ts +++ b/wowup-electron/src/environments/environment.prod.ts @@ -3,4 +3,6 @@ export const AppConfig = { environment: "PROD", wowUpApiUrl: "https://api.wowup.io", wowUpHubUrl: "https://hub.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.ts b/wowup-electron/src/environments/environment.ts index 984e7d14..6e4ad5a7 100644 --- a/wowup-electron/src/environments/environment.ts +++ b/wowup-electron/src/environments/environment.ts @@ -3,4 +3,6 @@ export const AppConfig = { environment: "LOCAL", wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.web.ts b/wowup-electron/src/environments/environment.web.ts index 5936cb06..b0d3deaa 100644 --- a/wowup-electron/src/environments/environment.web.ts +++ b/wowup-electron/src/environments/environment.web.ts @@ -5,7 +5,9 @@ export const AppConfig = { production: false, - environment: 'DEV', + environment: "DEV", wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; From e3013163d3f72d0edb50756f2c48a603d98cb7b7 Mon Sep 17 00:00:00 2001 From: john liddell Date: Fri, 16 Oct 2020 22:11:35 -0500 Subject: [PATCH 07/11] Fix issues related to error handler add some button tracking --- wowup-electron/src/app/app.component.ts | 7 +- wowup-electron/src/app/app.module.ts | 8 +- .../wow-client-options.component.html | 15 ++- wowup-electron/src/app/directive.module.ts | 13 +- .../user-action-tracker.directive.spec.ts | 8 ++ .../user-action-tracker.directive.ts | 20 +++ .../interceptors/error-handler-intercepter.ts | 36 +++++ .../app/pages/options/options.component.html | 14 +- .../app/pages/options/options.component.ts | 14 +- .../services/analytics/analytics.service.ts | 126 ++++++++++-------- .../src/app/services/wowup/wowup.service.ts | 12 -- wowup-electron/src/constants.ts | 2 - 12 files changed, 179 insertions(+), 96 deletions(-) create mode 100644 wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts create mode 100644 wowup-electron/src/app/directives/user-action-tracker.directive.ts create mode 100644 wowup-electron/src/app/interceptors/error-handler-intercepter.ts diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 5a4860a8..89ca3649 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -37,7 +37,7 @@ export class AppComponent implements AfterViewInit { if (this._analyticsService.shouldPromptTelemetry) { this.openDialog(); } else { - // TODO track startup + this._analyticsService.trackStartup(); } this.onAutoUpdateInterval(); @@ -53,7 +53,10 @@ export class AppComponent implements AfterViewInit { }); dialogRef.afterClosed().subscribe((result) => { - this._wowUpService.telemetryEnabled = result; + this._analyticsService.telemetryEnabled = result; + if (result) { + this._analyticsService.trackStartup(); + } }); } diff --git a/wowup-electron/src/app/app.module.ts b/wowup-electron/src/app/app.module.ts index 357a2806..8b62e433 100644 --- a/wowup-electron/src/app/app.module.ts +++ b/wowup-electron/src/app/app.module.ts @@ -10,7 +10,7 @@ import { HTTP_INTERCEPTORS, } from "@angular/common/http"; import { SharedModule } from "./shared/shared.module"; - +import { ErrorHandlerIntercepter } from './interceptors/error-handler-intercepter'; import { AppRoutingModule } from "./app-routing.module"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @@ -28,6 +28,8 @@ import { AnalyticsService } from "./services/analytics/analytics.service"; import { DirectiveModule } from "./directive.module"; import { MatModule } from "./mat-module"; import { MatProgressButtonsModule } from "mat-progress-buttons"; +import { ElectronService } from "./services"; +import { PreferenceStorageService } from "./services/storage/preference-storage.service"; // AoT requires an exported function for factories export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { @@ -61,8 +63,8 @@ export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { useClass: DefaultHeadersInterceptor, multi: true, }, - { provide: ErrorHandler, useClass: AnalyticsService }, + { provide: ErrorHandler, useClass: ErrorHandlerIntercepter, deps: [AnalyticsService] }, ], bootstrap: [AppComponent], }) -export class AppModule {} +export class AppModule { } diff --git a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html index e275fada..da781d35 100644 --- a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html +++ b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html @@ -4,9 +4,11 @@ {{clientTypeName}} Path - The folder that contains the {{clientTypeName | lowercase}} client folder "{{clientFolderName}}" + The folder that contains the {{clientTypeName | lowercase}} client folder "{{clientFolderName}}" + - +
@@ -14,7 +16,9 @@
{{'PAGES.OPTIONS.WOW.DEFAULT_ADDON_CHANNEL_SELECT_LABEL' | translate}} - + {{channel.name}} @@ -24,6 +28,7 @@

{{'PAGES.OPTIONS.WOW.AUTO_UPDATE_LABEL' | translate}}

{{'PAGES.OPTIONS.WOW.AUTO_UPDATE_DESCRIPTION' | translate}}
- + - \ No newline at end of file + diff --git a/wowup-electron/src/app/directive.module.ts b/wowup-electron/src/app/directive.module.ts index 035632b9..828962f5 100644 --- a/wowup-electron/src/app/directive.module.ts +++ b/wowup-electron/src/app/directive.module.ts @@ -1,8 +1,15 @@ import { NgModule } from "@angular/core"; import { ExternalLinkDirective } from "./directives/external-link.directive"; +import { UserActionTrackerDirective } from "./directives/user-action-tracker.directive"; @NgModule({ - declarations: [ExternalLinkDirective], - exports: [ExternalLinkDirective], + declarations: [ + ExternalLinkDirective, + UserActionTrackerDirective + ], + exports: [ + ExternalLinkDirective, + UserActionTrackerDirective + ], }) -export class DirectiveModule {} +export class DirectiveModule { } diff --git a/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts b/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts new file mode 100644 index 00000000..40f2ea87 --- /dev/null +++ b/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts @@ -0,0 +1,8 @@ +import { UserActionTrackerDirective } from './user-action-tracker.directive'; + +describe('UserActionTrackerDirective', () => { + it('should create an instance', () => { + const directive = new UserActionTrackerDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/directives/user-action-tracker.directive.ts b/wowup-electron/src/app/directives/user-action-tracker.directive.ts new file mode 100644 index 00000000..4100daaf --- /dev/null +++ b/wowup-electron/src/app/directives/user-action-tracker.directive.ts @@ -0,0 +1,20 @@ +import { Directive, HostListener, Input } from '@angular/core'; +import { AnalyticsService } from '../services/analytics/analytics.service'; + +@Directive({ + selector: '[appUserActionTracker]' +}) +export class UserActionTrackerDirective { + + @Input() appUserActionTracker: string; + @Input() category: string; + @Input() action: string; + @Input() label: string; + + @HostListener('click', ['$event']) onClick($event) { + this._analyticsService.trackUserAction(this.category, this.action, this.label); + } + + constructor(private _analyticsService: AnalyticsService) { } + +} diff --git a/wowup-electron/src/app/interceptors/error-handler-intercepter.ts b/wowup-electron/src/app/interceptors/error-handler-intercepter.ts new file mode 100644 index 00000000..7268e0a6 --- /dev/null +++ b/wowup-electron/src/app/interceptors/error-handler-intercepter.ts @@ -0,0 +1,36 @@ +import { ErrorHandler } from "@angular/core"; +import { AnalyticsService } from "app/services/analytics/analytics.service"; +import { AppConfig } from "environments/environment"; +import * as Rollbar from "rollbar"; + +export class ErrorHandlerIntercepter implements ErrorHandler { + + private readonly rollbarConfig = { + accessToken: AppConfig.rollbarAccessKey, + captureUncaught: true, + captureUnhandledRejections: true, + }; + + private _rollbar: Rollbar; + private get rollbar() { + if (!this._analytics.telemetryEnabled) { + return undefined; + } + + if (!this._rollbar) { + this._rollbar = new Rollbar(this.rollbarConfig); + } + return this._rollbar; + } + + constructor(private _analytics: AnalyticsService) { + + } + + // ErrorHandler + handleError(error: any): void { + console.error("Caught error", error); + + this.rollbar?.error(error.originalError || error); + } +} \ No newline at end of file diff --git a/wowup-electron/src/app/pages/options/options.component.html b/wowup-electron/src/app/pages/options/options.component.html index a4b3b3c1..075b81f4 100644 --- a/wowup-electron/src/app/pages/options/options.component.html +++ b/wowup-electron/src/app/pages/options/options.component.html @@ -7,7 +7,8 @@
{{'PAGES.OPTIONS.WOW.RESCAN_CLIENTS_LABEL' | translate}}
-
@@ -45,7 +46,8 @@

{{'PAGES.OPTIONS.APPLICATION.MINIMIZE_ON_CLOSE_LABEL' | translate}}

{{'PAGES.OPTIONS.APPLICATION.MINIMIZE_ON_CLOSE_DESCRIPTION' | translate}} - +
@@ -60,7 +62,8 @@

{{'PAGES.OPTIONS.DEBUG.LOG_FILES_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.LOG_FILES_DESCRIPTION' | translate}} - @@ -69,11 +72,12 @@

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION' | translate}} - - \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 92c24aca..3ba10916 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -4,7 +4,6 @@ import { WowClientType } from 'app/models/warcraft/wow-client-type'; import { ElectronService } from 'app/services'; import { WarcraftService } from 'app/services/warcraft/warcraft.service'; import { WowUpService } from 'app/services/wowup/wowup.service'; -import { telemetryEnabledKey } from '../../../constants'; import { filter, map } from 'rxjs/operators'; import * as _ from 'lodash'; import * as path from 'path'; @@ -13,6 +12,7 @@ import { AlertDialogComponent } from 'app/components/alert-dialog/alert-dialog.c import { getEnumList, getEnumName } from 'app/utils/enum.utils'; import { WowUpReleaseChannelType } from 'app/models/wowup/wowup-release-channel-type'; import { MatSelectChange } from '@angular/material/select'; +import { AnalyticsService } from 'app/services/analytics/analytics.service'; @Component({ selector: 'app-options', @@ -36,6 +36,7 @@ export class OptionsComponent implements OnInit, OnChanges { .map((type: WowUpReleaseChannelType) => ({ type, name: getEnumName(WowUpReleaseChannelType, type) })); constructor( + private _analyticsService: AnalyticsService, private warcraft: WarcraftService, private _electronService: ElectronService, private _warcraftService: WarcraftService, @@ -44,10 +45,9 @@ export class OptionsComponent implements OnInit, OnChanges { private zone: NgZone, public electronService: ElectronService ) { - _wowUpService.preferenceChange$ - .pipe(filter(change => change.key === telemetryEnabledKey)) - .subscribe(change => { - this.telemetryEnabled = change.value === true.toString() + _analyticsService.telemetryEnabled$ + .subscribe(enabled => { + this.telemetryEnabled = enabled; }) } @@ -71,7 +71,7 @@ export class OptionsComponent implements OnInit, OnChanges { } onTelemetryChange = (evt: MatSlideToggleChange) => { - this._wowUpService.telemetryEnabled = evt.checked; + this._analyticsService.telemetryEnabled = evt.checked; } onCollapseChange = (evt: MatSlideToggleChange) => { @@ -155,7 +155,7 @@ export class OptionsComponent implements OnInit, OnChanges { private loadData() { this.zone.run(() => { - this.telemetryEnabled = this._wowUpService.telemetryEnabled; + this.telemetryEnabled = this._analyticsService.telemetryEnabled; this.collapseToTray = this._wowUpService.collapseToTray; this.retailLocation = this.warcraft.getClientLocation(WowClientType.Retail); this.classicLocation = this.warcraft.getClientLocation(WowClientType.Classic); diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 4cb6df67..5bd14e45 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -1,68 +1,70 @@ import { ErrorHandler, Injectable } from "@angular/core"; -import * as Rollbar from "rollbar"; +import { v4 as uuidv4 } from "uuid"; import { PreferenceStorageService } from "../storage/preference-storage.service"; -import { telemetryEnabledKey } from "../../../constants"; import { AppConfig } from "environments/environment"; import { HttpClient, HttpParams } from "@angular/common/http"; +import { ElectronService } from "../electron/electron.service"; +import { BehaviorSubject } from "rxjs"; @Injectable({ providedIn: "root", }) -export class AnalyticsService implements ErrorHandler { +export class AnalyticsService { private readonly analyticsUrl = "https://www.google-analytics.com"; private readonly installIdPreferenceKey = "install_id"; - private readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; - private readonly telemetryEnabledKey = "telemetry_enabled"; + private readonly _installId: string; + private readonly _appVersion: string; + private readonly _telemetryEnabledSrc = new BehaviorSubject(false); - private readonly rollbarConfig = { - accessToken: AppConfig.rollbarAccessKey, - captureUncaught: true, - captureUnhandledRejections: true, - }; + public readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; + public readonly telemetryEnabledKey = "telemetry_enabled"; + public readonly telemetryEnabled$ = this._telemetryEnabledSrc.asObservable(); - private _rollbar: Rollbar; - private get rollbar() { - if (!this.telemetryEnabled) { - return undefined; - } - - if (!this._rollbar) { - this._rollbar = new Rollbar(this.rollbarConfig); - } - return this._rollbar; - } - - private get telemetryEnabled() { - return ( - this._preferenceStorageService.get(telemetryEnabledKey) === - true.toString() - ); + private get installId() { + return this._installId; } public get shouldPromptTelemetry() { return ( - this._preferenceStorageService.get(telemetryEnabledKey) === undefined + this._preferenceStorageService.get(this.telemetryEnabledKey) === undefined ); } - constructor( - private _preferenceStorageService: PreferenceStorageService, - private _httpClient: HttpClient - ) {} - - public async TrackStartup() { - await Track((request) => { - request.SetQueryParam("t", "pageview").SetQueryParam("dp", "app/startup"); - }); - - await TrackAppCenter("AppStartup"); + public get telemetryEnabled() { + const preference = this._preferenceStorageService.findByKey(this.telemetryEnabledKey); + return preference === true.toString(); } - // ErrorHandler - handleError(error: any): void { - console.error("Caught error", error); + public set telemetryEnabled(value: boolean) { + this._preferenceStorageService.set(this.telemetryEnabledKey, value); + this._telemetryEnabledSrc.next(value); + } - this.rollbar?.error(error.originalError || error); + constructor( + private _electronService: ElectronService, + private _httpClient: HttpClient, + private _preferenceStorageService: PreferenceStorageService + ) { + this._appVersion = _electronService.remote.app.getVersion(); + this._installId = this.loadInstallId(); + this._telemetryEnabledSrc.next(this.telemetryEnabled); + console.log('installId', this._installId); + } + + public async trackStartup() { + await this.track((params) => { + params.set("t", "pageview"); + params.set("dp", "app/startup"); + }); + } + + public async trackUserAction(category: string, action: string, label: string = null) { + await this.track(params => { + params.set("t", "event"); + params.set("ec", category); + params.set("ea", action); + params.set("el", label); + }); } private async track(action: (params: HttpParams) => void = undefined) { @@ -73,32 +75,42 @@ export class AnalyticsService implements ErrorHandler { var url = `${this.analyticsUrl}/collect`; try { - let params = new HttpParams(); + let params = new URLSearchParams(); params.set('v', '1'); params.set('tid', AppConfig.googleAnalyticsId); - params.set('cid', AppConfig.googleAnalyticsId); + params.set('cid', this._installId); + params.set('ua', window.navigator.userAgent); + params.set('an', "WowUp Client"); + params.set('av', this._appVersion); - action?.call(params); + action?.call(this, params); + + const fullUrl = `${url}?${params}`; const response = await this._httpClient .post( - url, + fullUrl, {}, { - params, + responseType: 'text' } ) .toPromise(); - var response = await request - .SetQueryParam("v", "1") - .SetQueryParam("tid", "") - .SetQueryParam("cid", InstallId) - .SetQueryParam("ua", HttpUtilities.UserAgent) - .SetQueryParam("an", "WowUp Client") - .SetQueryParam("av", AppUtilities.CurrentVersionString) - .PostJsonAsync(new {}()); - } catch (Exception) { + } catch (e) { // eat + console.error(e) } } + + private loadInstallId() { + let installId = this._preferenceStorageService.findByKey(this.installIdPreferenceKey); + if (installId) { + return installId; + } + + installId = uuidv4(); + this._preferenceStorageService.set(this.installIdPreferenceKey, installId); + + return installId; + } } diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 02092ba9..058185f3 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -24,7 +24,6 @@ import { defaultAutoUpdateKeySuffix, defaultChannelKeySuffix, lastSelectedWowClientTypeKey, - telemetryEnabledKey, wowupReleaseChannelKey } from "../../../constants"; @@ -75,17 +74,6 @@ export class WowUpService { this._preferenceChangeSrc.next({ key, value: value.toString() }) } - public get telemetryEnabled() { - const preference = this._preferenceStorageService.findByKey(telemetryEnabledKey); - return preference === 'true'; - } - - public set telemetryEnabled(value: boolean) { - const key = telemetryEnabledKey; - this._preferenceStorageService.set(key, value); - this._preferenceChangeSrc.next({ key, value: value.toString() }) - } - public get wowUpReleaseChannel() { const preference = this._preferenceStorageService.findByKey(wowupReleaseChannelKey); return parseInt(preference, 10) as WowUpReleaseChannelType; diff --git a/wowup-electron/src/constants.ts b/wowup-electron/src/constants.ts index ed7086d2..48e46a11 100644 --- a/wowup-electron/src/constants.ts +++ b/wowup-electron/src/constants.ts @@ -2,6 +2,4 @@ export const collapseToTrayKey = 'collapse_to_tray'; export const wowupReleaseChannelKey = 'wowup_release_channel'; export const defaultChannelKeySuffix = '_default_addon_channel'; export const defaultAutoUpdateKeySuffix = '_default_auto_update'; -export const telemetryEnabledKey = 'telemetry_enabled'; -export const telemetryPromptSentKey = 'telemetry_prompt_sent'; export const lastSelectedWowClientTypeKey = 'last_selected_client_type'; \ No newline at end of file From d63cb1fa8b5e22de2674b48b5d7e5a41281ecc31 Mon Sep 17 00:00:00 2001 From: john liddell Date: Fri, 16 Oct 2020 22:37:18 -0500 Subject: [PATCH 08/11] More analytics Fix all the cursor pointer issues i could find on mac. --- .../components/footer/footer.component.html | 27 ++++++++++--------- .../components/footer/footer.component.scss | 2 ++ .../titlebar/titlebar.component.html | 4 +-- .../src/app/pages/about/about.component.html | 4 +-- .../get-addons/get-addons.component.html | 12 ++++++--- .../pages/my-addons/my-addons.component.html | 19 ++++++++----- wowup-electron/src/styles.scss | 24 ++++++++++++++--- 7 files changed, 60 insertions(+), 32 deletions(-) diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index 1a4f8342..3cebbe4a 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -1,14 +1,15 @@
- - - - - - -

{{sessionService.statusText$ | async}}

-
-

{{sessionService.pageContextText$ | async}}

-

v{{wowUpService.applicationVersion}}

-
-
\ No newline at end of file + + + + + + +

{{sessionService.statusText$ | async}}

+
+

{{sessionService.pageContextText$ | async}}

+

v{{wowUpService.applicationVersion}}

+
+ diff --git a/wowup-electron/src/app/components/footer/footer.component.scss b/wowup-electron/src/app/components/footer/footer.component.scss index 59325c1c..b5fd3c15 100644 --- a/wowup-electron/src/app/components/footer/footer.component.scss +++ b/wowup-electron/src/app/components/footer/footer.component.scss @@ -37,6 +37,8 @@ footer { } .discord-link { + padding: 0 0.25em; + &:hover { background-color: $dark-3; } diff --git a/wowup-electron/src/app/components/titlebar/titlebar.component.html b/wowup-electron/src/app/components/titlebar/titlebar.component.html index 56f58fb7..58aea898 100644 --- a/wowup-electron/src/app/components/titlebar/titlebar.component.html +++ b/wowup-electron/src/app/components/titlebar/titlebar.component.html @@ -6,8 +6,8 @@
WowUp.io
-
- bug_report +
+ bug_report
bug_report diff --git a/wowup-electron/src/app/pages/about/about.component.html b/wowup-electron/src/app/pages/about/about.component.html index e413fb4a..07eea492 100644 --- a/wowup-electron/src/app/pages/about/about.component.html +++ b/wowup-electron/src/app/pages/about/about.component.html @@ -6,7 +6,7 @@

{{'PAGES.ABOUT.TITLE' | translate}}

v{{version}}
@@ -23,4 +23,4 @@
- \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index a0722be0..7c698421 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -23,10 +23,12 @@
- -
@@ -70,7 +72,9 @@ {{'PAGES.GET_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}} - + + @@ -78,4 +82,4 @@ - \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index 63cc69a3..b5848f48 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -4,7 +4,7 @@
{{'PAGES.MY_ADDONS.CLIENT_TYPE_SELECT_LABEL' | translate}} - {{warcraftService.getClientDisplayName(clientType)}} @@ -25,17 +25,20 @@
@@ -62,11 +65,13 @@ {{'PAGES.MY_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}}
-
@@ -219,4 +224,4 @@ {{'PAGES.MY_ADDONS.UPDATE_ALL_CONTEXT_MENU.UPDATE_ALL_CLIENTS_BUTTON' | translate}} - \ No newline at end of file + diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index 15c22de3..abc40ec0 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -24,8 +24,7 @@ img:not([draggable="true"]) { a[href^="http://"], a[href^="https://"], -a[href^="ftp://"] -{ +a[href^="ftp://"] { -webkit-user-drag: auto; user-drag: auto; /* Technically not supported in Electron yet */ @@ -59,9 +58,11 @@ img { .mr-1 { margin-right: .25em !important; } + .mr-2 { margin-right: .5em !important; } + .mr-3 { margin-right: 1em !important; } @@ -70,14 +71,29 @@ img { -webkit-app-region: no-drag; } -.pointer:hover { - cursor: pointer; +.pointer { + pointer-events: all !important; + + &:hover { + cursor: pointer !important; + } +} + +.mat-tab-label, +.mat-tab-label-content, +.mat-select-value, +.mat-form-field-infix, +.mat-button-wrapper span { + &:hover { + cursor: pointer !important; + } } .mat-slide-toggle-thumb, .mat-slide-toggle-bar, .mat-button-wrapper { -webkit-app-region: no-drag; + &:hover { cursor: pointer; } From d9eb69c3e1c4e63bacd59418fa408cc127bdb690 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 22:57:56 -0500 Subject: [PATCH 09/11] Last of the tracking --- .../install-from-url-dialog.component.html | 3 +- .../pages/my-addons/my-addons.component.html | 32 +++++++++++------ .../pages/my-addons/my-addons.component.ts | 1 - .../src/app/services/addons/addon.service.ts | 18 +++++++--- .../services/analytics/analytics.service.ts | 34 ++++++++++++------- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html index 23110f5c..f85a63c8 100644 --- a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html +++ b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html @@ -40,7 +40,8 @@ -
\ No newline at end of file diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index b5848f48..d4dd5111 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -157,36 +157,44 @@
+ (change)="onClickIgnoreAddon($event, listItem)" appUserActionTracker category="MyAddons" action="IgnoreAddon" + [label]="listItem.addon.name"> {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.IGNORE_ADDON_BUTTON' | translate}} + [checked]="listItem.addon.autoUpdateEnabled" (change)="onClickAutoUpdateAddon($event, listItem.addon)" + appUserActionTracker category="MyAddons" action="AutoUpdateAddon" [label]="listItem.addon.name"> {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.AUTO_UPDATE_ADDON_BUTTON' | translate}} - - - - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.STABLE_ADDON_CHANNEL' | translate}} - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.BETA_ADDON_CHANNEL' | translate}} - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.ALPHA_ADDON_CHANNEL' | translate}} @@ -217,11 +225,13 @@ - - - + \ No newline at end of file diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index df89531a..86e7db71 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -111,7 +111,6 @@ export class MyAddonsComponent implements OnInit, OnDestroy { ) { _sessionService.selectedHomeTab$.subscribe((tabIndex) => { this.isSelectedTab = tabIndex === this.tabIndex; - console.log("TAB CHANGE", tabIndex, this.tabIndex); if (this.isSelectedTab) { this.setPageContextText(); } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index b152cbee..edee475d 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -23,6 +23,7 @@ import { FileService } from "../files/file.service"; import { TocService } from "../toc/toc.service"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; import { AddonProviderFactory } from "./addon.provider.factory"; +import { AnalyticsService } from "../analytics/analytics.service"; @Injectable({ providedIn: "root", @@ -37,6 +38,7 @@ export class AddonService { constructor( private _addonStorage: AddonStorageService, + private _analyticsService: AnalyticsService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService, private _downloadService: DownloadSevice, @@ -65,7 +67,12 @@ export class AddonService { ); var searchResults = await Promise.all(searchTasks); - // await _analyticsService.TrackUserAction("Addons", "Search", $"{clientType}|{query}"); + await this._analyticsService.trackUserAction( + "Addons", + "Search", + `${clientType}|${query}` + ); + const flatResults = searchResults.flat(1); return _.orderBy(flatResults, "downloadCount").reverse(); @@ -107,7 +114,6 @@ export class AddonService { for (let clientTypeStr in clientTypeGroups) { const clientType: WowClientType = parseInt(clientTypeStr, 10); - // console.log('clientType', clientType, clientTypeGroups[clientType]); const synced = await this.syncAddons( clientType, @@ -126,7 +132,7 @@ export class AddonService { await this.installAddon(addon.id); updateCt += 1; } catch (err) { - // _analyticsService.Track(ex, "Failed to install addon"); + console.error(err); } } } @@ -209,7 +215,11 @@ export class AddonService { this._addonStorage.set(addon.id, addon); - // await _analyticsService.TrackUserAction("Addons", "InstallById", $"{addon.ClientType}|{addon.Name}"); + await this._analyticsService.trackUserAction( + "Addons", + "InstallById", + `${addon.clientType}|${addon.name}` + ); } catch (err) { console.error(err); diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 5bd14e45..29747f23 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -31,7 +31,9 @@ export class AnalyticsService { } public get telemetryEnabled() { - const preference = this._preferenceStorageService.findByKey(this.telemetryEnabledKey); + const preference = this._preferenceStorageService.findByKey( + this.telemetryEnabledKey + ); return preference === true.toString(); } @@ -48,7 +50,7 @@ export class AnalyticsService { this._appVersion = _electronService.remote.app.getVersion(); this._installId = this.loadInstallId(); this._telemetryEnabledSrc.next(this.telemetryEnabled); - console.log('installId', this._installId); + console.log("installId", this._installId); } public async trackStartup() { @@ -58,8 +60,12 @@ export class AnalyticsService { }); } - public async trackUserAction(category: string, action: string, label: string = null) { - await this.track(params => { + public async trackUserAction( + category: string, + action: string, + label: string = null + ) { + await this.track((params) => { params.set("t", "event"); params.set("ec", category); params.set("ea", action); @@ -76,12 +82,12 @@ export class AnalyticsService { try { let params = new URLSearchParams(); - params.set('v', '1'); - params.set('tid', AppConfig.googleAnalyticsId); - params.set('cid', this._installId); - params.set('ua', window.navigator.userAgent); - params.set('an', "WowUp Client"); - params.set('av', this._appVersion); + params.set("v", "1"); + params.set("tid", AppConfig.googleAnalyticsId); + params.set("cid", this._installId); + params.set("ua", window.navigator.userAgent); + params.set("an", "WowUp Client"); + params.set("av", this._appVersion); action?.call(this, params); @@ -92,18 +98,20 @@ export class AnalyticsService { fullUrl, {}, { - responseType: 'text' + responseType: "text", } ) .toPromise(); } catch (e) { // eat - console.error(e) + console.error(e); } } private loadInstallId() { - let installId = this._preferenceStorageService.findByKey(this.installIdPreferenceKey); + let installId = this._preferenceStorageService.findByKey( + this.installIdPreferenceKey + ); if (installId) { return installId; } From 6c81b8237618121b93f4836b6e49c24a4289be9c Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 23:24:38 -0500 Subject: [PATCH 10/11] Fix some author column crowding. Debug button does the logging we need now. --- .../addon-providers/curse-addon-provider.ts | 74 ++++---- .../get-addons/get-addons.component.html | 2 +- .../get-addons/get-addons.component.scss | 1 + .../pages/my-addons/my-addons.component.html | 2 +- .../app/pages/options/options.component.html | 2 +- .../app/pages/options/options.component.ts | 164 +++++++++++------- .../src/app/services/addons/addon.service.ts | 18 ++ 7 files changed, 163 insertions(+), 100 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 13b31b46..d53f477e 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -63,7 +63,7 @@ export class CurseAddonProvider implements AddonProvider { }); } - async scan( + public async scan( clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[] @@ -112,12 +112,45 @@ export class CurseAddonProvider implements AddonProvider { } catch (err) { console.error(scanResult); console.error(err); - // TODO - // _analyticsService.Track(ex, $"Failed to create addon for result {scanResult.FolderScanner.Fingerprint}"); } } } + public getScanResults = async ( + addonFolders: AddonFolder[] + ): Promise => { + const t1 = Date.now(); + + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseGetScanResultsResponse) => { + if (arg.error) { + return reject(arg.error); + } + + const appScanResults: AppCurseScanResult[] = arg.scanResults.map( + (scanResult) => { + const addonFolder = addonFolders.find( + (af) => af.path === scanResult.directory + ); + + return Object.assign({}, scanResult, { addonFolder }); + } + ); + + console.log("scan delta", Date.now() - t1); + resolve(appScanResults); + }; + + const request: CurseGetScanResultsRequest = { + filePaths: addonFolders.map((addonFolder) => addonFolder.path), + responseKey: uuidv4(), + }; + + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_GET_SCAN_RESULTS, request); + }); + }; + private async mapAddonFolders( scanResults: AppCurseScanResult[], clientType: WowClientType @@ -227,41 +260,6 @@ export class CurseAddonProvider implements AddonProvider { return action.call(this); } - private getScanResults = async ( - addonFolders: AddonFolder[] - ): Promise => { - const t1 = Date.now(); - - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseGetScanResultsResponse) => { - if (arg.error) { - return reject(arg.error); - } - - const appScanResults: AppCurseScanResult[] = arg.scanResults.map( - (scanResult) => { - const addonFolder = addonFolders.find( - (af) => af.path === scanResult.directory - ); - - return Object.assign({}, scanResult, { addonFolder }); - } - ); - - console.log("scan delta", Date.now() - t1); - resolve(appScanResults); - }; - - const request: CurseGetScanResultsRequest = { - filePaths: addonFolders.map((addonFolder) => addonFolder.path), - responseKey: uuidv4(), - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_GET_SCAN_RESULTS, request); - }); - }; - async getAll( clientType: WowClientType, addonIds: string[] diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index 7c698421..f05ea7b3 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -62,7 +62,7 @@ {{'PAGES.GET_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}} - + {{element.author}} diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss index 1edef68e..d541a785 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss @@ -73,6 +73,7 @@ .author-column { width: 100px; + padding-right: 1em; } .provider-column { min-width: 60px; diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index d4dd5111..9dcbc26a 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -127,7 +127,7 @@ {{'PAGES.MY_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}} - + {{element.addon.author}} diff --git a/wowup-electron/src/app/pages/options/options.component.html b/wowup-electron/src/app/pages/options/options.component.html index 075b81f4..71b0de33 100644 --- a/wowup-electron/src/app/pages/options/options.component.html +++ b/wowup-electron/src/app/pages/options/options.component.html @@ -72,7 +72,7 @@

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION' | translate}} - diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 3ba10916..689c87ae 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -1,41 +1,58 @@ -import { Component, OnInit, NgZone, OnChanges, SimpleChanges, Input } from '@angular/core'; -import { MatSlideToggleChange } from '@angular/material/slide-toggle'; -import { WowClientType } from 'app/models/warcraft/wow-client-type'; -import { ElectronService } from 'app/services'; -import { WarcraftService } from 'app/services/warcraft/warcraft.service'; -import { WowUpService } from 'app/services/wowup/wowup.service'; -import { filter, map } from 'rxjs/operators'; -import * as _ from 'lodash'; -import * as path from 'path'; -import { MatDialog } from '@angular/material/dialog'; -import { AlertDialogComponent } from 'app/components/alert-dialog/alert-dialog.component'; -import { getEnumList, getEnumName } from 'app/utils/enum.utils'; -import { WowUpReleaseChannelType } from 'app/models/wowup/wowup-release-channel-type'; -import { MatSelectChange } from '@angular/material/select'; -import { AnalyticsService } from 'app/services/analytics/analytics.service'; +import { + Component, + OnInit, + NgZone, + OnChanges, + SimpleChanges, + Input, +} from "@angular/core"; +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { WowClientType } from "app/models/warcraft/wow-client-type"; +import { ElectronService } from "app/services"; +import { WarcraftService } from "app/services/warcraft/warcraft.service"; +import { WowUpService } from "app/services/wowup/wowup.service"; +import { filter, map } from "rxjs/operators"; +import * as _ from "lodash"; +import * as path from "path"; +import { MatDialog } from "@angular/material/dialog"; +import { AlertDialogComponent } from "app/components/alert-dialog/alert-dialog.component"; +import { getEnumList, getEnumName } from "app/utils/enum.utils"; +import { WowUpReleaseChannelType } from "app/models/wowup/wowup-release-channel-type"; +import { MatSelectChange } from "@angular/material/select"; +import { AnalyticsService } from "app/services/analytics/analytics.service"; +import { AddonService } from "app/services/addons/addon.service"; @Component({ - selector: 'app-options', - templateUrl: './options.component.html', - styleUrls: ['./options.component.scss'] + selector: "app-options", + templateUrl: "./options.component.html", + styleUrls: ["./options.component.scss"], }) export class OptionsComponent implements OnInit, OnChanges { + @Input("tabIndex") tabIndex: number; - @Input('tabIndex') tabIndex: number; - - public retailLocation = ''; - public classicLocation = ''; - public retailPtrLocation = ''; - public classicPtrLocation = ''; - public betaLocation = ''; + public retailLocation = ""; + public classicLocation = ""; + public retailPtrLocation = ""; + public classicPtrLocation = ""; + public betaLocation = ""; public collapseToTray = false; public telemetryEnabled = false; - public wowClientTypes: WowClientType[] = getEnumList(WowClientType).filter(clientType => clientType !== WowClientType.None) as WowClientType[]; + public wowClientTypes: WowClientType[] = getEnumList(WowClientType).filter( + (clientType) => clientType !== WowClientType.None + ) as WowClientType[]; public wowUpReleaseChannel: WowUpReleaseChannelType; - public wowUpReleaseChannels: { type: WowUpReleaseChannelType, name: string }[] = getEnumList(WowUpReleaseChannelType) - .map((type: WowUpReleaseChannelType) => ({ type, name: getEnumName(WowUpReleaseChannelType, type) })); + public wowUpReleaseChannels: { + type: WowUpReleaseChannelType; + name: string; + }[] = getEnumList( + WowUpReleaseChannelType + ).map((type: WowUpReleaseChannelType) => ({ + type, + name: getEnumName(WowUpReleaseChannelType, type), + })); constructor( + private _addonService: AddonService, private _analyticsService: AnalyticsService, private warcraft: WarcraftService, private _electronService: ElectronService, @@ -45,14 +62,13 @@ export class OptionsComponent implements OnInit, OnChanges { private zone: NgZone, public electronService: ElectronService ) { - _analyticsService.telemetryEnabled$ - .subscribe(enabled => { - this.telemetryEnabled = enabled; - }) + _analyticsService.telemetryEnabled$.subscribe((enabled) => { + this.telemetryEnabled = enabled; + }); } ngOnChanges(changes: SimpleChanges): void { - console.log(changes) + console.log(changes); } ngOnInit(): void { @@ -63,25 +79,29 @@ export class OptionsComponent implements OnInit, OnChanges { onShowLogs = () => { this._wowUpService.showLogsFolder(); - } + }; onReScan = () => { this.warcraft.scanProducts(); this.loadData(); - } + }; onTelemetryChange = (evt: MatSlideToggleChange) => { this._analyticsService.telemetryEnabled = evt.checked; - } + }; onCollapseChange = (evt: MatSlideToggleChange) => { this._wowUpService.collapseToTray = evt.checked; - } + }; onWowUpChannelChange(evt: MatSelectChange) { this._wowUpService.wowUpReleaseChannel = evt.value; } + async onLogDebugData() { + await this._addonService.logDebugData(); + } + async onSelectRetailClientPath() { const selectedPath = await this.selectWowClientPath(WowClientType.Retail); if (selectedPath) { @@ -90,7 +110,9 @@ export class OptionsComponent implements OnInit, OnChanges { } async onSelectRetailPtrClientPath() { - const selectedPath = await this.selectWowClientPath(WowClientType.RetailPtr); + const selectedPath = await this.selectWowClientPath( + WowClientType.RetailPtr + ); if (selectedPath) { this.retailPtrLocation = selectedPath; } @@ -104,7 +126,9 @@ export class OptionsComponent implements OnInit, OnChanges { } async onSelectClassicPtrClientPath() { - const selectedPath = await this.selectWowClientPath(WowClientType.ClassicPtr); + const selectedPath = await this.selectWowClientPath( + WowClientType.ClassicPtr + ); if (selectedPath) { this.classicPtrLocation = selectedPath; } @@ -117,52 +141,74 @@ export class OptionsComponent implements OnInit, OnChanges { } } - private async selectWowClientPath(clientType: WowClientType): Promise { - const dialogResult = await this._electronService.remote.dialog.showOpenDialog({ - properties: ['openDirectory'] - }); + private async selectWowClientPath( + clientType: WowClientType + ): Promise { + const dialogResult = await this._electronService.remote.dialog.showOpenDialog( + { + properties: ["openDirectory"], + } + ); if (dialogResult.canceled) { - return ''; + return ""; } const selectedPath = _.first(dialogResult.filePaths); if (!selectedPath) { - console.warn('No path selected') - return ''; + console.warn("No path selected"); + return ""; } - console.log('dialogResult', selectedPath); + console.log("dialogResult", selectedPath); if (this._warcraftService.setWowFolderPath(clientType, selectedPath)) { return selectedPath; } - const clientFolderName = this._warcraftService.getClientFolderName(clientType); - const clientExecutableName = this._warcraftService.getExecutableName(clientType); - const clientExecutablePath = path.join(selectedPath, clientFolderName, clientExecutableName); + const clientFolderName = this._warcraftService.getClientFolderName( + clientType + ); + const clientExecutableName = this._warcraftService.getExecutableName( + clientType + ); + const clientExecutablePath = path.join( + selectedPath, + clientFolderName, + clientExecutableName + ); const dialogRef = this._dialog.open(AlertDialogComponent, { data: { title: `Alert`, - message: `Unable to set "${selectedPath}" as your ${getEnumName(WowClientType, clientType)} folder.\nPath not found: "${clientExecutablePath}".` - } + message: `Unable to set "${selectedPath}" as your ${getEnumName( + WowClientType, + clientType + )} folder.\nPath not found: "${clientExecutablePath}".`, + }, }); await dialogRef.afterClosed().toPromise(); - return ''; + return ""; } private loadData() { this.zone.run(() => { this.telemetryEnabled = this._analyticsService.telemetryEnabled; this.collapseToTray = this._wowUpService.collapseToTray; - this.retailLocation = this.warcraft.getClientLocation(WowClientType.Retail); - this.classicLocation = this.warcraft.getClientLocation(WowClientType.Classic); - this.retailPtrLocation = this.warcraft.getClientLocation(WowClientType.RetailPtr); - this.classicPtrLocation = this.warcraft.getClientLocation(WowClientType.ClassicPtr); + this.retailLocation = this.warcraft.getClientLocation( + WowClientType.Retail + ); + this.classicLocation = this.warcraft.getClientLocation( + WowClientType.Classic + ); + this.retailPtrLocation = this.warcraft.getClientLocation( + WowClientType.RetailPtr + ); + this.classicPtrLocation = this.warcraft.getClientLocation( + WowClientType.ClassicPtr + ); this.betaLocation = this.warcraft.getClientLocation(WowClientType.Beta); - }) + }); } - } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index edee475d..d2bea129 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -242,6 +242,24 @@ export class AddonService { }); } + public async logDebugData() { + const curseProvider = this._addonProviders.find( + (p) => p.name === "Curse" + ) as CurseAddonProvider; + + const clientTypes = await this._warcraftService.getWowClientTypes(); + for (let clientType of clientTypes) { + const addonFolders = await this._warcraftService.listAddons(clientType); + const scanResults = await curseProvider.getScanResults(addonFolders); + const map = {}; + + scanResults.forEach((sr) => (map[sr.folderName] = sr.fingerprint)); + + console.log(`clientType ${this._warcraftService.getClientDisplayName(clientType)} addon fingerprints`); + console.log(map); + } + } + private async getLatestGameVersion( baseDir: string, installedFolders: string[] From 7b092b819d8649249cbf61c1d5a449deadebe543 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 00:06:07 -0500 Subject: [PATCH 11/11] Add a simple message passing method. Add the auto update notification popup. --- wowup-electron/ipc-events.ts | 9 +++ wowup-electron/src/app/app.component.ts | 19 +++++- .../app/pages/options/options.component.ts | 13 ++-- .../app/services/electron/electron.service.ts | 64 ++++++++++++++----- .../src/app/services/files/file.service.ts | 8 +++ wowup-electron/src/common/constants.ts | 1 + 6 files changed, 89 insertions(+), 25 deletions(-) diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index a261dfdc..6aea993c 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -1,6 +1,7 @@ import { ipcMain, shell } from "electron"; import * as fs from "fs"; import * as async from "async"; +import * as path from "path"; import * as admZip from "adm-zip"; import { ncp } from "ncp"; import * as rimraf from "rimraf"; @@ -20,6 +21,7 @@ import { DELETE_DIRECTORY_CHANNEL, RENAME_DIRECTORY_CHANNEL, READ_FILE_CHANNEL, + GET_ASSET_FILE_PATH, } from "./src/common/constants"; import { CurseGetScanResultsRequest } from "./src/common/curse/curse-get-scan-results-request"; import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; @@ -53,6 +55,13 @@ ipcMain.on(SHOW_DIRECTORY, async (evt, arg: ShowDirectoryRequest) => { evt.reply(arg.responseKey, true); }); +ipcMain.on(GET_ASSET_FILE_PATH, async (evt, arg: ValueRequest) => { + const response: ValueResponse = { + value: path.join(__dirname, "assets", arg.value), + }; + evt.reply(arg.responseKey, response); +}); + ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { // console.log(CURSE_HASH_FILE_CHANNEL, arg); diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 89ca3649..573d8e01 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -6,6 +6,7 @@ import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetr import { ElectronService } from "./services"; import { AddonService } from "./services/addons/addon.service"; import { AnalyticsService } from "./services/analytics/analytics.service"; +import { FileService } from "./services/files/file.service"; import { WarcraftService } from "./services/warcraft/warcraft.service"; import { WowUpService } from "./services/wowup/wowup.service"; @@ -21,7 +22,8 @@ export class AppComponent implements AfterViewInit { constructor( private _analyticsService: AnalyticsService, - private electronService: ElectronService, + private _electronService: ElectronService, + private _fileService: FileService, private translate: TranslateService, private warcraft: WarcraftService, private _wowUpService: WowUpService, @@ -30,7 +32,7 @@ export class AppComponent implements AfterViewInit { ) { this.translate.setDefaultLang("en"); - this.translate.use(this.electronService.locale); + this.translate.use(this._electronService.locale); } ngAfterViewInit(): void { @@ -63,5 +65,18 @@ export class AppComponent implements AfterViewInit { private onAutoUpdateInterval = async () => { console.log("Auto update"); const updateCount = await this._addonService.processAutoUpdates(); + + if (updateCount === 0) { + return; + } + + const iconPath = await this._fileService.getAssetFilePath( + "wowup_logo_512np.png" + ); + + this._electronService.showNotification("Auto Updates", { + body: `Automatically updated ${updateCount} addons.`, + icon: iconPath, + }); }; } diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 689c87ae..fb0396fe 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -21,6 +21,7 @@ import { WowUpReleaseChannelType } from "app/models/wowup/wowup-release-channel- import { MatSelectChange } from "@angular/material/select"; import { AnalyticsService } from "app/services/analytics/analytics.service"; import { AddonService } from "app/services/addons/addon.service"; +import { GET_ASSET_FILE_PATH } from "common/constants"; @Component({ selector: "app-options", @@ -44,12 +45,12 @@ export class OptionsComponent implements OnInit, OnChanges { public wowUpReleaseChannels: { type: WowUpReleaseChannelType; name: string; - }[] = getEnumList( - WowUpReleaseChannelType - ).map((type: WowUpReleaseChannelType) => ({ - type, - name: getEnumName(WowUpReleaseChannelType, type), - })); + }[] = getEnumList(WowUpReleaseChannelType).map( + (type: WowUpReleaseChannelType) => ({ + type, + name: getEnumName(WowUpReleaseChannelType, type), + }) + ); constructor( private _addonService: AddonService, diff --git a/wowup-electron/src/app/services/electron/electron.service.ts b/wowup-electron/src/app/services/electron/electron.service.ts index 74c8e828..ceb2b5b0 100644 --- a/wowup-electron/src/app/services/electron/electron.service.ts +++ b/wowup-electron/src/app/services/electron/electron.service.ts @@ -1,14 +1,17 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; +import { v4 as uuidv4 } from "uuid"; // 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, shell } from 'electron'; -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import { BehaviorSubject } from 'rxjs'; +import { ipcRenderer, webFrame, remote, shell } from "electron"; +import * as childProcess from "child_process"; +import * as fs from "fs"; +import { BehaviorSubject } from "rxjs"; +import { ValueResponse } from "common/models/value-response"; +import { ValueRequest } from "common/models/value-request"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class ElectronService { private readonly _windowMaximizedSrc = new BehaviorSubject(false); @@ -31,7 +34,7 @@ export class ElectronService { } get locale(): string { - return this.remote.app.getLocale().split('-')[0]; + return this.remote.app.getLocale().split("-")[0]; } constructor() { @@ -39,27 +42,27 @@ export class ElectronService { if (!this.isElectron) { return; } - this.ipcRenderer = window.require('electron').ipcRenderer; - this.webFrame = window.require('electron').webFrame; - this.remote = window.require('electron').remote; - this.shell = window.require('electron').shell; + this.ipcRenderer = window.require("electron").ipcRenderer; + this.webFrame = window.require("electron").webFrame; + this.remote = window.require("electron").remote; + this.shell = window.require("electron").shell; - this.childProcess = window.require('child_process'); - this.fs = window.require('fs'); + this.childProcess = window.require("child_process"); + this.fs = window.require("fs"); - this.remote.getCurrentWindow().on('minimize', () => { + this.remote.getCurrentWindow().on("minimize", () => { this._windowMinimizedSrc.next(true); }); - this.remote.getCurrentWindow().on('restore', () => { + this.remote.getCurrentWindow().on("restore", () => { this._windowMinimizedSrc.next(false); }); - this.remote.getCurrentWindow().on('maximize', () => { + this.remote.getCurrentWindow().on("maximize", () => { this._windowMaximizedSrc.next(true); }); - this.remote.getCurrentWindow().on('unmaximize', () => { + this.remote.getCurrentWindow().on("unmaximize", () => { this._windowMaximizedSrc.next(false); }); @@ -86,4 +89,31 @@ export class ElectronService { this.remote.getCurrentWindow().close(); this.remote.app.quit(); } + + public showNotification(title: string, options?: NotificationOptions) { + const myNotification = new Notification(title, options); + } + + public sendIpcValueMessage( + channel: string, + value: TIN + ): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: ValueResponse) => { + if (arg.error) { + return reject(arg.error); + } + + resolve(arg.value); + }; + + const request: ValueRequest = { + value, + responseKey: uuidv4(), + }; + + this.ipcRenderer.once(request.responseKey, eventHandler); + this.ipcRenderer.send(channel, request); + }); + } } diff --git a/wowup-electron/src/app/services/files/file.service.ts b/wowup-electron/src/app/services/files/file.service.ts index 49ef0a68..909a696d 100644 --- a/wowup-electron/src/app/services/files/file.service.ts +++ b/wowup-electron/src/app/services/files/file.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@angular/core"; import { COPY_DIRECTORY_CHANNEL, DELETE_DIRECTORY_CHANNEL, + GET_ASSET_FILE_PATH, LIST_DIRECTORIES_CHANNEL, LIST_FILES_CHANNEL, PATH_EXISTS_CHANNEL, @@ -29,6 +30,13 @@ import { ValueResponse } from "common/models/value-response"; export class FileService { constructor(private _electronService: ElectronService) {} + public async getAssetFilePath(fileName: string) { + return await this._electronService.sendIpcValueMessage( + GET_ASSET_FILE_PATH, + fileName + ); + } + public showDirectory(sourceDir: string) { return new Promise((resolve, reject) => { const eventHandler = (_evt: any, arg: boolean) => { diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index fc0daaa2..b98d2094 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -13,3 +13,4 @@ export const CURSE_HASH_FILE_CHANNEL = "curse-hash-file"; export const SHOW_DIRECTORY = "show-directory"; export const CURSE_GET_SCAN_RESULTS = "curse-get-scan-results"; export const WOWUP_GET_SCAN_RESULTS = "wowup-get-scan-results"; +export const GET_ASSET_FILE_PATH = "get-asset-file-path";