Electron app loading improvements + Windows paths fix (#429)
- Electron no longer allows Windows paths in URLs (https://www.electronjs.org/docs/latest/breaking-changes#behavior-changed-custom-protocol-url-handling-on-windows), fixes with help of wabac.js to replace `C:\path\to\file` with `C//path/to/file` - Electron app cleanup: replace deprecated protocol handlers with new protocol.handle() method, update types - Electron app: ensure just filename is displayed on index page, consistent with other platforms - Update to wabac.js 2.23.2 - Update to Electron 36.3.2 - Fixes #427 - bump to 2.3.12
This commit is contained in:
@@ -639,13 +639,6 @@ export class ReplayWebApp extends LitElement {
|
||||
if (this.pageParams.get("swName")) {
|
||||
this.swName = this.pageParams.get("swName") || undefined;
|
||||
}
|
||||
|
||||
if (IS_APP && this.sourceUrl.startsWith("file://")) {
|
||||
this.loadInfo = {
|
||||
sourceUrl: this.sourceUrl,
|
||||
loadUrl: this.sourceUrl.replace("file://", "file2://"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
|
||||
|
||||
+18
-10
@@ -5,12 +5,15 @@ import fasUpload from "@fortawesome/fontawesome-free/svgs/solid/upload.svg";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
export interface FileWithPath extends File {
|
||||
path: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
declare let window: Window & {
|
||||
electron?: {
|
||||
getPath: (file: File) => string;
|
||||
getPaths: (file: File) => {
|
||||
loadUrl: string;
|
||||
sourceUrl: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -94,12 +97,13 @@ export class Chooser extends LitElement {
|
||||
|
||||
setFile(file: FileWithPath) {
|
||||
this.file = file;
|
||||
// file.path only available in electron app
|
||||
if (IS_APP && window.electron?.getPath) {
|
||||
this.file.path = window.electron.getPath(this.file);
|
||||
if (IS_APP && window.electron?.getPaths) {
|
||||
const { loadUrl, sourceUrl } = window.electron.getPaths(this.file);
|
||||
this.file.path = loadUrl;
|
||||
this.fileDisplayName = sourceUrl;
|
||||
} else {
|
||||
this.fileDisplayName = "file://" + file.name;
|
||||
}
|
||||
|
||||
this.fileDisplayName = "file://" + (file.path || file.name);
|
||||
}
|
||||
|
||||
async onChooseNativeFile() {
|
||||
@@ -149,9 +153,11 @@ export class Chooser extends LitElement {
|
||||
|
||||
if (this.file) {
|
||||
loadInfo.isFile = true;
|
||||
// file.path only available in electron app
|
||||
// should only be set in Electron app
|
||||
if (this.file.path) {
|
||||
loadInfo.loadUrl = "file2://" + this.file.path;
|
||||
loadInfo.loadUrl = this.file.path;
|
||||
loadInfo.sourceUrl = this.fileDisplayName;
|
||||
loadInfo.name = this.file.name;
|
||||
loadInfo.noCache = true;
|
||||
} else if (this.fileHandle) {
|
||||
loadInfo.loadUrl = this.fileDisplayName;
|
||||
@@ -163,7 +169,9 @@ export class Chooser extends LitElement {
|
||||
loadInfo.noCache = false;
|
||||
}
|
||||
loadInfo.size = this.file.size;
|
||||
loadInfo.name = this.fileDisplayName;
|
||||
if (!loadInfo.name) {
|
||||
loadInfo.name = this.fileDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
|
||||
+25
-3
@@ -3,12 +3,34 @@
|
||||
import { CollectionLoader } from "@webrecorder/wabac/swlib";
|
||||
import { type IpcRendererEvent } from "electron";
|
||||
|
||||
const { ipcRenderer, contextBridge, webUtils } = require("electron");
|
||||
import { sep as pathSep } from "node:path";
|
||||
|
||||
import { ipcRenderer, contextBridge, webUtils } from "electron";
|
||||
|
||||
const FILE_SCHEME = "file://";
|
||||
const CUSTOM_FILE_SCHEME = "file2://";
|
||||
|
||||
function rewriteLoadUrl(loadUrl: string) {
|
||||
if (pathSep !== "/") {
|
||||
// for windows, will replace such that C:\path\to\file becomes C//path/to/file
|
||||
loadUrl = loadUrl.replaceAll(pathSep, "/").replace(":", "/");
|
||||
}
|
||||
return CUSTOM_FILE_SCHEME + loadUrl;
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld("electron", {
|
||||
IS_APP: true,
|
||||
getPath(file: File) {
|
||||
return webUtils.getPathForFile(file);
|
||||
getPaths(file: File) {
|
||||
const url = webUtils.getPathForFile(file);
|
||||
const sourceUrl = FILE_SCHEME + url;
|
||||
const loadUrl = rewriteLoadUrl(url);
|
||||
return { loadUrl, sourceUrl };
|
||||
},
|
||||
getFileLoadUrl(sourceUrl: string) {
|
||||
if (!sourceUrl.startsWith(FILE_SCHEME)) {
|
||||
return sourceUrl;
|
||||
}
|
||||
return rewriteLoadUrl(sourceUrl.slice(FILE_SCHEME.length));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+150
-219
@@ -1,8 +1,5 @@
|
||||
/* eslint-env node */
|
||||
|
||||
import fetch from "node-fetch";
|
||||
import { Headers } from "node-fetch";
|
||||
|
||||
import {
|
||||
app,
|
||||
session,
|
||||
@@ -16,21 +13,20 @@ import {
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
import { ArchiveResponse, Rewriter } from "@webrecorder/wabac";
|
||||
import { ArchiveRequest, ArchiveResponse, Rewriter } from "@webrecorder/wabac";
|
||||
|
||||
import { PassThrough, Readable } from "stream";
|
||||
import { Readable } from "stream";
|
||||
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import log from "electron-log";
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7016 - Could not find a declaration file for module 'mime-types'. 'node_modules/mime-types/index.js' implicitly has an 'any' type.
|
||||
import mime from "mime-types";
|
||||
import url from "url";
|
||||
|
||||
// @ts-expect-error - TS2322 - Type 'typeof Headers' is not assignable to type '{ new (init?: HeadersInit | undefined): Headers; prototype: Headers; }'.
|
||||
global.Headers = Headers;
|
||||
// @ts-expect-error - TS2322 - Type '(url: RequestInfo, init?: RequestInit | undefined) => Promise<Response>' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'.
|
||||
global.fetch = fetch;
|
||||
import minimist from "minimist";
|
||||
|
||||
//global.Headers = Headers;
|
||||
//global.fetch = fetch;
|
||||
|
||||
const STATIC_PREFIX = "http://localhost:5471/";
|
||||
|
||||
@@ -95,7 +91,7 @@ class ElectronReplayApp {
|
||||
);
|
||||
app.quit();
|
||||
} else {
|
||||
app.on("second-instance", (event, commandLine /*, workingDir*/) => {
|
||||
app.on("second-instance", (_, commandLine) => {
|
||||
// Just create a new window in case of second instance request
|
||||
this.createMainWindow(commandLine);
|
||||
});
|
||||
@@ -147,9 +143,7 @@ class ElectronReplayApp {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
app.whenReady().then(() => this.onAppReady());
|
||||
void app.whenReady().then(() => this.onAppReady());
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on("window-all-closed", function () {
|
||||
@@ -164,9 +158,7 @@ class ElectronReplayApp {
|
||||
autoUpdater.logger = log;
|
||||
// @ts-expect-error - TS2339 - Property 'transports' does not exist on type 'Logger'.
|
||||
autoUpdater.logger.transports.file.level = "info";
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
void autoUpdater.checkForUpdatesAndNotify();
|
||||
}
|
||||
|
||||
onAppReady() {
|
||||
@@ -178,17 +170,13 @@ class ElectronReplayApp {
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
// load docs in native browser for now
|
||||
if (url === STATIC_PREFIX + "docs") {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
shell.openExternal("https://replayweb.page/docs/");
|
||||
void shell.openExternal("https://replayweb.page/docs/");
|
||||
return { action: "deny" };
|
||||
}
|
||||
|
||||
// load external URLs in native browser
|
||||
if (!url.startsWith(STATIC_PREFIX)) {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
}
|
||||
|
||||
@@ -198,16 +186,12 @@ class ElectronReplayApp {
|
||||
|
||||
const sesh = session.defaultSession;
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
sesh.protocol.interceptStreamProtocol("http", (request, callback) =>
|
||||
this.doIntercept(request, callback),
|
||||
sesh.protocol.handle("http", async (request: Request) =>
|
||||
this.doIntercept(request),
|
||||
);
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
protocol.registerStreamProtocol(FILE_PROTO, (request, callback) =>
|
||||
this.doHandleFile(request, callback),
|
||||
protocol.handle(FILE_PROTO, async (request: Request) =>
|
||||
this.doHandleFile(request),
|
||||
);
|
||||
|
||||
this.origUA = sesh.getUserAgent();
|
||||
@@ -215,69 +199,49 @@ class ElectronReplayApp {
|
||||
this.mainWindow = this.createMainWindow(process.argv);
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'request' implicitly has an 'any' type. | TS7006 - Parameter 'callback' implicitly has an 'any' type.
|
||||
async doHandleFile(request, callback) {
|
||||
async doHandleFile(request: Request): Promise<Response> {
|
||||
//const parsedUrl = new URL(request.url);
|
||||
//const filename = parsedUrl.searchParams.get("filename");
|
||||
|
||||
if (request.url === FILE_PROTO + "://localhost") {
|
||||
callback({ statusCode: 200, data: null });
|
||||
return;
|
||||
return new Response("", { status: 200 });
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const filename = url.fileURLToPath(request.url.replace(FILE_PROTO, "file"));
|
||||
let urlStr = request.url.replace(FILE_PROTO, "file");
|
||||
if (path.sep !== "/") {
|
||||
urlStr = urlStr.replace(/(\/\/\w)(\/\/)/, "$1:/");
|
||||
urlStr = urlStr.replaceAll("/", path.sep);
|
||||
}
|
||||
|
||||
const headers = { "Content-Type": "application/octet-stream" };
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const filename = url.fileURLToPath(urlStr);
|
||||
|
||||
const headers = new Headers({ "Content-Type": "application/octet-stream" });
|
||||
const reqHeaders = new Headers(request.headers);
|
||||
|
||||
if (filename) {
|
||||
const stat = await fs.promises.lstat(filename);
|
||||
|
||||
if (!stat.isFile()) {
|
||||
return this.notFound(filename, callback);
|
||||
return this.notFound(filename);
|
||||
}
|
||||
|
||||
const size = stat.size;
|
||||
|
||||
const { statusCode, start, end } = this.parseRange(
|
||||
reqHeaders,
|
||||
headers,
|
||||
size,
|
||||
);
|
||||
const { status, start, end } = this.parseRange(reqHeaders, headers, size);
|
||||
|
||||
const data =
|
||||
request.method === "HEAD"
|
||||
? null
|
||||
: fs.createReadStream(filename, { start, end });
|
||||
: Readable.toWeb(fs.createReadStream(filename, { start, end }));
|
||||
|
||||
callback({ statusCode, headers, data });
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
return new Response(data as any, { status, headers });
|
||||
} else {
|
||||
return this.notFound("No Resource Specified", callback);
|
||||
return this.notFound("No Resource Specified");
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'data' implicitly has an 'any' type.
|
||||
_bufferToStream(data) {
|
||||
const rv = new PassThrough();
|
||||
rv.push(data);
|
||||
rv.push(null);
|
||||
return rv;
|
||||
}
|
||||
|
||||
async doIntercept(
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'request' implicitly has an 'any' type.
|
||||
request,
|
||||
callback: (response: {
|
||||
statusCode: number;
|
||||
headers: Record<string, string>;
|
||||
data: fs.ReadStream;
|
||||
}) => void,
|
||||
) {
|
||||
async doIntercept(request: Request): Promise<Response> {
|
||||
console.log(`${request.method} ${request.url} from ${request.referrer}`);
|
||||
|
||||
// if local server
|
||||
@@ -289,8 +253,8 @@ class ElectronReplayApp {
|
||||
this.proxyColl = m[1];
|
||||
this.proxyTS = m[2];
|
||||
|
||||
request.url = m[3];
|
||||
return await this.resolveArchiveResponse(request, callback);
|
||||
//request.url = m[3];
|
||||
return await this.resolveArchiveResponse(request, m[3]);
|
||||
}
|
||||
} else {
|
||||
// try serve static file from app dir
|
||||
@@ -303,8 +267,6 @@ class ElectronReplayApp {
|
||||
filename = "docs/index.html";
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
let ext = path.extname(filename);
|
||||
if (!ext) {
|
||||
ext = ".html";
|
||||
@@ -314,157 +276,139 @@ class ElectronReplayApp {
|
||||
const mimeType = mime.contentType(ext);
|
||||
|
||||
if (mimeType) {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const fullPath = path.join(this.staticContentPath, filename);
|
||||
|
||||
console.log("fullPath: " + fullPath);
|
||||
|
||||
const data = fs.createReadStream(fullPath);
|
||||
|
||||
return callback({
|
||||
statusCode: 200,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
return new Response(Readable.toWeb(data) as any, {
|
||||
headers: { "content-type": mimeType },
|
||||
data,
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this.notFound(request.url, callback);
|
||||
return this.notFound(request.url);
|
||||
}
|
||||
|
||||
// possible 'live leak' attempt, return archived version, if any
|
||||
if (request.referrer?.startsWith(REPLAY_PREFIX)) {
|
||||
return await this.resolveArchiveResponse(request, callback);
|
||||
if (request.referrer.startsWith(REPLAY_PREFIX)) {
|
||||
return await this.resolveArchiveResponse(request);
|
||||
}
|
||||
|
||||
await this.proxyLive(request, callback);
|
||||
return await this.proxyLive(request);
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'request' implicitly has an 'any' type. | TS7006 - Parameter 'callback' implicitly has an 'any' type.
|
||||
async proxyLive(request, callback) {
|
||||
async proxyLive(request: Request): Promise<Response> {
|
||||
let headers = request.headers;
|
||||
const { method, url, uploadData } = request;
|
||||
const { method, url, body } = request;
|
||||
|
||||
const body = uploadData
|
||||
? Readable.from(readBody(uploadData, session.defaultSession))
|
||||
: null;
|
||||
// const body = uploadData
|
||||
// ? Readable.from(readBody(uploadData, session.defaultSession))
|
||||
// : null;
|
||||
|
||||
if (this.origUA) {
|
||||
// pass UA if origUA is set
|
||||
headers["User-Agent"] = this.origUA;
|
||||
headers = new Headers(headers);
|
||||
headers.set("User-Agent", this.origUA);
|
||||
}
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
response = await fetch(url, { method, headers, body });
|
||||
} catch (e) {
|
||||
console.warn("fetch failed for: " + url);
|
||||
callback({ statusCode: 502, headers: {}, data: null });
|
||||
return;
|
||||
return new Response("", { status: 502 });
|
||||
}
|
||||
const data = method === "HEAD" ? null : response.body;
|
||||
const statusCode = response.status;
|
||||
const status = response.status;
|
||||
|
||||
headers = Object.fromEntries(response.headers.entries());
|
||||
callback({ statusCode, headers, data });
|
||||
return new Response(data, { status, headers: response.headers });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {(props: {
|
||||
* statusCode: number;
|
||||
* headers: Record<string, string>;
|
||||
* data: unknown;
|
||||
* }) => void} callback
|
||||
*/
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'url' implicitly has an 'any' type. | TS7006 - Parameter 'callback' implicitly has an 'any' type.
|
||||
notFound(url, callback) {
|
||||
notFound(url: string) {
|
||||
console.log("not found: " + url);
|
||||
const data = this._bufferToStream(
|
||||
const headers = { "Content-Type": 'text/html; charset="utf-8"' };
|
||||
return new Response(
|
||||
`Sorry, the url <b>${url}</b> could not be found in this archive.`,
|
||||
{ status: 404, headers },
|
||||
);
|
||||
callback({
|
||||
statusCode: 404,
|
||||
headers: { "Content-Type": 'text/html; charset="utf-8"' },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'request' implicitly has an 'any' type. | TS7006 - Parameter 'callback' implicitly has an 'any' type.
|
||||
async resolveArchiveResponse(request, callback) {
|
||||
const channel = `req:${new Date().getTime()}:${request.url}`;
|
||||
async resolveArchiveResponse(
|
||||
request: Request,
|
||||
urlOverride?: string,
|
||||
): Promise<Response> {
|
||||
const url = urlOverride || request.url;
|
||||
const channel = `req:${new Date().getTime()}:${url}`;
|
||||
|
||||
ipcMain.once(channel, async (event, status, headers, payload) => {
|
||||
const url = request.url;
|
||||
let resolve: (r: Response) => void;
|
||||
|
||||
if (status === 404 && !payload) {
|
||||
return this.notFound(url, callback);
|
||||
} else {
|
||||
console.log("got response for: " + url);
|
||||
}
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
headers = new Headers(headers);
|
||||
const date = new Date();
|
||||
|
||||
let response: ArchiveResponse = new ArchiveResponse({
|
||||
const p = new Promise<Response>((r) => (resolve = r));
|
||||
ipcMain.once(
|
||||
channel,
|
||||
async (
|
||||
event,
|
||||
status: number,
|
||||
reqHeaders: Record<string, string>,
|
||||
payload,
|
||||
headers,
|
||||
status,
|
||||
date,
|
||||
url,
|
||||
});
|
||||
|
||||
const rewriter = new Rewriter({
|
||||
baseUrl: url,
|
||||
prefix: "",
|
||||
urlRewrite: false,
|
||||
contentRewrite: true,
|
||||
decode: true,
|
||||
useBaseRules: true,
|
||||
});
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
request.headers = new Headers(request.headers);
|
||||
|
||||
try {
|
||||
response = await rewriter.rewrite(response, request);
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
headers = Object.fromEntries(response.headers.entries());
|
||||
|
||||
let data = await response.getBuffer();
|
||||
if (!data) {
|
||||
data = new Uint8Array();
|
||||
) => {
|
||||
if (status === 404 && !payload) {
|
||||
return this.notFound(url);
|
||||
} else {
|
||||
console.log("got response for: " + url);
|
||||
}
|
||||
|
||||
if (status === 206 || status === 200) {
|
||||
const { statusCode, start, end } = this.parseRange(
|
||||
request.headers,
|
||||
headers,
|
||||
data.length,
|
||||
);
|
||||
if (start !== undefined) {
|
||||
data = data.slice(start, end);
|
||||
let headers = new Headers(reqHeaders);
|
||||
const date = new Date();
|
||||
|
||||
let response: ArchiveResponse = new ArchiveResponse({
|
||||
payload,
|
||||
headers,
|
||||
status,
|
||||
date,
|
||||
url,
|
||||
});
|
||||
|
||||
const rewriter = new Rewriter({
|
||||
baseUrl: url,
|
||||
prefix: "",
|
||||
urlRewrite: false,
|
||||
contentRewrite: true,
|
||||
decode: true,
|
||||
useBaseRules: true,
|
||||
});
|
||||
|
||||
const arRequest = new ArchiveRequest(url, request);
|
||||
|
||||
try {
|
||||
response = await rewriter.rewrite(response, arRequest);
|
||||
|
||||
headers = response.headers;
|
||||
|
||||
let data = await response.getBuffer();
|
||||
if (!data) {
|
||||
data = new Uint8Array();
|
||||
}
|
||||
status = statusCode;
|
||||
|
||||
if (status === 206 || status === 200) {
|
||||
headers = new Headers(response.headers);
|
||||
const res = this.parseRange(request.headers, headers, data.length);
|
||||
const { start, end } = res;
|
||||
status = res.status;
|
||||
if (start !== undefined) {
|
||||
data = data.slice(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(new Response(data, { status, headers }));
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
const result = this._bufferToStream(data);
|
||||
|
||||
callback({ statusCode: status, headers, data: result });
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (this.mainWindow) {
|
||||
this.mainWindow.webContents.send(
|
||||
@@ -475,45 +419,39 @@ class ElectronReplayApp {
|
||||
channel,
|
||||
);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'reqHeaders' implicitly has an 'any' type. | TS7006 - Parameter 'headers' implicitly has an 'any' type. | TS7006 - Parameter 'size' implicitly has an 'any' type.
|
||||
parseRange(reqHeaders, headers, size) {
|
||||
let statusCode = 200;
|
||||
parseRange(reqHeaders: Headers, headers: Headers, size: number) {
|
||||
let status = 200;
|
||||
const range = reqHeaders.get("range");
|
||||
|
||||
if (!range) {
|
||||
if (headers) {
|
||||
headers["Content-Length"] = "" + size;
|
||||
}
|
||||
return { statusCode };
|
||||
headers.set("content-length", "" + size);
|
||||
return { status };
|
||||
}
|
||||
|
||||
const m = range.match(/bytes=([\d]+)-([\d]*)/);
|
||||
if (!m) {
|
||||
return { statusCode };
|
||||
return { status };
|
||||
}
|
||||
|
||||
const start = Number(m[1]);
|
||||
const end = m[2] ? Number(m[2]) : size - 1;
|
||||
statusCode = 206;
|
||||
if (headers) {
|
||||
headers["Content-Range"] = `bytes ${start}-${end}/${size}`;
|
||||
headers["Content-Length"] = `${end - start + 1}`;
|
||||
}
|
||||
return { statusCode, start, end };
|
||||
status = 206;
|
||||
headers.set("content-range", `bytes ${start}-${end}/${size}`);
|
||||
headers.set("content-length", `${end - start + 1}`);
|
||||
return { status, start, end };
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'argv' implicitly has an 'any' type.
|
||||
createMainWindow(argv) {
|
||||
createMainWindow(argv: string[]) {
|
||||
const sourceString = this.getOpenUrl(argv);
|
||||
|
||||
// Create the browser window.
|
||||
const theWindow = new BrowserWindow({
|
||||
width: this.screenSize.width,
|
||||
height: this.screenSize.height,
|
||||
// @ts-expect-error - TS2345 - Argument of type '{ width: any; height: any; isMaximized: boolean; show: false; webPreferences: { plugins: boolean; preload: string; nativeWindowOpen: boolean; contextIsolation: boolean; enableRemoteModule: boolean; sandbox: boolean; nodeIntegration: boolean; }; }' is not assignable to parameter of type 'BrowserWindowConstructorOptions'.
|
||||
isMaximized: true,
|
||||
show: false,
|
||||
webPreferences: this.mainWindowWebPreferences,
|
||||
}).once("ready-to-show", () => {
|
||||
@@ -521,9 +459,7 @@ class ElectronReplayApp {
|
||||
theWindow.maximize();
|
||||
});
|
||||
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
theWindow.loadURL(STATIC_PREFIX + this.mainWindowUrl + sourceString);
|
||||
void theWindow.loadURL(STATIC_PREFIX + this.mainWindowUrl + sourceString);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
theWindow.webContents.openDevTools();
|
||||
}
|
||||
@@ -531,15 +467,15 @@ class ElectronReplayApp {
|
||||
return theWindow;
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'argv' implicitly has an 'any' type.
|
||||
getOpenUrl(argv) {
|
||||
argv = require("minimist")(argv.slice(process.defaultApp ? 2 : 1));
|
||||
getOpenUrl(argv: string[]) {
|
||||
const parsed = minimist(argv.slice(process.defaultApp ? 2 : 1));
|
||||
|
||||
const filename =
|
||||
this.openNextFile ||
|
||||
argv.filename ||
|
||||
argv.f ||
|
||||
(argv._.length && argv._[0]);
|
||||
parsed.filename ||
|
||||
parsed.f ||
|
||||
(parsed._.length && parsed._[0]);
|
||||
|
||||
this.openNextFile = null;
|
||||
|
||||
let sourceString = "";
|
||||
@@ -551,19 +487,15 @@ class ElectronReplayApp {
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
|
||||
const openUrl = argv.url;
|
||||
const openUrl = parsed.url as string | undefined;
|
||||
|
||||
const openTS = argv.ts || argv.timestamp;
|
||||
const openTS = (parsed.ts || parsed.timestamp) as string | undefined;
|
||||
|
||||
if (openUrl) {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
urlParams.set("url", openUrl);
|
||||
}
|
||||
|
||||
if (openTS) {
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
urlParams.set("ts", openTS);
|
||||
}
|
||||
|
||||
@@ -576,15 +508,14 @@ class ElectronReplayApp {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'body' implicitly has an 'any' type. | TS7006 - Parameter 'session' implicitly has an 'any' type.
|
||||
async function* readBody(body, session) {
|
||||
for (const chunk of body) {
|
||||
if (chunk.bytes) {
|
||||
yield await Promise.resolve(chunk.bytes);
|
||||
} else if (chunk.blobUUID) {
|
||||
yield await session.getBlobData(chunk.blobUUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
// async function* readBody(body, session) {
|
||||
// for (const chunk of body) {
|
||||
// if (chunk.bytes) {
|
||||
// yield await Promise.resolve(chunk.bytes);
|
||||
// } else if (chunk.blobUUID) {
|
||||
// yield await session.getBlobData(chunk.blobUUID);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
export { ElectronReplayApp, STATIC_PREFIX };
|
||||
|
||||
+33
-14
@@ -1,11 +1,10 @@
|
||||
import { LitElement, html, css, type PropertyValues, nothing } from "lit";
|
||||
import { wrapCss } from "./misc";
|
||||
import { IS_APP, wrapCss } from "./misc";
|
||||
import rwpLogo from "~assets/brand/replaywebpage-icon-color.svg";
|
||||
import rwpLogoAnimated from "~assets/brand/replaywebpage-icon-color-animated.svg";
|
||||
|
||||
import prettyBytes from "pretty-bytes";
|
||||
|
||||
import { parseURLSchemeHostPath } from "./pageutils";
|
||||
import { property } from "lit/decorators.js";
|
||||
import type { LoadInfo } from "./item";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@@ -28,6 +27,12 @@ const NO_ANIM_STATES: LoadingState[] = [
|
||||
"permission_needed",
|
||||
];
|
||||
|
||||
declare let window: Window & {
|
||||
electron?: {
|
||||
getFileLoadUrl: (sourceUrl: string) => string;
|
||||
};
|
||||
};
|
||||
|
||||
class Loader extends LitElement {
|
||||
@property({ type: String }) sourceUrl?: string;
|
||||
@property({ type: Object }) loadInfo: LoadInfo | null = null;
|
||||
@@ -46,7 +51,7 @@ class Loader extends LitElement {
|
||||
@property({ type: String }) extraMsg?: string;
|
||||
@property({ type: String }) swName?: string;
|
||||
|
||||
pingInterval: number | NodeJS.Timer = 0;
|
||||
pingInterval: number | NodeJS.Timeout = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- requestPermission() type mismatch
|
||||
fileHandle: any = null;
|
||||
noWebWorker = false;
|
||||
@@ -161,7 +166,8 @@ class Loader extends LitElement {
|
||||
|
||||
// custom protocol handlers here...
|
||||
try {
|
||||
const { scheme, host, path } = parseURLSchemeHostPath(sourceUrl!);
|
||||
const url = new URL(sourceUrl!);
|
||||
const scheme = url.protocol.slice(0, -1);
|
||||
|
||||
switch (scheme) {
|
||||
case "googledrive":
|
||||
@@ -174,22 +180,35 @@ class Loader extends LitElement {
|
||||
case "s3":
|
||||
source = {
|
||||
sourceUrl,
|
||||
loadUrl: `https://${host}.s3.amazonaws.com${path}`,
|
||||
loadUrl: `https://${url.hostname}.s3.amazonaws.com${url.href.slice(
|
||||
url.origin.length,
|
||||
)}`,
|
||||
name: this.sourceUrl,
|
||||
};
|
||||
break;
|
||||
|
||||
case "file":
|
||||
if (!this.loadInfo && !this.tryFileHandle) {
|
||||
this.state = "errored";
|
||||
this.error = `\
|
||||
File URLs can not be entered directly or shared.
|
||||
You can select a file to upload from the main page by clicking the 'Choose File...' button.`;
|
||||
this.errorAllowRetry = false;
|
||||
return;
|
||||
}
|
||||
if (!this.loadInfo && sourceUrl && IS_APP && window.electron) {
|
||||
const loadUrl = window.electron.getFileLoadUrl(sourceUrl);
|
||||
const name = loadUrl.slice(loadUrl.lastIndexOf("/") + 1);
|
||||
source = {
|
||||
sourceUrl,
|
||||
loadUrl,
|
||||
name,
|
||||
noCache: true,
|
||||
};
|
||||
} else {
|
||||
if (!this.loadInfo && !this.tryFileHandle) {
|
||||
this.state = "errored";
|
||||
this.error = `\
|
||||
File URLs can not be entered directly or shared.
|
||||
You can select a file to upload from the main page by clicking the 'Choose File...' button.`;
|
||||
this.errorAllowRetry = false;
|
||||
return;
|
||||
}
|
||||
|
||||
source = this.loadInfo;
|
||||
source = this.loadInfo;
|
||||
}
|
||||
break;
|
||||
|
||||
case "proxy":
|
||||
|
||||
+6
-7
@@ -103,7 +103,7 @@ async function sourceToId(url: string) {
|
||||
// simple parse of scheme, host, rest of path
|
||||
// not using URL due to different browser behavior
|
||||
function parseURLSchemeHostPath(url: string) {
|
||||
let inx = url.indexOf("://");
|
||||
let inx = url.indexOf(":");
|
||||
let hostInx = 0;
|
||||
let scheme = "";
|
||||
let host = "";
|
||||
@@ -111,12 +111,11 @@ function parseURLSchemeHostPath(url: string) {
|
||||
|
||||
if (inx >= 0) {
|
||||
scheme = url.slice(0, inx);
|
||||
inx += 3;
|
||||
} else {
|
||||
inx++;
|
||||
if (url.startsWith("//")) {
|
||||
inx += 2;
|
||||
}
|
||||
url = url.slice(inx + 1);
|
||||
inx += 1;
|
||||
}
|
||||
if (url.startsWith("//")) {
|
||||
inx += 2;
|
||||
}
|
||||
|
||||
hostInx = url.indexOf("/", inx);
|
||||
|
||||
Reference in New Issue
Block a user