RenaiApp/src/main/modules/app-window/url-app-window.ts

134 lines
4.2 KiB
TypeScript

import type { WebContents, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
import { promisify } from 'util';
import { AppWindow } from './app-window';
import type { UrlAppWindowInterface } from './url-app-window-interface';
import Timeout = NodeJS.Timeout;
export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInterface {
protected loadOptions: LoadURLOptions;
protected readyCheck?: (webContents: WebContents) => Promise<boolean>;
/**
* the wait interval after a failed load to try again
*/
private waitInterval: number = Milliseconds.TWO_HUNDRED;
private loadWaitTime: number = 0;
private loadWaitTimeStep: number = 10;
private loadWaitTimeStepResetTimeout: Timeout;
private loadWaitTimeResetTimeoutTime: number = Milliseconds.ONE_MINUTE;
/**
* when this promise is resolved a safe load is allowed
*
* it resets which each load to resolve again after a wait time
*
* @see loadWaitTime
*/
private loadWait: Promise<void> = Promise.resolve();
protected constructor(
logger: LoggerInterface,
uri: string,
options: BrowserWindowConstructorOptions = {},
loadOptions: LoadURLOptions = {}
) {
super(logger, uri, {
...options,
...{
webPreferences: {
enableRemoteModule: false,
nodeIntegration: false,
contextIsolation: true,
},
},
});
this.loadOptions = loadOptions;
this.loadWaitTimeStepResetTimeout = setTimeout(() => {}, 0);
}
public downloadUrlSafe(url: string, savePath: string, options?: LoadURLOptions): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.getWindow().webContents.session.once('will-download', (event, item) => {
item.setSavePath(savePath);
item.once('done', (doneEvent, state) => {
switch (state) {
case 'completed':
resolve();
break;
case 'cancelled':
case 'interrupted':
default:
reject(new Error(state));
break;
}
});
item.on('updated', () => {
item.resume();
});
});
void this.loadUrlSafe(url, undefined, options);
});
}
public async loadUrlSafe(
url: string,
readyCheck?: (webContents: WebContents) => Promise<boolean>,
options?: LoadURLOptions
): Promise<void> {
return this.loadWait.then(async () => {
let failedLoad = true;
while (failedLoad) {
await this.loadUrl(url, options).then((httpResponseCode) => {
failedLoad = HttpCode.BAD_REQUEST <= httpResponseCode;
if (HttpCode.TOO_MANY_REQUESTS === httpResponseCode) {
// go slower
this.loadWaitTime += this.loadWaitTimeStep;
// but go faster again after a time
clearTimeout(this.loadWaitTimeStepResetTimeout);
this.loadWaitTimeStepResetTimeout = setTimeout(() => {
this.loadWaitTime = 0;
}, this.loadWaitTimeResetTimeoutTime);
}
});
if (failedLoad) {
await promisify(setTimeout)(this.waitInterval);
}
}
this.loadWait = promisify(setTimeout)(this.loadWaitTime);
if (readyCheck) {
let isReady = await readyCheck(this.getWindow().webContents);
do {
await promisify(setTimeout)(Milliseconds.TEN);
isReady = await readyCheck(this.getWindow().webContents);
} while (!isReady);
}
});
}
/**
* This is the method used for loading specific URLs.
* It resolves when the url is loaded, successfully or not-
*
* It is meant to be overridden for site specific logic, e.g. C l o u d f l a r e
*
* @return a Promise of the http status code the url loaded with
*/
protected loadUrl(url: string, options?: LoadURLOptions): Promise<number> {
return new Promise((resolve) => {
this.getWindow().webContents.once('did-navigate', (event, navigationUrl, httpResponseCode) => {
resolve(httpResponseCode);
});
void this.getWindow().loadURL(url, options);
});
}
protected load(): Promise<void> {
return this.loadUrlSafe(this.uri, this.readyCheck, this.loadOptions).then();
}
}