diff --git a/.gitignore b/.gitignore index 9348b61..1838e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ dist/**/* /examples/**/build/ # tests -__app_example_1/ \ No newline at end of file +__app_example_1/ + +*/temp/**/* \ No newline at end of file diff --git a/lib/gateway.ts b/lib/gateway.ts new file mode 100644 index 0000000..8411d81 --- /dev/null +++ b/lib/gateway.ts @@ -0,0 +1,49 @@ +import { DevOptions } from "./dev"; + +function renderAttribute(name, value) { + return value !== undefined ? `${name}="${value}"` : ""; +} + +function htmlStringify(json) { + return JSON.stringify(json) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +export const handleReplacements = (html: string, opts: DevOptions): string => { + const envConfig = JSON.stringify({ + bosLoaderWs: `ws://127.0.0.1:${opts.port}`, + bosLoaderUrl: `http://127.0.0.1:${opts.port}/api/loader`, + enableHotReload: opts.NoHot ? false : true, + network: opts.network, + }); + + return normalizeHtml(injectHTML(html, { + ENV_CONFIG: { + pattern: "%ENV_CONFIG%", + replacement: envConfig + }, + OTHER: { + pattern: /<\/near-social-viewer>/g, + replacement: true + ? `` + : "", + } + })); +} + +function injectHTML(html: string, injections: Record) { + Object.keys(injections).forEach((key) => { + const { pattern, replacement } = injections[key]; + html = html.replace(pattern, replacement); + }); + return html; +}; + +function normalizeHtml(html) { + return html.replace(/\s+/g, ' ').trim(); +} \ No newline at end of file diff --git a/lib/server.ts b/lib/server.ts index e6582c2..4dd07e9 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -6,6 +6,7 @@ import express, { Request, Response } from 'express'; import { existsSync, readJson } from "fs-extra"; import http from 'http'; import path from "path"; +import { handleReplacements } from './gateway'; import { readFile } from "./utils/fs"; // the gateway dist path in node_modules @@ -27,7 +28,7 @@ const SOCIAL_CONTRACT = { * @param opts DevOptions * @returns http server */ -export function startDevServer(devJsonPath: string, opts: DevOptions,): http.Server { +export function startDevServer(devJsonPath: string, opts: DevOptions): http.Server { const app = createApp(devJsonPath, opts); const server = http.createServer(app); startServer(server, opts); @@ -143,7 +144,9 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic app.all('/api/proxy-rpc', proxyMiddleware(RPC_URL[opts.network])); if (!opts.NoGateway) { + // do things with gateway const gatewayPath = (opts.gateway && path.resolve(opts.gateway)) ?? GATEWAY_PATH; + // let's check if gateway/dist/index.html exists if (!(existsSync(path.join(gatewayPath, "index.html")))) { log.error("Gateway not found. Skipping..."); @@ -154,7 +157,7 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic if (req.path === "/") { return next(); } - express.static(gatewayPath)(req, res, next); + express.static(gatewayPath)(req, res, next); // serve static files }); app.get("*", (_, res) => { // Inject Gateway with Environment Variables @@ -162,14 +165,8 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic path.join(gatewayPath, "index.html"), "utf8", ).then((data) => { - const envConfig = JSON.stringify({ - bosLoaderWs: `ws://127.0.0.1:${opts.port}`, - bosLoaderUrl: `http://127.0.0.1:${opts.port}/api/loader`, - enableHotReload: opts.NoHot ? false : true, - network: opts.network, - }); - const withEnv = injectHTML(data, { ENV_CONFIG: envConfig }); - res.send(withEnv); + const modifiedDist = handleReplacements(data, opts); + res.send(modifiedDist); }).catch((err) => { log.error(err); return res.status(404).send("Something went wrong."); @@ -214,7 +211,7 @@ export function startServer(server, opts) { │ ➜ Bos Loader WebSocket: \u001b[32mws://127.0.0.1:${opts.port}\u001b[0m │` : "" } - │ ➜ Proxy RPC: \u001b[32mhttp://127.0.0.1:${opts.port}/api/proxy-rpc\u001b[0m │ + │ ➜ Proxy RPC: \u001b[32mhttp://127.0.0.1:${opts.port}/api/proxy-rpc\u001b[0m │ │ │ │ Optionaly, to open local widgets: │ │ 1. Visit either of the following sites: │ @@ -230,11 +227,4 @@ export function startServer(server, opts) { log.error(err.message); process.exit(1); }); -} - -function injectHTML(html: string, injections: Record) { - Object.keys(injections).forEach((key) => { - html = html.replace(`%${key}%`, injections[key]); - }); - return html; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tests/unit/gateway.ts b/tests/unit/gateway.ts new file mode 100644 index 0000000..fe0bc7e --- /dev/null +++ b/tests/unit/gateway.ts @@ -0,0 +1,48 @@ +import { DevOptions } from '@/lib/dev'; +import { Logger, LogLevel } from "@/lib/logger"; +import { handleReplacements } from '@/lib/gateway'; +import { Network } from '@/lib/types'; + +const unmockedLog = global.log; + +describe("gateway", () => { + + beforeEach(() => { + global.log = new Logger(LogLevel.DEV); + }); + + afterEach(() => { + global.log = unmockedLog; + }); + + // Mocked input options + const mockOpts: DevOptions = { + port: 8080, + NoHot: false, + network: 'testnet' as Network, + }; + + // Test replacement of environment configuration + it("should replace the ENV_CONFIG placeholder with correct JSON configuration", () => { + const htmlInput = "%ENV_CONFIG%"; + const expectedConfig = JSON.stringify({ + bosLoaderWs: `ws://127.0.0.1:8080`, + bosLoaderUrl: `http://127.0.0.1:8080/api/loader`, + enableHotReload: !mockOpts.NoHot, + network: mockOpts.network, + }); + const expectedHtmlOutput = `${expectedConfig}`; + + const result = handleReplacements(htmlInput, mockOpts); + expect(result).toBe(expectedHtmlOutput); + }); + + // Test replacement of the near-social-viewer component with an RPC attribute + it("should replace with near-social-viewer having an RPC attribute", () => { + const htmlInput = ""; + const expectedHtmlOutput = ``; + + const result = handleReplacements(htmlInput, mockOpts); + expect(result).toBe(expectedHtmlOutput); + }); +}); \ No newline at end of file