@neta-art/cohub 4.8.0 → 5.0.0
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/README.md +42 -1
- package/dist/chunks/http.d.ts +2 -2
- package/dist/chunks/http.js +2 -2
- package/dist/chunks/transport.js +7 -2
- package/dist/chunks/websocket.d.ts +192 -49
- package/dist/chunks/websocket.js +56 -4
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +131 -7
- package/dist/index.js +837 -230
- package/docs/work-runtime-guide.md +123 -12
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This guide explains how to use the Cohub SDK **inside a published Work** — the
|
|
4
4
|
only environment where runtime APIs (`context()`, `auth.request`,
|
|
5
|
-
`work.commerce.*`) function. Read this before writing any
|
|
6
|
-
capabilities from browser-side JavaScript.
|
|
5
|
+
`work.commerce.*`, `work.realtime.*`) function. Read this before writing any
|
|
6
|
+
Work that calls Cohub capabilities from browser-side JavaScript.
|
|
7
7
|
|
|
8
8
|
It is written to be self-contained: an agent or developer who reads only this
|
|
9
9
|
file plus the SDK type definitions should be able to build a working Work
|
|
@@ -24,6 +24,7 @@ without reverse-engineering source code.
|
|
|
24
24
|
- [File reads](#file-reads-spacefiles)
|
|
25
25
|
- [Account-level data](#account-level-data-spaceslist--userlistsessions--usergetusage)
|
|
26
26
|
- [Commerce](#commerce-workcommerce)
|
|
27
|
+
- [Realtime rooms](#realtime-rooms-workrealtime)
|
|
27
28
|
6. [Complete working example](#6-complete-working-example)
|
|
28
29
|
7. [Common pitfalls checklist](#7-common-pitfalls-checklist)
|
|
29
30
|
8. [Publishing a Work (API/SDK)](#8-publishing-a-work-apisdk)
|
|
@@ -55,18 +56,19 @@ requiring the viewer to paste an API key.
|
|
|
55
56
|
└─────────────────────────────────────────┘
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
Four runtime-only APIs form the foundation; everything else is standard SDK:
|
|
59
60
|
|
|
60
61
|
| API | What it does | Returns |
|
|
61
62
|
|---|---|---|
|
|
62
63
|
| `client.context()` | Asks the host for the Work's identity | `{ work, space, viewer?, permissions }` or `null` |
|
|
63
64
|
| `client.auth.request({ scopes, reason })` | Shows the viewer a consent dialog; on approval, caches a token carrying those scopes | `true` / `false` |
|
|
64
65
|
| `client.work.commerce.*` | Entitlement checks, credit consumption, purchases | (see Commerce section) |
|
|
66
|
+
| `client.work.realtime.*` | Temporary rooms, events, presence, and membership | (see Realtime rooms section) |
|
|
65
67
|
|
|
66
|
-
> **Runtime-only constraint.** These
|
|
68
|
+
> **Runtime-only constraint.** These APIs only work inside a **published**
|
|
67
69
|
> Work. Outside that context (a static asset URL, a local `file://` preview,
|
|
68
|
-
> a plain Node script) `context()` returns `null` and
|
|
69
|
-
>
|
|
70
|
+
> a plain Node script) `context()` returns `null` and the other runtime APIs
|
|
71
|
+
> fail. Always develop against a published Work.
|
|
70
72
|
|
|
71
73
|
---
|
|
72
74
|
|
|
@@ -220,6 +222,7 @@ result needs `taskrun.view` (a work scope).
|
|
|
220
222
|
| Commerce: entitlements | `client.work.commerce.getEntitlements()` | *(runtime only, no scope)* | — |
|
|
221
223
|
| Commerce: consume credits | `client.work.commerce.consumeCredits()` | *(runtime only, no scope)* | — |
|
|
222
224
|
| Commerce: purchase | `client.work.commerce.purchase()` | *(runtime only, no scope)* | — |
|
|
225
|
+
| Realtime rooms | `client.work.realtime.createRoom()` / `joinRoom()` | *(runtime only, no scope)* | — |
|
|
223
226
|
|
|
224
227
|
### Minimal scope sets for common Work types
|
|
225
228
|
|
|
@@ -268,11 +271,11 @@ Works are typically single HTML files with no bundler. Import the SDK from an
|
|
|
268
271
|
ESM CDN:
|
|
269
272
|
|
|
270
273
|
```js
|
|
271
|
-
import { createCohubClient } from "https://esm.sh/@neta-art/cohub@
|
|
274
|
+
import { createCohubClient } from "https://esm.sh/@neta-art/cohub@latest";
|
|
272
275
|
```
|
|
273
276
|
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
`@latest` keeps a Work on the current SDK release. Pin an exact version only
|
|
278
|
+
when a deployment needs reproducible dependency updates.
|
|
276
279
|
|
|
277
280
|
### Environment detection — critical
|
|
278
281
|
|
|
@@ -660,12 +663,120 @@ if (checkoutState.orderId) {
|
|
|
660
663
|
retries the call after a timeout, pass the same `purchaseAttemptId` to ensure
|
|
661
664
|
the retry resolves to the original Billing order.
|
|
662
665
|
|
|
666
|
+
### Realtime rooms (`work.realtime`)
|
|
667
|
+
|
|
668
|
+
**Scopes:** none — uses the published Work's runtime identity without an
|
|
669
|
+
additional consent dialog. The CLI and ordinary server auth cannot create or
|
|
670
|
+
join these rooms.
|
|
671
|
+
|
|
672
|
+
```js
|
|
673
|
+
const room = await client.work.realtime.createRoom({
|
|
674
|
+
code: "TEAM-ALPHA", // optional; generated when omitted
|
|
675
|
+
maxParticipants: 64,
|
|
676
|
+
expiresInSeconds: 2 * 60 * 60,
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const stopEvents = room.subscribe("shared.state.updated", (event) => {
|
|
680
|
+
console.log(event.sequence, event.data, event.self);
|
|
681
|
+
});
|
|
682
|
+
console.log(room.members); // initial snapshot
|
|
683
|
+
const stopMembers = room.onMembersChanged((members) => {
|
|
684
|
+
console.log(members);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await room.setPresence({ status: "active" });
|
|
688
|
+
await room.publish("shared.state.updated", { value: 42 });
|
|
689
|
+
|
|
690
|
+
stopEvents();
|
|
691
|
+
stopMembers();
|
|
692
|
+
await room.leave();
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Join an existing room with
|
|
696
|
+
`client.work.realtime.joinRoom({ code: "TEAM-ALPHA" })`. Codes are scoped to
|
|
697
|
+
one Work and are identifiers, not credentials; the runtime session and a
|
|
698
|
+
short-lived admission ticket provide authorization.
|
|
699
|
+
|
|
700
|
+
| Surface | Purpose |
|
|
701
|
+
|---|---|
|
|
702
|
+
| `createRoom()` / `joinRoom()` | Create or enter a code-scoped room |
|
|
703
|
+
| `subscribe()` / `subscribeAll()` | Receive typed or all business events |
|
|
704
|
+
| `publish()` | Send an acknowledged event; accepts an optional correlation-only `clientEventId` |
|
|
705
|
+
| `send()` / `onSendError()` | Send without an ACK for high-rate traffic; observe asynchronous failures |
|
|
706
|
+
| `members` / `onMembersChanged()` | Read the initial member snapshot and later membership or presence changes |
|
|
707
|
+
| `setPresence()` | Replace this participant's transient presence object |
|
|
708
|
+
| `state` / `onStateChange()` | Observe `connecting`, `joined`, `reconnecting`, `expired`, or `closed` |
|
|
709
|
+
| `onOutOfSync()` | Detect sequence jumps in the current live stream |
|
|
710
|
+
| `leave()` | Release membership and SDK listeners |
|
|
711
|
+
|
|
712
|
+
Room events are ordered while connected but are not replayed. A reconnect
|
|
713
|
+
refreshes the member snapshot and advances the sequence cursor, so use
|
|
714
|
+
`onStateChange()` to resync authoritative application state after reconnecting;
|
|
715
|
+
`onOutOfSync()` only reports gaps visible in the current live stream. Payloads
|
|
716
|
+
are transient and are not stored in the Work.
|
|
717
|
+
|
|
718
|
+
| Limit | Value |
|
|
719
|
+
|---|---|
|
|
720
|
+
| Room code | Generated when omitted; custom codes are 3–48 uppercase letters, digits, `_`, or `-`, starting with a letter or digit |
|
|
721
|
+
| Lifetime | 2 hours by default; 60 seconds to 24 hours, absolute from creation |
|
|
722
|
+
| Participants | 16 by default; 2–128 |
|
|
723
|
+
| Active rooms | 512 per Work |
|
|
724
|
+
| Event name | 1–64 ASCII letters, digits, `.`, `_`, `:`, or `-`, starting with a letter or digit; `cohub.*` is reserved |
|
|
725
|
+
| Event payload | 16 KB of JSON |
|
|
726
|
+
| Presence payload | 2 KB of JSON |
|
|
727
|
+
| Publish rate | 2,000 events per second per room |
|
|
728
|
+
| Presence rate | 30 updates per second per connection |
|
|
729
|
+
| Pending mutations | 256 per connection before backpressure errors |
|
|
730
|
+
|
|
731
|
+
`createRoom()` returns HTTP 429 with `ROOM_QUOTA_EXCEEDED` at the active-room
|
|
732
|
+
limit. Activity never extends `expiresAt`; expired rooms release their slot
|
|
733
|
+
automatically.
|
|
734
|
+
|
|
735
|
+
#### High-frequency events
|
|
736
|
+
|
|
737
|
+
`publish()` waits for an ACK, so a loop that awaits each call is capped at
|
|
738
|
+
roughly `1000 / RTT` events per second. Use `send()` for input frames and other
|
|
739
|
+
high-rate traffic:
|
|
740
|
+
|
|
741
|
+
```js
|
|
742
|
+
room.onSendError((error) => console.warn("dropped frame", error.message));
|
|
743
|
+
room.onStateChange((state) => {
|
|
744
|
+
if (state !== "joined") pauseSimulation();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
room.send("input.frame", { frame, pad });
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
`send()` preserves server ordering but drops calls while the room is not joined
|
|
751
|
+
and reports rate, validation, membership, and backpressure failures through
|
|
752
|
+
`onSendError()`. Use `publish()` whenever a specific event must be confirmed.
|
|
753
|
+
|
|
754
|
+
#### Seats and participant identity
|
|
755
|
+
|
|
756
|
+
Every connection is a participant by default, so two tabs appear twice. Each
|
|
757
|
+
member includes an opaque `userKey` that is stable for one room and viewer,
|
|
758
|
+
letting an application group connections without seeing the account ID.
|
|
759
|
+
|
|
760
|
+
Set `seatPerUser: true` when each viewer should occupy at most one seat:
|
|
761
|
+
|
|
762
|
+
```js
|
|
763
|
+
const room = await client.work.realtime.createRoom({
|
|
764
|
+
maxParticipants: 2,
|
|
765
|
+
seatPerUser: true,
|
|
766
|
+
});
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
A second tab or reconnect takes over the existing seat instead of consuming a
|
|
770
|
+
new one. The server keeps the participant ID, updates `room.participantId`, and
|
|
771
|
+
closes the superseded connection without emitting a leave event. Without this
|
|
772
|
+
mode, an unclean disconnect can retain its seat lease for up to one minute.
|
|
773
|
+
|
|
663
774
|
---
|
|
664
775
|
|
|
665
776
|
## 6. Complete working example
|
|
666
777
|
|
|
667
|
-
A no-build HTML Work
|
|
668
|
-
|
|
778
|
+
A no-build HTML Work for LLM chat and image generation. Use it as a starting
|
|
779
|
+
point and keep only the capabilities you need.
|
|
669
780
|
|
|
670
781
|
> **Publish this Work with:**
|
|
671
782
|
> - workScopes: `["space.view", "session.view", "taskrun.view"]`
|
|
@@ -724,7 +835,7 @@ exact pattern that was verified end-to-end. Adapt it to your needs.
|
|
|
724
835
|
### `app.js`
|
|
725
836
|
|
|
726
837
|
```js
|
|
727
|
-
import { createCohubClient } from "https://esm.sh/@neta-art/cohub@
|
|
838
|
+
import { createCohubClient } from "https://esm.sh/@neta-art/cohub@latest";
|
|
728
839
|
|
|
729
840
|
// --- Environment detection (critical: browsers don't inject ENV) ---
|
|
730
841
|
const isDevWork =
|