@rstreamlabs/react 1.1.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 ADDED
@@ -0,0 +1,3 @@
1
+ # `@rstreamlabs/react`
2
+
3
+ React hooks and components for building rstream-enabled UIs.
@@ -0,0 +1,90 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ITerminalOptions, Terminal } from '@xterm/xterm';
3
+ import { WebTTYClientConfig, WebTTYExecutionConfig, WebTTYEvents } from '@rstreamlabs/webtty';
4
+ import { RstreamAuth } from '@rstreamlabs/rstream';
5
+
6
+ interface WebTTYTerminalProps extends WebTTYClientConfig, WebTTYExecutionConfig, WebTTYEvents {
7
+ /**
8
+ * xterm.js TerminalOptions override
9
+ */
10
+ terminalOptions?: ITerminalOptions;
11
+ /**
12
+ * Called once the xterm Terminal is created (before connect).
13
+ * Can be used to add your own add-ons or manipulate the Terminal instance.
14
+ */
15
+ onTerminalCreated?: (terminal: Terminal) => void;
16
+ }
17
+ /**
18
+ * A React component that binds a WebTTY session to an xterm.js instance.
19
+ */
20
+ declare function WebTTYTerminal(props: WebTTYTerminalProps): react_jsx_runtime.JSX.Element;
21
+
22
+ /**
23
+ * Configuration for the UseRstream hook.
24
+ */
25
+ interface UseRstreamOptions {
26
+ /**
27
+ * Short-term authentication token provider.
28
+ */
29
+ auth?: RstreamAuth;
30
+ /**
31
+ * Timeout (in milliseconds) to wait before attempting to reconnect
32
+ * after the SSE connection is closed.
33
+ *
34
+ * @default 1000
35
+ */
36
+ reconnectTimeout?: number;
37
+ /**
38
+ * Timeout (in milliseconds) to wait before showing an error message
39
+ * when the connection is not yet established.
40
+ *
41
+ * @default 5000
42
+ */
43
+ errorTimeout?: number;
44
+ }
45
+ /**
46
+ * A React hook to subscribe to rstream resources.
47
+ *
48
+ * This hook fetches the list of clients and tunnels from the rstream API.
49
+ * It handles automatic reconnection and displays a warning if the connection
50
+ * is not established within the timeout.
51
+ *
52
+ * @param options - The configuration options for the hook.
53
+ * @returns An object with the current error (if any), and arrays of tunnels and clients.
54
+ */
55
+ declare function useRstream(options?: UseRstreamOptions): {
56
+ error: {
57
+ message: string;
58
+ type: "warning" | "danger";
59
+ } | null;
60
+ tunnels: {
61
+ status: "online" | "offline";
62
+ id: string;
63
+ client_id: string;
64
+ user_id: string;
65
+ protocol: string;
66
+ publish: boolean;
67
+ tls_mode: string;
68
+ mtls: boolean;
69
+ token_auth: boolean;
70
+ path?: string | undefined;
71
+ name?: string | undefined;
72
+ labels?: Record<string, string | undefined> | undefined;
73
+ host?: string | undefined;
74
+ tls_min_version?: string | undefined;
75
+ }[];
76
+ clients: {
77
+ status: "online" | "offline";
78
+ id: string;
79
+ user_id: string;
80
+ labels?: Record<string, string | undefined> | undefined;
81
+ details?: {
82
+ agent?: string | undefined;
83
+ os?: string | undefined;
84
+ version?: string | undefined;
85
+ protocol_version?: string | undefined;
86
+ } | undefined;
87
+ }[];
88
+ };
89
+
90
+ export { type UseRstreamOptions, WebTTYTerminal, type WebTTYTerminalProps, useRstream };
@@ -0,0 +1,90 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ITerminalOptions, Terminal } from '@xterm/xterm';
3
+ import { WebTTYClientConfig, WebTTYExecutionConfig, WebTTYEvents } from '@rstreamlabs/webtty';
4
+ import { RstreamAuth } from '@rstreamlabs/rstream';
5
+
6
+ interface WebTTYTerminalProps extends WebTTYClientConfig, WebTTYExecutionConfig, WebTTYEvents {
7
+ /**
8
+ * xterm.js TerminalOptions override
9
+ */
10
+ terminalOptions?: ITerminalOptions;
11
+ /**
12
+ * Called once the xterm Terminal is created (before connect).
13
+ * Can be used to add your own add-ons or manipulate the Terminal instance.
14
+ */
15
+ onTerminalCreated?: (terminal: Terminal) => void;
16
+ }
17
+ /**
18
+ * A React component that binds a WebTTY session to an xterm.js instance.
19
+ */
20
+ declare function WebTTYTerminal(props: WebTTYTerminalProps): react_jsx_runtime.JSX.Element;
21
+
22
+ /**
23
+ * Configuration for the UseRstream hook.
24
+ */
25
+ interface UseRstreamOptions {
26
+ /**
27
+ * Short-term authentication token provider.
28
+ */
29
+ auth?: RstreamAuth;
30
+ /**
31
+ * Timeout (in milliseconds) to wait before attempting to reconnect
32
+ * after the SSE connection is closed.
33
+ *
34
+ * @default 1000
35
+ */
36
+ reconnectTimeout?: number;
37
+ /**
38
+ * Timeout (in milliseconds) to wait before showing an error message
39
+ * when the connection is not yet established.
40
+ *
41
+ * @default 5000
42
+ */
43
+ errorTimeout?: number;
44
+ }
45
+ /**
46
+ * A React hook to subscribe to rstream resources.
47
+ *
48
+ * This hook fetches the list of clients and tunnels from the rstream API.
49
+ * It handles automatic reconnection and displays a warning if the connection
50
+ * is not established within the timeout.
51
+ *
52
+ * @param options - The configuration options for the hook.
53
+ * @returns An object with the current error (if any), and arrays of tunnels and clients.
54
+ */
55
+ declare function useRstream(options?: UseRstreamOptions): {
56
+ error: {
57
+ message: string;
58
+ type: "warning" | "danger";
59
+ } | null;
60
+ tunnels: {
61
+ status: "online" | "offline";
62
+ id: string;
63
+ client_id: string;
64
+ user_id: string;
65
+ protocol: string;
66
+ publish: boolean;
67
+ tls_mode: string;
68
+ mtls: boolean;
69
+ token_auth: boolean;
70
+ path?: string | undefined;
71
+ name?: string | undefined;
72
+ labels?: Record<string, string | undefined> | undefined;
73
+ host?: string | undefined;
74
+ tls_min_version?: string | undefined;
75
+ }[];
76
+ clients: {
77
+ status: "online" | "offline";
78
+ id: string;
79
+ user_id: string;
80
+ labels?: Record<string, string | undefined> | undefined;
81
+ details?: {
82
+ agent?: string | undefined;
83
+ os?: string | undefined;
84
+ version?: string | undefined;
85
+ protocol_version?: string | undefined;
86
+ } | undefined;
87
+ }[];
88
+ };
89
+
90
+ export { type UseRstreamOptions, WebTTYTerminal, type WebTTYTerminalProps, useRstream };
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ WebTTYTerminal: () => WebTTYTerminal,
34
+ useRstream: () => useRstream
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/components/webtty.tsx
39
+ var import_xterm = require("@xterm/xterm/css/xterm.css");
40
+ var import_addon_fit = require("@xterm/addon-fit");
41
+ var import_xterm2 = require("@xterm/xterm");
42
+ var import_addon_unicode11 = require("@xterm/addon-unicode11");
43
+ var import_addon_webgl = require("@xterm/addon-webgl");
44
+ var import_addon_web_links = require("@xterm/addon-web-links");
45
+ var import_webtty = require("@rstreamlabs/webtty");
46
+ var React = __toESM(require("react"));
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ function WebTTYTerminal(props) {
49
+ const {
50
+ // WebTTY client config
51
+ url,
52
+ sendHeartbeat,
53
+ heartbeatIntervalMs,
54
+ // Execution config
55
+ cmdArgs,
56
+ envVars,
57
+ allocateTty,
58
+ interactive,
59
+ username,
60
+ workdir,
61
+ // Events
62
+ onStdout,
63
+ onStdoutEos,
64
+ onStderr,
65
+ onStderrEos,
66
+ onConnect,
67
+ onComplete,
68
+ onError,
69
+ // xterm.js options
70
+ terminalOptions,
71
+ // Callbacks
72
+ onTerminalCreated
73
+ } = props;
74
+ const ref = React.useRef(null);
75
+ React.useEffect(() => {
76
+ if (!ref.current) {
77
+ return;
78
+ }
79
+ let disposeOnData = null;
80
+ let disposeOnResize = null;
81
+ let resizeObserver = null;
82
+ const clear = () => {
83
+ disposeOnData?.dispose();
84
+ disposeOnData = null;
85
+ disposeOnResize?.dispose();
86
+ disposeOnResize = null;
87
+ resizeObserver?.disconnect();
88
+ resizeObserver = null;
89
+ };
90
+ const terminal = new import_xterm2.Terminal({
91
+ allowProposedApi: true,
92
+ cursorBlink: false,
93
+ scrollback: 1e4,
94
+ ...terminalOptions
95
+ });
96
+ const fitAddon = new import_addon_fit.FitAddon();
97
+ terminal.loadAddon(fitAddon);
98
+ terminal.loadAddon(new import_addon_web_links.WebLinksAddon());
99
+ terminal.loadAddon(new import_addon_unicode11.Unicode11Addon());
100
+ try {
101
+ terminal.loadAddon(new import_addon_webgl.WebglAddon());
102
+ } catch (err) {
103
+ console.warn("WebGL addon could not be loaded:", err);
104
+ }
105
+ terminal.open(ref.current);
106
+ onTerminalCreated?.(terminal);
107
+ const webtty = new import_webtty.WebTTY(
108
+ { url, sendHeartbeat, heartbeatIntervalMs },
109
+ { cmdArgs, envVars, allocateTty, interactive, username, workdir },
110
+ {
111
+ onStdout: (chunk) => {
112
+ onStdout?.(chunk);
113
+ terminal.write(chunk);
114
+ },
115
+ onStdoutEos: () => {
116
+ onStdoutEos?.();
117
+ },
118
+ onStderr: (chunk) => {
119
+ onStderr?.(chunk);
120
+ terminal.write(
121
+ "\x1B[31m" + new TextDecoder().decode(chunk) + "\x1B[0m"
122
+ );
123
+ },
124
+ onStderrEos: () => {
125
+ onStderrEos?.();
126
+ },
127
+ onConnect: () => {
128
+ onConnect?.();
129
+ terminal.focus();
130
+ try {
131
+ webtty.resize(terminal.rows, terminal.cols);
132
+ } catch (e) {
133
+ console.error("Cannot resize remote TTY:", e);
134
+ }
135
+ disposeOnData = terminal.onData((data) => {
136
+ try {
137
+ webtty.writeStdin(new TextEncoder().encode(data));
138
+ } catch (e) {
139
+ console.error("Cannot writeStdin:", e);
140
+ }
141
+ });
142
+ disposeOnResize = terminal.onResize((size) => {
143
+ try {
144
+ webtty.resize(size.rows, size.cols);
145
+ } catch (e) {
146
+ console.error("Cannot resize remote TTY:", e);
147
+ }
148
+ });
149
+ },
150
+ onComplete: (code) => {
151
+ onComplete?.(code);
152
+ terminal.write(`\r
153
+ Process exited with code ${code}\r
154
+ `);
155
+ clear();
156
+ },
157
+ onError: (err) => {
158
+ onError?.(err);
159
+ terminal.write(`\r
160
+ [ERROR] ${err}\r
161
+ `);
162
+ clear();
163
+ }
164
+ }
165
+ );
166
+ webtty.connect();
167
+ resizeObserver = new ResizeObserver((entries) => {
168
+ for (const entry of entries) {
169
+ if (entry.target === ref.current) {
170
+ fitAddon.fit();
171
+ }
172
+ }
173
+ });
174
+ resizeObserver.observe(ref.current);
175
+ return () => {
176
+ clear();
177
+ terminal.dispose();
178
+ };
179
+ });
180
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, style: { height: "100%", width: "100%" } });
181
+ }
182
+
183
+ // src/hooks/use-rstream.ts
184
+ var import_rstream = require("@rstreamlabs/rstream");
185
+ var React2 = __toESM(require("react"));
186
+ function useRstream(options) {
187
+ const [state, setState] = React2.useState("disconnected");
188
+ const [error, setError] = React2.useState(null);
189
+ const { reconnectTimeout = 1e3, errorTimeout = 5e3 } = options || {};
190
+ const [clients, setClients] = React2.useState([]);
191
+ const [tunnels, setTunnels] = React2.useState([]);
192
+ React2.useEffect(() => {
193
+ if (!options?.auth) return;
194
+ let active = true;
195
+ let watch = null;
196
+ let timeout = null;
197
+ const run = async () => {
198
+ if (!options?.auth) return;
199
+ setState("connecting");
200
+ watch = new import_rstream.Watch(
201
+ { auth: options.auth },
202
+ {
203
+ onEvent: (event) => {
204
+ if (!active) return;
205
+ if (event.type.startsWith("client")) {
206
+ setClients((previous) => {
207
+ if (event.type === "client.created") {
208
+ return [...previous, event.object];
209
+ } else if (event.type === "client.updated") {
210
+ return previous.map((client) => {
211
+ if (client.id === event.object.id) {
212
+ return event.object;
213
+ }
214
+ return client;
215
+ });
216
+ } else if (event.type === "client.deleted") {
217
+ return previous.filter(
218
+ (client) => client.id !== event.object.id
219
+ );
220
+ }
221
+ return previous;
222
+ });
223
+ } else if (event.type.startsWith("tunnel")) {
224
+ setTunnels((previous) => {
225
+ if (event.type === "tunnel.created") {
226
+ return [...previous, event.object];
227
+ } else if (event.type === "tunnel.updated") {
228
+ return previous.map((tunnel) => {
229
+ if (tunnel.id === event.object.id) {
230
+ return event.object;
231
+ }
232
+ return tunnel;
233
+ });
234
+ } else if (event.type === "tunnel.deleted") {
235
+ return previous.filter(
236
+ (tunnel) => tunnel.id !== event.object.id
237
+ );
238
+ }
239
+ return previous;
240
+ });
241
+ }
242
+ },
243
+ onConnect: () => {
244
+ if (!active) return;
245
+ setState("connected");
246
+ },
247
+ onClose: () => {
248
+ if (!active) return;
249
+ watch = null;
250
+ setState("connecting");
251
+ timeout = setTimeout(() => {
252
+ if (!active) return;
253
+ timeout = null;
254
+ run();
255
+ }, reconnectTimeout);
256
+ }
257
+ }
258
+ );
259
+ };
260
+ run();
261
+ return () => {
262
+ active = false;
263
+ if (watch) {
264
+ watch.disconnect();
265
+ watch = null;
266
+ }
267
+ if (timeout) {
268
+ clearTimeout(timeout);
269
+ timeout = null;
270
+ }
271
+ };
272
+ }, [options, reconnectTimeout]);
273
+ React2.useEffect(() => {
274
+ if (options?.auth) {
275
+ if (error && error.type === "danger") return;
276
+ if (state !== "connected") {
277
+ const timeout = setTimeout(() => {
278
+ setError({
279
+ message: "Failed to fetch rstream ressources. Retrying...",
280
+ type: "warning"
281
+ });
282
+ }, errorTimeout);
283
+ return () => {
284
+ clearTimeout(timeout);
285
+ };
286
+ } else if (state === "connected") {
287
+ setError(null);
288
+ }
289
+ } else {
290
+ setError(null);
291
+ }
292
+ }, [options, state, error, errorTimeout]);
293
+ React2.useEffect(() => {
294
+ if (options?.auth === void 0 || state !== "connected") {
295
+ setClients([]);
296
+ setTunnels([]);
297
+ }
298
+ }, [options, state]);
299
+ return { error, tunnels, clients };
300
+ }
301
+ // Annotate the CommonJS export names for ESM import in node:
302
+ 0 && (module.exports = {
303
+ WebTTYTerminal,
304
+ useRstream
305
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,269 @@
1
+ // src/components/webtty.tsx
2
+ import "@xterm/xterm/css/xterm.css";
3
+ import { FitAddon } from "@xterm/addon-fit";
4
+ import { Terminal } from "@xterm/xterm";
5
+ import { Unicode11Addon } from "@xterm/addon-unicode11";
6
+ import { WebglAddon } from "@xterm/addon-webgl";
7
+ import { WebLinksAddon } from "@xterm/addon-web-links";
8
+ import {
9
+ WebTTY
10
+ } from "@rstreamlabs/webtty";
11
+ import * as React from "react";
12
+ import { jsx } from "react/jsx-runtime";
13
+ function WebTTYTerminal(props) {
14
+ const {
15
+ // WebTTY client config
16
+ url,
17
+ sendHeartbeat,
18
+ heartbeatIntervalMs,
19
+ // Execution config
20
+ cmdArgs,
21
+ envVars,
22
+ allocateTty,
23
+ interactive,
24
+ username,
25
+ workdir,
26
+ // Events
27
+ onStdout,
28
+ onStdoutEos,
29
+ onStderr,
30
+ onStderrEos,
31
+ onConnect,
32
+ onComplete,
33
+ onError,
34
+ // xterm.js options
35
+ terminalOptions,
36
+ // Callbacks
37
+ onTerminalCreated
38
+ } = props;
39
+ const ref = React.useRef(null);
40
+ React.useEffect(() => {
41
+ if (!ref.current) {
42
+ return;
43
+ }
44
+ let disposeOnData = null;
45
+ let disposeOnResize = null;
46
+ let resizeObserver = null;
47
+ const clear = () => {
48
+ disposeOnData?.dispose();
49
+ disposeOnData = null;
50
+ disposeOnResize?.dispose();
51
+ disposeOnResize = null;
52
+ resizeObserver?.disconnect();
53
+ resizeObserver = null;
54
+ };
55
+ const terminal = new Terminal({
56
+ allowProposedApi: true,
57
+ cursorBlink: false,
58
+ scrollback: 1e4,
59
+ ...terminalOptions
60
+ });
61
+ const fitAddon = new FitAddon();
62
+ terminal.loadAddon(fitAddon);
63
+ terminal.loadAddon(new WebLinksAddon());
64
+ terminal.loadAddon(new Unicode11Addon());
65
+ try {
66
+ terminal.loadAddon(new WebglAddon());
67
+ } catch (err) {
68
+ console.warn("WebGL addon could not be loaded:", err);
69
+ }
70
+ terminal.open(ref.current);
71
+ onTerminalCreated?.(terminal);
72
+ const webtty = new WebTTY(
73
+ { url, sendHeartbeat, heartbeatIntervalMs },
74
+ { cmdArgs, envVars, allocateTty, interactive, username, workdir },
75
+ {
76
+ onStdout: (chunk) => {
77
+ onStdout?.(chunk);
78
+ terminal.write(chunk);
79
+ },
80
+ onStdoutEos: () => {
81
+ onStdoutEos?.();
82
+ },
83
+ onStderr: (chunk) => {
84
+ onStderr?.(chunk);
85
+ terminal.write(
86
+ "\x1B[31m" + new TextDecoder().decode(chunk) + "\x1B[0m"
87
+ );
88
+ },
89
+ onStderrEos: () => {
90
+ onStderrEos?.();
91
+ },
92
+ onConnect: () => {
93
+ onConnect?.();
94
+ terminal.focus();
95
+ try {
96
+ webtty.resize(terminal.rows, terminal.cols);
97
+ } catch (e) {
98
+ console.error("Cannot resize remote TTY:", e);
99
+ }
100
+ disposeOnData = terminal.onData((data) => {
101
+ try {
102
+ webtty.writeStdin(new TextEncoder().encode(data));
103
+ } catch (e) {
104
+ console.error("Cannot writeStdin:", e);
105
+ }
106
+ });
107
+ disposeOnResize = terminal.onResize((size) => {
108
+ try {
109
+ webtty.resize(size.rows, size.cols);
110
+ } catch (e) {
111
+ console.error("Cannot resize remote TTY:", e);
112
+ }
113
+ });
114
+ },
115
+ onComplete: (code) => {
116
+ onComplete?.(code);
117
+ terminal.write(`\r
118
+ Process exited with code ${code}\r
119
+ `);
120
+ clear();
121
+ },
122
+ onError: (err) => {
123
+ onError?.(err);
124
+ terminal.write(`\r
125
+ [ERROR] ${err}\r
126
+ `);
127
+ clear();
128
+ }
129
+ }
130
+ );
131
+ webtty.connect();
132
+ resizeObserver = new ResizeObserver((entries) => {
133
+ for (const entry of entries) {
134
+ if (entry.target === ref.current) {
135
+ fitAddon.fit();
136
+ }
137
+ }
138
+ });
139
+ resizeObserver.observe(ref.current);
140
+ return () => {
141
+ clear();
142
+ terminal.dispose();
143
+ };
144
+ });
145
+ return /* @__PURE__ */ jsx("div", { ref, style: { height: "100%", width: "100%" } });
146
+ }
147
+
148
+ // src/hooks/use-rstream.ts
149
+ import { Watch } from "@rstreamlabs/rstream";
150
+ import * as React2 from "react";
151
+ function useRstream(options) {
152
+ const [state, setState] = React2.useState("disconnected");
153
+ const [error, setError] = React2.useState(null);
154
+ const { reconnectTimeout = 1e3, errorTimeout = 5e3 } = options || {};
155
+ const [clients, setClients] = React2.useState([]);
156
+ const [tunnels, setTunnels] = React2.useState([]);
157
+ React2.useEffect(() => {
158
+ if (!options?.auth) return;
159
+ let active = true;
160
+ let watch = null;
161
+ let timeout = null;
162
+ const run = async () => {
163
+ if (!options?.auth) return;
164
+ setState("connecting");
165
+ watch = new Watch(
166
+ { auth: options.auth },
167
+ {
168
+ onEvent: (event) => {
169
+ if (!active) return;
170
+ if (event.type.startsWith("client")) {
171
+ setClients((previous) => {
172
+ if (event.type === "client.created") {
173
+ return [...previous, event.object];
174
+ } else if (event.type === "client.updated") {
175
+ return previous.map((client) => {
176
+ if (client.id === event.object.id) {
177
+ return event.object;
178
+ }
179
+ return client;
180
+ });
181
+ } else if (event.type === "client.deleted") {
182
+ return previous.filter(
183
+ (client) => client.id !== event.object.id
184
+ );
185
+ }
186
+ return previous;
187
+ });
188
+ } else if (event.type.startsWith("tunnel")) {
189
+ setTunnels((previous) => {
190
+ if (event.type === "tunnel.created") {
191
+ return [...previous, event.object];
192
+ } else if (event.type === "tunnel.updated") {
193
+ return previous.map((tunnel) => {
194
+ if (tunnel.id === event.object.id) {
195
+ return event.object;
196
+ }
197
+ return tunnel;
198
+ });
199
+ } else if (event.type === "tunnel.deleted") {
200
+ return previous.filter(
201
+ (tunnel) => tunnel.id !== event.object.id
202
+ );
203
+ }
204
+ return previous;
205
+ });
206
+ }
207
+ },
208
+ onConnect: () => {
209
+ if (!active) return;
210
+ setState("connected");
211
+ },
212
+ onClose: () => {
213
+ if (!active) return;
214
+ watch = null;
215
+ setState("connecting");
216
+ timeout = setTimeout(() => {
217
+ if (!active) return;
218
+ timeout = null;
219
+ run();
220
+ }, reconnectTimeout);
221
+ }
222
+ }
223
+ );
224
+ };
225
+ run();
226
+ return () => {
227
+ active = false;
228
+ if (watch) {
229
+ watch.disconnect();
230
+ watch = null;
231
+ }
232
+ if (timeout) {
233
+ clearTimeout(timeout);
234
+ timeout = null;
235
+ }
236
+ };
237
+ }, [options, reconnectTimeout]);
238
+ React2.useEffect(() => {
239
+ if (options?.auth) {
240
+ if (error && error.type === "danger") return;
241
+ if (state !== "connected") {
242
+ const timeout = setTimeout(() => {
243
+ setError({
244
+ message: "Failed to fetch rstream ressources. Retrying...",
245
+ type: "warning"
246
+ });
247
+ }, errorTimeout);
248
+ return () => {
249
+ clearTimeout(timeout);
250
+ };
251
+ } else if (state === "connected") {
252
+ setError(null);
253
+ }
254
+ } else {
255
+ setError(null);
256
+ }
257
+ }, [options, state, error, errorTimeout]);
258
+ React2.useEffect(() => {
259
+ if (options?.auth === void 0 || state !== "connected") {
260
+ setClients([]);
261
+ setTunnels([]);
262
+ }
263
+ }, [options, state]);
264
+ return { error, tunnels, clients };
265
+ }
266
+ export {
267
+ WebTTYTerminal,
268
+ useRstream
269
+ };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@rstreamlabs/react",
3
+ "version": "1.1.0",
4
+ "description": "React hooks and components for building rstream-enabled UIs.",
5
+ "license": "Apache-2.0",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build:watch": "tsup --watch",
15
+ "build": "tsup",
16
+ "clean": "rm -rf dist",
17
+ "lint": "eslint \"./**/*.ts*\"",
18
+ "type-check": "tsc --noEmit"
19
+ },
20
+ "devDependencies": {
21
+ "@turbo/gen": "^2.4.0",
22
+ "@types/node": "^22.13.1",
23
+ "@types/react-dom": "19.0.3",
24
+ "@types/react": "19.0.8",
25
+ "eslint-config": "*",
26
+ "typescript-config": "*",
27
+ "typescript": "5.7.3"
28
+ },
29
+ "dependencies": {
30
+ "@rstreamlabs/rstream": "1.1.0",
31
+ "@rstreamlabs/webtty": "1.1.0",
32
+ "@xterm/addon-fit": "^0.10.0",
33
+ "@xterm/addon-unicode11": "^0.8.0",
34
+ "@xterm/addon-web-links": "^0.11.0",
35
+ "@xterm/addon-webgl": "^0.18.0",
36
+ "@xterm/xterm": "^5.5.0",
37
+ "react": "^19.0.0",
38
+ "react-dom": "^19.0.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@types/react": "*",
42
+ "react": "^18.0 || ^19.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "@types/react": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }