Skip to content

Commit

Permalink
Merge branch 'main' of github.com:NEARBuilders/bos-workspace into 100…
Browse files Browse the repository at this point in the history
…-implement-upload-command
  • Loading branch information
bb-face committed Aug 9, 2024
2 parents 65f190f + 0def6ee commit 179516b
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 66 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ A fully featured config may look like this:
"uploadApi": "https://ipfs.near.social/add",
"uploadApiHeaders": {},
},
"gateway": {
"bundleUrl": "https://ipfs.web4.near.page/ipfs/bafybeibe63hqugbqr4writdxgezgl5swgujay6t5uptw2px7q63r7crk2q/",
"tagName": "near-social-viewer"
}
}
```

Expand All @@ -131,6 +135,9 @@ The `bos.config.json` file consists of a base configuration that defines default
* `format`: (Optional) Indicates whether to format code on build. Default value is `true`.
* `aliases`: (Optional) Provides a list of alias files to use for replacing network-specific values with correct overrides.
* `index`: (Optional) Default widget src to use when using a custom gateway dist.
* `gateway`: (Optional) Configures gateway object.
* `bundleUrl`: gateway url.
* `tagName`: element tag name.

---

Expand Down Expand Up @@ -229,14 +236,13 @@ Running the bos-workspace dev server will start a local gateway with a standard
bw dev --no-gateway
```

