restream-sdk 0.1.9 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "restream-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.4.0",
4
4
  "description": "Plug-and-play headless React hooks to add multi-platform restreaming (connect socials, go live, live chat + viewers, collaborative split-screen) with zero client backend.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -55,4 +55,4 @@
55
55
  "react",
56
56
  "sdk"
57
57
  ]
58
- }
58
+ }
package/src/core.js CHANGED
@@ -5,6 +5,12 @@
5
5
  // into goLive() — this module never captures or publishes media itself.
6
6
 
7
7
  export const PLATFORMS = ["twitch", "kick", "youtube", "facebook", "instagram", "tiktok", "restream"];
8
+ // FALLBACK only. The real routing (oauth | restream | off) is fetched per-client from
9
+ // GET /api/v1/connect/platforms → `routing`, so the platform team controls native↔Restream
10
+ // entirely from the admin with no SDK/app change. These statics are used solely if that
11
+ // fetch hasn't happened yet (e.g. a sync call before loadRouting()).
12
+ const NATIVE_CONNECT_PLATFORMS = new Set(["youtube", "facebook"]);
13
+ const RESTREAM_BRIDGE_PLATFORMS = new Set(["tiktok"]);
8
14
 
9
15
  const TERMINAL_STATUS = new Set(["stopped", "failed", "ended", "error", "completed"]);
10
16
 
