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

@elum/etp-socket

v0.1.2

Published

Socket.IO-like browser client for Elum Transport Protocol

Readme

@elum/etp-socket

Browser ETP client with a Socket.IO-like API. WebSocket, ETP handshake, authentication, reconnect, heartbeat and request timeouts run inside a dedicated Web Worker. The application only receives typed events and request results.

import { io } from "@elum/etp-socket";

type ClientEvents = {
  "message.send": {
    request: { dialog: string; text: string };
    response: { id: string };
  };
};

type ServerEvents = {
  "message.new": { id: string; text: string };
};

const socket = io<ClientEvents, ServerEvents>({
  url: "wss://api.example.com/ws",
  auth: () => sessionStorage.getItem("token") ?? "",
  timeout: 10_000,
  reconnection: true,
});

socket.on("message.new", (message) => console.log(message));

const created = await socket.emit("message.send", {
  dialog: "dialog-id",
  text: "Hello",
});

socket.emit("message.send", { dialog: "dialog-id", text: "Hello" }, (error, response) => {
  if (error) return;
  console.log(response?.id);
});

emit creates an ETP request and resolves only a response carrying the same request ID. The timeout is enforced inside the Worker. disconnect() keeps the Worker for a later connect(), while close() terminates it and rejects pending requests.

Requests during reconnect

The worker queues requests made before the ETP handshake completes or while the transport is disconnected. After authentication and HelloAck, it starts queued requests in insertion order. Request timeouts begin when the first request frame is passed to WebSocket.send, not while waiting for a connection. Calls retain their original Promise or callback; the application does not need to catch socket is not connected and re-emit them.

Reconnection retries are unlimited by default, using the configured exponential backoff and maxDelay. Set reconnection.attempts to a finite number to stop and reject waiting requests with a connection error when that limit is exhausted. With autoConnect: false, requests wait for connect(). With reconnection disabled, automatic retries are disabled and new offline requests wait for a manual connect().

AbortSignal cancels a queued request without sending it. disconnect() rejects waiting requests with closed; close() rejects them with terminated. Authentication rejection, an auth-provider failure, or a fatal protocol error rejects the queue immediately and prevents new calls until an explicit connect() starts a new attempt.

The queue is held in memory in this worker/session; it is not a persistent outbox. It defaults to 256 requests and a conservative 64 MiB payload budget. Exceeding these limits rejects the new request with a protocol error. Configure the limits when constructing the socket:

const socket = io({
  url: "wss://api.example.com/ws",
  maxQueuedRequests: 256,
  maxQueuedBytes: 64 << 20,
});

On a transport loss the worker returns unsent requests (including rate-limited frames) to the queue. An inline request already passed to WebSocket.send is not replayed: the server may have committed it before the reply was lost. Its Promise/callback receives a closed error. Application-level idempotency keys are needed to retry those operations safely. Binary transfers continue using negotiated ETP transfer resume; this is separate from replaying an RPC.

Server requests use the same event API and an acknowledgment callback:

socket.on("client.confirm", (request, respond) => {
  respond({ accepted: true });
});

File, Blob, FormData, arrays of files, and objects containing files are automatically sent as ETP multipart transfers. Small JSON stays inline; large JSON automatically switches to chunked transfer without changing the API.

await socket.emit("attach.upload", {
  dialog: "dialog-id",
  files: [fileA, fileB],
});

The client supports authentication identity, capability negotiation, request and response correlation, ACK/NACK retry, receiver windows, SHA-256 verification, cancellation, transfer progress, reconnect resume, heartbeat, protocol errors, text frames, and graceful drain/close. Protocol limits are configurable:

const socket = io<ClientEvents, ServerEvents>({
  url: "wss://api.example.com/ws",
  auth: getToken,
  protocol: {
    chunkSize: 16 << 10,
    maxTransferBytes: 64 << 20,
    maxConcurrentTransfers: 16,
    maxInFlightChunks: 16,
    heartbeatInterval: 10_000,
    heartbeatTimeout: 20_000,
    ackTimeout: 2_000,
    retryLimit: 3,
    maxRequestsPerSecond: 200,
    maxFramesPerSecond: 2_000,
    maxBytesPerSecond: 64 << 20,
    checksum: true,
  },
});

During HelloAck, the Go server advertises its request, frame, and byte token buckets. The worker paces its outbound queues automatically; file chunks consume frame/byte quota but not logical-request quota, and realtime/control traffic is scheduled ahead of bulk chunks. These client checks improve behavior only: the server enforces every advertised limit even when a modified client ignores them.

Browser demo

Run the local Go ETP server and Vite client together:

npm run demo

Open http://127.0.0.1:5173. The demo supports ping/pong requests, generated 8-128 MiB test files, regular file selection, chunk progress, SHA-256 verification, and ping requests during an active upload.

Checks

npm run check
npm test
npm run test:coverage
npm run test:go
npm run build
npm run package:check

Publishing

Package releases are published by .github/workflows/publish.yml. Create a GitHub Release with a SemVer tag carrying a v prefix, for example v0.0.1. The workflow writes that tag version into the npm manifest and lockfile, then validates TypeScript, unit tests, the production build, and the npm tarball before publishing. Go conformance remains available as npm run test:go and requires a matching local go-etp checkout.

For the first release, add an npm granular access token with package publish permission and 2FA bypass as the NPM_TOKEN repository secret. After the package exists, configure npm Trusted Publishing for elum-utils/etp-socket and workflow publish.yml; the workflow already grants the required OIDC permission and publishes with provenance. The token secret can then be removed.