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 9b14329f..279d6742 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -4,7 +4,6 @@ import { AddonSearchResultDependency } from "../models/wowup/addon-search-result import { CurseDependency } from "../../common/curse/curse-dependency"; import { CurseDependencyType } from "../../common/curse/curse-dependency-type"; import * as _ from "lodash"; -import * as CircuitBreaker from "opossum"; import { from, Observable } from "rxjs"; import { first, map, timeout } from "rxjs/operators"; import { v4 as uuidv4 } from "uuid"; @@ -32,17 +31,13 @@ import { ElectronService } from "../services"; import { CachingService } from "../services/caching/caching-service"; import { AddonProvider } from "./addon-provider"; import { AppConfig } from "../../environments/environment"; +import { CircuitBreakerWrapper, NetworkService } from "app/services/network/network.service"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; const CHANGELOG_CACHE_TTL_MS = 30 * 60 * 1000; -const CHANGELOG_FETCH_TIMEOUT_MS = 1500; export class CurseAddonProvider implements AddonProvider { - private readonly _circuitBreaker: CircuitBreaker<[clientType: () => Promise], any>; - - private getCircuitBreaker() { - return this._circuitBreaker as CircuitBreaker<[clientType: () => Promise], T>; - } + private readonly _circuitBreaker: CircuitBreakerWrapper; public readonly name = ADDON_PROVIDER_CURSEFORGE; public readonly forceIgnore = false; @@ -54,22 +49,13 @@ export class CurseAddonProvider implements AddonProvider { constructor( private _httpClient: HttpClient, private _cachingService: CachingService, - private _electronService: ElectronService + private _electronService: ElectronService, + _networkService: NetworkService ) { - this._circuitBreaker = new CircuitBreaker((action) => this.sendRequest(action), { - resetTimeout: 60000, - }); - - this._circuitBreaker.on("open", () => { - console.log(`${this.name} circuit breaker open`); - }); - this._circuitBreaker.on("close", () => { - console.log(`${this.name} circuit breaker close`); - }); + this._circuitBreaker = _networkService.getCircuitBreaker(`${this.name}_main`); } public async getChangelog(clientType: WowClientType, externalId: string, externalReleaseId: string): Promise { - console.debug("GET CHANGE LOG"); const cacheKey = `changelog_${externalId}_${externalReleaseId}`; const cachedChangelog = this._cachingService.get(cacheKey); if (cachedChangelog) { @@ -78,10 +64,7 @@ export class CurseAddonProvider implements AddonProvider { try { const url = new URL(`${API_URL}/addon/${externalId}/file/${externalReleaseId}/changelog`); - const changelogResponse = await this._httpClient - .get(url.toString(), { responseType: "text" }) - .pipe(first(), timeout(CHANGELOG_FETCH_TIMEOUT_MS)) - .toPromise(); + const changelogResponse = await this._circuitBreaker.getText(url); this._cachingService.set(cacheKey, changelogResponse, CHANGELOG_CACHE_TTL_MS); @@ -201,24 +184,14 @@ export class CurseAddonProvider implements AddonProvider { return gameVersionFlavor === this.getGameVersionFlavor(clientType); } - private async getAddonsByFingerprintsW(fingerprints: number[]) { + private getAddonsByFingerprintsW(fingerprints: number[]) { const url = `${AppConfig.wowUpHubUrl}/curseforge/addons/fingerprint`; console.log(`Wowup Fetching fingerprints`, JSON.stringify(fingerprints)); - return await this._httpClient - .post(url, { - fingerprints, - }) - .toPromise(); - - // If CurseForge API is ever fixed, put this back. - // return await this.getCircuitBreaker().fire( - // async () => - // await this._httpClient - // .post(url, fingerprints) - // .toPromise() - // ); + return this._circuitBreaker.postJson(url, { + fingerprints, + }); } private async getAddonsByFingerprints(fingerprints: number[]): Promise { @@ -226,9 +199,7 @@ export class CurseAddonProvider implements AddonProvider { console.log(`Curse Fetching fingerprints`, JSON.stringify(fingerprints)); - return await this.getCircuitBreaker().fire( - async () => await this._httpClient.post(url, fingerprints).toPromise() - ); + return await this._circuitBreaker.postJson(url, fingerprints); } private async getAllIds(addonIds: number[]): Promise { @@ -238,9 +209,7 @@ export class CurseAddonProvider implements AddonProvider { const url = `${API_URL}/addon`; - return await this.getCircuitBreaker().fire( - async () => await this._httpClient.post(url, addonIds).toPromise() - ); + return await this._circuitBreaker.postJson(url, addonIds); } private sendRequest(action: () => Promise): Promise { @@ -354,19 +323,13 @@ export class CurseAddonProvider implements AddonProvider { url.searchParams.set("gameId", "1"); url.searchParams.set("searchFilter", query); - return await this.getCircuitBreaker().fire( - async () => await this._httpClient.get(url.toString()).toPromise() - ); + return await this._circuitBreaker.getJson(url); } getById(addonId: string, clientType: WowClientType): Observable { const url = `${API_URL}/addon/${addonId}`; - return from( - this.getCircuitBreaker().fire( - async () => await this._httpClient.get(url).toPromise() - ) - ).pipe( + return from(this._circuitBreaker.getJson(url)).pipe( map((result) => { if (!result) { return null; @@ -470,9 +433,7 @@ export class CurseAddonProvider implements AddonProvider { updatedCount: 0, }; - const result = await this.getCircuitBreaker().fire( - async () => await this._httpClient.post(url, body).toPromise() - ); + const result = await this._circuitBreaker.postJson(url, body); if (!result) { return []; diff --git a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts index 72544644..992b5e01 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -1,7 +1,6 @@ import { HttpClient } from "@angular/common/http"; import { ADDON_PROVIDER_TUKUI } from "../../common/constants"; import * as _ from "lodash"; -import * as CircuitBreaker from "opossum"; import { from, Observable } from "rxjs"; import { map, switchMap, timeout } from "rxjs/operators"; import { v4 as uuidv4 } from "uuid"; @@ -13,25 +12,24 @@ import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { CachingService } from "../services/caching/caching-service"; -import { NetworkService } from "../services/network/network.service"; +import { CircuitBreakerWrapper, NetworkService } from "../services/network/network.service"; import { AddonProvider } from "./addon-provider"; import { AppConfig } from "../../environments/environment"; const API_URL = "https://www.tukui.org/api.php"; const CLIENT_API_URL = "https://www.tukui.org/client-api.php"; const WOWUP_API_URL = AppConfig.wowUpHubUrl; -const CHANGELOG_FETCH_TIMEOUT_MS = 1500; const CHANGELOG_CACHE_TTL_MS = 30 * 60 * 1000; export class TukUiAddonProvider implements AddonProvider { - private readonly _circuitBreaker: CircuitBreaker<[clientType: WowClientType], TukUiAddon[]>; - private readonly _changelogCircuitBreaker: CircuitBreaker<[clientType: TukUiAddon], string>; + private readonly _circuitBreaker: CircuitBreakerWrapper; public readonly name = ADDON_PROVIDER_TUKUI; public readonly forceIgnore = false; public readonly allowReinstall = true; public readonly allowChannelChange = false; public readonly allowEdit = true; + public enabled = true; constructor( @@ -39,20 +37,13 @@ export class TukUiAddonProvider implements AddonProvider { private _cachingService: CachingService, private _networkService: NetworkService ) { - this._circuitBreaker = this._networkService.getCircuitBreaker<[clientType: WowClientType], TukUiAddon[]>( - `${this.name}_main`, - this.fetchApiResults - ); - - this._changelogCircuitBreaker = this._networkService.getCircuitBreaker<[clientType: TukUiAddon], string>( - `${this.name}_changelog`, - this.fetchChangelogHtml - ); + this._circuitBreaker = this._networkService.getCircuitBreaker(`${this.name}_main`); } public async getChangelog(clientType: WowClientType, externalId: string, externalReleaseId: string): Promise { const addons = await this.getAllAddons(clientType); - return _.find(addons, (addon) => addon.id.toString() === externalId)?.changelog; + const addon = _.find(addons, (addon) => addon.id.toString() === externalId.toString()); + return await this.formatChangelog(addon); } async getAll(clientType: WowClientType, addonIds: string[]): Promise { @@ -182,7 +173,7 @@ export class TukUiAddonProvider implements AddonProvider { private async formatChangelog(addon: TukUiAddon) { if (["-1", "-2"].includes(addon.id.toString())) { try { - return await this._changelogCircuitBreaker.fire(addon); + return await this.fetchChangelogHtml(addon); } catch (e) { console.error("Failed to get changelog", e); } @@ -198,10 +189,7 @@ export class TukUiAddonProvider implements AddonProvider { return cachedChangelog; } - const html = await this._httpClient - .get(addon.changelog, { responseType: "text" }) - .pipe(timeout(CHANGELOG_FETCH_TIMEOUT_MS)) - .toPromise(); + const html = await this._circuitBreaker.getText(addon.changelog); this._cachingService.set(cacheKey, html, CHANGELOG_CACHE_TTL_MS); @@ -253,7 +241,7 @@ export class TukUiAddonProvider implements AddonProvider { } try { - const addons = await this._circuitBreaker.fire(clientType); + const addons = await this.fetchApiResults(clientType); this._cachingService.set(cacheKey, addons); return addons; @@ -278,10 +266,11 @@ export class TukUiAddonProvider implements AddonProvider { const url = new URL(API_URL); url.searchParams.append(query, "all"); - const addons = await this._httpClient.get(url.toString()).toPromise(); + const addons = await this._circuitBreaker.getJson(url); + if (this.isRetail(clientType)) { - addons.push(await this.getTukUiRetailAddon().toPromise()); - addons.push(await this.getElvUiRetailAddon().toPromise()); + addons.push(await this.getTukUiRetailAddon()); + addons.push(await this.getElvUiRetailAddon()); } return addons; @@ -295,11 +284,11 @@ export class TukUiAddonProvider implements AddonProvider { return this.getClientApiAddon("elvui"); } - private getClientApiAddon(addonName: string): Observable { + private getClientApiAddon(addonName: string): Promise { const url = new URL(CLIENT_API_URL); url.searchParams.append("ui", addonName); - return this._httpClient.get(url.toString()); + return this._circuitBreaker.getJson(url); } private isRetail(clientType: WowClientType) { diff --git a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts index b522bc6e..a2f59981 100644 --- a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts @@ -1,9 +1,8 @@ import { HttpClient } from "@angular/common/http"; import { ADDON_PROVIDER_WOWINTERFACE } from "../../common/constants"; import * as _ from "lodash"; -import * as CircuitBreaker from "opossum"; import { from, Observable } from "rxjs"; -import { first, map, timeout } from "rxjs/operators"; +import { map } from "rxjs/operators"; import { v4 as uuidv4 } from "uuid"; import { Addon } from "../entities/addon"; import { WowClientType } from "../models/warcraft/wow-client-type"; @@ -12,18 +11,16 @@ import { AddonChannelType } from "../models/wowup/addon-channel-type"; import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; -import { ElectronService } from "../services"; import { CachingService } from "../services/caching/caching-service"; -import { FileService } from "../services/files/file.service"; import { AddonProvider } from "./addon-provider"; import { convertBbcode } from "../utils/bbcode.utils"; +import { CircuitBreakerWrapper, NetworkService } from "app/services/network/network.service"; const API_URL = "https://api.mmoui.com/v4/game/WOW"; const ADDON_URL = "https://www.wowinterface.com/downloads/info"; -const CHANGELOG_FETCH_TIMEOUT_MS = 1500; export class WowInterfaceAddonProvider implements AddonProvider { - private readonly _circuitBreaker: CircuitBreaker<[addonId: string], AddonDetailsResponse>; + private readonly _circuitBreaker: CircuitBreakerWrapper; public readonly name = ADDON_PROVIDER_WOWINTERFACE; public readonly forceIgnore = false; @@ -35,19 +32,9 @@ export class WowInterfaceAddonProvider implements AddonProvider { constructor( private _httpClient: HttpClient, private _cachingService: CachingService, - private _electronService: ElectronService, - private _fileService: FileService + private _networkService: NetworkService ) { - this._circuitBreaker = new CircuitBreaker(this.getAddonDetails, { - resetTimeout: 60000, - }); - - this._circuitBreaker.on("open", () => { - console.log(`${this.name} circuit breaker open`); - }); - this._circuitBreaker.on("close", () => { - console.log(`${this.name} circuit breaker close`); - }); + this._circuitBreaker = this._networkService.getCircuitBreaker(`${this.name}_main`); } async getAll(clientType: WowClientType, addonIds: string[]): Promise { @@ -84,7 +71,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { throw new Error(`Addon ID not found ${addonUri}`); } - var addon = await this._circuitBreaker.fire(addonId); + var addon = await this.getAddonDetails(addonId); if (addon == null) { throw new Error(`Bad addon api response ${addonUri}`); } @@ -102,7 +89,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { } public getById(addonId: string, clientType: WowClientType): Observable { - return from(this._circuitBreaker.fire(addonId)).pipe( + return from(this.getAddonDetails(addonId)).pipe( map((result) => (result ? this.toAddonSearchResult(result, "") : undefined)) ); } @@ -129,7 +116,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { continue; } - const details = await this._circuitBreaker.fire(addonFolder.toc.wowInterfaceId); + const details = await this.getAddonDetails(addonFolder.toc.wowInterfaceId); addonFolder.matchingAddon = this.toAddon(details, clientType, addonChannelType, addonFolder); } @@ -152,17 +139,11 @@ export class WowInterfaceAddonProvider implements AddonProvider { throw new Error(`Unhandled URL: ${addonUri}`); } - private getAddonDetails = (addonId: string): Promise => { + private getAddonDetails = async (addonId: string): Promise => { const url = new URL(`${API_URL}/filedetails/${addonId}.json`); - return this._httpClient - .get(url.toString()) - .pipe( - first(), - timeout(CHANGELOG_FETCH_TIMEOUT_MS), - map((responses) => _.first(responses)) - ) - .toPromise(); + const responses = await this._circuitBreaker.getJson(url); + return _.first(responses); }; private getThumbnailUrl(response: AddonDetailsResponse) { diff --git a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts index 96446832..b3ed682d 100644 --- a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts @@ -1,5 +1,5 @@ import { HttpClient } from "@angular/common/http"; -import { Observable, of } from "rxjs"; +import { from, Observable } from "rxjs"; import { v4 as uuidv4 } from "uuid"; import { ADDON_PROVIDER_HUB, WOWUP_GET_SCAN_RESULTS } from "../../common/constants"; import { WowUpScanResult } from "../../common/wowup/wowup-scan-result"; @@ -25,6 +25,7 @@ import * as _ from "lodash"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { getGameVersion } from "../utils/addon.utils"; import { map } from "rxjs/operators"; +import { CircuitBreakerWrapper, NetworkService } from "app/services/network/network.service"; const API_URL = AppConfig.wowUpHubUrl; @@ -33,6 +34,8 @@ export interface GetAddonBatchResponse { } export class WowUpAddonProvider implements AddonProvider { + private readonly _circuitBreaker: CircuitBreakerWrapper; + public readonly name = ADDON_PROVIDER_HUB; public readonly forceIgnore = false; public readonly allowReinstall = true; @@ -40,16 +43,26 @@ export class WowUpAddonProvider implements AddonProvider { public readonly allowEdit = true; public enabled = true; - constructor(private _httpClient: HttpClient, private _electronService: ElectronService) {} + constructor( + private _httpClient: HttpClient, + private _electronService: ElectronService, + _networkService: NetworkService + ) { + this._circuitBreaker = _networkService.getCircuitBreaker( + `${this.name}_main`, + AppConfig.defaultHttpResetTimeoutMs, + AppConfig.wowUpHubHttpTimeoutMs + ); + } async getAll(clientType: WowClientType, addonIds: string[]): Promise { const gameType = this.getWowGameType(clientType); const url = new URL(`${API_URL}/addons/batch/${gameType}`); - const response = await this._httpClient - .post(url.toString(), { - addonIds: _.map(addonIds, (id) => parseInt(id, 10)), - }) - .toPromise(); + const addonIdList = _.map(addonIds, (id) => parseInt(id, 10)); + + const response = await this._circuitBreaker.postJson(url, { + addonIds: addonIdList, + }); const searchResults = _.map(response?.addons, (addon) => this.getSearchResult(addon)); return searchResults; @@ -58,7 +71,7 @@ export class WowUpAddonProvider implements AddonProvider { public async getFeaturedAddons(clientType: WowClientType): Promise { const gameType = this.getWowGameType(clientType); const url = new URL(`${API_URL}/addons/featured/${gameType}?count=30`); - const addons = await this._httpClient.get(url.toString()).toPromise(); + const addons = await this._circuitBreaker.getJson(url); const searchResults = _.map(addons?.addons, (addon) => this.getSearchResult(addon)); return searchResults; @@ -68,7 +81,7 @@ export class WowUpAddonProvider implements AddonProvider { const gameType = this.getWowGameType(clientType); const url = new URL(`${API_URL}/addons/search/${gameType}?query=${query}&limit=10`); - const addons = await this._httpClient.get(url.toString()).toPromise(); + const addons = await this._circuitBreaker.getJson(url); const searchResults = _.map(addons?.addons, (addon) => this.getSearchResult(addon)); return searchResults; @@ -91,7 +104,7 @@ export class WowUpAddonProvider implements AddonProvider { getById(addonId: string, clientType: WowClientType): Observable { const url = new URL(`${API_URL}/addons/${addonId}`); - return this._httpClient.get(url.toString()).pipe( + return from(this._circuitBreaker.getJson(url)).pipe( map((result) => { return this.getSearchResult(result.addon); }) @@ -120,7 +133,7 @@ export class WowUpAddonProvider implements AddonProvider { console.debug("ScanResults", scanResults.length); const fingerprints = scanResults.map((result) => result.fingerprint); console.log("fingerprintRequest", JSON.stringify(fingerprints)); - const fingerprintResponse = await this.getAddonsByFingerprints(fingerprints).toPromise(); + const fingerprintResponse = await this.getAddonsByFingerprints(fingerprints); console.debug("fingerprintResponse", fingerprintResponse); @@ -180,10 +193,10 @@ export class WowUpAddonProvider implements AddonProvider { return release.game_type === this.getWowGameType(clientType); } - private getAddonsByFingerprints(fingerprints: string[]): Observable { + private getAddonsByFingerprints(fingerprints: string[]): Promise { const url = `${API_URL}/addons/fingerprint`; - return this._httpClient.post(url, { + return this._circuitBreaker.postJson(url, { fingerprints, }); } diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts index 9a66c488..c5c1d9e0 100644 --- a/wowup-electron/src/app/services/addons/addon.provider.factory.ts +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -10,7 +10,6 @@ import { RaiderIoAddonProvider } from "../../addon-providers/raiderio-provider"; import { CachingService } from "../caching/caching-service"; import { ElectronService } from "../electron/electron.service"; import { WowUpService } from "../wowup/wowup.service"; -import { FileService } from "../files/file.service"; import { NetworkService } from "../network/network.service"; @Injectable({ @@ -23,7 +22,6 @@ export class AddonProviderFactory { private _cachingService: CachingService, private _electronService: ElectronService, private _httpClient: HttpClient, - private _fileService: FileService, private _wowupService: WowUpService, private _networkService: NetworkService ) {} @@ -33,7 +31,7 @@ export class AddonProviderFactory { } public createCurseAddonProvider(): CurseAddonProvider { - return new CurseAddonProvider(this._httpClient, this._cachingService, this._electronService); + return new CurseAddonProvider(this._httpClient, this._cachingService, this._electronService, this._networkService); } public createTukUiAddonProvider(): TukUiAddonProvider { @@ -41,12 +39,7 @@ export class AddonProviderFactory { } public createWowInterfaceAddonProvider(): WowInterfaceAddonProvider { - return new WowInterfaceAddonProvider( - this._httpClient, - this._cachingService, - this._electronService, - this._fileService - ); + return new WowInterfaceAddonProvider(this._httpClient, this._cachingService, this._networkService); } public createGitHubAddonProvider(): GitHubAddonProvider { @@ -54,7 +47,7 @@ export class AddonProviderFactory { } public createWowUpAddonProvider(): WowUpAddonProvider { - return new WowUpAddonProvider(this._httpClient, this._electronService); + return new WowUpAddonProvider(this._httpClient, this._electronService, this._networkService); } public getAll(): AddonProvider[] { diff --git a/wowup-electron/src/app/services/network/network.service.ts b/wowup-electron/src/app/services/network/network.service.ts index a556f441..15072e07 100644 --- a/wowup-electron/src/app/services/network/network.service.ts +++ b/wowup-electron/src/app/services/network/network.service.ts @@ -1,12 +1,75 @@ +import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; +import { AppConfig } from "environments/environment"; import * as CircuitBreaker from "opossum"; import { Subject } from "rxjs"; +import { first, tap, timeout } from "rxjs/operators"; export interface CircuitBreakerChangeEvent { state: "open" | "closed"; } -const DEFAULT_RESET_TIMEOUT_MS = 60 * 1000; +export class CircuitBreakerWrapper { + private readonly _name: string; + private readonly _cb: CircuitBreaker; + private readonly _httpClient: HttpClient; + private readonly _defaultTimeoutMs: number; + + constructor( + name: string, + httpClient: HttpClient, + resetTimeoutMs = AppConfig.defaultHttpResetTimeoutMs, + httpTimeoutMs = AppConfig.defaultHttpTimeoutMs + ) { + this._name = name; + this._httpClient = httpClient; + this._defaultTimeoutMs = httpTimeoutMs; + this._cb = new CircuitBreaker(this.internalAction, { + resetTimeout: resetTimeoutMs, + }); + this._cb.on("open", () => { + console.log(`${name} circuit breaker open`); + }); + this._cb.on("close", () => { + console.log(`${name} circuit breaker close`); + }); + } + + public async fire(action: () => Promise): Promise { + return (await this._cb.fire(action)) as TOUT; + } + + public getJson(url: URL | string, timeoutMs?: number): Promise { + return this.fire(() => + this._httpClient + .get(url.toString()) + .pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs)) + .toPromise() + ); + } + + public getText(url: URL | string, timeoutMs?: number): Promise { + return this.fire(() => + this._httpClient + .get(url.toString(), { responseType: "text" }) + .pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs)) + .toPromise() + ); + } + + public postJson(url: URL | string, body: any, timeoutMs?: number): Promise { + return this.fire(() => + this._httpClient + .post(url.toString(), body) + .pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs)) + .toPromise() + ); + } + + private internalAction = (action: () => Promise) => { + return action?.call(this); + }; +} @Injectable({ providedIn: "root", @@ -16,21 +79,13 @@ export class NetworkService { public breakerChanged$ = this._breakerChangedSrc.asObservable(); - public getCircuitBreaker( - name: string, - action: (...args: TI) => Promise, - resetTimeoutMs: number = DEFAULT_RESET_TIMEOUT_MS - ): CircuitBreaker { - const cb = new CircuitBreaker(action, { - resetTimeout: resetTimeoutMs, - }); - cb.on("open", () => { - console.log(`${name} circuit breaker open`); - }); - cb.on("close", () => { - console.log(`${name} circuit breaker close`); - }); + public constructor(private _httpClient: HttpClient) {} - return cb; + public getCircuitBreaker( + name: string, + resetTimeoutMs: number = AppConfig.defaultHttpResetTimeoutMs, + httpTimeoutMs: number = AppConfig.defaultHttpTimeoutMs + ): CircuitBreakerWrapper { + return new CircuitBreakerWrapper(name, this._httpClient, resetTimeoutMs, httpTimeoutMs); } } diff --git a/wowup-electron/src/common/curse/curse-folder-scanner.ts b/wowup-electron/src/common/curse/curse-folder-scanner.ts index f289d92d..a8245f1b 100644 --- a/wowup-electron/src/common/curse/curse-folder-scanner.ts +++ b/wowup-electron/src/common/curse/curse-folder-scanner.ts @@ -133,7 +133,6 @@ export class CurseFolderScanner { try { nativePath = this.getRealPath(fileInfo); } catch (e) { - log.debug(`Include file path does not exist: ${fileInfo}`); return; } diff --git a/wowup-electron/src/common/wowup/wowup-folder-scanner.ts b/wowup-electron/src/common/wowup/wowup-folder-scanner.ts index c58d1561..2246fb95 100644 --- a/wowup-electron/src/common/wowup/wowup-folder-scanner.ts +++ b/wowup-electron/src/common/wowup/wowup-folder-scanner.ts @@ -136,7 +136,6 @@ export class WowUpFolderScanner { try { nativePath = this.getRealPath(fileInfo); } catch (e) { - log.debug(`Include file path does not exist: ${fileInfo}`); return; } diff --git a/wowup-electron/src/environments/environment.prod.ts b/wowup-electron/src/environments/environment.prod.ts index a6c3040d..030758d4 100644 --- a/wowup-electron/src/environments/environment.prod.ts +++ b/wowup-electron/src/environments/environment.prod.ts @@ -10,4 +10,7 @@ export const AppConfig = { }, autoUpdateIntervalMs: 3600000, // 1 hour appUpdateIntervalMs: 3600000, // 1 hour + defaultHttpTimeoutMs: 2000, + defaultHttpResetTimeoutMs: 60000, + wowUpHubHttpTimeoutMs: 10000, }; diff --git a/wowup-electron/src/environments/environment.ts b/wowup-electron/src/environments/environment.ts index 90f95df1..4bfab2d6 100644 --- a/wowup-electron/src/environments/environment.ts +++ b/wowup-electron/src/environments/environment.ts @@ -10,4 +10,7 @@ export const AppConfig = { }, autoUpdateIntervalMs: 3600000, // 1 hour appUpdateIntervalMs: 3600000, // 1 hour + defaultHttpTimeoutMs: 2000, + defaultHttpResetTimeoutMs: 60000, + wowUpHubHttpTimeoutMs: 10000, };