@signalling/sdk 1.0.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/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # `@signalling/sdk`
2
+
3
+ A browser-first JavaScript SDK for the **Confrnce signalling API**. It is
4
+ explicitly designed as an **API wrapper, not a parallel stack** — every public
5
+ method maps onto a real route or wire-protocol artefact on the server.
6
+ The integrator writes their own UI; the SDK handles auth, DPoP signing, the
7
+ WebSocket handshake, WebRTC peer orchestration, and room state.
8
+
9
+ ## Standalone distribution
10
+
11
+ This folder is **self-contained**. Third parties can vendor it alone (copy the
12
+ directory, `npm install`, `npm run build`) or split it into a dedicated Git
13
+ repository — no backend or monorepo checkout is required. Protocol details
14
+ (DPoP, WebSocket handshake) are summarized under `docs/` in this package.
15
+
16
+ > **Internal reference:** If you also maintain the server repo, architectural
17
+ > notes may live alongside the backend (e.g. `AUTH_FLOW.md`). This README is the
18
+ > SDK consumer entry point.
19
+
20
+ ## Table of contents
21
+
22
+ - [Why this SDK](#why-this-sdk)
23
+ - [What's inside](#whats-inside)
24
+ - [Installation](#installation)
25
+ - [Quickstart](#quickstart)
26
+ - [Public API](#public-api)
27
+ - [Backend contract this SDK wraps](#backend-contract-this-sdk-wraps)
28
+ - [Documentation](#documentation)
29
+
30
+ ## Why this SDK
31
+
32
+ Without the SDK, an integrator has to:
33
+
34
+ - run the OIDC + PKCE handshake themselves;
35
+ - generate a non-extractable P-256 keypair, persist it in IndexedDB, never
36
+ let it leave the device;
37
+ - sign every outbound HTTP request with a fresh DPoP JWT, including the
38
+ `htm` / `htu` / `iat` / `jti` / `nonce` claims;
39
+ - handle `Use-DPoP-Nonce` retry loops;
40
+ - run a two-stage WebSocket handshake (HTTP ticket + WS upgrade + DPoP first
41
+ frame within 5 s + ack), strip query / fragment from `htu`, decode the
42
+ ticket nonce out of the JWT body, and react to a dozen 4xxx close codes;
43
+ - wire `RTCPeerConnection` instances, fan SDP offers / answers / ICE
44
+ candidates over the room WebSocket, fall back to TURN when ICE fails,
45
+ surface the local screen-share lifecycle.
46
+
47
+ This SDK does all of that for you. Under the hood, every call ultimately hits
48
+ the backend’s HTTP routes and WebSocket wire format your server documents.
49
+
50
+ ## What's inside
51
+
52
+ ```text
53
+ src/
54
+ api/ REST client (DPoP-signed; nonce-retry)
55
+ auth/ Login / bind / logout / me
56
+ dpop/ WebCrypto P-256 keypair + JWS proofs (IndexedDB-persisted)
57
+ websocket/ Ticket → upgrade → DPoP first-frame → ack
58
+ room/ REST wrapper + local clientId tracking
59
+ webrtc/ Peer orchestration over the room WebSocket
60
+ devices/ getUserMedia / getDisplayMedia / device enumeration
61
+ internal/ base64url, logger, typed event emitter (NOT exported)
62
+ types/ Public domain types + error classes
63
+ index.ts SignallingSDK + Call composition
64
+ ```
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ npm install @signalling/sdk
70
+ ```
71
+
72
+ The package ships ESM (`dist/index.mjs`), CJS (`dist/index.js`), and TS
73
+ declarations. No runtime dependencies are required.
74
+
75
+ ## Quickstart
76
+
77
+ ```ts
78
+ import { SignallingSDK } from "@signalling/sdk";
79
+
80
+ // 1. Initialise (eagerly loads the DPoP keypair from IndexedDB)
81
+ const sdk = await SignallingSDK.create({
82
+ baseUrl: "https://api.example.com",
83
+ authMode: "bff",
84
+ logLevel: "info",
85
+ });
86
+
87
+ // 2. Authenticate (BFF redirect → Keycloak → /auth/complete page)
88
+ if (!(await sdk.auth.isAuthenticated())) {
89
+ // First load: send the user to Keycloak.
90
+ sdk.auth.login("/dashboard");
91
+ // ...
92
+ }
93
+
94
+ // 3. On the SPA's /auth/complete page:
95
+ const bindToken = sdk.auth.bindTokenFromFragment();
96
+ if (bindToken) {
97
+ const { returnTo } = await sdk.auth.completeBind(bindToken);
98
+ window.location.assign(returnTo || "/");
99
+ }
100
+
101
+ // 4. Create or join a room.
102
+ const room = await sdk.rooms.create(); // { roomId, clientId }
103
+ const call = await sdk.calls.joinRoom({
104
+ roomId: room.roomId,
105
+ media: { audio: true, video: true },
106
+ });
107
+
108
+ // 5. React to events.
109
+ call.on("peer.joined", (p) => console.log("joined:", p.clientId));
110
+ call.on("stream.added", ({ clientId, stream }) => attachToDom(clientId, stream));
111
+ call.on("peer.left", (p) => removeFromDom(p.clientId));
112
+ call.on("connection.closed", ({ code }) => console.warn("WS closed:", code));
113
+
114
+ // 6. Tear down on unmount.
115
+ await call.leave();
116
+ ```
117
+
118
+ ## Public API
119
+
120
+ ```ts
121
+ class SignallingSDK {
122
+ static create(config: SignallingSDKConfig): Promise<SignallingSDK>;
123
+
124
+ // Module entry points (all stateful).
125
+ auth: AuthClient; // login / completeBind / logout / me / updateProfile
126
+ rooms: RoomManager; // create / join / view / leave / screenshare / permissions
127
+ devices: DeviceManager; // getLocalStream / getScreenShare / listInventory
128
+ api: ApiClient; // raw REST surface (escape hatch)
129
+ ws: WebSocketClient; // raw WebSocket (escape hatch)
130
+ webrtc: WebRTCManager; // raw RTCPeerConnection orchestration
131
+ dpop: DPoPManager; // raw DPoP signer
132
+ logger: Logger;
133
+
134
+ // High-level orchestration: join + ws + webrtc in one call.
135
+ calls: { joinRoom(config: CallConfig): Promise<Call> };
136
+ }
137
+
138
+ class Call extends TypedEventEmitter<CallEventMap> {
139
+ readonly roomId: number | string;
140
+ readonly clientId: string;
141
+ localStream: MediaStream | null;
142
+
143
+ setMicEnabled(on: boolean): void;
144
+ setCameraEnabled(on: boolean): void;
145
+ setLocalStream(s: MediaStream | null): Promise<void>;
146
+ startScreenShare(): Promise<MediaStream>;
147
+ stopScreenShare(): Promise<void>;
148
+ leave(): Promise<void>;
149
+ }
150
+ ```
151
+
152
+ The full strongly-typed event map (`CallEventMap`, `WebRTCEventMap`,
153
+ `WebSocketEventMap`, `AuthEventMap`) is exported from the entry point.
154
+
155
+ ## Backend contract this SDK wraps
156
+
157
+ Every public method ultimately reaches one of:
158
+
159
+ | SDK method | Backend route |
160
+ |---------------------------------------------|-------------------------------------------------------------------|
161
+ | `sdk.auth.login()` | `GET /auth/login` *(browser navigation)* |
162
+ | `sdk.auth.completeBind(token)` | `POST /auth/dpop/bind` |
163
+ | `sdk.auth.getCurrentUser()` | `GET /me` |
164
+ | `sdk.auth.updateProfile(...)` | `PATCH /me` |
165
+ | `sdk.auth.logout()` | `POST /auth/logout` |
166
+ | `sdk.auth.logoutAll()` | `POST /auth/logout-all` |
167
+ | `sdk.auth.openAccountConsole()` | `GET /auth/account` *(browser navigation)* |
168
+ | `sdk.rooms.create()` | `POST /createroom` |
169
+ | `sdk.rooms.join(id)` | `POST /joinroom/{roomId}` |
170
+ | `sdk.rooms.view(id)` | `GET /viewroom/{roomId}` |
171
+ | `sdk.rooms.leave()` | `DELETE /leaveroom/{roomId}/{clientId}` |
172
+ | `sdk.rooms.startScreenShare()` | `POST /room/{roomId}/{clientId}/screen-share/start` |
173
+ | `sdk.rooms.stopScreenShare()` | `POST /room/{roomId}/{clientId}/screen-share/stop` |
174
+ | `sdk.rooms.myPermissions()` | `GET /room/{roomId}/{clientId}/permissions` |
175
+ | `sdk.rooms.updatePermissions(...)` | `PUT /room/{roomId}/{clientId}/permissions` |
176
+ | `sdk.api.getTurnCredentials()` | `POST /v1/turn/credentials` |
177
+ | `sdk.api.searchUserByEmail(email)` | `GET /users/search?email=...` |
178
+ | `sdk.api.issueWSTicket()` | `POST /auth/ws-ticket` |
179
+ | Internal — used by `ws.connectRoom` | `WS /ws/{roomId}/{clientId}?ticket=...` |
180
+ | Internal — used by `ws.connectGlobal` | `WS /global/ws?ticket=...` |
181
+
182
+ Every authed REST call carries a `DPoP: <jws>` header generated fresh by
183
+ `DPoPManager`. Every WS upgrade is followed by a single
184
+ `{ "type": "dpop_handshake", "proof": "..." }` frame; the SDK waits for
185
+ `{ "type": "dpop_handshake_ack" }` before passing application messages
186
+ through.
187
+
188
+ ## Documentation
189
+
190
+ The `docs/` folder contains task-oriented guides:
191
+
192
+ - [`docs/quickstart.md`](./docs/quickstart.md) — installation, init, first call
193
+ - [`docs/auth.md`](./docs/auth.md) — BFF flow, Bearer mode for native apps, DPoP, completeBind, logout
194
+ - [`docs/deployment.md`](./docs/deployment.md) — publish + consume the SDK, Keycloak prerequisites for both modes
195
+ - [`docs/rooms-and-calls.md`](./docs/rooms-and-calls.md) — rooms, events, screen share
196
+ - [`docs/media.md`](./docs/media.md) — device enumeration, getUserMedia, TURN
197
+ - [`docs/api-reference.md`](./docs/api-reference.md) — full method index
198
+ - [`docs/troubleshooting.md`](./docs/troubleshooting.md) — common errors + fixes
199
+
200
+ If you change a public method here, update the matching doc in the same PR.
201
+ The docs are checked manually in code review against the shipped surface.
202
+ "# signalling-js-sdk"