restream-sdk 0.1.1 → 0.1.3

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.1",
3
+ "version": "0.1.3",
4
4
  "description": "Plug-and-play headless React hooks to add multi-platform restreaming (connect socials, go live, live chat + viewers) with zero client backend.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -16,7 +16,12 @@
16
16
  "import": "./src/core.js"
17
17
  }
18
18
  },
19
- "files": ["src", "types", "README.md", "LICENSE"],
19
+ "files": [
20
+ "src",
21
+ "types",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
20
25
  "sideEffects": false,
21
26
  "engines": {
22
27
  "node": ">=18"
@@ -25,7 +30,9 @@
25
30
  "react": ">=17"
26
31
  },
27
32
  "peerDependenciesMeta": {
28
- "react": { "optional": true }
33
+ "react": {
34
+ "optional": true
35
+ }
29
36
  },
30
37
  "repository": {
31
38
  "type": "git",
@@ -38,5 +45,14 @@
38
45
  },
39
46
  "author": "Ravant Technologies FZCO",
40
47
  "license": "MIT",
41
- "keywords": ["restream", "streaming", "twitch", "kick", "youtube", "multistream", "react", "sdk"]
42
- }
48
+ "keywords": [
49
+ "restream",
50
+ "streaming",
51
+ "twitch",
52
+ "kick",
53
+ "youtube",
54
+ "multistream",
55
+ "react",
56
+ "sdk"
57
+ ]
58
+ }
package/src/core.js CHANGED
@@ -39,6 +39,8 @@ export class RestreamStudio extends EventTarget {
39
39
  this._pollTimer = null;
40
40
  this._sseReopen = null;
41
41
  this._armed = null;
42
+ this._watchPoll = null;
43
+ this._watchUserId = null;
42
44
  }
43
45
 
44
46
  _emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail })); }
