@robono/server
v0.9.1
Published
Provider-neutral server SDK for the Robono Bridge API.
Maintainers
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/serverThe 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-testThe 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-testVersions 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.
