npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@octopwngmbh/comms

v0.0.2

Published

OctoPwn browser comms library (main-thread WebSocket + Worker protocol client)

Readme

OctoPwn Javascript Comms Library

Technical Debt

  • The comms library functions are not named in a conventional way (eg. some functions that would trigger a change don't start with onXXX)
  • The protobuf message types are not stored as enum but string comparison is used, this comes with performance penalty
  • Since this protocol was never refactored, there are functions with overlapping utility (eg. sending a command to be executed on the server can be done with 4 different functions)
  • Documentation needs drastic improvement

Communications

The frontend communicates with the backend (OctoPwn Core) over a websocket connection. The message exchange is based on protobuf messages.

  • The communication MUST always start with authentication. In this repo/demo, NONE auth (empty user/pass) is the primary supported/tested flow. See authenticate(...).
  • After authentication the server will send a massive data packet (onRestoreSession funct, OctoSession packet) containin the descriptors of all current sessions, what clients / scanners / utils etc allowed, all credentials and targets and current sessions

All messages are encapsulated in OctoMessage protobuf message type. This is processed by the onIncomingRawMessage function, which just a small wrapper that deserializes the incoming message then dispatches it to onIncomingMessage function.

Architecture

On a high level the octopwn framework consists of the following components:

  • Core
  • Screen handler
  • UI

Core Components

The Core is built of the following components:

  • Targets: Targets are basically server addresses and some accompaning metadata that helps octopwn's non-ocre features to connect to the target hosts.
  • Credentials: Credentials hold authentication material that is used by the authentication protocol. They are also used to hold user secrets obtained by the modules which cannot be directly used for authentication, but can be cracked or otherwise show an attack's success.
  • Proxies: Basically targets but not to reach hosts rather to reach proxies to reach the hosts using those proxies. In this group we technically differentiate between a Proxy and a Proxychain, latter is merely a list of Proxy IDs that will be connected to when reaching the Target.
  • Clients: Basically protocol clients which have commands that perform certain operations using the given protocol, like invoking the "list" command on an SMB client will list all files/folders on the current share. Clients can be used for the manual pentesting both for attacks, and information gathering, changing configuration on the servers the client connected to. A client is created using a Target and a Credential and optionally a Proxy. Of course depending on the client type eg. DNS credential can be omitted. IMPORTANT: There is one special client session, with sessionID of "0" (named MAIN). This is NOT a protocol client but the octopwn core. Anything that's directly octopwn related is done on this client, for example: if you want to create a new credential / target / proxy / client / scanner etc you'd need to invoke the appropriate create command on this session.
  • Scanners: A scanner performs a simple task on a given protocol depending on the scanner type but does it on multiple targets in parallel. A scanner can be created without any parameters, only the scanner's name. Upon creation the UI will get a descriptor that shows all scanner parameters and their types. Scanners have a minimal set of commands that the UI can (or should) invoke, "scan" starts the scan "stop" stops the scan, "options" lists all scanner parameters with their current values "set" can be used to change a parameter. All parameters are string-serialized on the protocol level, lists are comma separated values. When a scan is started, the core contantly will send messages to the UI reporting progress/finish and streaming scannerdata.
  • Servers: Similar to scanners in operation logic, server sessions can be created without parameters. Parameters can be set after creation, same commands just like scanners, except the "scan" command is replaced by the "serve" command.
  • Attacks: Same as scanners in logic, but performs a given attack.
  • Utils: These are special sessions, most of them are in developement. They can be created without parameters, they have a parameters table just like scanners but it's not always used (highly dependent on what the util does). Example: We have a util that can parse LSASS files.

Screen Handler

This is the communications handler between the UI and the Core. Every time the Core wants to send message to the UI, the screenhandler's appropriate function is called, and the othwer way any time the UI wants to send a command to the Core, the UI's own screenhandler implementation is called, which usually serializes the message and passes it to the Core's screen handler. Exception is if the UI is bundled in to the Core then there is only one screenhandler used, currently there is only one implementation of that which is the terminal-based CLI.

UI

The user interface component is independent from the Core but has its own screen handler to interface with it. While Octopwn comes with a UI, the UI is completely independent from octopwn, this way developers can write their own UI/screenhandler implementations.

Events that MUST be supported by any UI:

  • Generic events:
    • createNewSessionWindow
    • newTargetAdded
    • newTargetsAdded
    • refreshTargets
    • newCredentialAdded
    • newCredentialsAdded
    • refreshCredentials
    • newProxyAdded
    • newProxyAdded (of type chain)
    • changeFavorite
    • showServerMessage
    • updateSyncModal
    • stopSyncModal
    • startSyncModal
    • newTargetPortAdded
    • paramsChanged
  • Session-specific events:
    • onConsoleMessage
    • tabMessage

Command execution: Invoking a command from the frontend will result in a single data packet sent to the server, reply to the command can vary depending on what you invoked. All commands MUST specify a sessionID where the command will be dispatched to. Eg. if you create an SMB client, it will have a unique sessionID, if you want to invoke a command on that session, you'll have to pass the sessionID. Clarification: Every message type has a token field that can be used to track the invocations and singnal success/error/more data expected occurrences.

  • Fire and forget: Command invoked, the server will send back a packet either "OK" "ERR" "CONTINUE" type, but frontend don't care, these will be discarded.

  • Invoke and wait until response: Command invoked, server will will send back a packet either "OK" "ERR" "CONTINUE" type, but this time the javascript function doesn't return until the response has been received. If the response is "ERR" an exception will be thrown with the message the server sent in the "ERR" message. If the response type is "OK", the server MIGHT send additinal data (result) in the SAME PACKET regardles, both ERR and OK signals that there will be no more data sent regarding this invocation. "CONTINUE" packet is not expected in this case, but sometimes I mess up the code, it can be treated as an "OK" without data.

  • Invoke and communicate: Same as previous one, but if a "CONTINUE" is sent from the server, it means that the command has been acknowledged by the server and depending on the command either the server or the client (javascript) can send additional data using the "TOKEN" that's inside of the CONTINUE message.

  • commands can be invoked with

    • runSimpleCommand -> Invoke and wait until response.
    • sendMainSessionCommand -> older version of runSimpleCommand
    • runCommand -> this should not be used, it's old. originally it was used for fire and forget type invocations for terminal console commands
    • sendGenericCommand -> this is the "raw" remote command invocation method, it will return a tokenID and a queue where you can monitor for responses from the serer for that specific command

Framework-friendly (Worker) integration

If you don't want to implement a callback-heavy "UI object" (or deal with protobuf globals), use the Worker-based client:

// In an app, prefer the npm package import:
//   import WorkerClient from "@octopwngmbh/comms";
// In this repo (local dev), you can import from src:
import WorkerClient from "./src/WorkerClient.mjs";

const client = new WorkerClient({
  logger: (level, ...args) => console.log(level, ...args),
});

client.on("targetsAdded", (targets) => console.log("targetsAdded", targets));
client.on("sessionWindowCreated", (s) => console.log("sessionWindowCreated", s));
client.on("windowMessage", (m) => console.log("windowMessage", m));

await client.connect("ws://localhost:8080"); // WebSocket is on the main thread
await client.authenticate("NONE", "user", "");

// MAIN session id = 0
const res = await client.runSimpleCommand(0, "createscanner", ["NMAP", "debug"]);
console.log("result", res);

// Convenience helpers (protobuf messages, not CLI commands)
await client.addTarget("10.10.10.10", { hostname: "dc01.test.lab", dcip: "10.10.10.10", realm: "TEST.LAB" });
await client.addCredential("TEST", "Administrator", "Passw0rd!", "password");
await client.addProxy("SOCKS5", "127.0.0.1", 1080, { description: "local proxy" });
  • WebSocket on main thread: WorkerClient owns the WS connection.
  • Protocol/protobuf in a background Worker: CommsManager runs in src/WorkerCommsManager.mjs.
  • No UI callbacks required: you subscribe to events with client.on(type, handler).

For an LLM-friendly, contract-first description of the protocol and UI event model, see LLM_PROTOCOL_GUIDE.md.

Stable event contract (public API)

Event names are stable and safe for frontend apps to depend on.

  • Server
    • serverInfo: { version, motd, allowedServers, allowedClients, allowedScanners, allowedUtils, allowedAttacks, screensettings, clientDescriptors, scannerDescriptors }
      • The Worker parses the descriptor blobs automatically and emits objectified fields:
        • screensettings: parsed object (plus screensettingsRaw)
        • clientDescriptors: parsed object (plus clientDescriptorsRaw)
        • scannerDescriptors: parsed object (plus scannerDescriptorsRaw)
      • If parsing ever fails (unexpected), the Worker emits workerError and still emits serverInfo with safe default objects.
      • clientDescriptors contains per-client-type creator metadata (used for building UI forms):
        • MANDATORY: keys that must be present in the dictionary/object passed when creating a new client
        • ATYPES: for a given auth type, which secret types are acceptable
        • DEFAULTS: default values the server will assume if the key is omitted/left empty (e.g. PID=0, TIMEOUT=5, PORT=389)

Example: Creating a client using serverInfo.clientDescriptors

High-level flow:

  • Pick a client type from Object.keys(serverInfo.clientDescriptors) (e.g. "SMB", "LDAP", "RDP", ...).
  • Build a dictionary/object that includes the keys listed in descriptor.MANDATORY.
  • Fill missing fields with descriptor.DEFAULTS (optional, but recommended for UI convenience).
  • Call createclient_json on the MAIN session (cid=0):
/**
 * @param {import("./src/WorkerClient.mjs").default} client
 * @param {import("./src/WorkerClient.d.ts").CreateClientParams} cparams
 */
async function createClient(client, cparams) {
  return await client.runSimpleCommand(0, "createclient_json", [JSON.stringify(cparams)]);
}

client.on("serverInfo", async (info) => {
  const desc = info.clientDescriptors["LDAP"];
  const defaults = desc.DEFAULTS || {};

  /** @type {import("./src/WorkerClient.d.ts").CreateClientParams} */
  const cparams = {
    CTYPE: "LDAP",
    ATYPE: "NTLM",
    TID: 123,
    CID: 456,
    PORT: typeof defaults.PORT === "number" ? defaults.PORT : 389,
    TIMEOUT: typeof defaults.TIMEOUT === "number" ? defaults.TIMEOUT : 5,
    PID: typeof defaults.PID === "number" ? defaults.PID : 0,
    SETTINGS: JSON.stringify({ /* client-specific settings */ }),
  };

  // UI should validate: all keys in desc.MANDATORY are present in cparams (non-null)
  const res = await createClient(client, cparams);
  console.log("createclient_json result", res);
});
  • Sessions
    • sessionWindowCreated: { cid, cname, config, allMessagesSynced, startHidden }
      • The Worker parses these automatically and emits an objectified config:
        • config.params: parsed params array
        • config.commandDescriptor: parsed command descriptor object
      • The original JSON strings are preserved for debugging:
        • config.paramsRaw
        • config.commandDescriptorRaw
      • config.commands is the list of documented commands for this session, intended for terminal autocomplete.
      • config.ctype/atype/targetId/credentialId/proxyId exist mainly for legacy CLIENT sessions and are not generally relied on anymore.

Tip: you can still use src/sessionwindow.mjs helpers if you consume raw config from elsewhere, but normal Worker events already include the parsed fields.

Example: ParamManager built from sessionWindowCreated.config.params

config.params is already parsed (no JSON.parse needed). You can build a simple parameter helper like this:

class ParamManager {
  /**
   * @param {import("./src/WorkerClient.mjs").default} client
   * @param {number} cid
   * @param {Array<{Parameter:string, Value:string}>} params
   */
  constructor(client, cid, params) {
    this.client = client;
    this.cid = cid;
    this.params = params || [];
  }

  onParamsChanged(msg) {
    // server sent a full update of params
    this.params = msg;
  }

  get(name) {
    const p = this.params.find(x => x.Parameter === name);
    return p ? p.Value : undefined;
  }

  async set(name, value) {
    // Protocol expects string-serialized values
    await this.client.runSimpleCommand(this.cid, "setparam", [String(name), String(value), "false"]);
  }

  async clear(name) {
    await this.client.runSimpleCommand(this.cid, "clearparam", [String(name)]);
  }

  getInfo(defaultValue = null) {
    const p = this.params.find(x => x.Parameter === "__info");
    return p && p.Description ? p.Description : defaultValue;
  }
}

// Usage
client.on("sessionWindowCreated", ({ cid, config }) => {
  const pm = new ParamManager(client, cid, config.params);
  console.log("timeout:", pm.get("timeout"));
});

client.on("paramsChanged", ({ cid, msg }) => {
  // If you keep a map cid -> ParamManager, call pm.onParamsChanged(msg)
  console.log("paramsChanged", cid, msg);
});