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

gluer-js

v1.3.17

Published

Browser/JavaScript SDK for Gluer, a managed real-time integration layer: WebSocket messaging with RPC, pub/sub, automatic reconnect, offline queue and HTTP fallback.

Readme

gluer-js

Browser/JavaScript SDK for Gluer — a managed real-time integration layer that connects frontends, backends, webhooks and local LLMs through a single message bus.

npm license

What problem it solves

Wiring a frontend to a backend usually means inventing plumbing: an HTTP API, a WebSocket server, reconnect logic, a queue for messages sent while offline, and a second code path for when the socket is down.

Gluer replaces that plumbing with a hosted mesh. Every participant — browsers, mobile apps, backend workers in any language, third-party webhooks, locally running LLMs — connects to the same bus and addresses the others by project / plugin / action. gluer-js is the client for the browser side of that bus:

browser (gluer-js)  ─┐
mobile app          ─┤
webhook / REST call ─┼──►  Gluer mesh (wss://ws.gluer.io)  ──►  backend worker
local LLM           ─┘                                          (gluer-go, gluer-python,
                                                                 gluer-nodejs, gluer-elx…)

This SDK gives you: a persistent WebSocket connection with automatic reconnect and heartbeat, request/response (RPC) calls over that socket, pub/sub channel subscriptions that survive reconnects, an offline message queue, and an automatic HTTP fallback when the socket is unavailable.

Install

npm install gluer-js

The package ships ESM (dist/gluer-js.module.js), CommonJS (dist/gluer-js.cjs) and UMD (dist/gluer-js.umd.js) builds, so a bundler-based app can simply import it.

The UMD build does not inline dependencies: loading it directly from a CDN also requires uuid and @msgpack/msgpack to be available as the globals uuid and msgpack, and the singleton is then reached as gluerJs.default (the named export is gluerJs.GluerJS). Using a bundler is the supported path.

Requirements: an environment with WebSocket, fetch and EventTarget (modern browsers; see SSR for Node/SSR notes).

Quickstart (60 seconds)

import gluer from "gluer-js";

// 1. Point the client at your project. Use the PUBLIC frontend key here.
gluer.setup({ project: "your-frontend-key" });

// 2. Open the connection.
gluer.connect();

gluer.addEventListener("connection", (e) => {
  console.log("connected, session:", e.detail.sessionId);
});

// 3. Call an action on your backend worker and await the response.
const res = await gluer.sendMsgSync("users:list", { page: 1 });
console.log(res.data);

// 4. Listen to a broadcast channel.
gluer.subscribe("orders", (msg) => console.log("order event", msg.data));

sendMsgSync resolves with the full response message ({ smh, uuid, data, ... } as sent by the worker), so application data lives under res.data.

Architecture and protocol

Every frame is a MessagePack-encoded object with a routing header in the smh field:

smh = "<direction>:<sessionId>:<project>:<action>"

| Direction | Meaning | | --- | --- | | > | request sent to the mesh | | < | response coming back | | + | subscribe (channels in data.channels) | | - | unsubscribe |

gluer-js builds these headers for you: sendMsg/sendMsgSync/sendHttp emit >, and subscribe emits +. Requests carry a uuid used to correlate the response. The action segment is whatever string you pass as the first argument, so a plugin-qualified action is written as "plugin:action" (for example sendMsgSync("mailer:send", …)).

Incoming frames are dispatched as follows:

  • frames with a channel field → the callback registered for that channel via subscribe, plus a DOM-style event named after the channel;
  • frames with a uuid matching a pending request → that request's promise or callback. A frame with d === "*" is treated as a streaming response: the handler is invoked and kept registered for further frames instead of being removed.

API reference

Exports

import gluer, { GluerJS } from "gluer-js";

| Export | Description | | --- | --- | | default | A ready-to-use singleton GluerJS instance, created with default configuration at import time. Not connected until you call connect(). | | GluerJS | The class, in case you need several independent connections. Extends EventTarget. |

The singleton is the normal entry point; use new GluerJS(config) when a page must talk to more than one project or mesh.

new GluerJS(config = {})

Creates a client and applies config through setup(). Does not open a connection.

setup(config = {})

Applies configuration. Every field is optional and only overwrites the current value when present, so setup() can be called repeatedly (for example to set the session id after login). Changing url, project or sessionId takes effect on the next connect().

| Option | Type | Default | Description | | --- | --- | --- | --- | | project | string | "sm" | Project key used in the connection URL and in every smh header. Use the public frontend key. | | url | string | "wss://ws.gluer.io" | Base WebSocket URL. The client connects to `${url}/ws/${project}/${sessionId}`. | | sessionId | string | "ws-" + uuidv4() | Identifies this client on the mesh. Generated once per instance if not supplied. | | httpFallback | boolean | true | When true, sendMsg/sendMsgSync fall back to HTTP if the socket is not open. | | httpUrl | string | "https://api.gluer.io" | Base URL for the HTTP fallback; requests are POSTed to `${httpUrl}/api`. |

Returns: undefined.

connect()

Opens the WebSocket connection, binds the socket handlers and starts the heartbeat.

  • If WebSocket is undefined (SSR), logs a warning and returns without connecting.
  • If a socket is already OPEN or CONNECTING, it is reused; for an already-open socket a connection event with detail.reconnected === true is dispatched and the offline queue is flushed.
  • If constructing the socket throws, the error is logged and a reconnect is scheduled.

Returns: undefined (it does not return a promise — listen for the connection event or just start sending, since messages are queued until the socket opens).

disconnect()

Closes the socket if there is one. See Reconnect semantics for an important caveat: the automatic reconnect timer is still armed by the socket's close handler, so a manually disconnected client will attempt to reconnect after 5 s.

sendMsgSync(action, data = {}, timeoutMs = 10000)

Sends a request and awaits the correlated response.

| Parameter | Type | Description | | --- | --- | --- | | action | string | Action to invoke, e.g. "users:list". Becomes the last smh segment. | | data | object | Payload. Merged with the context data set via setContextItem. | | timeoutMs | number | Time budget for the whole call. Default 10000. |

Returns: Promise<object> resolving with the response message.

Rejects with:

  • the value of message.data.error (as sent by the worker) when the response carries an error field — note this is the raw value, not necessarily an Error;
  • Error("Gluer Timeout: Sem resposta para '<action>' após <timeoutMs>ms") when no response arrives in time;
  • Error("Connection Lost") if the socket closes while the call is pending;
  • Error("Gluer: Não foi possível enviar (Sem Conexão).") if the client is disconnected and httpFallback is false;
  • whatever fetch/msgpack throws when the HTTP fallback is used and fails (e.g. Error("HTTP Error: 502")).

If the client is not connected, it first waits for a connection (up to 3 s when httpFallback is enabled, otherwise timeoutMs - 1000) and only then falls back to HTTP.

try {
  const res = await gluer.sendMsgSync("users:get", { id: 7 }, 5000);
  render(res.data);
} catch (err) {
  console.error("call failed", err);
}

sendMsg(action, data = {}, callback = null)

Fire-and-forget send, with an optional callback for the response.

| Parameter | Type | Description | | --- | --- | --- | | action | string | Action to invoke. | | data | object | Payload, merged with context data. | | callback | function \| null | Invoked with the response message. Kept registered while responses carry d === "*" (streaming), otherwise removed after the first response. |

Returns: Promise<void> — it is async because it may wait up to 2 s for a connection, but it never resolves with the response; use the callback or sendMsgSync for that.

Behaviour when offline: waits up to 2 s for the socket; if that fails and httpFallback is on, sends over HTTP and passes the decoded response to callback; if the HTTP attempt also fails, the message is queued and delivered when the socket comes back. With httpFallback disabled the message is simply queued.

gluer.sendMsg("metrics:track", { event: "page_view" });
gluer.sendMsg("users:list", {}, (msg) => console.log(msg.data));

subscribe(channels, callback)

Subscribes to one or more broadcast channels.

| Parameter | Type | Description | | --- | --- | --- | | channels | string \| string[] | Channel name(s). | | callback | function | Invoked with each message received on those channels. One callback per channel is stored; subscribing again to the same channel replaces it. |

Returns: undefined. The subscribe frame goes through the normal send path, so it is queued while offline, and all registered channels are re-subscribed automatically on every (re)connect.

Channel messages are additionally dispatched as events named after the channel, so this also works:

gluer.addEventListener("orders", (e) => console.log(e.detail));

send(payload)

Low-level send. Merges contextData into payload.data, MessagePack-encodes the payload and writes it to the socket; if the socket is not open (or send throws) the payload is pushed to the offline queue and flushed on the next successful connection. Build the smh header yourself when using this.

Returns: undefined.

sendHttp(action, data = {})

Sends a request over HTTP explicitly, bypassing the WebSocket. Same argument shape as sendMsg.

Returns: Promise<object> with the decoded MessagePack response. Rejects with Error("HTTP Error: <status>") for non-2xx responses, or the underlying fetch error. Dispatches the httpFallback event before performing the request.

setContextItem(key, value) / deleteContextItem(key)

Set or remove a value that is merged into data of every outgoing message (WebSocket and HTTP). Useful for auth tokens and tenant ids.

gluer.setContextItem("token", jwt);
gluer.deleteContextItem("token"); // on logout

Both return undefined. Context keys win over per-call data keys of the same name.

Events

GluerJS extends EventTarget, so use addEventListener / removeEventListener.

| Event | detail | When | | --- | --- | --- | | connection | { sessionId, reconnected } | Socket opened (reconnected: false) or connect() was called on an already-open socket (reconnected: true). | | disconnect | — | Socket closed, for any reason. | | error | the raw socket error event | Socket-level error. | | httpFallback | { command, uuid, url } | Just before a request is sent over HTTP instead of the socket. | | channel name | the channel message | A message arrived on a subscribed channel. |

Instance state

Read-only in practice, but useful for diagnostics: isConnected, socket, msgQueue, subscriptions (Map channel → callback), callbacks (Map uuid → pending handler), contextData.

Configuration and environment variables

The library reads no environment variables and has no build-time configuration — everything goes through setup(). In a bundler-based app, keep the project key in your framework's public env mechanism, e.g.:

gluer.setup({
  project: import.meta.env.VITE_GLUER_PROJECT,   // public frontend key
  url: import.meta.env.VITE_GLUER_WS_URL,        // optional, for staging meshes
});

Error handling and reconnect semantics

Reconnect

  • The socket's close handler marks the client disconnected, stops the heartbeat, rejects all pending sendMsgSync promises with Error("Connection Lost"), dispatches disconnect, and schedules a reconnect 5 s later (fixed delay, no exponential backoff). The reconnect only runs when window is defined.
  • On reconnect, all channels in subscriptions are re-subscribed and the offline queue is flushed.
  • A heartbeat ping request is sent every 30 s while connected.
  • disconnect() clears the pending reconnect timer before closing, but the close handler then schedules a new one — so a manual disconnect is followed by a reconnect attempt 5 s later. If you need a permanent disconnect today, remove your listeners and stop using the instance, or subclass and override _scheduleReconnect.

Offline behaviour

Messages sent while the socket is down are appended to msgQueue and flushed in order once the connection opens, so sendMsg never throws for being offline. sendMsgSync needs a live round trip, so it either falls back to HTTP or rejects.

HTTP fallback

With httpFallback: true (default), a sendMsg/sendMsgSync issued while the socket is down is POSTed as MessagePack to `${httpUrl}/api` with Content-Type: application/msgpack, and the response is MessagePack-decoded. An httpFallback event is dispatched for observability. Failures are logged and rethrown (sendMsgSync) or queued (sendMsg).

Server-side rendering

connect() is a no-op when WebSocket is undefined and reconnects are skipped when window is undefined, so the client itself is SSR-aware. Call connect() from a client-only lifecycle hook.

Caveat: the published bundles are transpiled to ES5, and the EventTarget subclass in that output throws TypeError: Class constructor EventTarget cannot be invoked without 'new' when imported in Node (Node's EventTarget is not a native function, so the transpiler's native-super shim does not kick in). Browsers are unaffected. If your framework evaluates the import on the server, load the module dynamically on the client (await import("gluer-js") inside a client-only hook / onMount / useEffect).

Security

Gluer projects have two keys, and only one of them belongs in this SDK:

| Key | Where it belongs | | --- | --- | | Public frontend key | Safe in browser bundles. This is the key you pass to setup({ project }), and the one used in REST bridge URLs (POST https://ws.gluer.io/api/:project/:plugin/:action) — the request passes through the mesh, which routes it to the connected backend worker. | | Private backend key | Only for backend workers calling connect() from a server SDK (gluer-go, gluer-python, gluer-nodejs…). It must never appear in client code, a bundle, or a git repository. |

Additional notes:

  • Treat anything a browser sends as untrusted; enforce authorization in the backend worker, not in the frontend.
  • Values passed to setContextItem (tokens, ids) are sent with every message — scope them accordingly and delete them on logout.
  • The transport is MessagePack over TLS (wss:///https://); never point url at a plaintext ws:// endpoint in production.

Related projects

Development

npm install
npm run build     # microbundle → dist/ (cjs, module, umd)
npm run bundle    # single UMD bundle
npm run dev       # watch mode

Releases are cut by ./publish.sh "<commit message>", which commits, runs npm version patch and pushes the tag; GitLab CI publishes tagged builds to npm.

Contributing

See CONTRIBUTING.md. Changes are tracked in CHANGELOG.md.

License

ISC © Thiago Magro