initial commit/backup

This commit is contained in:
2024-11-01 20:55:18 -04:00
commit bc53ce53b1
39 changed files with 10456 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
import { StaticNetFilteringEngine } from '@gorhill/ubo-core';
import fs from 'fs/promises';
import { checkInternetConnectivity } from '../utils/misc.js';
import loggermod from '../utils/logger.cjs';
const { logger } = loggermod;
const blocklists = [
"https://ublockorigin.github.io/uAssetsCDN/filters/badlists.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/filters.min.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/privacy.min.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/badware.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/quick-fixes.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/unbreak.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/annoyances.txt",
"https://ublockorigin.github.io/uAssetsCDN/filters/lan-block.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easyprivacy.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-annoyances.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-cookies.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-newsletters.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-notifications.txt",
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-social.txt",
"https://raw.githubusercontent.com/laylavish/uBlockOrigin-HUGE-AI-Blocklist/main/list.txt"
];
async function fetchList(url) {
return fetch(url).then(r => {
return r.text();
}).then(raw => {
return { raw };
}).catch(reason => {
logger.error(reason);
});
}
const snfe = await StaticNetFilteringEngine.create();
// const rsf = await fetch('https://api.github.com/repos/uBlockOrigin/uAssets/contents/filters'),
// safeLists = (await rsf.json()).map(o => o.download_url);
const pathToSelfie = 'cache/selfie.txt';
// Up to date serialization data (aka selfie) available?
let selfie;
const ageInDays = await fs.stat(pathToSelfie).then(stat => {
const fileDate = new Date(stat.mtime);
return (Date.now() - fileDate.getTime()) / (7 * 24 * 60 * 60);
}).catch(() => Number.MAX_SAFE_INTEGER);
// Use a selfie if available and not older than 7 days
if (ageInDays <= 7) {
selfie = await fs.readFile(pathToSelfie, { encoding: 'utf8' })
.then(data => typeof data === 'string' && data !== '' && data)
.catch(() => { });
if (typeof selfie === 'string') {
await snfe.deserialize(selfie);
}
}
// Fetch filter lists if no up to date selfie available
if (!selfie && (await checkInternetConnectivity())) {
logger.info(`Fetching lists...`);
await snfe.useLists(blocklists.map(fetchList).filter(o => o));
const selfie = await snfe.serialize();
fs.mkdir('cache', { recursive: true });
await fs.writeFile(pathToSelfie, selfie);
}
/**
* runs ublock origin url safe-checking
*/
const blocked = (url, originURL = undefined, mimeType = undefined) => url ? snfe.matchRequest({ url, originURL, type: mimeType }) : false;
export default blocked;
+60
View File
@@ -0,0 +1,60 @@
const { createClient } = require('redis');
const { exec } = require('child_process');
const fs = require('fs');
const loggermod = require('../utils/logger.cjs');
const { logger } = loggermod;
/** @type {import('redis').RedisClientType} */
let client;
async function addHistory(uid, query, code, title) {
// logger.info(uid, query);
return await client.lPush(`searchHistory:${uid}`, JSON.stringify({ title, query, timestamp: new Date(), code }));
}
async function getHistory(uid) {
return await client.lRange(`searchHistory:${uid}`, 0, -1);
}
/**
* @param {Electron.WebContents} webContents
*/
async function displayHistory(uid, webContents) {
const history = JSON.stringify(await getHistory(uid));
webContents.executeJavaScript(`showHistory(${history})`);
}
const quitRedis = () => client.quit().then(() => logger.info('redis quit')).catch(_ => null);
async function setupRedis() {
await new Promise((resolve, reject) => {
if (!fs.existsSync('../cache/redis.conf')) {
fs.writeFileSync('../cache/redis.conf', `dir ./\ndbfilename dump.rdb`);
}
const p = exec('redis-server ../cache/redis.conf', (err, stdout, stderr) => {
if (err) return reject(err);
});
p.on('message', logger.info);
p.on('error', logger.error);
p.on('spawn', resolve);
});
client = await createClient()
.on('error', err => {
logger.info('Redis Client Error', err);
})
.connect();
// clear history on browser boot
client.flushDb();
logger.info('Redis Client Connected!');
}
module.exports = { setupRedis, redisclient: client, getHistory, addHistory, displayHistory, quitRedis };
+32
View File
@@ -0,0 +1,32 @@
import loggermod from '../utils/logger.cjs';
import intercept from '../serverJS/intercept.js';
import setUpShortcuts from '../serverJS/shortcuts.js';
import { organizeTabIds } from '../serverJS/tabs_server.js';
import { getSavedTabs, loadTabs } from '../utils/clearCache.js';
import flushCookies from '../utils/cookies.js';
import { askUserQuestion } from '../utils/dialogue.js';
import ipcinit from '../utils/ipc.js';
import { checkInternetConnectivity } from '../utils/misc.js';
import { findPath } from '../utils/paths.js';
import { createWebview, handleWebViewInit } from '../utils/webviewHelpers.js';
const { setupRedis, quitRedis } = await import('../serverJS/history.cjs');
const { logger } = loggermod;
export {
logger,
intercept,
setUpShortcuts,
organizeTabIds,
getSavedTabs,
loadTabs,
flushCookies,
askUserQuestion,
ipcinit,
checkInternetConnectivity,
findPath,
createWebview,
handleWebViewInit,
setupRedis,
quitRedis
};
+140
View File
@@ -0,0 +1,140 @@
import { findPath } from "../utils/paths.js";
import blocked from "./adblock.js";
import fs from "fs";
import * as history from "./history.cjs";
const { addHistory } = history;
import { net, shell } from "electron";
import spawnworker from "./spawnworker.js";
import { checkInternetConnectivity } from "../utils/misc.js";
import loggermod from '../utils/logger.cjs';
const { logger } = loggermod;
export const noworker = ['www.youtube.com', 'accounts.youtube.com', 'accounts.google.com', '.*\.googlevideo\.com'];
export const transformduckurl = (u) => {
try {
const urlParams = new URLSearchParams(new URL(u).search);
const actualUrl = decodeURIComponent(urlParams.get('uddg'));
return actualUrl;
}
catch (err) {
console.warn(err);
return u;
}
}
/**
* @param {GlobalRequest} req
*/
export default async function intercept(request, uid) {
try {
const u = new URL(request.url);
if (u.protocol === 'file:' || u.hostname.startsWith('ion-local')) {
const filePath = await findPath(u.pathname.split('/')?.at(-1), true);
if (!filePath || !fs.existsSync(filePath)) return new Response(`file"${filePath}" not found!`, { status: 404 });
// read the file from the filesystem
let fileData = fs.readFileSync(filePath).toString();
// guess the mime type (e.g., text/html, application/javascript)
let mimeType = 'text/plain';
if (filePath.endsWith('.html')) mimeType = 'text/html';
else if (filePath.endsWith('.js')) mimeType = 'application/javascript';
else if (filePath.endsWith('.css')) mimeType = 'text/css';
if (filePath.endsWith('HTML/nointernet.html')) {
const hascon = await checkInternetConnectivity();
const rNew = { 'Location': 'https://start.duckduckgo.com', 'Content-Type': 'text/html' };
if (hascon) return new Response(Buffer.from(`<html><body>Loading...</body></html>`, 'utf-8'), { status: 301, headers: rNew });
}
// send the file content along with a mime type
return new Response(fileData, { headers: { 'Content-Type': mimeType } });
}
else {
if (blocked(request.url)) return new Response('Request Blocked by UBlock Origin', { status: 503, statusText: 'Request Blocked by UBlock Origin' });
let newURL = request.url;
// force dark mode and turn off safe search
if (u.hostname.includes('duckduckgo.com')) newURL += (u.search) ? '&kae=d&kp=-2' : '?kae=d';
else if (u.hostname.includes('google.com')) newURL += (u.search) ? '&safe=off&&pccc=1' : '?pccc=1';
// here to avoid `TypeError: Cannot set property url of #<_Request> which has only a getter`
try { request.url = newURL; }
catch (_) { }
const iswebpagereq = request.method?.toUpperCase() === 'GET' && request.headers.get('Accept').includes('text/html');
// Odd duckduckgo redir thing (I hate it)
if (request.url.startsWith('https://duckduckgo.com/l/?')) {
const newURL = transformduckurl(request.url);
const rNew = {
'Location': newURL, // Set the redirect location header
'Content-Type': 'text/html',
}
return new Response(Buffer.from(`<html><body>Redirecting to <a href="${newURL}">${newURL}</a>...</body></html>`, 'utf-8'), { status: 301, headers: rNew });
}
let r;
// special case
if (u.href.match(/https:\/\/accounts\.(google|youtube)\.com\/(.*\/)?(signin\/challenge|ServiceLogin)\/?.*/gm)) {
// const urlObj = new URL(u.href);
// // Decode the `continue` parameter and modify it
// let continueUrl = decodeURIComponent(urlObj.searchParams.get('continue'));
// // You can modify the `continue` URL to use your custom protocol (myapp://callback)
// continueUrl = 'iobrowser://callback';
// // Encode and set the updated `continue` parameter back in the original URL
// urlObj.searchParams.set('continue', encodeURIComponent(continueUrl));
// // This is your modified URL that you will use to launch the browser
// return shell.openExternal(urlObj.href, { logUsage: true, activate: true });
r = await net.fetch(request);
}
// sloppy fix
else r = await net.fetch(request);
// else if (iswebpagereq || noworker.find(o => u.hostname.match(o))) r = await net.fetch(request);
// else r = await spawnworker(request, uid);
if (request.headers.get('Accept').includes('text/html')) {
if (u.hostname === 'lite.duckduckgo.com') {
const params = new URLSearchParams(u.search);
addHistory(uid, `${u.href}?${body}`, r.status, `DuckDuckGo${params.has('q') ? (' - ' + params.get('q')) : ''}`);
}
else {
// const res = await fetch(u.href, { method: 'HEAD' }).catch(_ => null);
// logger.info(res);
// addHistory(uid, u.href, r.status, 'title!');
}
}
return r;
/*
REMOVED BECAUSE IT'S TOO EXPENSIVE (SIGKILL-ed)
// https://accounts.google.com/v3/signin/_/AccountsSignInUi/browserinfo?f.sid=3210847140573431127&bl=boq_identityfrontendauthuiserver_20241015.01_p0&hl=en&_reqid=139420&rt=j
if (request.url === 'https://www.youtube.com/' || skip.includes(u.hostname)) return net.fetch(request);
let newURL = request.url;
if (u.hostname.includes('duckduckgo.com')) newURL += (u.search) ? '&kae=d&kp=-2' : '?kae=d';
else if (u.hostname.includes('google.com')) newURL += (u.search) ? '&safe=off&&pccc=1' : '?pccc=1';
const r = await net.fetch(request);
return r;
*/
}
}
catch (err) {
logger.error(request.url, err);
return new Response('Error', { status: 500 });
}
}
+32
View File
@@ -0,0 +1,32 @@
import { session, globalShortcut } from "electron";
import { changeZoom } from "../JS/display.js";
import { getCurrentTab, getCurrentWindow } from "./tabs_server.js";
import { logger } from "./imports.js";
/**
* @param {Electron.Event} e
* @param {Electron.BrowserWindow} window
*/
export default async function setUpShortcuts(uid) {
globalShortcut.register('Control+Shift+I', () => {
console.log("A", getCurrentWindow().currentView);
getCurrentWindow().currentView.webContents.toggleDevTools();
// getCurrentWindow().isFocused() ? getCurrentTab()?.toggleDevTools() : null
});
globalShortcut.register('Control+H', () => getCurrentTab()?.webContents.executeJavaScript('window.electronAPI.displayHistory()'));
// zoom
globalShortcut.register('Control+=', () => changeZoom(getCurrentTab(), true));
globalShortcut.register('Control+-', () => changeZoom(getCurrentTab(), false));
globalShortcut.register('Control+Plus', () => changeZoom(getCurrentTab(), false, true));
globalShortcut.register('Control+T', () => window.webContents.executeJavaScript('window.tabAPI.addTab()'))
// window.webContents.on('did-navigate', async (_, url, code, stat) => {
// if (isValidURL(url)?.hostname === 'lite.duckduckgo.com') return;
// const title = await window.webContents.executeJavaScript('document.title');
// });
}
+55
View File
@@ -0,0 +1,55 @@
import { Worker } from 'worker_threads';
import { findPath } from '../utils/paths.js';
import loggermod from '../utils/logger.cjs';
const { logger } = loggermod;
// need to find something for this to do, as it breaks a lot of stuff if used for requests
/**
* runs the given function with the given args in a Nodejs Worker
* @param {Function} fn
* @param {any[]} args
* @returns
*/
export default function spawnWorker(fn, args) {
return new Promise(async (resolve, reject) => {
// find path to worker.js
const workerScriptPath = await findPath('worker.js');
// create a new worker with request data
const worker = new Worker(workerScriptPath, {
workerData: { fn: fn.toString(), args },
// workerData: request,
});
logger.info(`spawning worker for ${fn}(${args})`);
worker.on('online', () => logger.info(`started worker for ${fn}(${args})`));
// handle messages from the worker
worker.on('message', (msg) => {
try {
resolve(msg);
} catch (err) {
logger.error('Error creating response:', err);
resolve(msg);
}
// terminate the worker when done (removed bc it results in interrupts)
// worker.terminate().then(c => console.log(`${request.url} - ${c}`));
});
// handle errors from the worker
worker.on('error', (error) => {
logger.error('Worker error:', error);
resolve({ status: 500 });
});
// handle worker exiting unexpectedly
worker.on('exit', (code) => {
if (code !== 0) {
logger.warn(`Worker exited with code ${code} for ${request.url}`);
resolve({ status: 500 });
}
});
});
}
+177
View File
@@ -0,0 +1,177 @@
import fs from 'fs';
import mhtml2html from 'mhtml2html';
import { BaseWindow, WebContentsView } from 'electron';
import { createWebview } from '../utils/webviewHelpers.js';
import { CACHE_DIRECTORY, getLoadPath, saveTabState } from '../utils/clearCache.js';
import path from 'path';
import { logger } from './imports.js';
const webViewContentsMap = {}; // Memory storage for active tabs
/**
* returns the focused window, if there is no focused window, returns the first one spawned
*/
const getCurrentWindow = () => {
const allWins = BaseWindow.getAllWindows(),
w = allWins.find((win) => win.isFocused());
if (!w && allWins.length > 0) return allWins?.at(0);
else return w;
};
/**
* @returns {Electron.CrossProcessExports.WebContentsView | undefined}
*/
const getCurrentTab = () => {
const cw = getCurrentWindow();
return cw?.currentView;
}
const settabqual = (tabId) => getCurrentTab().webContents.executeJavaScript('setQuality()').catch(err => logger.warn(`setting quality for window ${tabId} failed with reason:\`\`\`${err}\`\`\``));
/**
* Switch to the specified view by its ID.
* @param {string | Electron.WebContentsView} tabId
*/
async function switchToView(tabId) {
const currentWindow = getCurrentWindow();
/** @type {WebContentsView} */
const viewData = (tabId instanceof WebContentsView) ? tabId : webViewContentsMap[tabId];
if (!viewData || !currentWindow) return;
else if (viewData.id < 0) return; // Don't modify views with negative IDs
viewData.webContents.setBackgroundThrottling(false);
const id = tabId.id || tabId;
// Save the current active view state
// find the non-background-playing window
/** @type {WebContentsView} */
const oldView = currentWindow.contentView.children.find((view) => view instanceof WebContentsView && (view.id >= 0 && !view.webContents.isCurrentlyAudible()));
// undo the optimizations
const undoOptimize = async () => {
await viewData.webContents.executeJavaScript('revertQuality()');
viewData.setVisible(true);
}
if (oldView) {
// REMOVEME
// currentWindow.contentView.removeChildView(oldView);
console.log(`saving`, JSON.stringify(id));
// DO NOT AWAIT THIS CALL FFS, INEFFICIENT!!!
saveTabState(oldView.id, oldView).then(() => console.log(`saved ${id}`));
currentWindow.contentView.removeChildView(oldView);
if (viewData.webContents.isCurrentlyAudible()) return undoOptimize();
}
else if (viewData?.webContents?.isCurrentlyAudible()) return undoOptimize();
// Set the new view as active and add it to the window
// viewData.webContents.setBackgroundThrottling(true);
currentWindow.contentView.addChildView(viewData);
await viewData.webContents.loadURL('https://start.duckduckgo.com');
currentWindow.contentView.children.map(c => c.setVisible((c.id === id) || c.id < 0));
currentWindow.contentView.children.map(c => console.log(c.id, c.webContents.isCurrentlyAudible()));
settabqual(tabId);
}
/**
* moves the tab to the "background" and tries to minimize the footprint
* @param {Electron.BaseWindow} currentWindow
* @param {String} tabId
* @param {Electron.WebContentsView} oldView
*/
async function shiftTabToBK(currentWindow, tabId, oldView, customSession) {
try {
webViewContentsMap[oldView.id] = oldView;
// this is currently playing stuff, keep it open
oldView.webContents.setBackgroundThrottling(false);
oldView.setVisible(false);
// TODO: optimize the page more
settabqual(tabId);
const newView = await createWebview(tabId, currentWindow, customSession);
webViewContentsMap[tabId] = newView;
switchToView(newView);
}
catch (err) {
console.error(err);
return null;
}
}
/**
* Add a new tab with caching.
* @param {string} tabId
* @param {Electron.Session} customSession
* @param {string} [url]
*/
async function addTab(event, tabId, customSession, url = 'https://duckduckgo.com', isOpen = false) {
const currentWindow = getCurrentWindow();
const tabPath = getLoadPath(tabId),
currentTab = getCurrentTab();
if (currentTab?.webContents?.isCurrentlyAudible() && !isOpen) return shiftTabToBK(currentWindow, tabId, currentTab, customSession);
else if (webViewContentsMap[tabId]?.webContents.isCurrentlyAudible()) return switchToView(tabId);
const newView = await createWebview(tabId, currentWindow, customSession);
webViewContentsMap[tabId] = newView;
if (tabPath && fs.existsSync(tabPath)) newView.webContents.loadFile(tabPath);
else newView.webContents.loadURL(url);
switchToView(newView);
}
/**
* Close a tab and save its state to disk.
* @param {string} tabId
*/
async function closeTab(event, tabId) {
const currentWindow = getCurrentWindow();
const view = webViewContentsMap[tabId];
if (view && view.id >= 0) {
await saveTabState(tabId, view.webContents);
currentWindow.contentView.removeChildView(view);
view.webContents.destroy();
delete webViewContentsMap[tabId];
}
}
/**
* Open an existing tab by restoring its cached state.
* @param {string} tabId
*/
function openTab(event, tabId, customSession) {
addTab(event, tabId, customSession, undefined, true); // Reopen the tab by calling addTab with its ID
}
function organizeTabIds() {
const tabs = fs.readdirSync(CACHE_DIRECTORY, { withFileTypes: true })
.filter(o => (o.isFile() && o.name.endsWith('.html')))
.map(o => o.name);
const tmpcachepath = 'cache/tmp/tabs';
fs.mkdirSync(tmpcachepath, { recursive: true });
for (let i = 0; i < tabs.length; i++) {
fs.cpSync(`${CACHE_DIRECTORY}/${tabs[i]}`, `${tmpcachepath}/${i}.html`);
}
fs.rmSync(CACHE_DIRECTORY, { recursive: true });
fs.cpSync(tmpcachepath, CACHE_DIRECTORY, { recursive: true });
fs.rmSync(tmpcachepath, { recursive: true });
}
export { closeTab, addTab, openTab, getCurrentWindow, getCurrentTab, organizeTabIds };
+56
View File
@@ -0,0 +1,56 @@
import { parentPort, workerData } from 'worker_threads';
import fs from 'fs';
import mhtml2html from "mhtml2html";
import { JSDOM } from 'jsdom';
function MHTMLtoHTML(savePath, tabId) {
return new Promise((resolve) => {
// read the MHTML file content
const mhtmlContent = fs.readFileSync(savePath, 'utf8');
/** @type {JSDOM} */
const parsedResult = mhtml2html.convert(mhtmlContent, { parseDOM: (html) => new JSDOM(html) });
// save the extracted HTML file
const htmlPath = savePath.replace('.mhtml', '.html');
fs.writeFile(htmlPath, parsedResult.serialize(), (err) => {
if (err) {
logger.error(`error saving ${tabId}`);
return resolve(false);
}
fs.rmSync(savePath);
resolve(true)
});
});
}
// a function to process the request data
const processRequest = async (data) => {
try {
const { fn, args } = data;
let result;
switch (fn) {
case 'convertpage': result = await MHTMLtoHTML(...args);
break;
default: console.log(`unknown function "${fn}(${args})`);
}
return result;
} catch (err) {
console.error(err);
// handle and report any errors
return { success: false, error: err.message };
}
};
// read input from the workerData passed from the main thread and process it
(async () => {
const result = await processRequest(workerData);
// send the response to the main process
parentPort.postMessage(result);
process.exit(0);
})();
+37
View File
@@ -0,0 +1,37 @@
// read input from stdin (from main process) and process it
process.stdin.on('data', async (data) => {
try {
// parse the request data (assumed to be in JSON format)
const requestData = JSON.parse(data.toString());
// create a new Request object using the parsed request data
const request = new Request(requestData.url, {
method: requestData.method || 'GET',
headers: requestData.headers || {},
body: requestData.body ? JSON.stringify(requestData.body) : undefined,
params: requestData.params,
query: requestData.query
})
// perform the fetch using Node.js native Fetch API
const response = await fetch(request);
// read the response as an array buffer
const buffer = await response.arrayBuffer();
// create response object
const responseObject = {
success: true,
headers: Object.fromEntries(Array.from(response.headers)),
mimeType: response.headers.get('Content-Type') || 'application/octet-stream',
data: Buffer.from(buffer).toString('base64'), // encode as base64 for transmission
}
// write the response to stdout
logger.info(JSON.stringify(responseObject));
} catch (error) {
// handle and report any errors
const errorResponse = { success: false, error: error.message }
process.stdout.write(JSON.stringify(errorResponse) + '\n');
}
});
+75
View File
@@ -0,0 +1,75 @@
import { spawn } from 'child_process';
import { findPath } from '../utils/paths.js';
import loggermod from '../utils/logger.cjs';
const { logger } = loggermod;
export default function spawnworker(request, uid) {
return new Promise(async (resolve, reject) => {
const child = spawn('node', [await findPath('worker.js')]);
const requestObject = {
method: request.method,
headers: Object.fromEntries(Array.from(request.headers)),
url: request.url,
body: await request.text(),
params: request.params,
query: request.query,
};
// send the request data to the child process
child.stdin.write(JSON.stringify(requestObject) + '\n');
// Accumulate data from child process stdout
let accumulatedData = '';
// handle response from the child process
child.stdout.on('data', (data) => {
accumulatedData += data.toString();
// Check if accumulatedData contains a complete JSON object
if (accumulatedData.trim().endsWith('}')) {
let response;
try {
response = JSON.parse(accumulatedData);
} catch (err) {
logger.error('Failed to parse response from child process:', err);
child.kill(1);
resolve({ status: 500 });
return;
}
// If JSON is parsed successfully, resolve the promise
try {
// Decode the base64 data back to a buffer
if (response.data) response.data = Buffer.from(response.data, 'base64');
resolve(new Response(response.data || response, {
headers: response.headers,
status: response.status || 200,
}));
} catch (err) {
logger.error('Error creating response:', err);
resolve(response);
}
child.kill(0);
// Reset accumulatedData for the next potential message
accumulatedData = '';
}
});
// handle errors from the child process
child.stderr.on('data', (error) => {
logger.error(`Child process stderr: ${error}`);
resolve({ status: 500 });
});
// handle if the child process exits unexpectedly
child.on('close', (code) => {
// not 0 or null
if (!!code) {
logger.error(`Child process exited with code ${code}`);
resolve({ status: 500 });
}
});
});
}