Skip to content

Commit

Permalink
feat: allow to open the interface with no initial package
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreDemailly committed Jan 23, 2025
1 parent 96e4983 commit 1ab3104
Show file tree
Hide file tree
Showing 15 changed files with 121 additions and 14 deletions.
1 change: 1 addition & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ prog
.command("open [json]")
.describe(i18n.getTokenSync("cli.commands.open.desc"))
.option("-p, --port", i18n.getTokenSync("cli.commands.open.option_port"), process.env.PORT)
.option("-f, --fresh-start", i18n.getTokenSync("cli.commands.open.option_fresh_start"), process.env.PORT)
.action(commands.http.start);

prog
Expand Down
3 changes: 2 additions & 1 deletion docs/cli/open.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ $ nsecure open [json]

> [!NOTE]
> If the `[json]` property is omitted, the command will default to searching for a `nsecure-result.json` file in the current working directory.
> If no `nsecure-result.json` file is found, it will behave as same as with `--fresh-start` option.
## ⚙️ Available Options

| Name | Shortcut | Default Value | Description |
|---|---|---|---|
| `--port` | `-p` | `process.env.PORT` | Specify the port on which the HTTP server should run. |

| `--fresh-start` | `-f` | `false` | Open the UI with no initial package. Also, the app will use a dedicated cache. |
3 changes: 2 additions & 1 deletion i18n/english.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const cli = {
},
open: {
desc: "Run an HTTP Server with a given nsecure JSON file",
option_port: "Define the running port"
option_port: "Define the running port",
option_fresh_start: "Launch the server from scratch, ignoring any existing payload file"
},
verify: {
desc: "Run a complete advanced analysis for a given npm package",
Expand Down
3 changes: 2 additions & 1 deletion i18n/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const cli = {
},
open: {
desc: "Démarre un serveur HTTP avec un fichier .json nsecure donné",
option_port: "Port à utiliser"
option_port: "Port à utiliser",
option_fresh_start: "Lance le serveur à partir de zéro, en ignorant tout fichier de payload existant"
},
verify: {
desc: "Démarre une analyse AST avancée pour un package npm donné",
Expand Down
18 changes: 18 additions & 0 deletions public/components/navigation/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,22 @@ export class ViewNavigation {
this.setNewActiveMenu(selectedNav);
}
}

hideMenu(menuName) {
const menu = this.menus.get(menuName);
if (!menu) {
return;
}

menu.classList.add("hidden");
}

showMenu(menuName) {
const menu = this.menus.get(menuName);
if (!menu) {
return;
}

menu.classList.remove("hidden");
}
}
4 changes: 4 additions & 0 deletions public/components/searchbar/searchbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ div.search-result-pannel .package+.package {
box-shadow: 2px 1px 10px #26107f7a;
}

#search-nav:has(#searchbar[style*="display: none;"]) {
display: none;
}

#search-nav .search-result-pannel .package {
height: 30px;
color: rgb(229, 229, 229);
Expand Down
4 changes: 4 additions & 0 deletions public/core/search-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ function initPackagesNavigation(data) {
classList: ["packages"]
});

if (packages.length === 0) {
return fragment;
}

for (const pkg of packages) {
const { name, version, local } = parseNpmSpec(pkg);

Expand Down
13 changes: 13 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ async function init(options = {}) {
});
await secureDataSet.init();

if (secureDataSet.data === null) {
window.navigation.hideMenu("network--view");
window.navigation.hideMenu("home--view");
window.navigation.setNavByName("search--view");

searchview ??= new SearchView(null, null);

return;
}

window.navigation.showMenu("network--view");
window.navigation.showMenu("home--view");

window.vulnerabilityStrategy = secureDataSet.data.vulnerabilityStrategy;

// Initialize vis Network
Expand Down
15 changes: 13 additions & 2 deletions src/commands/http.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Import Node.js Dependencies
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";

// Import Third-party Dependencies
import * as SemVer from "semver";
Expand All @@ -9,6 +10,7 @@ import * as i18n from "@nodesecure/i18n";

// Import Internal Dependencies
import { buildServer } from "../http-server/index.js";
import { appCache } from "../http-server/cache.js";

// CONSTANTS
const kRequiredScannerRange = ">=5.1.0";
Expand All @@ -18,6 +20,7 @@ export async function start(
options = {}
) {
const port = Number(options.port);
const freshStart = Boolean(options.f);
const fileExtension = path.extname(payloadFileBasename);
if (fileExtension !== ".json" && fileExtension !== "") {
throw new Error("You must provide a JSON file (scanner payload) to open");
Expand All @@ -27,10 +30,18 @@ export async function start(
process.cwd(),
fileExtension === "" ? `${payloadFileBasename}.json` : payloadFileBasename
);
assertScannerVersion(dataFilePath);
const dataFilePathExists = fs.existsSync(dataFilePath);
const runFromPayload = dataFilePathExists && freshStart === false;
if (runFromPayload) {
assertScannerVersion(dataFilePath);
}
else {
appCache.prefix = crypto.randomBytes(4).toString("hex");
}

const httpServer = buildServer(dataFilePath, {
port: Number.isNaN(port) ? 0 : port
port: Number.isNaN(port) ? 0 : port,
runFromPayload
});

for (const eventName of ["SIGINT", "SIGTERM"]) {
Expand Down
28 changes: 23 additions & 5 deletions src/http-server/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const CACHE_PATH = path.join(os.tmpdir(), "nsecure-cli");
export const DEFAULT_PAYLOAD_PATH = path.join(process.cwd(), "nsecure-result.json");

class _AppCache {
prefix = "";
startFromZero = false;

constructor() {
fs.mkdirSync(kPayloadsPath, { recursive: true });
}
Expand Down Expand Up @@ -58,12 +61,12 @@ class _AppCache {
}

async updatePayloadsList(payloadsList) {
await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify(payloadsList));
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));
}

async payloadsList() {
try {
const { data } = await cacache.get(CACHE_PATH, kPayloadsCache);
const { data } = await cacache.get(CACHE_PATH, `${this.prefix}${kPayloadsCache}`);

return JSON.parse(data.toString());
}
Expand All @@ -75,6 +78,21 @@ class _AppCache {
}

async #initDefaultPayloadsList() {
if (this.startFromZero) {
const payloadsList = {
lru: [],
current: null,
older: [],
lastUsed: {},
root: null
};

logger.info(`[cache|init](startFromZero)`);
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));

return;
}

const payload = JSON.parse(fs.readFileSync(DEFAULT_PAYLOAD_PATH, "utf-8"));
const version = Object.keys(payload.dependencies[payload.rootDependencyName].versions)[0];
const formatted = `${payload.rootDependencyName}@${version}`;
Expand All @@ -89,7 +107,7 @@ class _AppCache {
};

logger.info(`[cache|init](dep: ${formatted}|version: ${version}|rootDependencyName: ${payload.rootDependencyName})`);
await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify(payloadsList));
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));
this.updatePayload(formatted, payload);
}

Expand All @@ -98,7 +116,7 @@ class _AppCache {

try {
// prevent re-initialization of the cache
await cacache.get(CACHE_PATH, kPayloadsCache);
await cacache.get(CACHE_PATH, `${this.prefix}${kPayloadsCache}`);

return;
}
Expand All @@ -116,7 +134,7 @@ class _AppCache {
logger.info(`[cache|init](packagesInFolder: ${packagesInFolder})`);
}

await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify({
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify({
older: packagesInFolder,
current: null,
lru: []
Expand Down
7 changes: 7 additions & 0 deletions src/http-server/endpoints/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { logger } from "../logger.js";
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");

export async function get(_req, res) {
if (appCache.startFromZero) {
logger.info("[data|get](no content)");
send(res, 204);

return;
}

try {
const { current, lru } = await appCache.payloadsList();
logger.info(`[data|get](current: ${current})`);
Expand Down
13 changes: 10 additions & 3 deletions src/http-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,24 @@ import * as report from "./endpoints/report.js";
import * as middleware from "./middleware.js";
import * as wsHandlers from "./websocket/index.js";
import { logger } from "./logger.js";
import { appCache } from "./cache.js";

export function buildServer(dataFilePath, options = {}) {
const httpConfigPort = typeof options.port === "number" ? options.port : 0;
const openLink = typeof options.openLink === "boolean" ? options.openLink : true;
const enableWS = options.enableWS ?? process.env.NODE_ENV !== "test";

fs.accessSync(dataFilePath, fs.constants.R_OK | fs.constants.W_OK);
const runFromPayload = options.runFromPayload ?? true;

const httpServer = polka();

httpServer.use(middleware.buildContextMiddleware(dataFilePath));
if (runFromPayload) {
fs.accessSync(dataFilePath, fs.constants.R_OK | fs.constants.W_OK);
httpServer.use(middleware.buildContextMiddleware(dataFilePath));
}
else {
appCache.startFromZero = true;
}

httpServer.use(middleware.addStaticFiles);
httpServer.get("/", root.get);

Expand Down
12 changes: 12 additions & 0 deletions src/http-server/websocket/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export async function search(ws, pkg) {
await appCache.updatePayloadsList(updatedList);
ws.send(JSON.stringify(cache));

if (appCache.startFromZero) {
ws.send(JSON.stringify({
status: "RELOAD",
...updatedList
}));
appCache.startFromZero = false;
}

return;
}

Expand All @@ -41,6 +49,8 @@ export async function search(ws, pkg) {
...updatedList
}));

appCache.startFromZero = false;

return;
}

Expand Down Expand Up @@ -76,6 +86,8 @@ export async function search(ws, pkg) {
...updatedList
}));

appCache.startFromZero = false;

logger.info(`[ws|search](data sent to client|cache: updated)`);
}
}
7 changes: 6 additions & 1 deletion workspaces/vis-network/src/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,14 @@ export default class NodeSecureDataSet extends EventTarget {
}

this.FLAGS = FLAGS;
this.warnings = data.warnings;
this.data = data;

if (data === null) {
return;
}

this.warnings = data.warnings;

const dataEntries = Object.entries(data.dependencies);
this.dependenciesCount = dataEntries.length;

Expand Down
4 changes: 4 additions & 0 deletions workspaces/vis-network/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export async function getJSON(path, customHeaders = Object.create(null)) {
};
}

if (raw.status === 204) {
return null;
}

return raw.json();
}

Expand Down

0 comments on commit 1ab3104

Please sign in to comment.