116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
import type { BrowserWindow, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron';
|
|
import { promisify } from 'util';
|
|
import type { SessionHelperInterface } from '../session/session-helper-interface';
|
|
import { AppWindow } from './app-window';
|
|
import type { UrlAppWindowInterface } from './url-app-window-interface';
|
|
import { WindowClosedError } from './window-closed-error';
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInterface {
|
|
protected loadOptions: LoadURLOptions;
|
|
|
|
/**
|
|
* 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(
|
|
sessionHelper: SessionHelperInterface,
|
|
uri: string,
|
|
options: BrowserWindowConstructorOptions = {},
|
|
loadOptions: LoadURLOptions = {}
|
|
) {
|
|
super(sessionHelper, 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) => {
|
|
if (!this._window) {
|
|
throw new WindowClosedError();
|
|
}
|
|
this._window.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, options);
|
|
});
|
|
}
|
|
|
|
public async loadUrlSafe(url: string, options?: LoadURLOptions): Promise<void> {
|
|
return this.loadWait.then(async () => {
|
|
let failedLoad = true;
|
|
while (failedLoad) {
|
|
await new Promise<void>((resolve) => {
|
|
if (!this._window) {
|
|
throw new WindowClosedError();
|
|
}
|
|
this._window.webContents.once('did-navigate', (event, navigationUrl, 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);
|
|
}
|
|
resolve();
|
|
});
|
|
void this._window.loadURL(url, options);
|
|
});
|
|
if (failedLoad) {
|
|
await promisify(setTimeout)(this.waitInterval);
|
|
}
|
|
}
|
|
this.loadWait = promisify(setTimeout)(this.loadWaitTime);
|
|
});
|
|
}
|
|
|
|
protected load(window: BrowserWindow): Promise<void> {
|
|
return window.loadURL(this.uri, this.loadOptions);
|
|
}
|
|
}
|