However, there is an option to override this default gateway with a custom `/dist`. This is helpful when building widgets that utilize [custom VM elements](https://github.com/NEARBuilders/near-bos-webcomponent?tab=readme-ov-file#configuring-vm-custom-elements). To use this feature, use the `-g` flag with a path to the local custom distribution or link to package published on [nearfs](https://github.com/vgrichina/nearfs) or via cdn:
However, there is an option to override this default gateway with a custom `/dist`. This is helpful when building widgets that utilize [custom VM elements](https://github.com/NEARBuilders/near-bos-webcomponent?tab=readme-ov-file#configuring-vm-custom-elements). To use this feature, specify the gateway bundle url and the tag name in the `bos.config.json` file.

```cmd
bw dev -g path/to/dist
```

```cmd
bw dev -g https://ipfs.web4.near.page/ipfs/bafybeiancp5im5nfkdki3cfvo7ownl2knjovqh7bseegk4zvzsl4buryoi
"gateway": {
"bundleUrl": "https://ipfs.web4.near.page/ipfs/bafybeibe63hqugbqr4writdxgezgl5swgujay6t5uptw2px7q63r7crk2q/",
"tagName": "near-social-viewer"
}
```

This will automatically start the local gateway serving your widgets through the provided dist.
Expand Down
6 changes: 5 additions & 1 deletion examples/single/bos.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
"aliases": ["./aliases.testnet.json"],
"index": "quickstart.testnet/widget/home"
}
}
},
"gateway": {
"bundleUrl": "https://ipfs.web4.near.page/ipfs/bafybeibe63hqugbqr4writdxgezgl5swgujay6t5uptw2px7q63r7crk2q/",
"tagName": "near-social-viewer"
}
}
2 changes: 0 additions & 2 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ async function run() {
.option("-n, --network <network>", "network to build for", "mainnet")
.option("-l, --loglevel <loglevel>", "log level (ERROR, WARN, INFO, DEV, BUILD, DEBUG)", "DEV")
.option("-p, --port <port>", "Port to run the server on", "8080")
.option("-g, --gateway <gateway>", "Path to custom gateway dist", true)
.option("--no-gateway", "Disable the gateway")
.option("--no-hot", "Disable hot reloading")
.option("--no-open", "Disable opening the browser")
Expand Down Expand Up @@ -62,7 +61,6 @@ async function run() {
.option("-n, --network <network>", "network to build for", "mainnet")
.option("-l, --loglevel <loglevel>", "log level (ERROR, WARN, INFO, DEV, BUILD, DEBUG)")
.option("-p, --port <port>", "Port to run the server on", "8080")
.option("-g, --gateway <gateway>", "Path to custom gateway dist", true)
.option("--no-gateway", "Disable the gateway")
.option("--no-hot", "Disable hot reloading")
.option("--no-open", "Disable opening the browser")
Expand Down
8 changes: 7 additions & 1 deletion lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Joi from 'joi';
import { readJson } from '@/lib/utils/fs';
import { Network } from './types';
import path from 'path';
import { GatewayConfig } from './dev';

export interface BaseConfig {
account?: string; // default account to serve widgets from
Expand All @@ -23,6 +24,7 @@ export interface BaseConfig {
data?: {
include: string[]; // specify folder's array to upload along with widget
}
gateway?: GatewayConfig // gateway config object
}

interface NetworkConfig {
Expand Down Expand Up @@ -67,7 +69,11 @@ const baseConfigSchema = Joi.object({
index: Joi.string().allow(null),
data: Joi.object({
include: Joi.array().items(Joi.string()).min(1).required()
}).optional()
}).optional(),
gateway: Joi.object({
tagName: Joi.string(),
bundleUrl: Joi.string(),
}).and('tagName', 'bundleUrl').allow(null),
});

const networkConfigSchema = Joi.object({
Expand Down
42 changes: 39 additions & 3 deletions lib/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,28 @@ var appDevOptions: null | DevOptions = null;
let io: null | IoServer = null;
let fileWatcher: null | FSWatcher = null;

export const DEFAULT_GATEWAY = {
enabled: true,
bundleUrl: "https://ipfs.web4.near.page/ipfs/bafybeibe63hqugbqr4writdxgezgl5swgujay6t5uptw2px7q63r7crk2q/",
tagName: "near-social-viewer"
};

export type DevOptions = {
port?: number; // port to run dev server
hot?: boolean; // enable hot reloading
open?: boolean; // open browser
network?: Network; // network to use
gateway?: string | boolean; // path to custom gateway dist, or false to disable
gateway?: boolean; // path to custom gateway dist, or false to disable
index?: string; // widget to use as index
output?: string; // output directory
};

export type GatewayConfig = {
enabled: boolean;
tagName: string;
bundleUrl: string;
};

/**
* Build and watch app according to bos.config.json
*
Expand All @@ -38,8 +50,10 @@ export async function dev(src: string, dest: string, opts: DevOptions) {
const dist = path.join(src, dest);
const devJsonPath = path.join(dist, "bos-loader.json");

// Build the app for the first time
// Build the app for the first timo

const config = await loadConfig(src, opts.network);

let devJson = await generateApp(src, dist, config, opts);
await writeJson(devJsonPath, devJson);

Expand All @@ -55,7 +69,10 @@ export async function dev(src: string, dest: string, opts: DevOptions) {
appDevJsonPath = devJsonPath;
opts.output = dist;
appDevOptions = opts;
const server = startDevServer(appSrcs, appDists, appDevJsonPath, appDevOptions);

const gatewayObject: GatewayConfig = buildGatewayObject(opts.gateway, config.gateway)

const server = startDevServer(appSrcs, appDists, appDevJsonPath, appDevOptions, gatewayObject);

// Start the socket server if hot reload is enabled
if (opts.hot) {
Expand Down Expand Up @@ -236,3 +253,22 @@ async function generateDevJson(src: string, config: BaseConfig): Promise<DevJson

return devJson;
}

export function buildGatewayObject(commandGateway, configGateway) {
// Gateway logic:
// if --no-gateway is provided (== commandGateway = false), gateway should be disabled ("");
// if --no-gateway is not provided, gateway is enabled, takes bundleUrl and tagName from `bos.config.json`
// if --no-gateway is not provided but there's no gateway configuration in `bos.config.json`, gateway should be the default gateway object;
const gatewayObject = DEFAULT_GATEWAY;

gatewayObject.enabled = commandGateway;

if (configGateway?.bundleUrl && configGateway?.tagName) {
gatewayObject.bundleUrl = configGateway.bundleUrl;
gatewayObject.tagName = configGateway.tagName;
}

gatewayObject.bundleUrl = gatewayObject.bundleUrl.replace(/\/$/, ''); // remove trailing slash

return gatewayObject
}
6 changes: 3 additions & 3 deletions lib/gateway.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { DevOptions } from "./dev";
import { DevOptions, GatewayConfig } from "./dev";

import { JSDOM } from "jsdom";

Expand Down Expand Up @@ -29,7 +29,7 @@ function normalizeHtml(html) {
return html.replace(/\s+/g, ' ').trim();
}

export function modifyIndexHtml(content: string, opts: DevOptions, dependencies: string[]) {
export function modifyIndexHtml(content: string, opts: DevOptions, dependencies: string[], gateway: GatewayConfig) {
const dom = new JSDOM(content);
const document = dom.window.document;

Expand All @@ -41,7 +41,7 @@ export function modifyIndexHtml(content: string, opts: DevOptions, dependencies:
document.head.appendChild(script);
});

const elementTag = "near-social-viewer";
const elementTag = gateway.tagName;

// Create and configure the near-social-viewer element
const container = document.getElementById("bw-root");
Expand Down
56 changes: 27 additions & 29 deletions lib/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DevJson, DevOptions, addApps } from '@/lib/dev';
import { DEFAULT_GATEWAY, DevJson, DevOptions, GatewayConfig, addApps } from '@/lib/dev';
import { fetchJson } from "@near-js/providers";
import axios from 'axios';
import bodyParser from "body-parser";
Expand All @@ -14,8 +14,6 @@ import { readFile, readJson, promises } from "./utils/fs";
// the gateway dist path in node_modules
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'
});
Expand Down Expand Up @@ -48,12 +46,13 @@ export const SOCIAL_CONTRACT = {
* Starts the dev server
* @param devJsonPath path to json redirect map
* @param opts DevOptions
* @param gateway gateway
* @returns http server
*/
export function startDevServer(srcs: string[], dists: string[], devJsonPath: string, opts: DevOptions): http.Server {
const app = createApp(devJsonPath, opts);
export function startDevServer(srcs: string[], dists: string[], devJsonPath: string, opts: DevOptions, gateway: GatewayConfig = DEFAULT_GATEWAY): http.Server {
const app = createApp(devJsonPath, opts, gateway);
const server = http.createServer(app);
startServer(server, opts, () => {
startServer(server, opts, gateway, () => {
const postData = JSON.stringify({ srcs: srcs.map((src) => path.resolve(src)), dists: dists.map((dist) => path.resolve(dist)) });
const options = {
hostname: '127.0.0.1',
Expand Down Expand Up @@ -96,8 +95,9 @@ export function startDevServer(srcs: string[], dists: string[], devJsonPath: str
* (separated out to enable endpoint testing)
* @param opts
* @param devJsonPath
* @param gateway
*/
export function createApp(devJsonPath: string, opts: DevOptions): Express.Application {
export function createApp(devJsonPath: string, opts: DevOptions, gateway: GatewayConfig): Express.Application {
const app = express();

log.success("HTTP server setup successfully.");
Expand Down Expand Up @@ -220,18 +220,15 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic
*/
app.all('/api/proxy-rpc', proxyMiddleware(RPC_URL[opts.network]));

if (opts.gateway) {
if (gateway.enabled) {
log.debug("Setting up gateway...");
if (opts.index) {

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
const isLocalPath = !gateway.bundleUrl.startsWith('http');

initializeGateway(gatewayUrl, isLocalPath, opts, devJsonPath);
initializeGateway(gateway, isLocalPath, opts, devJsonPath);

// Middleware to ensure gateway is initialized before handling requests
app.use(async (req, res, next) => {
Expand All @@ -255,7 +252,7 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic
log.debug(`Request for: ${req.path}`);

if (isLocalPath) {
const fullUrl = path.join(__dirname, gatewayUrl, req.path);
const fullUrl = path.join(__dirname, gateway.bundleUrl, req.path);

try {
log.debug(`Attempting to serve file from local path: ${fullUrl}`);
Expand All @@ -273,9 +270,9 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic
}
}
} else {
log.debug(`Proxying request to: ${gatewayUrl}${req.path}`);
log.debug(`Proxying request to: ${gateway.bundleUrl}${req.path}`);
// Proxy the request to the remote gateway
proxy.web(req, res, { target: `${gatewayUrl}${req.path}`, agent: httpsAgent });
proxy.web(req, res, { target: `${gateway.bundleUrl}${req.path}`, agent: httpsAgent });
}
} else {
// what about images?
Expand Down Expand Up @@ -335,8 +332,8 @@ export function createApp(devJsonPath: string, opts: DevOptions): Express.Applic
return app;
}

function initializeGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) {
gatewayInitPromise = setupGateway(gatewayUrl, isLocalPath, opts, devJsonPath)
function initializeGateway(gateway: GatewayConfig, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) {
gatewayInitPromise = setupGateway(gateway, isLocalPath, opts, devJsonPath)
.then(() => {
log.success("Gateway initialized successfully.");
})
Expand All @@ -346,12 +343,12 @@ function initializeGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevOp
});
}

async function setupGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) {
log.debug(`Setting up ${isLocalPath ? "local " : ""}gateway: ${gatewayUrl}`);
async function setupGateway(gateway: GatewayConfig, isLocalPath: boolean, opts: DevOptions, devJsonPath: string) {
log.debug(`Setting up ${isLocalPath ? "local " : ""}gateway: ${gateway.bundleUrl}`);

const manifestUrl = isLocalPath
? path.join(gatewayUrl, "/asset-manifest.json")
: `${gatewayUrl}/asset-manifest.json`;
? path.join(gateway.bundleUrl, "/asset-manifest.json")
: `${gateway.bundleUrl}/asset-manifest.json`;

try {
log.debug(`Fetching manifest from: ${manifestUrl}`);
Expand All @@ -360,8 +357,8 @@ async function setupGateway(gatewayUrl: string, isLocalPath: boolean, opts: DevO
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);
const dependencies = manifest.entrypoints.map((entrypoint: string) => isLocalPath ? `${entrypoint}` : `${gateway.bundleUrl}/${entrypoint}`);
modifiedHtml = modifyIndexHtml(htmlContent, opts, dependencies, gateway);

// 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
Expand Down Expand Up @@ -402,10 +399,11 @@ async function fetchManifest(url: string): Promise<any> {
* Starts BosLoader Server and optionally opens gateway in browser
* @param server http server
* @param opts DevOptions
* @param gateway gateway object
*/
export function startServer(server, opts, sendAddApps) {
export function startServer(server, opts, gateway, sendAddApps) {
server.listen(opts.port, "127.0.0.1", () => {
if (opts.gateway && opts.open) {
if (gateway.enabled && opts.open) {
// open gateway in browser
let start =
process.platform == "darwin"
Expand All @@ -419,7 +417,7 @@ export function startServer(server, opts, sendAddApps) {
log.log(`
┌─────────────────────────────────────────────────────────────┐
│ BosLoader Server is Up and Running │
│ │${opts.gateway
│ │${gateway.enabled
? `
│ ➜ Local Gateway: \u001b[32mhttp://127.0.0.1:${opts.port}\u001b[0m │`
: ""
Expand Down Expand Up @@ -451,4 +449,4 @@ export function startServer(server, opts, sendAddApps) {
process.exit(1);
}
});
}
}
8 changes: 5 additions & 3 deletions tests/unit/dev.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { buildApp } from "@/lib/build";
import { DEFAULT_CONFIG, loadConfig } from "@/lib/config";
import { dev, DevOptions, addApps } from "@/lib/dev";
import { dev, DevOptions, addApps, DEFAULT_GATEWAY } from "@/lib/dev";
import { Logger, LogLevel } from "@/lib/logger";
import { startDevServer } from "@/lib/server";
import { startSocket } from "@/lib/socket";
Expand Down Expand Up @@ -56,11 +56,13 @@ describe("dev", () => {
expect(loadConfig).toHaveBeenCalledWith(mockSrc, mockOpts.network);
});

it("should call generateApp with src, dist, config, opts, and devJsonPath", async () => {
it("should call generateApp with src, dist, config, opts, gateway, and devJsonPath", async () => {
await dev(mockSrc, "build", mockOpts);
const mockDist = path.join(mockSrc, 'build');
const mockDevJsonPath = path.join(mockSrc, 'build', 'bos-loader.json');
expect(startDevServer).toHaveBeenCalledWith([mockSrc], [mockDist], mockDevJsonPath, mockOpts);
const mockGateway = DEFAULT_GATEWAY;

expect(startDevServer).toHaveBeenCalledWith([mockSrc], [mockDist], mockDevJsonPath, mockOpts, mockGateway);
});

it("should start the socket server if hot reload is enabled", async () => {
Expand Down
Loading

0 comments on commit 179516b

Please sign in to comment.