@robono/server 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +21 -205
- package/SBOM.spdx.json +8 -8
- package/dist/client.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
- Simplifies the public implementation path while retaining the complete endpoint, authorization, event, and testing contracts.
|
|
6
|
+
- Renames the public client-side API reference and removes unnecessary infrastructure terminology.
|
|
7
|
+
|
|
3
8
|
## 0.8.0
|
|
4
9
|
|
|
5
10
|
- Added typed external-user export and deletion operations with status lookup.
|
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# `@robono/server`
|
|
2
2
|
|
|
3
|
-
Headless Node/TypeScript SDK for connecting an existing app or
|
|
3
|
+
Headless Node/TypeScript SDK for connecting an existing app or private ecosystem to Robono Bridge.
|
|
4
4
|
|
|
5
|
-
## License
|
|
5
|
+
## License
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
See the [SDK license FAQ](https://robono.com/sdk-license) for procurement guidance and the route for organization terms.
|
|
7
|
+
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](https://robono.com/sdk-license).
|
|
10
8
|
|
|
11
9
|
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.
|
|
12
10
|
|
|
@@ -16,10 +14,7 @@ Robono's own app and third-party connected apps appear as endpoints in the same
|
|
|
16
14
|
npm install @robono/server
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
The package includes `SBOM.spdx.json
|
|
20
|
-
production components.
|
|
21
|
-
|
|
22
|
-
The package is ESM-only and supports Node.js 18+ and Deno 2.x through npm compatibility, including Supabase Edge Functions:
|
|
17
|
+
The package is ESM-only, supports Node.js 18+ and Deno 2.x through npm compatibility, and includes TypeScript declarations and `SBOM.spdx.json`.
|
|
23
18
|
|
|
24
19
|
CommonJS applications must use a dynamic import:
|
|
25
20
|
|
|
@@ -27,13 +22,6 @@ CommonJS applications must use a dynamic import:
|
|
|
27
22
|
const { RobonoServer } = await import("@robono/server");
|
|
28
23
|
```
|
|
29
24
|
|
|
30
|
-
```ts
|
|
31
|
-
import {
|
|
32
|
-
createRobonoBackendAdapter,
|
|
33
|
-
RobonoServer,
|
|
34
|
-
} from "npm:@robono/server@0.8.0";
|
|
35
|
-
```
|
|
36
|
-
|
|
37
25
|
## Discover endpoints
|
|
38
26
|
|
|
39
27
|
```ts
|
|
@@ -91,129 +79,33 @@ const result = await robono.endpointMessages.send({
|
|
|
91
79
|
}, { idempotencyKey: messageOperationId });
|
|
92
80
|
```
|
|
93
81
|
|
|
94
|
-
The normalized `connection.connection_id` is valid for
|
|
82
|
+
The normalized `connection.connection_id` is valid for subsequent unified SDK calls.
|
|
95
83
|
|
|
96
84
|
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`.
|
|
97
|
-
|
|
98
|
-
New server-only integrations can use the normalized namespaces for the rest of the lifecycle without branching on endpoint type:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
const page = await robono.endpointConnections.list({
|
|
102
|
-
external_user_id: user.id,
|
|
103
|
-
limit: 50,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const history = await robono.endpointMessages.list({
|
|
107
|
-
connection,
|
|
108
|
-
external_user_id: user.id,
|
|
109
|
-
limit: 50,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const firstMessage = history.messages[0];
|
|
113
|
-
if (firstMessage) {
|
|
114
|
-
await robono.endpointMessages.mark({
|
|
115
|
-
connection,
|
|
116
|
-
external_user_id: user.id,
|
|
117
|
-
message_id: firstMessage.message_id,
|
|
118
|
-
event: "read",
|
|
119
|
-
}, { idempotencyKey: receiptOperation.id });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await robono.endpointConnections.update({
|
|
123
|
-
connection,
|
|
124
|
-
external_user_id: user.id,
|
|
125
|
-
external_profile: currentProfile,
|
|
126
|
-
}, { idempotencyKey: profileUpdate.operationId });
|
|
127
|
-
|
|
128
|
-
await robono.endpointConnections.disconnect({
|
|
129
|
-
connection,
|
|
130
|
-
external_user_id: user.id,
|
|
131
|
-
}, { idempotencyKey: disconnectOperation.id });
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Use `page.next_cursor` and `history.next_before` for older pages. Use `history.sync_cursor` as the next `after` value when checking only for newer messages.
|
|
135
|
-
Create and persist each operation ID before its first request, then reuse that ID
|
|
136
|
-
after a timeout, retry, or restart.
|
|
85
|
+
The same normalized namespaces list, update, and disconnect connections; load message history; and mark messages delivered, read, or heard. See the [Server SDK reference](https://robono.com/sdk-reference/server/).
|
|
137
86
|
|
|
138
87
|
## Protect client operations
|
|
139
88
|
|
|
140
|
-
Keep the API key on your server. Mount the
|
|
89
|
+
Keep the API key on your server. Mount the app-facing route at `/robono/*` behind your existing authentication:
|
|
141
90
|
|
|
142
91
|
```ts
|
|
143
92
|
const handleRobono = createRobonoBackendAdapter({
|
|
144
93
|
robono,
|
|
145
94
|
authenticate: async request =>
|
|
146
95
|
(await yourAuth.findUser(request))?.id ?? null,
|
|
147
|
-
authorize:
|
|
148
|
-
|
|
149
|
-
if (!user) return false;
|
|
150
|
-
if (action === "networks.list") return true;
|
|
151
|
-
if (action === "network_connections.respond") {
|
|
152
|
-
return guardianApprovals.mayRespond(user, input);
|
|
153
|
-
}
|
|
154
|
-
if (action.endsWith(".list")) return access.mayRead(user, input);
|
|
155
|
-
if (
|
|
156
|
-
action === "message_transforms.create" ||
|
|
157
|
-
action === "speech_transforms.create"
|
|
158
|
-
) return processing.hasPermissionAndCredits(user, input);
|
|
159
|
-
if (
|
|
160
|
-
action.endsWith(".request") ||
|
|
161
|
-
action.endsWith(".create") ||
|
|
162
|
-
action.endsWith(".send") ||
|
|
163
|
-
action.endsWith(".mark") ||
|
|
164
|
-
action.endsWith(".update") ||
|
|
165
|
-
action.endsWith(".update_profile") ||
|
|
166
|
-
action.endsWith(".disconnect") ||
|
|
167
|
-
action === "push_diagnostics.report"
|
|
168
|
-
) return access.mayChange(user, action, input);
|
|
169
|
-
return false;
|
|
170
|
-
},
|
|
96
|
+
authorize: context =>
|
|
97
|
+
yourPermissions.allowRobonoAction(context),
|
|
171
98
|
});
|
|
172
99
|
```
|
|
173
100
|
|
|
174
|
-
`authenticate` proves
|
|
175
|
-
Return `null` or `undefined` for a normal missing or invalid session. If your
|
|
176
|
-
authentication library must throw for that case, throw
|
|
177
|
-
`RobonoAuthenticationError`; the adapter safely returns HTTP 401. Unexpected
|
|
178
|
-
authentication-system failures remain sanitized HTTP 500 responses.
|
|
101
|
+
`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.
|
|
179
102
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## Language and speech tools
|
|
183
|
-
|
|
184
|
-
The typed transform methods cover transcription, translation, TTS, translated voice, and existing-message transforms:
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
const { languages } = await robono.languages();
|
|
188
|
-
showAvailableTransformLanguages(languages);
|
|
189
|
-
|
|
190
|
-
const result = await robono.transforms.speech({
|
|
191
|
-
input: {
|
|
192
|
-
type: "audio",
|
|
193
|
-
source_url: audioUrl,
|
|
194
|
-
mime_type: "audio/mp4",
|
|
195
|
-
language: "en",
|
|
196
|
-
},
|
|
197
|
-
outputs: ["transcript", "translated_voice"],
|
|
198
|
-
target_language: "es",
|
|
199
|
-
}, { idempotencyKey: transformOperationId });
|
|
200
|
-
|
|
201
|
-
console.log(result.status); // "completed"
|
|
202
|
-
console.log(result.artifacts); // Typed transcript and generated voice
|
|
203
|
-
console.log(result.billing.operations); // Exact transcription/translation/TTS steps
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Transforms complete synchronously. SDK errors report request failures, while signed `transform.completed` and `transform.failed` webhooks provide an auditable server event. Generated media URLs include `source_url_expires_at` and currently expire after ten minutes. Reuse the same idempotency key when retrying the same operation; cached message artifacts are not charged again.
|
|
207
|
-
|
|
208
|
-
## Verify webhooks
|
|
103
|
+
## Verify signed events
|
|
209
104
|
|
|
210
105
|
Verify the signature against the exact raw request body before parsing JSON:
|
|
211
106
|
|
|
212
107
|
```ts
|
|
213
|
-
import {
|
|
214
|
-
toClientPushPayload,
|
|
215
|
-
verifyRobonoWebhook,
|
|
216
|
-
} from "@robono/server";
|
|
108
|
+
import { verifyRobonoWebhook } from "@robono/server";
|
|
217
109
|
|
|
218
110
|
const webhookSecret = process.env.ROBONO_WEBHOOK_SECRET;
|
|
219
111
|
if (!webhookSecret) throw new Error("ROBONO_WEBHOOK_SECRET is required");
|
|
@@ -229,80 +121,10 @@ await webhookInbox.insertPendingIfAbsent({
|
|
|
229
121
|
rawBody,
|
|
230
122
|
event: verified.event,
|
|
231
123
|
});
|
|
232
|
-
|
|
233
|
-
// Return any HTTP 2xx within 10 seconds; 202 is recommended here.
|
|
234
|
-
// Timeouts, network failures, redirects, and non-2xx responses receive up to
|
|
235
|
-
// five retries with increasing delay capped at 15 minutes. Duplicate event IDs
|
|
236
|
-
// must also return 2xx. A worker polls durable pending rows and runs:
|
|
237
|
-
async function processPendingWebhook(pending: typeof verified) {
|
|
238
|
-
switch (pending.event.event) {
|
|
239
|
-
case "bridge.connection_requested":
|
|
240
|
-
await pendingRequests.store(pending.event);
|
|
241
|
-
break;
|
|
242
|
-
case "connection.status_changed":
|
|
243
|
-
case "bridge.connection_status_changed":
|
|
244
|
-
await existingMessagingSystem.updateConnection(pending.event);
|
|
245
|
-
break;
|
|
246
|
-
case "message.created":
|
|
247
|
-
await existingMessagingSystem.storeMessage(pending.event);
|
|
248
|
-
break;
|
|
249
|
-
case "bridge.message_created":
|
|
250
|
-
await existingMessagingSystem.storeMessage(pending.event.message);
|
|
251
|
-
break;
|
|
252
|
-
case "transform.completed":
|
|
253
|
-
case "transform.failed":
|
|
254
|
-
await existingMessagingSystem.updateTransform(pending.event);
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const externalUserId =
|
|
259
|
-
await existingMessagingSystem.recipientFor(pending.event);
|
|
260
|
-
if (externalUserId) {
|
|
261
|
-
await yourPushProvider.send(externalUserId, {
|
|
262
|
-
data: toClientPushPayload(pending.event),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
await webhookInbox.markComplete(pending.eventId);
|
|
266
|
-
}
|
|
124
|
+
return new Response("accepted", { status: 202 });
|
|
267
125
|
```
|
|
268
126
|
|
|
269
|
-
|
|
270
|
-
Message-created webhooks contain the complete message: direct-endpoint content is
|
|
271
|
-
on the event itself and app-to-app content is in `event.message`. Client push
|
|
272
|
-
data contains routing IDs only; the authenticated client fetches current state.
|
|
273
|
-
|
|
274
|
-
For `bridge.connection_requested`, resolve `event.target.identifier` on your
|
|
275
|
-
backend, store the request pending any required guardian approval, and call
|
|
276
|
-
`networkConnections.respond()` only from an authorized server action. Use a
|
|
277
|
-
generic requester-facing result for `not_found`, rejection, and unauthorized
|
|
278
|
-
lookups so account existence cannot be tested.
|
|
279
|
-
|
|
280
|
-
## Automate user data requests
|
|
281
|
-
|
|
282
|
-
Create exports and deletions only after your backend authenticates and
|
|
283
|
-
authorizes the privacy request:
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
const request = await robono.dataRequests.deleteUser({
|
|
287
|
-
external_user_id: user.id,
|
|
288
|
-
external_request_id: privacyCase.id,
|
|
289
|
-
}, { idempotencyKey: privacyCase.operationId });
|
|
290
|
-
|
|
291
|
-
const status = await robono.dataRequests.status({
|
|
292
|
-
data_request_id: request.data_request.id,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
if (status.export) {
|
|
296
|
-
await yourPrivacySystem.storeCompletedExport(status.export);
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
Deletion requires a stable idempotency key. Signed `data.export_*` and
|
|
301
|
-
`data.deletion_*` events report completion or failure; status lookup remains
|
|
302
|
-
available if a webhook is delayed. A completed JSON export remains available
|
|
303
|
-
from status responses until `result_expires_at`. Use a `data:read` or
|
|
304
|
-
`data:write` API key when the operation should not share the integration's
|
|
305
|
-
broader credential.
|
|
127
|
+
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.
|
|
306
128
|
|
|
307
129
|
## Run the sandbox lifecycle
|
|
308
130
|
|
|
@@ -313,9 +135,7 @@ ROBONO_SANDBOX_NETWORK_B_ID=... \
|
|
|
313
135
|
npx --package @robono/server robono-sandbox-test
|
|
314
136
|
```
|
|
315
137
|
|
|
316
|
-
The
|
|
317
|
-
|
|
318
|
-
The lifecycle runner tests Robono's sandbox API, not your authentication, CORS, or deployed adapter. Test those separately:
|
|
138
|
+
The lifecycle runner tests Robono's isolated Sandbox. Test your own authentication, authorization, and CORS separately:
|
|
319
139
|
|
|
320
140
|
```bash
|
|
321
141
|
ROBONO_ADAPTER_URL=https://your-backend.example/robono \
|
|
@@ -328,18 +148,14 @@ npx --package @robono/server robono-adapter-test
|
|
|
328
148
|
|
|
329
149
|
All packages are currently early-access `0.x` releases. Patch releases contain
|
|
330
150
|
compatible fixes. Before `1.0`, a minor release may contain a breaking change;
|
|
331
|
-
read the packaged `CHANGELOG.md` before upgrading.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
[robono.com/docs#test](https://robono.com/docs#test). Production credentials
|
|
337
|
-
start at 300 requests per rolling minute; Sandbox credentials start at 600.
|
|
338
|
-
There is no separate published concurrency quota. Honor `Retry-After` on rate
|
|
339
|
-
limits and retryable temporary failures.
|
|
151
|
+
read the packaged `CHANGELOG.md` before upgrading. Each minor package line
|
|
152
|
+
receives critical security and Bridge compatibility fixes for at least 12
|
|
153
|
+
months after its successor is published. Public API versions receive at least
|
|
154
|
+
12 months' notice before retirement unless continued support would create an
|
|
155
|
+
active security risk.
|
|
340
156
|
|
|
341
157
|
Report SDK problems at [robono.com/contact?topic=sdk](https://robono.com/contact?topic=sdk).
|
|
342
158
|
Include the package version, runtime, safe reproduction steps, and request ID;
|
|
343
159
|
never include credentials or private message content.
|
|
344
160
|
|
|
345
|
-
See the complete build flow,
|
|
161
|
+
See the complete build flow, optional language and privacy operations, platform support, and testing guide at [robono.com/docs](https://robono.com/docs).
|
package/SBOM.spdx.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "@robono/server@0.8.
|
|
6
|
-
"documentNamespace": "http://spdx.org/spdxdocs/@robono%2fserver-0.8.
|
|
5
|
+
"name": "@robono/server@0.8.1",
|
|
6
|
+
"documentNamespace": "http://spdx.org/spdxdocs/@robono%2fserver-0.8.1-5363c57b-f29a-4d3f-bcb9-1e07d0a950d0",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-07-
|
|
8
|
+
"created": "2026-07-30T22:37:53.518Z",
|
|
9
9
|
"creators": [
|
|
10
10
|
"Tool: npm/cli-12.0.2"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
13
|
"documentDescribes": [
|
|
14
|
-
"SPDXRef-Package-robono.server-0.8.
|
|
14
|
+
"SPDXRef-Package-robono.server-0.8.1"
|
|
15
15
|
],
|
|
16
16
|
"packages": [
|
|
17
17
|
{
|
|
18
18
|
"name": "@robono/server",
|
|
19
|
-
"SPDXID": "SPDXRef-Package-robono.server-0.8.
|
|
20
|
-
"versionInfo": "0.8.
|
|
19
|
+
"SPDXID": "SPDXRef-Package-robono.server-0.8.1",
|
|
20
|
+
"versionInfo": "0.8.1",
|
|
21
21
|
"packageFileName": "",
|
|
22
22
|
"description": "Provider-neutral server SDK for the Robono Bridge API.",
|
|
23
23
|
"primaryPackagePurpose": "LIBRARY",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
{
|
|
30
30
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
31
31
|
"referenceType": "purl",
|
|
32
|
-
"referenceLocator": "pkg:npm/%40robono/server@0.8.
|
|
32
|
+
"referenceLocator": "pkg:npm/%40robono/server@0.8.1"
|
|
33
33
|
}
|
|
34
34
|
]
|
|
35
35
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"relationships": [
|
|
38
38
|
{
|
|
39
39
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
40
|
-
"relatedSpdxElement": "SPDXRef-Package-robono.server-0.8.
|
|
40
|
+
"relatedSpdxElement": "SPDXRef-Package-robono.server-0.8.1",
|
|
41
41
|
"relationshipType": "DESCRIBES"
|
|
42
42
|
}
|
|
43
43
|
]
|
package/dist/client.js
CHANGED
|
@@ -45,7 +45,7 @@ export class RobonoServer {
|
|
|
45
45
|
code: "fetch_required",
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
this.userAgent = options.userAgent ?? "@robono/server/0.8.
|
|
48
|
+
this.userAgent = options.userAgent ?? "@robono/server/0.8.1";
|
|
49
49
|
this.apiVersion = options.apiVersion?.trim() || DEFAULT_API_VERSION;
|
|
50
50
|
this.directory = {
|
|
51
51
|
list: (input = {}, requestOptions) => this.request("/networks", {
|