From 3a819c1ae5c35f55a6f515fb1b6d8af91941d3b5 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:43:31 -0700 Subject: [PATCH 1/4] uses web component --- lib/dev.ts | 17 ++- lib/gateway.ts | 122 +++++++++++++------ lib/repository.ts | 2 +- lib/server.ts | 235 ++++++++++++++++++++++++++++--------- lib/utils/fs.ts | 5 +- package.json | 4 +- public/index.html | 266 ++++++++++++++++++++++++++++++++++++++++++ tests/unit/gateway.ts | 82 ++++++++++--- tests/unit/server.ts | 41 ++----- tsconfig.json | 2 +- yarn.lock | 37 +++++- 11 files changed, 659 insertions(+), 154 deletions(-) create mode 100644 public/index.html diff --git a/lib/dev.ts b/lib/dev.ts index 085202b..41a8321 100644 --- a/lib/dev.ts +++ b/lib/dev.ts @@ -1,15 +1,14 @@ -import { readJson, writeJson } from "fs-extra"; import { Gaze } from "gaze"; import path from "path"; import { Server as IoServer } from "socket.io"; -import { buildApp } from "./build"; -import { BaseConfig, loadConfig } from "./config"; -import { startDevServer } from "./server"; -import { startSocket } from "./socket"; -import { Network } from "./types"; -import { loopThroughFiles, readFile } from "./utils/fs"; -import { mergeDeep, substractDeep } from "./utils/objects"; -import { startFileWatcher } from "./watcher"; +import { buildApp } from "@/lib/build"; +import { BaseConfig, loadConfig } from "@/lib/config"; +import { startDevServer } from "@/lib/server"; +import { startSocket } from "@/lib/socket"; +import { Network } from "@/lib/types"; +import { loopThroughFiles, readFile, readJson, writeJson } from "@/lib/utils/fs"; +import { mergeDeep, substractDeep } from "@/lib/utils/objects"; +import { startFileWatcher } from "@/lib/watcher"; var appSrcs = [], appDists = []; var appDevJsons = []; diff --git a/lib/gateway.ts b/lib/gateway.ts index a1ac5e1..aa200d3 100644 --- a/lib/gateway.ts +++ b/lib/gateway.ts @@ -1,21 +1,8 @@ + import { DevOptions } from "./dev"; -import axios from "axios"; import { JSDOM } from "jsdom"; -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({ enableHotReload: opts.hot, @@ -42,30 +29,97 @@ function normalizeHtml(html) { return html.replace(/\s+/g, ' ').trim(); } -const contentCache = {}; - -export async function fetchAndCacheContent(url) { - if (!contentCache[url]) { - const response = await axios.get(url); - contentCache[url] = response.data; - } - return contentCache[url]; -} - -export function modifyIndexHtml(content: string, opts: DevOptions) { +export function modifyIndexHtml(content: string, opts: DevOptions, dependencies: string[]) { const dom = new JSDOM(content); const document = dom.window.document; - const viewer = document.querySelector('near-social-viewer'); + // Add script tags for each dependency + dependencies.forEach((dependency: string) => { + const script = document.createElement('script'); + script.src = dependency; + script.defer = true; + document.head.appendChild(script); + }); + + const elementTag = "near-social-viewer"; + + // Create and configure the near-social-viewer element + const container = document.getElementById("bw-root"); + const element = document.createElement(elementTag); // this could be configurable + element.setAttribute("src", opts.index); + element.setAttribute("rpc", `http://127.0.0.1:${opts.port}/api/proxy-rpc`); + element.setAttribute("network", opts.network); - if (viewer) { - viewer.setAttribute('src', opts.index); - viewer.setAttribute('rpc', `http://127.0.0.1:${opts.port}/api/proxy-rpc`); - viewer.setAttribute('network', opts.network); - if (opts.hot) { - viewer.setAttribute('enablehotreload', ""); + const config = { + dev: { + hotreload: { + enabled: opts.hot + } + }, + vm: { + features: { + enableComponentSrcDataKey: true + } } - } + }; + + element.setAttribute('config', JSON.stringify(config)); + + container.appendChild(element); + + // Add wallet selector + + // Stylesheet + const styleLink = document.createElement('link'); + styleLink.rel = 'stylesheet'; + styleLink.href = 'https://cdn.jsdelivr.net/npm/@near-wallet-selector/modal-ui-js@8.7.2/styles.css'; + document.head.appendChild(styleLink); + + // Import wallets and setup selector + const webcomponentapp = document.createElement('script'); + // We could configure wallets from bos.config.json + webcomponentapp.textContent = ` + import { setupWalletSelector } from "@near-wallet-selector/core"; + import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; + import { setupHereWallet } from "@near-wallet-selector/here-wallet"; + import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; + import { setupSender } from "@near-wallet-selector/sender"; + import { setupNightly } from "@near-wallet-selector/nightly"; + import { setupMintbaseWallet } from "@near-wallet-selector/mintbase-wallet"; + + const selector = await setupWalletSelector({ + network: "${opts.network}", + modules: [ + setupMyNearWallet(), + setupHereWallet(), + setupMeteorWallet(), + setupSender(), + setupNightly(), + setupMintbaseWallet() + ], + }); + + const viewer = document.querySelector("${elementTag}"); + viewer.selector = selector; +`; + webcomponentapp.type = 'module'; + document.body.appendChild(webcomponentapp); return dom.serialize(); -} \ No newline at end of file +} + +// // This should be modified to only run when necessary... only needs to be done when initializing the project +// // or when the dependencies change (which could just be managed by here... really just need to add it to the json.) +// export async function importPackages(html: string): Promise { +// try { +// const { Generator } = await import('@jspm/generator'); +// const generator = new Generator(); +// return await generator.htmlInject(html, { +// trace: true, +// esModuleShims: true +// }); +// } catch (error) { +// console.error('Error importing or using @jspm/generator:', error); +// return html; // Return original HTML if there's an error +// } +// } \ No newline at end of file diff --git a/lib/repository.ts b/lib/repository.ts index 6c36bc1..a0a5d88 100644 --- a/lib/repository.ts +++ b/lib/repository.ts @@ -1,5 +1,5 @@ import { AccountID, Log } from "@/lib/types"; -import { DEFAULT_CONFIG, readConfig } from "./config"; +import { DEFAULT_CONFIG, readConfig } from "@/lib/config"; import fs, { existsSync, outputFile, writeFile } from "fs-extra"; import path from "path"; import { SHA256 } from "crypto-js"; diff --git a/lib/server.ts b/lib/server.ts index 86b39ab..cb190af 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -1,16 +1,38 @@ import { DevJson, DevOptions, addApps } from '@/lib/dev'; import { fetchJson } from "@near-js/providers"; +import axios from 'axios'; import bodyParser from "body-parser"; import { exec } from "child_process"; -import express, { Request, Response } from 'express'; -import { existsSync, readJson, writeJson } from "fs-extra"; +import express, { NextFunction, Request, Response } from 'express'; import http from 'http'; +import httpProxy from 'http-proxy'; +import * as https from 'https'; import path from "path"; -import { fetchAndCacheContent, handleReplacements, modifyIndexHtml } from './gateway'; -import { readFile } from "./utils/fs"; +import { handleReplacements, modifyIndexHtml } from './gateway'; +import { readFile, readJson, promises } from "./utils/fs"; // the gateway dist path in node_modules -const GATEWAY_PATH = path.join(__dirname, "../..", "gateway", "dist"); +export const DEFAULT_LOCAL_GATEWAY_PATH = path.join(__dirname, "../..", "gateway", "dist"); + +export const DEFAULT_REMOTE_GATEWAY_URL = "https://ipfs.web4.near.page/ipfs/bafybeibe63hqugbqr4writdxgezgl5swgujay6t5uptw2px7q63r7crk2q/"; + +const httpsAgent = new https.Agent({ + secureProtocol: 'TLSv1_2_method' +}); + +const proxy = httpProxy.createProxyServer({ + secure: false, + changeOrigin: true, + logLevel: 'debug', +}); + +proxy.on('error', (err, req, res) => { + console.error('Proxy error:', err); + res.status(500).send('Proxy error'); +}); + +let modifiedHtml: string | null = null; +let gatewayInitPromise: Promise | null = null; export const RPC_URL = { mainnet: "https://free.rpc.fastnear.com", @@ -198,17 +220,96 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic */ app.all('/api/proxy-rpc', proxyMiddleware(RPC_URL[opts.network])); - if (opts.gateway) { // Gateway setup, may be string or boolean + if (opts.gateway) { + log.debug("Setting up gateway..."); + if (opts.index) { - /** - * starts gateway from local path - */ - const setupLocalGateway = (gatewayPath: string) => { - if (!existsSync(path.join(gatewayPath, "index.html"))) { - log.error("Gateway not found. Skipping..."); - opts.gateway = false; - return; - } + log.debug("Index provided. Using new gateway setup."); + // use new path + let gatewayUrl = typeof opts.gateway === 'string' ? opts.gateway : DEFAULT_REMOTE_GATEWAY_URL; + const isLocalPath = !gatewayUrl.startsWith('http'); + gatewayUrl = gatewayUrl.replace(/\/$/, ''); // remove trailing slash + opts.gateway = gatewayUrl; // standardize to url string + + initializeGateway(gatewayUrl, isLocalPath, opts, devJsonPath); + + // Middleware to ensure gateway is initialized before handling requests + app.use(async (req, res, next) => { + if (gatewayInitPromise) { + try { + await gatewayInitPromise; + } catch (error) { + return next(error); + } + } + next(); + }); + + app.use(async (req, res, next) => { + try { + if (req.path === '/' || req.path === '/index.html') { + // Serve the modified HTML + res.type('text/html').send(modifiedHtml); + } else if (path.extname(req.path) === '.js' || path.extname(req.path) === '.css') { + // Proxy requests for JS and CSS files + log.debug(`Request for: ${req.path}`); + + if (isLocalPath) { + const fullUrl = path.join(__dirname, gatewayUrl, req.path); + + try { + log.debug(`Attempting to serve file from local path: ${fullUrl}`); + // Attempt to serve the file from the local path + await promises.access(fullUrl); + res.sendFile(fullUrl); + } catch (err) { + if (err.code === 'ENOENT') { + // File not found, continue to next middleware + log.debug(`File not found: ${fullUrl}`); + next(); + } else { + // Other error, handle it + next(err); + } + } + } else { + log.debug(`Proxying request to: ${gatewayUrl}${req.path}`); + // Proxy the request to the remote gateway + proxy.web(req, res, { target: `${gatewayUrl}${req.path}`, agent: httpsAgent }); + } + } else { + // what about images? + // Do we need to express static local bundle if it references images or other assets? + next(); + } + } catch (error) { + next(error); + } + }); + + // To handle client side routing + app.use('*', (req, res) => { + res.type('text/html').send(modifiedHtml); + }); + + // Error handling middleware + app.use((err: Error, req: Request, res: Response, next: NextFunction) => { + log.error(`Error: ${err.message}`); + res.status(500).json({ + error: 'Internal Server Error', + message: err.message + }); + }); + log.success("Gateway setup successfully."); + return app; + } else { + // is single app with no index + // or is a multi app workspace + // (use old path) + + log.debug("No index provided. Using default gateway setup."); + + const gatewayPath = DEFAULT_LOCAL_GATEWAY_PATH; app.use((req, res, next) => { if (req.path !== "/") { @@ -220,54 +321,82 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic app.get("*", (_, res) => { readFile(path.join(gatewayPath, "index.html"), "utf8") .then(data => { - let modifiedDist = modifyIndexHtml(data, opts); - if (gatewayPath === GATEWAY_PATH) { - modifiedDist = handleReplacements(modifiedDist, opts); - } - res.type('text/html').send(modifiedDist); + let modifiedHtml = handleReplacements(data, opts); + res.type('text/html').send(modifiedHtml); }) .catch(err => { log.error(err); res.status(404).send("Something went wrong."); }); }); - }; - - if (typeof opts.gateway === "string") { // Gateway is a string, could be local path or remote url - if (opts.gateway.startsWith("http")) { // remote url (web4) - app.use(async (req, res) => { - try { // forward requests to the web4 bundle - const filePath = req.path; - const ext = path.extname(filePath); - let fullUrl = (opts.gateway as string).replace(/\/$/, ''); // remove trailing slash - - if (ext === '.js' || ext === '.css') { - fullUrl += filePath; - const content = await fetchAndCacheContent(fullUrl); - res.type(ext === '.js' ? 'application/javascript' : 'text/css'); - res.send(content); - } else { - fullUrl += "/index.html"; - let content = await fetchAndCacheContent(fullUrl); - content = modifyIndexHtml(content, opts); - res.type('text/html').send(content); - } - } catch (error) { - log.error(`Error fetching content: ${error}`); - res.status(404).send('Not found'); - } - }); - } else { // local path - setupLocalGateway(path.resolve(opts.gateway)); - } - } else { // Gateway is boolean, setup default gateway - setupLocalGateway(GATEWAY_PATH); } log.success("Gateway setup successfully."); } - return app; -}; +} + +function initializeGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) { + gatewayInitPromise = setupGateway(gatewayUrl, isLocalPath, opts, devJsonPath) + .then(() => { + log.success("Gateway initialized successfully."); + }) + .catch((error) => { + log.error(`Failed to initialize gateway: ${error}`); + throw error; + }); +} + +async function setupGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) { + log.debug(`Setting up ${isLocalPath ? "local " : ""}gateway: ${gatewayUrl}`); + + const manifestUrl = isLocalPath + ? path.join(gatewayUrl, "/asset-manifest.json") + : `${gatewayUrl}/asset-manifest.json`; + + try { + log.debug(`Fetching manifest from: ${manifestUrl}`); + const manifest = await fetchManifest(manifestUrl); + + log.debug(`Received manifest. Modifying HTML...`); + const htmlContent = await readFile(path.join(__dirname, '../../public/index.html'), 'utf8'); + + const dependencies = manifest.entrypoints.map((entrypoint: string) => isLocalPath ? `${entrypoint}` : `${gatewayUrl}/${entrypoint}`); + modifiedHtml = modifyIndexHtml(htmlContent, opts, dependencies); + + // log.debug(`Importing packages...`); <-- this used jpsm to create import map for wallet selector + // modifiedHtml = await importPackages(modifiedHtml); // but didn't want it to run each time dev server started, so commented out + + // // Write the modified HTML to the output path + // const outputDir = path.join(opts.output); + // const outputPath = path.join(outputDir, 'index.html'); + + // // Make sure the directory exists + // await fs.promises.mkdir(outputDir, { recursive: true }); + + // // Write modified html + // await fs.promises.writeFile(outputPath, modifiedHtml); + + // log.debug(`Modified HTML written to: ${outputPath}`); + } catch (error) { + log.error(`Error setting up gateway: ${error}`); + throw error; + } +} + + +async function fetchManifest(url: string): Promise { + try { + if (url.startsWith('http')) { + const response = await axios.get(url); + return response.data; + } else { + return JSON.parse(await readFile(url, 'utf8')); + } + } catch (error) { + log.error(`Error fetching manifest from: ${url}`); + throw new Error('Failed to fetch manifest'); + } +} /** * Starts BosLoader Server and optionally opens gateway in browser diff --git a/lib/utils/fs.ts b/lib/utils/fs.ts index 0b21363..52ef78b 100644 --- a/lib/utils/fs.ts +++ b/lib/utils/fs.ts @@ -1,4 +1,4 @@ -import { copy, ensureDir, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson } from 'fs-extra'; +import { copy, ensureDir, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson, existsSync, promises } from 'fs-extra'; import path from 'path'; async function loopThroughFiles(pwd: string, callback: (file: string) => Promise) { @@ -16,5 +16,4 @@ async function loopThroughFiles(pwd: string, callback: (file: string) => Promise } } -export { copy, ensureDir, loopThroughFiles, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson }; - +export { copy, ensureDir, loopThroughFiles, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson, existsSync, promises }; diff --git a/package.json b/package.json index 89e7a76..77ef515 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "dist", "bin", "gateway/dist/*", - "templates" + "templates", + "public" ], "keywords": [], "main": "./bin/bw.js", @@ -36,6 +37,7 @@ "fs-extra": "^11.2.0", "gaze": "^1.1.3", "glob": "^10.3.10", + "http-proxy": "^1.18.1", "https": "^1.0.0", "joi": "^17.11.0", "jsdom": "^24.1.0", diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3104b59 --- /dev/null +++ b/public/index.html @@ -0,0 +1,266 @@ + + + + {app_name} + + + + + + + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/tests/unit/gateway.ts b/tests/unit/gateway.ts index f5d6ed0..201958d 100644 --- a/tests/unit/gateway.ts +++ b/tests/unit/gateway.ts @@ -2,9 +2,20 @@ import { DevOptions } from '@/lib/dev'; import { handleReplacements, modifyIndexHtml } from '@/lib/gateway'; import { Logger, LogLevel } from "@/lib/logger"; import { Network } from '@/lib/types'; +import { JSDOM } from 'jsdom'; const unmockedLog = global.log; +const baseHtml = ` + + + + +
+ + + `; + describe("gateway", () => { beforeEach(() => { @@ -36,30 +47,65 @@ describe("gateway", () => { 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 = modifyIndexHtml(htmlInput, mockOpts); - expect(result).toBe(expectedHtmlOutput); + it('adds script tags for dependencies', () => { + const dependencies = ['dep1.js', 'dep2.js']; + const result = modifyIndexHtml(baseHtml, mockOpts, dependencies); + const dom = new JSDOM(result); + const scripts = dom.window.document.querySelectorAll('script'); + + expect(scripts[0].src).toBe('dep1.js'); + expect(scripts[1].src).toBe('dep2.js'); + expect(scripts[0].defer).toBe(true); + expect(scripts[1].defer).toBe(true); }); - it("should replace with hotreload attribute if enabled", () => { - mockOpts.hot = true; - const htmlInput = ""; - const expectedHtmlOutput = ``; + it('creates and configures near-social-viewer element', () => { + const result = modifyIndexHtml(baseHtml, mockOpts, []); + const dom = new JSDOM(result); + const viewer = dom.window.document.querySelector('near-social-viewer'); - const result = modifyIndexHtml(htmlInput, mockOpts); - expect(result).toBe(expectedHtmlOutput); + expect(viewer).not.toBeNull(); + expect(viewer.getAttribute('src')).toBe(mockOpts.index); + expect(viewer.getAttribute('rpc')).toBe(`http://127.0.0.1:${mockOpts.port}/api/proxy-rpc`); + expect(viewer.getAttribute('network')).toBe(mockOpts.network); }); - it("should not replace with hotreload attribute if disabled", () => { - mockOpts.hot = false; - const htmlInput = ""; - const expectedHtmlOutput = ``; + it('sets correct config attribute on near-social-viewer', () => { + const result = modifyIndexHtml(baseHtml, mockOpts, []); + const dom = new JSDOM(result); + const viewer = dom.window.document.querySelector('near-social-viewer'); - const result = modifyIndexHtml(htmlInput, mockOpts); - expect(result).toBe(expectedHtmlOutput); + const config = JSON.parse(viewer.getAttribute('config')); + expect(config.dev.hotreload.enabled).toBe(false); + expect(config.vm.features.enableComponentSrcDataKey).toBe(true); + }); + + it('appends near-social-viewer to the container', () => { + const result = modifyIndexHtml(baseHtml, mockOpts, []); + const dom = new JSDOM(result); + const container = dom.window.document.getElementById('bw-root'); + + expect(container.children.length).toBe(1); + expect(container.children[0].tagName).toBe('NEAR-SOCIAL-VIEWER'); + }); + + it('uses provided options correctly', () => { + const customOpts = { + index: 'test.near/widget/index', + port: 4000, + network: 'mainnet' as Network, + hot: false + }; + const result = modifyIndexHtml(baseHtml, customOpts, []); + const dom = new JSDOM(result); + const viewer = dom.window.document.querySelector('near-social-viewer'); + + expect(viewer.getAttribute('src')).toBe(customOpts.index); + expect(viewer.getAttribute('rpc')).toBe(`http://127.0.0.1:${customOpts.port}/api/proxy-rpc`); + expect(viewer.getAttribute('network')).toBe(customOpts.network); + + const config = JSON.parse(viewer.getAttribute('config')); + expect(config.dev.hotreload.enabled).toBe(false); }); }); \ No newline at end of file diff --git a/tests/unit/server.ts b/tests/unit/server.ts index 0fa6243..1374d49 100644 --- a/tests/unit/server.ts +++ b/tests/unit/server.ts @@ -40,6 +40,13 @@ describe('createApp', () => { vol.fromJSON(app_example_1, mockSrc); global.log = new Logger(LogLevel.DEV); + vol.reset(); + vol.fromJSON(app_example_1, mockSrc); + + global.log = new Logger(LogLevel.DEV); + + app = createApp(devJsonPath, opts); + app = createApp(devJsonPath, opts); }); @@ -49,7 +56,7 @@ describe('createApp', () => { global.fetch = unmockedFetch; }); - it('should set up the app correctly when opts.gateway is a valid local path', () => { + it.skip('should set up the app correctly when opts.gateway is a valid local path', () => { const mockGatewayPath = "/mock_gateway_1"; opts.gateway = `${mockGatewayPath}/dist`; vol.mkdirSync(path.join(mockGatewayPath, 'dist'), { recursive: true }); @@ -67,7 +74,7 @@ describe('createApp', () => { .expect('modified'); }); - it('should log an error when opts.gateway is an invalid local path', () => { + it.skip('should log an error when opts.gateway is an invalid local path', () => { const mockGatewayPath = '/invalid/gateway/path'; opts.gateway = mockGatewayPath; @@ -78,36 +85,6 @@ describe('createApp', () => { expect(logSpy).toHaveBeenCalledWith("Gateway not found. Skipping..."); }); - it('should set up the app correctly when opts.gateway is a valid http URL', async () => { - const mockGatewayUrl = 'http://mock-gateway.com'; - opts.gateway = mockGatewayUrl; - - jest.spyOn(gateway, 'fetchAndCacheContent').mockResolvedValue(''); - jest.spyOn(gateway, 'modifyIndexHtml').mockReturnValue('modified'); - - app = createApp(devJsonPath, opts); - expect(app).toBeDefined(); - - const response = await supertest(app).get('/'); - expect(response.status).toBe(200); - expect(response.headers['content-type']).toMatch(/html/); - expect(response.text).toBe('modified'); - }); - - it('should handle errors when fetching content from http gateway', async () => { - const mockGatewayUrl = 'http://mock-gateway.com'; - opts.gateway = mockGatewayUrl; - - jest.spyOn(gateway, 'fetchAndCacheContent').mockRejectedValue(new Error('Fetch error')); - - app = createApp(devJsonPath, opts); - expect(app).toBeDefined(); - - const response = await supertest(app).get('/'); - expect(response.status).toBe(404); - expect(response.text).toBe('Not found'); - }); - it('/api/loader should return devJson', async () => { const response = await supertest(app).get('/api/loader'); expect(response.status).toBe(200); diff --git a/tsconfig.json b/tsconfig.json index a3e8e6b..7b80e88 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "exclude": ["node_modules", "./tests", "./gateway", "./bin", "./jest.config.ts", "./__app_example_1", "./templates"], + "exclude": ["node_modules", "./gateway", "./bin", "./jest.config.ts", "./__app_example_1", "./templates"], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ diff --git a/yarn.lock b/yarn.lock index 81d9ba1..76a15bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1826,6 +1826,11 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + exec-sh@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz" @@ -1964,7 +1969,7 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -follow-redirects@^1.15.6: +follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -2270,6 +2275,15 @@ http-proxy-agent@^7.0.2: agent-base "^7.1.0" debug "^4.3.4" +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" @@ -2288,7 +2302,7 @@ https-proxy-agent@^7.0.4: https@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/https/-/https-1.0.0.tgz" + resolved "https://registry.npmjs.org/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg== human-signals@^2.1.0: @@ -2974,6 +2988,11 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" @@ -3232,6 +3251,15 @@ negotiator@0.6.3: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +nock@^13.5.4: + version "13.5.4" + resolved "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479" + integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + node-fetch@2.6.7: version "2.6.7" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" @@ -3456,6 +3484,11 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" From e6429b13c50f42f43e4135ff663bd76175740ef8 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:49:43 -0700 Subject: [PATCH 2/4] outputs html --- lib/dev.ts | 2 ++ lib/server.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/dev.ts b/lib/dev.ts index 41a8321..2bb4e88 100644 --- a/lib/dev.ts +++ b/lib/dev.ts @@ -24,6 +24,7 @@ export type DevOptions = { network?: Network; // network to use gateway?: string | boolean; // path to custom gateway dist, or false to disable index?: string; // widget to use as index + output?: string; // output directory }; /** @@ -52,6 +53,7 @@ export async function dev(src: string, dest: string, opts: DevOptions) { appDists = [dist]; appDevJsons = [devJson]; appDevJsonPath = devJsonPath; + opts.output = dist; appDevOptions = opts; const server = startDevServer(appSrcs, appDists, appDevJsonPath, appDevOptions); diff --git a/lib/server.ts b/lib/server.ts index cb190af..348388b 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -366,17 +366,17 @@ async function setupGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevO // log.debug(`Importing packages...`); <-- this used jpsm to create import map for wallet selector // modifiedHtml = await importPackages(modifiedHtml); // but didn't want it to run each time dev server started, so commented out - // // Write the modified HTML to the output path - // const outputDir = path.join(opts.output); - // const outputPath = path.join(outputDir, 'index.html'); + // Write the modified HTML to the output path + const outputDir = opts.output; + const outputPath = path.join(outputDir, 'index.html'); - // // Make sure the directory exists - // await fs.promises.mkdir(outputDir, { recursive: true }); + // Make sure the directory exists + await promises.mkdir(outputDir, { recursive: true }); - // // Write modified html - // await fs.promises.writeFile(outputPath, modifiedHtml); + // Write modified html + await promises.writeFile(outputPath, modifiedHtml); - // log.debug(`Modified HTML written to: ${outputPath}`); + log.debug(`Modified HTML written to: ${outputPath}`); } catch (error) { log.error(`Error setting up gateway: ${error}`); throw error; From d42c6c4fd4d0c86ceb8952483065010150deb434 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:50:21 -0700 Subject: [PATCH 3/4] incremenet version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77ef515..99dc15e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bos-workspace", - "version": "1.0.0-alpha.36", + "version": "1.0.0-alpha.37", "description": "", "bin": { "bos-workspace": "./bin/bw.js", From aff589a8153d2ebdc8f1e48e7e2e42fc04d793d6 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:19:32 -0700 Subject: [PATCH 4/4] adds deploying to web4 to readme --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 97499c7..bdc24ff 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,19 @@ It is easy to build and distribute a custom gateway using the [near-bos-webcompo The bos-workspace dev server is specially configured with the near-bos-webcomponent to automatically set the `rpc` attribute with the [proxy-rpc](#proxy-rpc). +## Deploying to Web4 + +If you specify an `index` in your bos.config.json, then bos-workspace will display your widgets through the latest version of [near-bos-webcomponent](https://github.com/nearbuilders/near-bos-webcomponent), or the gateway provided via the `-g` flag. + +This involves some html manipulation in order to set the web component's attributes. The html that is created can be found in the designated destination (defaults to `/build`). This html can be used to easily deploy your site with widgets to [web4](https://github.com/vgrichina/web4). + +1. Be sure to have deployed a web4 smart contract, such as the [web4-min-contract](https://github.com/vgrichina/web4-min-contract) +2. Move the output index.html to your `/public` or `/dist` if not using a bundler. +3. [TEMP] Remove the rpc and config attributes from `near-social-viewer` element. +4. Run [web4 deploy](https://github.com/vgrichina/web4-deploy) with src being the directory that holds this index.html and the account you have a contract deployed to. + +**This is a rough first draft of the implementation and will be improved upon.** + ## Commands You can run `bw` or `bos-workspace` to see the list of commands.