@@ -33,6 +39,19 @@ export class RestreamStudio extends EventTarget {
33
39
  this.viewers = null;
34
40
  this.comments = [];
35
41
 
42
+ // Per-client routing map { <platform>: "oauth" | "restream" | "off" }, fetched from
43
+ // GET /connect/platforms. Null until loadRouting()/listOAuthPlatforms() runs; the
44
+ // connect helpers fall back to the static defaults above while it's null.
45
+ this._routing = null;
46
+
47
+ // Real-time presence/layout state ({ screenShare, camera, collab, cameraPosition,
48
+ // cameraSize }) pushed to the server via setStreamState(). Coalesced (trailing debounce)
49
+ // so rapid toggles send once. _pendingState holds state set before goLive() (flushed once
50
+ // the job exists).
51
+ this._streamState = {};
52
+ this._pendingState = null;
53
+ this._stateTimer = null;
54
+
36
55
  this._token = null;
37
56
  this._tokenExp = 0;
38
57
  this._es = null;
@@ -131,9 +150,57 @@ export class RestreamStudio extends EventTarget {
131
150
  return this.connections;
132
151
  }
133
152
 
134
- /** Ask the backend which OAuth platforms are configured for this client. */
153
+ /** Ask the backend which OAuth platforms are configured for this client.
154
+ * Also caches the per-client `routing` map ({platform: oauth|restream|off}) that
155
+ * drives connectPlatform()/restreamAddChannelUrl() — so native↔Restream is decided
156
+ * by the admin, not hardcoded here. */
135
157
  async listOAuthPlatforms() {
136
- return this._req("GET", `/api/v1/connect/platforms${this._userQuery()}`);
158
+ const r = await this._req("GET", `/api/v1/connect/platforms${this._userQuery()}`);
159
+ if (r && r.routing && typeof r.routing === "object") this._routing = r.routing;
160
+ return r;
161
+ }
162
+
163
+ /** Load (and cache) the per-client routing map. Call once on init so the synchronous
164
+ * connect helpers route correctly. Returns { platform: "oauth"|"restream"|"off" }. */
165
+ async loadRouting() {
166
+ await this.listOAuthPlatforms();
167
+ return this._routing || {};
168
+ }
169
+
170
+ /** Effective route for a platform: "oauth" | "restream" | "off" | null (unknown). Uses
171
+ * the cached admin routing if loaded; otherwise the static fallbacks. */
172
+ routeFor(platform) {
173
+ const p = String(platform || "").toLowerCase();
174
+ if (this._routing && this._routing[p]) return this._routing[p];
175
+ if (NATIVE_CONNECT_PLATFORMS.has(p)) return "oauth";
176
+ if (RESTREAM_BRIDGE_PLATFORMS.has(p)) return "restream";
177
+ return null;
178
+ }
179
+
180
+ _isNativeConnect(platform) {
181
+ return this.routeFor(platform) === "oauth";
182
+ }
183
+
184
+ _isBridge(platform) {
185
+ return this.routeFor(platform) === "restream";
186
+ }
187
+
188
+ /**
189
+ * Single dynamic connect entry point — the app calls this for ANY platform and the
190
+ * SDK routes it per the admin's configuration (no app change when routing flips):
191
+ * "oauth" → native OAuth popup (connect)
192
+ * "restream" → Restream bridge add-channel flow (connectRestreamChannel)
193
+ * "off" → throws (platform disabled for this client)
194
+ * Loads routing first if it hasn't been cached yet. Must be called from a click.
195
+ * @param {string} platform
196
+ * @param {object} [opts] forwarded to connectRestreamChannel for bridge platforms
197
+ */
198
+ async connectPlatform(platform, opts = {}) {
199
+ if (!this._routing) { try { await this.loadRouting(); } catch { /* fall back to statics */ } }
200
+ const route = this.routeFor(platform);
201
+ if (route === "oauth") return this.connect(platform);
202
+ if (route === "restream") return this.connectRestreamChannel(platform, opts);
203
+ throw new Error(`"${platform}" is not available to connect for this client (routed "off").`);
137
204
  }
138
205
 
139
206
  /**
@@ -155,6 +222,15 @@ export class RestreamStudio extends EventTarget {
155
222
  * unknown params), so it safely falls back to the channel page.
156
223
  */
157
224
  restreamAddChannelUrl(platform) {
225
+ const normalized = String(platform || "").toLowerCase();
226
+ if (this._isNativeConnect(normalized) && this.publishableKey) {
227
+ const q = new URLSearchParams({ pk: this.publishableKey });
228
+ if (this.userId) q.set("userId", this.userId);
229
+ return `${this.apiBase}/api/v1/connect/${platform}/start?${q}`;
230
+ }
231
+ if (normalized && !this._isBridge(normalized)) {
232
+ throw new Error(`"${platform}" is not routed through the Restream bridge for this client. Use native OAuth (connect) instead.`);
233
+ }
158
234
  const base = "https://restream.io/channel";
159
235
  return platform ? `${base}?add=${encodeURIComponent(platform)}` : base;
160
236
  }
@@ -174,6 +250,23 @@ export class RestreamStudio extends EventTarget {
174
250
  * closed the popup without adding one.
175
251
  */
176
252
  async connectRestreamChannel(platform, { pollMs = 3000, timeoutMs = 180_000 } = {}) {
253
+ const normalized = String(platform || "").toLowerCase();
254
+ // Load routing so native↔bridge is decided by the admin, not the statics.
255
+ if (!this._routing) { try { await this.loadRouting(); } catch { /* fall back to statics */ } }
256
+ if (this._isNativeConnect(normalized)) {
257
+ const result = await this.connect(platform);
258
+ return {
259
+ id: `native:${platform}`,
260
+ platform,
261
+ displayName: null,
262
+ url: null,
263
+ active: !result?.closed,
264
+ };
265
+ }
266
+ if (!this._isBridge(normalized)) {
267
+ throw new Error(`"${platform}" is not routed through the Restream bridge for this client. Use native OAuth (connect) instead.`);
268
+ }
269
+
177
270
  // Open synchronously to preserve the click's user-activation (else it's blocked).
178
271
  let popup = window.open("", "restream_add_channel", "width=760,height=900");
179
272
  if (!popup) throw new Error("popup blocked — allow popups to connect accounts");
@@ -184,6 +277,7 @@ export class RestreamStudio extends EventTarget {
184
277
  await this.listConnections();
185
278
  if (!this.connections.some((c) => c.platform === "restream")) {
186
279
  const q = await this._authQuery();
280
+ q.platform = normalized;
187
281
  popup.location = `${this.apiBase}/api/v1/connect/restream/start?${new URLSearchParams(q)}`;
188
282
  await this._awaitRestreamConnected(timeoutMs);
189
283
  // Our OAuth callback page auto-closes the popup; reopen for the add step.
@@ -299,7 +393,7 @@ export class RestreamStudio extends EventTarget {
299
393
  * Restream channels. Any field omitted falls back to the
300
394
  * default pre-set for this creator on the platform.
301
395
  */
302
- async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta } = {}) {
396
+ async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta, streamingProvider, layout } = {}) {
303
397
  if (!publisherSessionId) throw new Error("publisherSessionId is required (from your existing stream session)");
304
398
  const body = {
305
399
  publisherSessionId,
@@ -321,11 +415,24 @@ export class RestreamStudio extends EventTarget {
321
415
  if (destinationMeta && typeof destinationMeta === "object" && Object.keys(destinationMeta).length) {
322
416
  body.destinationMeta = destinationMeta;
323
417
  }
418
+ // Streaming provider selection per destination.
419
+ // Example: { youtube: "youtube_native" } to use native YouTube instead of Restream bridge.
420
+ // Default is "restream" for all destinations.
421
+ if (streamingProvider && typeof streamingProvider === "object" && Object.keys(streamingProvider).length) {
422
+ body.streamingProvider = streamingProvider;
423
+ }
324
424
  if (this.publishableKey && this.userId) body.clientUserId = this.userId; // token mode binds server-side
325
425
  const job = await this._req("POST", "/api/v1/jobs", body);
326
426
  this.job = job;
327
427
  this._emit("job", job);
328
428
  if (job?.id) this.startRealtime(job.id);
429
+ // Push the initial presence/layout state (explicit `layout`, or whatever was set via
430
+ // setStreamState() before we had a job id). Best-effort — never block go-live on it.
431
+ const initialState = { ...(this._pendingState || {}), ...(layout || {}) };
432
+ if (job?.id && Object.keys(initialState).length) {
433
+ this._pendingState = null;
434
+ this.setStreamState(initialState).catch(() => {});
435
+ }
329
436
  return job;
330
437
  }
331
438
 
@@ -333,6 +440,7 @@ export class RestreamStudio extends EventTarget {
333
440
  if (!this.job?.id) return null;
334
441
  const r = await this._req("DELETE", `/api/v1/jobs/${this.job.id}`);
335
442
  this.stopRealtime();
443
+ if (this._stateTimer) { clearTimeout(this._stateTimer); this._stateTimer = null; } // drop any pending state POST
336
444
  this.job = { ...this.job, status: "stopping" };
337
445
  this._emit("job", this.job);
338
446
  return r;
@@ -355,6 +463,40 @@ export class RestreamStudio extends EventTarget {
355
463
  return this._req("PATCH", `/api/v1/jobs/${this.job.id}/overlay`, opts);
356
464
  }
357
465
 
466
+ /**
467
+ * Push the creator's real-time presence/layout state to the server. Call on EVERY change
468
+ * (screen-share toggled, camera on/off, collab on/off, camera moved/resized) so the server
469
+ * can swap banner templates per state and keep assets clear of the camera. Partial patches
470
+ * merge; the full merged state is sent (trailing-debounced ~150ms so rapid toggles send once).
471
+ *
472
+ * @param {object} patch
473
+ * @param {boolean} [patch.screenShare]
474
+ * @param {boolean} [patch.camera]
475
+ * @param {boolean} [patch.collab]
476
+ * @param {"top-left"|"top-right"|"bottom-left"|"bottom-right"|null} [patch.cameraPosition]
477
+ * @param {"small"|"medium"|"large"|number|null} [patch.cameraSize] enum or % (1-100)
478
+ * @returns {object} the merged local state (the network send is debounced/best-effort)
479
+ */
480
+ setStreamState(patch = {}) {
481
+ this._streamState = { ...this._streamState, ...patch };
482
+ this._emit("state", this._streamState);
483
+ if (!this.job?.id) {
484
+ // Not live yet — remember it and flush when goLive() creates the job.
485
+ this._pendingState = { ...this._streamState };
486
+ return this._streamState;
487
+ }
488
+ const jobId = this.job.id;
489
+ if (this._stateTimer) clearTimeout(this._stateTimer);
490
+ this._stateTimer = setTimeout(() => {
491
+ this._stateTimer = null;
492
+ this._req("POST", `/api/v1/jobs/${jobId}/state`, this._streamState).catch((e) => this._emit("error", e));
493
+ }, 150);
494
+ return this._streamState;
495
+ }
496
+
497
+ /** Last presence/layout state set via setStreamState() (local view). */
498
+ get streamState() { return this._streamState; }
499
+
358
500
  // ---- realtime (chat SSE + job-status/viewer polling) ----
359
501
  async startRealtime(jobId) {
360
502
  this.stopRealtime();
@@ -457,9 +599,17 @@ export class RestreamStudio extends EventTarget {
457
599
  * Invite another creator (same client) to a split-screen collab. The composite
458
600
  * starts only when they accept. Pass your own publisherSessionId + destinations so
459
601
  * the server can begin the merged broadcast on accept (or rely on your active job).
602
+ *
603
+ * collaboratorRestreamOn defaults to TRUE: the invited creator's connected social
604
+ * accounts MUST receive the side-by-side composite unless they explicitly opt out
605
+ * (pass `collaboratorRestreamOn: false`). This matches the server product intent in
606
+ * routes/collab.js and CollabService.createInvite. The bug: defaulting the SDK to
607
+ * `false` here sent `false` in the request body, which the server honored, so the
608
+ * collaborator's broadcasts/fan-out were silently skipped and the collab frame never
609
+ * appeared on the collaborator's own social accounts.
460
610
  * @param {object} opts { collaboratorUserId, collaboratorRestreamOn?, publisherSessionId?, destinations?, metadata? }
461
611
  */
462
- async requestCollab({ collaboratorUserId, collaboratorRestreamOn = false, publisherSessionId, destinations, metadata } = {}) {
612
+ async requestCollab({ collaboratorUserId, collaboratorRestreamOn = true, publisherSessionId, destinations, metadata } = {}) {
463
613
  if (!collaboratorUserId) throw new Error("collaboratorUserId is required");
464
614
  const invite = await this._req("POST", "/api/v1/collab/request", this._withUser({
465
615
  collaboratorUserId, collaboratorRestreamOn, publisherSessionId, destinations, metadata,
package/src/react.js CHANGED
@@ -49,12 +49,15 @@ export function useRestream(opts) {
49
49
  listRestreamChannels: useCallback(() => studio.listRestreamChannels(), [studio]),
50
50
  connectRestreamChannel: useCallback((p, o) => studio.connectRestreamChannel(p, o), [studio]),
51
51
  restreamAddChannelUrl: useCallback((p) => studio.restreamAddChannelUrl(p), [studio]),
52
+ connectPlatform: useCallback((p, o) => studio.connectPlatform(p, o), [studio]),
53
+ loadRouting: useCallback(() => studio.loadRouting(), [studio]),
52
54
  arm: useCallback((o) => studio.arm(o), [studio]),
53
55
  goLive: useCallback((o) => studio.goLive(o), [studio]),
54
56
  stopLive: useCallback(() => studio.stopLive(), [studio]),
55
57
  refreshJob: useCallback(() => studio.refreshJob(), [studio]),
56
58
  sendChat: useCallback((m, p) => studio.sendChat(m, p), [studio]),
57
59
  updateOverlay: useCallback((o) => studio.updateOverlay(o), [studio]),
60
+ setStreamState: useCallback((s) => studio.setStreamState(s), [studio]),
58
61
  // collaborative split-screen
59
62
  requestCollab: useCallback((o) => studio.requestCollab(o), [studio]),
60
63
  acceptCollab: useCallback((id, o) => studio.acceptCollab(id, o), [studio]),
@@ -70,8 +73,8 @@ export function useRestream(opts) {
70
73
 
71
74
  /** Convenience: just the social-connection state + actions. */
72
75
  export function useConnections(opts) {
73
- const { connections, connect, disconnect, listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl } = useRestream(opts);
74
- return { connections, connect, disconnect, refresh: listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl };
76
+ const { connections, connect, disconnect, listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl, connectPlatform, loadRouting } = useRestream(opts);
77
+ return { connections, connect, disconnect, refresh: listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl, connectPlatform, loadRouting };
75
78
  }
76
79
 
77
80
  /** Convenience: live chat (incoming comments + outbound send). Pass an existing studio. */
package/types/index.d.ts CHANGED
@@ -1,5 +1,19 @@
1
1
  export type Platform = "twitch" | "kick" | "youtube" | "facebook" | "instagram" | "tiktok" | "restream";
2
2
 
3
+ /** How a platform is reached for this client (admin-controlled): native OAuth, the
4
+ * Restream bridge, or disabled. */
5
+ export type RoutingValue = "oauth" | "restream" | "off";
6
+ /** Per-client routing map returned by GET /connect/platforms (`routing`). */
7
+ export type Routing = Partial<Record<Platform, RoutingValue>>;
8
+ /** Response of listOAuthPlatforms(). `routing` is the single source of truth for
9
+ * native↔Restream; `platforms` is the legacy natively-connectable set. */
10
+ export interface OAuthPlatformsResponse {
11
+ platforms: Platform[];
12
+ routing?: Routing;
13
+ frontendOrigin: string;
14
+ redirectOrigin: string;
15
+ }
16
+
3
17
  /** A channel the user connected inside their Restream account (a fan-out target). */
4
18
  export interface RestreamChannel {
5
19
  id: string;
@@ -88,6 +102,8 @@ export interface GoLiveParams {
88
102
  * default → the creator's pre-set global default → the platform's existing value.
89
103
  */
90
104
  destinationMeta?: DestinationMeta;
105
+ /** Initial presence/layout state for the stream (same shape as setStreamState). */
106
+ layout?: StreamState;
91
107
  }
92
108
 
93
109
  export interface StreamMeta {
@@ -99,6 +115,26 @@ export interface StreamMeta {
99
115
  /** Per-platform stream metadata overrides, keyed by platform name. */
100
116
  export type DestinationMeta = Partial<Record<Platform, StreamMeta>> & Record<string, StreamMeta>;
101
117
 
118
+ /** Where the creator's frontend placed the camera while screen-sharing (template hint). */
119
+ export type CameraPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
120
+ /** Camera size as an enum or a percentage of the frame (1-100). */
121
+ export type CameraSize = "small" | "medium" | "large" | number;
122
+
123
+ /**
124
+ * Real-time presence/layout state pushed via setStreamState(). All fields optional —
125
+ * partial patches merge server-side. Camera position/size only matter while screen-sharing
126
+ * and are template metadata (they don't move media).
127
+ */
128
+ export interface StreamState {
129
+ screenShare?: boolean;
130
+ camera?: boolean;
131
+ collab?: boolean;
132
+ cameraPosition?: CameraPosition | null;
133
+ cameraSize?: CameraSize | null;
134
+ /** Server-stamped on each update (read-only on the way back). */
135
+ updatedAt?: string;
136
+ }
137
+
102
138
  export interface OverlayUpdate {
103
139
  topBanner?: string;
104
140
  bottomBanner?: string;
@@ -176,22 +212,34 @@ export class RestreamStudio extends EventTarget {
176
212
  on(type: "comment", cb: (c: Comment) => void): () => void;
177
213
  on(type: "comments", cb: (c: Comment[]) => void): () => void;
178
214
  on(type: "error", cb: (e: Error) => void): () => void;
215
+ on(type: "state", cb: (s: StreamState) => void): () => void;
179
216
  on(type: "collab" | "collab-invite" | "collab-accepted" | "collab-declined" | "collab-revoked" | "collab-ended" | "collab-expired" | "collab-composite" | "collab-snapshot", cb: (e: CollabEvent) => void): () => void;
180
217
  listConnections(): Promise<Connection[]>;
181
- listOAuthPlatforms(): Promise<{ platforms: Platform[]; frontendOrigin: string; redirectOrigin: string }>;
218
+ listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
182
219
  listRestreamChannels(): Promise<RestreamChannel[]>;
183
- /** URL of Restream's add-channel page (best-effort deep-link to `platform`). */
220
+ /** URL of Restream's add-channel page; YouTube/Facebook return native OAuth URLs in publishable-key mode. */
184
221
  restreamAddChannelUrl(platform?: string): string;
185
222
  /**
186
223
  * One-call "Connect <platform> via Restream" — ensures the Restream account is
187
224
  * connected, opens Restream's add-channel page, and resolves once the new channel
188
- * appears. Returns the new channel, or null if the user closed the popup without
225
+ * appears. YouTube and Facebook are routed to native OAuth instead. Returns the
226
+ * new channel, a native placeholder, or null if the user closed the popup without
189
227
  * adding one. Must be called from a user gesture (opens a popup).
190
228
  */
191
229
  connectRestreamChannel(
192
230
  platform: string,
193
231
  opts?: { pollMs?: number; timeoutMs?: number }
194
232
  ): Promise<RestreamChannel | null>;
233
+ /** Load + cache the per-client routing map; call once on init. */
234
+ loadRouting(): Promise<Routing>;
235
+ /** Effective route for a platform (uses cached admin routing, else static fallback). */
236
+ routeFor(platform: string): RoutingValue | null;
237
+ /** Single dynamic connect entry point — routes to native OAuth or the Restream bridge
238
+ * per the admin's configuration. Must be called from a user gesture. */
239
+ connectPlatform(
240
+ platform: string,
241
+ opts?: { pollMs?: number; timeoutMs?: number }
242
+ ): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
195
243
  connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
196
244
  disconnect(platform: Platform): Promise<Connection[]>;
197
245
  arm(opts: { destinations: Platform[] }): unknown;
@@ -200,6 +248,11 @@ export class RestreamStudio extends EventTarget {
200
248
  refreshJob(): Promise<Job | null>;
201
249
  sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
202
250
  updateOverlay(opts: OverlayUpdate): Promise<unknown>;
251
+ /** Push real-time presence/layout state (screen-share/camera/collab + camera corner/size).
252
+ * Call on every change; sends are coalesced (trailing-debounced). Returns the merged state. */
253
+ setStreamState(patch: StreamState): StreamState;
254
+ /** Last presence/layout state set locally. */
255
+ readonly streamState: StreamState;
203
256
  startRealtime(jobId: string): Promise<void>;
204
257
  stopRealtime(): void;
205
258
  // collaborative split-screen
@@ -230,19 +283,25 @@ export interface UseRestream {
230
283
  connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
231
284
  disconnect(platform: Platform): Promise<Connection[]>;
232
285
  listConnections(): Promise<Connection[]>;
233
- listOAuthPlatforms(): Promise<{ platforms: Platform[]; frontendOrigin: string; redirectOrigin: string }>;
286
+ listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
234
287
  listRestreamChannels(): Promise<RestreamChannel[]>;
235
288
  restreamAddChannelUrl(platform?: string): string;
236
289
  connectRestreamChannel(
237
290
  platform: string,
238
291
  opts?: { pollMs?: number; timeoutMs?: number }
239
292
  ): Promise<RestreamChannel | null>;
293
+ connectPlatform(
294
+ platform: string,
295
+ opts?: { pollMs?: number; timeoutMs?: number }
296
+ ): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
297
+ loadRouting(): Promise<Routing>;
240
298
  arm(opts: { destinations: Platform[] }): unknown;
241
299
  goLive(opts: GoLiveParams): Promise<Job>;
242
300
  stopLive(): Promise<unknown>;
243
301
  refreshJob(): Promise<Job | null>;
244
302
  sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
245
303
  updateOverlay(opts: OverlayUpdate): Promise<unknown>;
304
+ setStreamState(patch: StreamState): StreamState;
246
305
  requestCollab(opts: RequestCollabParams): Promise<CollabInvite>;
247
306
  acceptCollab(inviteId: string, opts: AcceptCollabParams): Promise<{ invite: CollabInvite; job: Job }>;
248
307
  declineCollab(inviteId: string): Promise<CollabInvite>;
@@ -260,13 +319,18 @@ export function useConnections(opts: RestreamOptions): {
260
319
  connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
261
320
  disconnect(platform: Platform): Promise<Connection[]>;
262
321
  refresh(): Promise<Connection[]>;
263
- listOAuthPlatforms(): Promise<{ platforms: Platform[]; frontendOrigin: string; redirectOrigin: string }>;
322
+ listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
264
323
  listRestreamChannels(): Promise<RestreamChannel[]>;
265
324
  restreamAddChannelUrl(platform?: string): string;
266
325
  connectRestreamChannel(
267
326
  platform: string,
268
327
  opts?: { pollMs?: number; timeoutMs?: number }
269
328
  ): Promise<RestreamChannel | null>;
329
+ connectPlatform(
330
+ platform: string,
331
+ opts?: { pollMs?: number; timeoutMs?: number }
332
+ ): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
333
+ loadRouting(): Promise<Routing>;
270
334
  };
271
335
  export function useLiveChat(studio: RestreamStudio): { comments: Comment[]; send(message: string, platforms?: Platform[]): Promise<unknown> };
272
336
  export function useViewers(studio: RestreamStudio): ViewerCounts | null;