cables_dev/cables_electron/src/electron/electron_endpoint.js
- import { protocol, session, net, shell } from "electron";
- import fs from "fs";
- import path from "path";
- import mime from "mime";
-
- import cables from "../cables.js";
- import logger from "../utils/logger.js";
- import doc from "../utils/doc_util.js";
- import opsUtil from "../utils/ops_util.js";
- import subPatchOpUtil from "../utils/subpatchop_util.js";
- import settings from "./electron_settings.js";
- import helper from "../utils/helper_util.js";
- import electronApp from "./main.js";
- import projectsUtil from "../utils/projects_util.js";
-
-
- protocol.registerSchemesAsPrivileged([
- {
- "scheme": "cables",
- "privileges": {
- "bypassCSP": true,
- "supportFetchAPI": true
- }
- },
- {
- "scheme": "file",
- "privileges": {
- "stream": true,
- "bypassCSP": true,
- "supportFetchAPI": true
- }
- }
- ]);
-
- class ElectronEndpoint
- {
- constructor()
- {
- this._log = logger;
- }
-
- init()
- {
- const partition = settings.SESSION_PARTITION;
- const ses = session.fromPartition(partition, { "cache": false });
-
- ses.protocol.handle("file", async (request) =>
- {
- let urlFile = request.url;
- let absoluteFile = helper.fileURLToPath(urlFile, false);
- let projectFile = helper.fileURLToPath(urlFile, true);
- if (fs.existsSync(absoluteFile))
- {
- const response = await net.fetch(helper.pathToFileURL(absoluteFile), { "bypassCustomProtocolHandlers": true });
- this._addDefaultHeaders(response, absoluteFile);
- return response;
- }
- else if (fs.existsSync(projectFile))
- {
- const response = await net.fetch(helper.pathToFileURL(projectFile), { "bypassCustomProtocolHandlers": true });
- this._addDefaultHeaders(response, projectFile);
- return response;
- }
- else
- {
- try
- {
- if (projectFile.includes("?"))
- {
- projectFile = projectFile.split("?")[0];
- }
- if (fs.existsSync(projectFile))
- {
- const response = await net.fetch(helper.pathToFileURL(projectFile), { "bypassCustomProtocolHandlers": true });
- this._addDefaultHeaders(response, projectFile);
- return response;
- }
- else
- {
- return new Response(null, { "headers": { "status": 404 } });
- }
- }
- catch (e)
- {
- return net.fetch(request.url, { "bypassCustomProtocolHandlers": true });
- }
- }
- });
-
- ses.protocol.handle("cables", async (request) =>
- {
- const url = new URL(request.url);
- const urlPath = url.pathname;
- if (urlPath.startsWith("/api/corelib/"))
- {
- const libName = urlPath.split("/", 4)[3];
- const libCode = this.apiGetCoreLibs(libName);
- if (libCode)
- {
- return new Response(libCode, {
- "headers": { "content-type": "application/javascript" }
- });
- }
- else
- {
- return new Response(libCode, {
- "headers": { "content-type": "application/javascript" },
- "status": 500
- });
- }
- }
- else if (urlPath.startsWith("/api/lib/"))
- {
- const libName = urlPath.split("/", 4)[3];
- const libCode = this.apiGetLibs(libName);
- if (libCode)
- {
- return new Response(libCode, {
- "headers": { "content-type": "application/javascript" }
- });
- }
- else
- {
- return new Response(libCode, {
- "headers": { "content-type": "application/javascript" },
- "status": 500
- });
- }
- }
- else if (urlPath === "/api/errorReport")
- {
- return new Response(JSON.stringify(this.apiErrorReport(request)));
- }
- else if (urlPath === "/api/changelog")
- {
- return new Response(JSON.stringify(this.apiGetChangelog()), {
- "headers": { "content-type": "application/json" }
- });
- }
- else if (urlPath.startsWith("/api/ops/code/project"))
- {
- const code = this.apiGetProjectOpsCode();
- return new Response(code, {
- "headers": { "content-type": "application/json" }
- });
- }
- else if (urlPath.startsWith("/api/ops/code"))
- {
- const code = this.apiGetCoreOpsCode();
- if (code)
- {
- return new Response(code, {
- "headers": { "content-type": "application/javascript" }
- });
- }
- else
- {
- return new Response(code, {
- "headers": { "content-type": "application/javascript" },
- "status": 500
- });
- }
- }
- else if (urlPath.startsWith("/api/op/layout/"))
- {
- let opName = urlPath.split("/", 5)[4];
- if (opsUtil.isOpId(opName))
- {
- opName = opsUtil.getOpNameById(opName);
- }
- const layoutSvg = this.apiOpLayout(opName);
- if (layoutSvg)
- {
- return new Response(layoutSvg, {
- "headers": { "content-type": "image/svg+xml" }
- });
- }
- else
- {
- return new Response("", {
- "headers": { "content-type": "image/svg+xml" },
- "status": 500
- });
- }
- }
- else if (urlPath.startsWith("/api/op/"))
- {
- let opName = urlPath.split("/", 4)[3];
- if (opsUtil.isOpId(opName))
- {
- opName = opsUtil.getOpNameById(opName);
- }
- if (opName)
- {
- const opCode = this.apiGetOpCode({ "opName": opName });
- if (opCode)
- {
- return new Response(opCode, {
- "headers": { "content-type": "application/javascript" }
- });
- }
- else
- {
- return new Response(opCode, {
- "headers": { "content-type": "application/javascript" },
- "status": 500
- });
- }
- }
- else
- {
- return new Response("", {
- "headers": { "content-type": "application/javascript" },
- "status": 404
- });
- }
- }
- else if (urlPath.startsWith("/op/screenshot"))
- {
- let opName = urlPath.split("/", 4)[3];
- if (opName) opName = opName.replace(/.png$/, "");
- const absoluteFile = opsUtil.getOpAbsolutePath(opName);
- const file = path.join(absoluteFile, "screenshot.png");
- const response = await net.fetch(helper.pathToFileURL(file), { "bypassCustomProtocolHandlers": true });
- this._addDefaultHeaders(response, file);
- return response;
- }
- else if (urlPath.startsWith("/edit/"))
- {
- let patchId = urlPath.split("/", 3)[2];
- let projectFile = null;
- if (patchId)
- {
- projectFile = settings.getRecentProjectFile(patchId);
- }
- if (projectFile)
- {
- await electronApp.openPatch(projectFile, true);
- }
- else
- {
- await electronApp.pickProjectFileDialog();
- }
- return new Response(null, { "status": 302 });
- }
- else if (urlPath.startsWith("/openDir/"))
- {
- let dir = urlPath.replace("/openDir/", "");
- // dir = path.dirname(dir);
- await shell.showItemInFolder(dir);
- return new Response(null, { "status": 404 });
- }
- else
- {
- return new Response("", {
- "headers": { "content-type": "application/javascript" },
- "status": 404
- });
- }
- });
- }
-
-
- apiGetCoreOpsCode()
- {
- const opDocs = doc.getOpDocs();
- const code = opsUtil.buildCode(cables.getCoreOpsPath(), null, true, true, opDocs);
- if (!code) this._log.warn("FAILED TO GET CODE FOR COREOPS FROM", cables.getCoreOpsPath());
- return code;
- }
-
- apiGetProjectOpsCode()
- {
- const project = settings.getCurrentProject();
-
- let code = "";
- let missingOps = [];
- if (project)
- {
- let opDocs = doc.getOpDocs(false, false);
- let allOps = [];
- if (project.ops) allOps = project.ops.filter((op) => { return !opDocs.some((d) => { return d.id === op.opId; }); });
- const opsInProjectDir = projectsUtil.getOpDocsInProjectDirs(project);
- const ops = subPatchOpUtil.getOpsUsedInSubPatches(project);
- allOps = allOps.concat(opsInProjectDir);
- allOps = allOps.concat(ops);
- missingOps = allOps.filter((op) => { return !opDocs.some((d) => { return d.id === op.opId || d.id === op.id; }); });
- }
-
- const opsWithCode = [];
- let codeNamespaces = [];
-
- missingOps.forEach((missingOp) =>
- {
- const opId = missingOp.opId || missingOp.id;
- const opName = missingOp.name || opsUtil.getOpNameById(opId);
- if (opId && opName)
- {
- if (!opsWithCode.includes(opName))
- {
- const parts = opName.split(".");
- for (let k = 1; k < parts.length; k++)
- {
- let partPartname = "";
- for (let j = 0; j < k; j++) partPartname += parts[j] + ".";
-
- partPartname = partPartname.substr(0, partPartname.length - 1);
- codeNamespaces.push(partPartname + "=" + partPartname + " || {};");
- }
- const fn = opsUtil.getOpAbsoluteFileName(opName);
- if (fn)
- {
- code += opsUtil.getOpFullCode(fn, opName, opId);
- opsWithCode.push(opName);
- }
- }
- doc.addOpToLookup(opId, opName);
- }
- });
-
- codeNamespaces = helper.sortAndReduce(codeNamespaces);
- let fullCode = opsUtil.OPS_CODE_PREFIX;
- if (codeNamespaces && codeNamespaces.length > 0)
- {
- codeNamespaces[0] = "var " + codeNamespaces[0];
- fullCode += codeNamespaces.join("\n") + "\n\n";
- }
-
- fullCode += code;
- return fullCode;
- }
-
- apiGetOpCode(params)
- {
- const opName = params.opName;
- let code = "";
- const currentProject = settings.getCurrentProject();
- try
- {
- const attachmentOps = opsUtil.getSubPatchOpAttachment(opName);
- const bpOps = subPatchOpUtil.getOpsUsedInSubPatches(attachmentOps);
-
- if (!bpOps)
- {
- return code;
- }
- else
- {
- let opNames = [];
- for (let i = 0; i < bpOps.length; i++)
- {
- const bpOp = bpOps[i];
- const bpOpName = opsUtil.getOpNameById(bpOp.opId);
- if (opsUtil.isCoreOp(bpOpName) && (!opsUtil.isOpOldVersion(bpOpName) && !opsUtil.isDeprecated(bpOpName))) continue;
- if (currentProject && currentProject.ops && currentProject.ops.some((projectOp) => { return projectOp.opId === bpOp.opId; })) continue;
- opNames.push(bpOpName);
- }
-
- if (opsUtil.isExtension(opName) || opsUtil.isTeamNamespace(opName))
- {
- const collectionName = opsUtil.getCollectionNamespace(opName);
- opNames = opNames.concat(opsUtil.getCollectionOpNames(collectionName));
- opNames.push(opName);
- }
- else
- {
- opNames.push(opName);
- }
-
- const ops = [];
- opNames.forEach((name) =>
- {
- ops.push({
- "objName": name,
- "opId": opsUtil.getOpIdByObjName(name)
- });
- });
-
- code = opsUtil.buildFullCode(ops, "none");
- return code;
- }
- }
- catch (e)
- {
- this._log.error("FAILED TO BUILD OPCODE FOR", opName, e);
- return code;
- }
- }
-
- apiGetCoreLibs(name)
- {
- const fn = path.join(cables.getCoreLibsPath(), name + ".js");
-
- if (fs.existsSync(fn))
- {
- let info = fs.readFileSync(fn);
- info += "\n\nCABLES.loadedCoreLib(\"" + name + "\")";
- return info;
- }
- else
- {
- this._log.error("COULD NOT FIND CORELIB FILE AT", fn);
- return "";
- }
- }
-
- apiGetLibs(name)
- {
- const fn = path.join(cables.getLibsPath(), name);
- if (fs.existsSync(fn))
- {
- let info = fs.readFileSync(fn);
- info = info + "\n\nCABLES.loadedLib(\"" + name + "\")";
- return info;
- }
- else
- {
- this._log.error("COULD NOT FIND LIB FILE AT", fn);
- return "";
- }
- }
-
- apiGetChangelog()
- {
- return {
- "ts": Date.now(),
- "items": []
- };
- }
-
- apiOpLayout(opName)
- {
- return opsUtil.getOpSVG(opName);
- }
-
- _addDefaultHeaders(response, existingFile)
- {
- try
- {
- const stats = fs.statSync(existingFile);
- if (stats)
- {
- response.headers.append("Accept-Ranges", "bytes");
- response.headers.append("Content-Length", stats.size);
- response.headers.append("Content-Range", "bytes 0-" + stats.size + "/" + (stats.size + 1));
- response.headers.append("Last-Modified", stats.mtime.toUTCString());
- }
- let mimeType = mime.getType(existingFile);
- if (mimeType)
- {
- if (mimeType === "application/node") mimeType = "text/javascript";
- response.headers.set("Content-Type", mimeType);
- }
- }
- catch (e) {}
- return response;
- }
-
- apiErrorReport(request)
- {
- try
- {
- request.json().then((report) =>
- {
- const communityUrl = cables.getCommunityUrl();
- if (cables.sendErrorReports() && communityUrl)
- {
- try
- {
- const errorReportSend = net.request({
- "url": path.join(communityUrl, "/api/errorReport"),
- "method": "POST",
- });
- delete report.url;
- delete report.file;
- if (report.log)
- {
- report.log.forEach((log) =>
- {
- if (log.errorStack)
- {
- log.errorStack.forEach((stack) =>
- {
- if (stack.fileName)
- {
- stack.fileName = path.basename(stack.fileName);
- }
- if (stack.source)
- {
- delete stack.source;
- }
- });
- }
- });
- }
- report.username = "standalone";
- errorReportSend.setHeader("Content-Type", "application/json");
- errorReportSend.write(JSON.stringify(report), "utf-8");
- errorReportSend.end();
- }
- catch (e)
- {
- this._log.debug("failed to send error report", e);
- }
- }
- });
- }
- catch (e)
- {
- this._log.info("failed to parse error report", e);
- }
- return { "success": true };
- }
- }
-
- export default new ElectronEndpoint();