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

@robono/server

v0.9.1

Published

Provider-neutral server SDK for the Robono Bridge API.

Readme

@robono/server

Headless Node/TypeScript SDK for connecting an existing app or private ecosystem to Robono Bridge.

License

Licensed for authorized Robono integrations. Redistribution as a standalone SDK, bypassing Robono, and competing bridge services are prohibited. See the included LICENSE and license FAQ.

Robono's own app and third-party connected apps appear as endpoints in the same directory. Your product keeps its existing accounts, screens, storage, safety rules, and notifications.

Install

npm install @robono/server

The package is ESM-only, supports Node.js 18+ and Deno 2.x through npm compatibility, and includes TypeScript declarations and SBOM.spdx.json.

CommonJS applications must use a dynamic import:

const { RobonoServer } = await import("@robono/server");

Discover endpoints

import { RobonoServer } from "@robono/server";

const robono = new RobonoServer({
  apiKey: process.env.ROBONO_API_KEY!,
});

const { directory } = await robono.directory.list();

for (const endpoint of directory) {
  console.log(
    endpoint.display_name,
    endpoint.accepted_identifier.label,
  );
}

Show the returned endpoints in your app and ask for the identifier described by the selected endpoint. Do not hard-code Robono as a separate integration path. Each accepted_identifier also includes input_type, pattern, length, normalization, and case rules so the same value can be validated consistently before lookup.

Use the same methods after any endpoint is selected:

const endpoint = directory.find(item =>
  item.slug === selectedEndpointSlug
);
if (!endpoint) throw new Error("Endpoint is unavailable");

// Persist these IDs before the first network attempt. Reuse them after a
// timeout, worker retry, or application restart.
const connectionOperationId = connectionDraft.operationId;
const connection = await robono.endpointConnections.connect({
  endpoint,
  external_user_id: user.id,
  external_display_name: user.displayName,
  external_profile: {
    display_name: user.displayName,
    preferred_language: user.language,
  },
  target_identifier: identifierEnteredByUser,
  capabilities: {
    allowed_outbound_message_kinds: ["text"],
    allowed_inbound_message_kinds: ["text"],
    text: { max_characters: 1000 },
  },
}, { idempotencyKey: connectionOperationId });

const messageOperationId = outgoingMessage.operationId;
const result = await robono.endpointMessages.send({
  connection,
  external_user_id: user.id,
  external_message_id: outgoingMessage.id,
  message_kind: "text",
  text_body: "Hello",
}, { idempotencyKey: messageOperationId });

The normalized connection.connection_id is valid for subsequent unified SDK calls.

Localized user messages

Pass each viewer's BCP 47 language, such as es or fr-CA, with the final request option { language: user.language }. After sign-in and whenever a participant changes language, call this once:

await robono.participants.updateLanguage({
  external_user_id: user.id,
  preferred_language: user.language,
}, { idempotencyKey: languageChangeOperationId });

The preference belongs to this connected app and external_user_id; it is not app-wide, connection-specific, or shared with guardians. A constructor default is appropriate only when the entire integration uses one language. User-safe SDK errors are available as error.userMessage?.localized_message; returned connection and message records may include status_message.localized_message. Keep the stable code for application logic and the technical error message for logs. The preference localizes Robono-generated user notices; it does not translate chat content or developer diagnostics.

For delayed webhooks, code and parameters are authoritative. An included translation is only a convenience for the event participant. Localize the same event separately for a child, guardian, or other viewer when their languages differ:

const { user_message } = await robono.userMessages.localize({
  code: event.status_message.code,
  parameters: event.status_message.parameters,
  language: viewer.language,
});

Push contains identifiers, not visible wording. Synchronize after push, then use the fetched localized status or localize its stable code. robono.languages() returns capability flags for service notices, message translation, transcription, and speech output. Fallback is full locale, recognized script, base language, then English.

The unified send validates negotiated message type, text length, media size, duration, MIME type, and attachment count before making the API request. For media composed as one message, give every item the same attachment_batch.id, its zero-based index, and the common count.

The same normalized namespaces list, update, and disconnect connections; load message history; and mark messages delivered, read, or heard. When a user ends a friendship, call the disconnect operation rather than making a local-only change. To reconnect, request the same endpoint and identifier again. Robono preserves history and returns a pending state until the recipient explicitly accepts; do not enable messaging early. See the connection lifecycle and Server SDK reference.

When a participant deletes their account in your app, call dataRequests.deleteUser() from an authorized backend action with a persisted idempotency key. Robono anonymizes the participant, disconnects every affected relationship, preserves friend-held history, and sends peers bridge.connection_status_changed with reason: "account_deleted". Use the stable connection ID because the deleted participant identifier becomes null. See the account-deletion lifecycle.

Protect client operations

Keep the API key on your server. Mount the app-facing route at /robono/* behind your existing authentication:

const handleRobono = createRobonoBackendAdapter({
  robono,
  authenticate: async request =>
    (await yourAuth.findUser(request))?.id ?? null,
  authorize: context =>
    yourPermissions.allowRobonoAction(context),
});

authenticate proves identity. The required authorize callback checks the exact action against your account, relationship, guardian, safety, and processing rules. Missing or false decisions are denied. The route uses standard Web Request and Response objects, so it works with common Node, serverless, and Deno frameworks.

Verify signed events

Verify the signature against the exact raw request body before parsing JSON:

import { verifyRobonoWebhook } from "@robono/server";

const webhookSecret = process.env.ROBONO_WEBHOOK_SECRET;
if (!webhookSecret) throw new Error("ROBONO_WEBHOOK_SECRET is required");

const verified = await verifyRobonoWebhook(
  rawBody,
  request.headers,
  webhookSecret,
);

await webhookInbox.insertPendingIfAbsent({
  eventId: verified.eventId,
  rawBody,
  event: verified.event,
});
return new Response("accepted", { status: 202 });

Store each event once by event ID, return HTTP 2xx within 10 seconds, and process it asynchronously. Duplicate events must not create duplicate work. Connection requests must be resolved and approved only by an authorized server action.

Run the sandbox lifecycle

ROBONO_SANDBOX_KEY_A=rbn_test_... \
ROBONO_SANDBOX_KEY_B=rbn_test_... \
ROBONO_SANDBOX_NETWORK_B_ID=... \
npx --package @robono/server robono-sandbox-test

The lifecycle runner tests Robono's isolated Sandbox. Test your own authentication, authorization, and CORS separately:

ROBONO_ADAPTER_URL=https://your-backend.example/robono \
ROBONO_ADAPTER_ACCESS_TOKEN=your_test_user_token \
ROBONO_ADAPTER_EXPECTED_ORIGIN=https://your-app.example \
npx --package @robono/server robono-adapter-test

Versions and support

All packages are currently early-access 0.x releases. Patch releases contain compatible fixes. Before 1.0, a minor release may contain a breaking change; read the packaged CHANGELOG.md before upgrading. Each minor package line receives critical security and Bridge compatibility fixes for at least 12 months after its successor is published. Public API versions receive at least 12 months' notice before retirement unless continued support would create an active security risk.

Report SDK problems at robono.com/contact?topic=sdk. Include the package version, runtime, safe reproduction steps, and request ID; never include credentials or private message content.

See the complete build flow, optional language and privacy operations, platform support, and testing guide at robono.com/docs.