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 +21 -5
- package/src/core.js +46 -4
- package/src/react.js +26 -0
- package/types/index.d.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "restream-sdk",
|
|
3
|
-
"version": "0.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": [
|
|
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": {
|
|
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": [
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
const popup = window.open(
|
|
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
|
-
|
|
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 };
|