@@ -129,10 +131,12 @@ export class RestreamStudio extends EventTarget {
129
131
 
130
132
  /** Open the hosted OAuth popup for a platform; resolves when connected (or the popup closes). */
131
133
  async connect(platform) {
132
- const q = await this._authQuery();
133
- const url = `${this.apiBase}/api/v1/connect/${platform}/start?${new URLSearchParams(q)}`;
134
- const popup = window.open(url, "restream_connect", "width=560,height=760");
134
+ // Open the popup synchronously, inside the click's user-activation window —
135
+ // any await before window.open consumes the gesture and the browser blocks it.
136
+ const popup = window.open("", "restream_connect", "width=560,height=760");
135
137
  if (!popup) throw new Error("popup blocked — allow popups to connect accounts");
138
+ const q = await this._authQuery();
139
+ popup.location = `${this.apiBase}/api/v1/connect/${platform}/start?${new URLSearchParams(q)}`;
136
140
  return new Promise((resolve, reject) => {
137
141
  const cleanup = () => {
138
142
  window.removeEventListener("message", onMsg);
@@ -267,5 +271,43 @@ export class RestreamStudio extends EventTarget {
267
271
  if (this._sseReopen) { clearTimeout(this._sseReopen); this._sseReopen = null; }
268
272
  }
269
273
 
270
- destroy() { this.stopRealtime(); }
274
+ // ---- viewer mode (read-only) ----
275
+ /**
276
+ * Watch a streamer's LIVE social chat + viewer counts, read-only (no goLive).
277
+ * Resolves their active job and streams `comment`/`viewers` events just like the
278
+ * broadcaster sees. If the stream isn't live yet (or ends and restarts), it keeps
279
+ * polling and (re)attaches automatically. Needs only the `read` scope.
280
+ * @param {string} userId The streamer's external user id (e.g. wallet).
281
+ * @param {object} [opts] { pollMs=5000 } — 0 disables the keep-trying poll.
282
+ * @returns {Promise<{id:string}|null>} the active job if one is live now.
283
+ */
284
+ async watch(userId, { pollMs = 5000 } = {}) {
285
+ this.unwatch();
286
+ this._watchUserId = userId;
287
+ const attach = async () => {
288
+ if (this._watchUserId !== userId) return;
289
+ if (this._es || this._pollTimer) return; // already attached to a live job
290
+ try {
291
+ const r = await this._req("GET", `/api/v1/jobs/active?userId=${encodeURIComponent(userId)}`);
292
+ if (r?.jobId && this._watchUserId === userId) {
293
+ this.job = { id: r.jobId, status: r.status };
294
+ this._emit("job", this.job);
295
+ this.startRealtime(r.jobId);
296
+ }
297
+ } catch { /* transient — retry next tick */ }
298
+ };
299
+ await attach();
300
+ if (pollMs > 0) this._watchPoll = setInterval(attach, pollMs);
301
+ return this.job;
302
+ }
303
+
304
+ /** Stop watching (clears the keep-trying poll + the realtime streams). */
305
+ unwatch() {
306
+ this._watchUserId = null;
307
+ if (this._watchPoll) { clearInterval(this._watchPoll); this._watchPoll = null; }
308
+ this.stopRealtime();
309
+ this.job = null;
310
+ }
311
+
312
+ destroy() { this.unwatch(); }
271
313
  }
package/src/react.js CHANGED
@@ -69,3 +69,29 @@ export function useViewers(studio) {
69
69
  useEffect(() => (studio ? studio.on("viewers", setViewers) : undefined), [studio]);
70
70
  return viewers;
71
71
  }
72
+
73
+ /**
74
+ * Read-only viewer mode — watch a streamer's live social chat + viewer counts.
75
+ * Any viewer (not just the broadcaster) can call this; needs only the `read` scope.
76
+ *
77
+ * @param {object} opts { apiBase, publishableKey } (no userId needed)
78
+ * @param {string} userId The streamer to watch.
79
+ */
80
+ export function useWatch(opts, userId) {
81
+ const { apiBase, publishableKey, tokenEndpoint } = opts || {};
82
+ const [comments, setComments] = useState([]);
83
+ const [viewers, setViewers] = useState(null);
84
+ const [job, setJob] = useState(null);
85
+ useEffect(() => {
86
+ if (!userId || !apiBase) return undefined;
87
+ const studio = new RestreamStudio({ apiBase, publishableKey, tokenEndpoint });
88
+ const offs = [
89
+ studio.on("comments", setComments),
90
+ studio.on("viewers", setViewers),
91
+ studio.on("job", setJob),
92
+ ];
93
+ studio.watch(userId);
94
+ return () => { offs.forEach((off) => off()); studio.destroy(); };
95
+ }, [apiBase, publishableKey, tokenEndpoint, userId]);
96
+ return { comments, viewers, job };
97
+ }
package/types/index.d.ts CHANGED
@@ -94,6 +94,10 @@ export class RestreamStudio extends EventTarget {
94
94
  updateOverlay(opts: OverlayUpdate): Promise<unknown>;
95
95
  startRealtime(jobId: string): Promise<void>;
96
96
  stopRealtime(): void;
97
+ /** Read-only viewer mode: stream a streamer's live social chat + viewer counts. */
98
+ watch(userId: string, opts?: { pollMs?: number }): Promise<{ id: string } | null>;
99
+ /** Stop watching (clears the poll + realtime streams). */
100
+ unwatch(): void;
97
101
  destroy(): void;
98
102
  }
99
103
 
@@ -124,3 +128,7 @@ export function useConnections(opts: RestreamOptions): {
124
128
  };
125
129
  export function useLiveChat(studio: RestreamStudio): { comments: Comment[]; send(message: string, platforms?: Platform[]): Promise<unknown> };
126
130
  export function useViewers(studio: RestreamStudio): ViewerCounts | null;
131
+ export function useWatch(
132
+ opts: { apiBase: string; publishableKey?: string; tokenEndpoint?: string },
133
+ userId: string
134
+ ): { comments: Comment[]; viewers: ViewerCounts | null; job: Job | null };