@fishjam-cloud/webrtc-client 0.25.0-rc.2 → 0.25.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/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@ import { BinaryWriter, BinaryReader } from '@bufbuild/protobuf/wire';
2
2
  import TypedEmitter from 'typed-emitter';
3
3
 
4
4
  declare const getLogger: (enableLogging: boolean) => {
5
+ readonly debug: (arg: unknown, ...args: unknown[]) => void;
5
6
  readonly warn: (arg: unknown, ...args: unknown[]) => void;
6
7
  readonly error: (arg: unknown, ...args: unknown[]) => void;
7
8
  };
@@ -417,6 +418,20 @@ interface WebRTCEndpointEvents {
417
418
  trackId: string;
418
419
  metadata: unknown;
419
420
  }) => void;
421
+ /**
422
+ * Emitted when data channels (for both reliable and lossy) are created and ready to send data.
423
+ * This event is fired after calling connectDataChannels().
424
+ */
425
+ dataChannelsReady: () => void;
426
+ /**
427
+ * Emitted when data is received on any data channel.
428
+ * The payload includes the channel type (reliable/lossy) and the binary data.
429
+ */
430
+ dataChannelPayload: (payload: DataChannelMessagePayload) => void;
431
+ /**
432
+ * Emitted when any data channel errors.
433
+ */
434
+ dataChannelsError: (error: Error) => void;
420
435
  }
421
436
  /**
422
437
  * Interface describing Endpoint.
@@ -439,6 +454,39 @@ interface Endpoint {
439
454
  */
440
455
  tracks: Map<string, TrackContext>;
441
456
  }
457
+ /**
458
+ * Options for publishing or subscribing to data.
459
+ */
460
+ interface DataChannelOptions {
461
+ /**
462
+ * If true, uses the reliable data channel (ordered, guaranteed delivery).
463
+ * If false, uses the lossy data channel (unordered, low latency).
464
+ */
465
+ reliable: boolean;
466
+ }
467
+ /**
468
+ * Callback type for receiving data from a data channel.
469
+ * @param data - The received data as a Uint8Array
470
+ */
471
+ type DataCallback = (data: Uint8Array) => void;
472
+ /**
473
+ * Internal type for channel classification
474
+ * @internal
475
+ */
476
+ type DataChannelType = 'reliable' | 'lossy';
477
+ /**
478
+ * Payload for data received events from data channels.
479
+ */
480
+ interface DataChannelMessagePayload {
481
+ /**
482
+ * The type of channel the data was received on.
483
+ */
484
+ channelType: DataChannelType;
485
+ /**
486
+ * The binary payload data.
487
+ */
488
+ data: Uint8Array;
489
+ }
442
490
  type WebRTCEndpointProps = {
443
491
  /**
444
492
  * Enables Fishjam SDK's debug logs in the console.
@@ -460,6 +508,7 @@ declare class ConnectionManager {
460
508
  removeTrack: (sender: RTCRtpSender) => void;
461
509
  findSender: (mediaStreamTrackId: MediaStreamTrackId) => RTCRtpSender;
462
510
  addIceCandidate: (iceCandidate: RTCIceCandidate) => Promise<void>;
511
+ createDataChannel: (label: string, config: RTCDataChannelInit) => RTCDataChannel;
463
512
  }
464
513
 
465
514
  declare const TrackContextImpl_base: new () => TypedEmitter<Required<TrackContextEvents>>;
@@ -493,6 +542,7 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
493
542
  private readonly remote;
494
543
  private readonly local;
495
544
  private readonly commandsQueue;
545
+ private readonly dataChannelManager;
496
546
  private proposedIceServers;
497
547
  private logger;
498
548
  bandwidthEstimation: bigint;
@@ -556,6 +606,7 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
556
606
  getRemoteEndpoints(): Record<string, EndpointWithTrackContext>;
557
607
  getLocalEndpoint(): EndpointWithTrackContext;
558
608
  getBandwidthEstimation(): bigint;
609
+ getDataChannelsReadiness(): boolean;
559
610
  private handleMediaEvent;
560
611
  private onSdpAnswer;
561
612
  /**
@@ -760,6 +811,54 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
760
811
  * that endpoint was removed in {@link WebRTCEndpointEvents.endpointRemoved},
761
812
  */
762
813
  disconnect: () => void;
814
+ /**
815
+ * Create both reliable and lossy data channel.
816
+ * This method must be called before publishData() can be used.
817
+ * Emits the 'dataChannelsReady' event when both channels are open and ready.
818
+ *
819
+ * @example
820
+ * ```ts
821
+ * const webrtc = new WebRTCEndpoint();
822
+ *
823
+ * webrtc.on('dataChannelsReady', () => {
824
+ * console.log('Data channels ready, can now send data');
825
+ * webrtc.publishData(new TextEncoder().encode('Hello'), { reliable: true });
826
+ * });
827
+ *
828
+ * webrtc.connectDataChannels();
829
+ * ```
830
+ */
831
+ connectDataChannels: () => Promise<void>;
832
+ /**
833
+ * Publish data through a data channel.
834
+ * The data channels must be created first by calling connectDataChannels() or enabling negotiateOnConnect.
835
+ * Throws an error if the channel doesn't exist or isn't ready yet.
836
+ *
837
+ * @param data - The data to send as Uint8Array
838
+ * @param options - Options specifying which channel to use (reliable or lossy)
839
+ * @throws Error if the channel doesn't exist or isn't ready
840
+ *
841
+ * @example
842
+ * ```ts
843
+ * // Subscribe to incoming data
844
+ * webrtc.on('dataReceived', ({ channelType, data }) => {
845
+ * console.log(`Received on ${channelType}:`, new TextDecoder().decode(data));
846
+ * });
847
+ *
848
+ * webrtc.on('dataChannelsReady', () => {
849
+ * // Send reliable data
850
+ * const data = new TextEncoder().encode('Hello World');
851
+ * webrtc.publishData(data, { reliable: true });
852
+ *
853
+ * // Send lossy data for low-latency updates
854
+ * const gameState = new Uint8Array([1, 2, 3, 4, 5]);
855
+ * webrtc.publishData(gameState, { reliable: false });
856
+ * });
857
+ *
858
+ * webrtc.connectDataChannels();
859
+ * ```
860
+ */
861
+ publishData: (data: Uint8Array, options: DataChannelOptions) => void;
763
862
  /**
764
863
  * Cleans up {@link WebRTCEndpoint} instance.
765
864
  */
@@ -777,4 +876,4 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
777
876
  private onIceConnectionStateChange;
778
877
  }
779
878
 
780
- export { type BandwidthLimit, type EncodingReason, type Endpoint, type Logger, type MediaEvent, type SerializedMediaEvent, type SimulcastBandwidthLimit, MediaEvent_Track_SimulcastConfig as SimulcastConfig, type TrackBandwidthLimit, type TrackContext, type TrackContextEvents, type TrackKind, type VadStatus, Variant, WebRTCEndpoint, type WebRTCEndpointEvents, getLogger };
879
+ export { type BandwidthLimit, type DataCallback, type DataChannelMessagePayload, type DataChannelOptions, type DataChannelType, type EncodingReason, type Endpoint, type Logger, type MediaEvent, type SerializedMediaEvent, type SimulcastBandwidthLimit, MediaEvent_Track_SimulcastConfig as SimulcastConfig, type TrackBandwidthLimit, type TrackContext, type TrackContextEvents, type TrackKind, type VadStatus, Variant, WebRTCEndpoint, type WebRTCEndpointEvents, getLogger };
package/dist/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
1
  // src/logger.ts
2
2
  var FISHJAM_PREFIX = "[FISHJAM]";
3
3
  var getLogger = (enableLogging) => ({
4
+ debug: (arg, ...args) => {
5
+ if (enableLogging) console.debug(FISHJAM_PREFIX, arg, ...args);
6
+ },
4
7
  warn: (arg, ...args) => {
5
8
  if (enableLogging) console.warn(FISHJAM_PREFIX, arg, ...args);
6
9
  },
@@ -1393,7 +1396,7 @@ function isSet2(value) {
1393
1396
  }
1394
1397
 
1395
1398
  // src/webRTCEndpoint.ts
1396
- import EventEmitter2 from "events";
1399
+ import EventEmitter3 from "events";
1397
1400
  import { v4 as uuidv4 } from "uuid";
1398
1401
 
1399
1402
  // src/CommandsQueue.ts
@@ -1527,6 +1530,306 @@ var ConnectionManager = class {
1527
1530
  addIceCandidate = async (iceCandidate) => {
1528
1531
  await this.connection.addIceCandidate(iceCandidate);
1529
1532
  };
1533
+ createDataChannel = (label, config) => {
1534
+ return this.connection.createDataChannel(label, config);
1535
+ };
1536
+ };
1537
+
1538
+ // src/dataChannels/DataChannelManager.ts
1539
+ import { EventEmitter } from "events";
1540
+
1541
+ // src/dataChannels/DataChannel.ts
1542
+ var DataChannel = class {
1543
+ constructor(type, logger) {
1544
+ this.type = type;
1545
+ this.logger = logger;
1546
+ }
1547
+ channel = null;
1548
+ dataCallback = null;
1549
+ openCallback = null;
1550
+ errorCallback = null;
1551
+ _status = "init";
1552
+ /**
1553
+ * Get the current status of the data channel
1554
+ */
1555
+ get status() {
1556
+ return this._status;
1557
+ }
1558
+ /**
1559
+ * Set the underlying RTCDataChannel and set up event listeners
1560
+ */
1561
+ setChannel(channel) {
1562
+ this._status = "creating";
1563
+ this.channel = channel;
1564
+ this.setupListeners();
1565
+ }
1566
+ /**
1567
+ * Set callback to be called when the channel is opened
1568
+ */
1569
+ setOnOpen(callback) {
1570
+ this.openCallback = callback;
1571
+ }
1572
+ /**
1573
+ * Set callback to be called when the channel errors
1574
+ */
1575
+ setOnError(callback) {
1576
+ this.errorCallback = callback;
1577
+ }
1578
+ /**
1579
+ * Send data through the channel
1580
+ * @param data - The data to send as Uint8Array
1581
+ */
1582
+ send(data) {
1583
+ if (!this.channel || this._status !== "open") {
1584
+ this.logger.warn(`Cannot send data on ${this.type} channel: channel not ready (status: ${this._status})`);
1585
+ return;
1586
+ }
1587
+ try {
1588
+ this.channel.send(data);
1589
+ } catch (error) {
1590
+ this.logger.error(`Error sending data on ${this.type} channel:`, error);
1591
+ }
1592
+ }
1593
+ /**
1594
+ * Set the callback for receiving data
1595
+ * @param callback - Function to call when data is received
1596
+ */
1597
+ setCallback(callback) {
1598
+ this.dataCallback = callback;
1599
+ }
1600
+ /**
1601
+ * Close the data channel
1602
+ */
1603
+ close() {
1604
+ if (this.channel) {
1605
+ this.channel.close();
1606
+ }
1607
+ this._status = "closed";
1608
+ this.dataCallback = null;
1609
+ }
1610
+ /**
1611
+ * Set up event listeners for the RTCDataChannel
1612
+ * @private
1613
+ */
1614
+ setupListeners() {
1615
+ if (!this.channel) return;
1616
+ this.channel.onopen = () => {
1617
+ this._status = "open";
1618
+ this.logger.debug(`Data channel ${this.type} opened`);
1619
+ this.openCallback?.();
1620
+ };
1621
+ this.channel.onclose = () => {
1622
+ this._status = "closed";
1623
+ this.logger.debug(`Data channel ${this.type} closed`);
1624
+ };
1625
+ this.channel.onerror = (event) => {
1626
+ this.logger.error(`Data channel ${this.type} error:`, event);
1627
+ this.errorCallback?.(event);
1628
+ };
1629
+ this.channel.onmessage = (event) => {
1630
+ this.handleMessage(event.data);
1631
+ };
1632
+ }
1633
+ /**
1634
+ * Handle incoming message data
1635
+ * @private
1636
+ */
1637
+ handleMessage(data) {
1638
+ if (!this.dataCallback) {
1639
+ this.logger.warn(`Received data on ${this.type} channel but no callback is set`);
1640
+ return;
1641
+ }
1642
+ try {
1643
+ let uint8Data;
1644
+ if (data instanceof ArrayBuffer) {
1645
+ uint8Data = new Uint8Array(data);
1646
+ } else if (data instanceof Uint8Array) {
1647
+ uint8Data = data;
1648
+ } else if (typeof data === "string") {
1649
+ uint8Data = new TextEncoder().encode(data);
1650
+ } else if (data instanceof Blob) {
1651
+ const reader = new FileReader();
1652
+ reader.onload = () => {
1653
+ if (reader.result instanceof ArrayBuffer) {
1654
+ this.dataCallback?.(new Uint8Array(reader.result));
1655
+ }
1656
+ };
1657
+ reader.readAsArrayBuffer(data);
1658
+ return;
1659
+ } else {
1660
+ this.logger.warn(`Received unsupported data type on ${this.type} channel:`, typeof data);
1661
+ return;
1662
+ }
1663
+ this.dataCallback(uint8Data);
1664
+ } catch (error) {
1665
+ this.logger.error(`Error handling message on ${this.type} channel:`, error);
1666
+ }
1667
+ }
1668
+ };
1669
+
1670
+ // src/dataChannels/DataChannelManager.ts
1671
+ var DataChannelManager = class extends EventEmitter {
1672
+ constructor(createDataChannel, triggerRenegotiation, logger) {
1673
+ super();
1674
+ this.createDataChannel = createDataChannel;
1675
+ this.triggerRenegotiation = triggerRenegotiation;
1676
+ this.logger = logger;
1677
+ }
1678
+ reliableChannel = null;
1679
+ lossyChannel = null;
1680
+ /**
1681
+ * Initialize channels and trigger renegotiation.
1682
+ * Returns when both channels are open or throws if any channel errors.
1683
+ */
1684
+ async connect() {
1685
+ if (this.getChannelsReadiness()) return;
1686
+ this.createChannels();
1687
+ this.triggerRenegotiation();
1688
+ await this.waitForChannelsReady();
1689
+ }
1690
+ /**
1691
+ * Wait for both channels to be ready or throw on error.
1692
+ * @private
1693
+ */
1694
+ waitForChannelsReady() {
1695
+ if (this.getChannelsReadiness()) return Promise.resolve();
1696
+ return new Promise((resolve, reject) => {
1697
+ const onReady = () => {
1698
+ cleanup();
1699
+ resolve();
1700
+ };
1701
+ const onError = (type, error) => {
1702
+ cleanup();
1703
+ reject(new Error(`Data channel ${type} error: ${error}`));
1704
+ };
1705
+ const cleanup = () => {
1706
+ this.removeListener("ready", onReady);
1707
+ this.removeListener("error", onError);
1708
+ };
1709
+ this.on("ready", onReady);
1710
+ this.on("error", onError);
1711
+ });
1712
+ }
1713
+ /**
1714
+ * Publish data through a data channel.
1715
+ * Throws an error if the channel doesn't exist or isn't ready.
1716
+ * @param data - The data to send as Uint8Array
1717
+ * @param options - Options specifying which channel to use
1718
+ * @throws Error if the channel doesn't exist or isn't open
1719
+ */
1720
+ publishData(data, options) {
1721
+ const type = options.reliable ? "reliable" : "lossy";
1722
+ const channel = type === "reliable" ? this.reliableChannel : this.lossyChannel;
1723
+ if (!channel) {
1724
+ throw new Error(
1725
+ `Cannot publish data: ${type} channel not created. Call connectDataPublishers() first or enable negotiateOnConnect.`
1726
+ );
1727
+ }
1728
+ if (channel.status !== "open") {
1729
+ throw new Error(
1730
+ `Cannot publish data: ${type} channel not ready (status: ${channel.status}). Wait for dataChannelsReady event.`
1731
+ );
1732
+ }
1733
+ channel.send(data);
1734
+ }
1735
+ /**
1736
+ * Close all data channels and remove all event listeners.
1737
+ * Called during cleanup/disconnect.
1738
+ */
1739
+ cleanup() {
1740
+ this.logger.warn("Cleaning up data channels");
1741
+ if (this.reliableChannel) {
1742
+ this.reliableChannel.close();
1743
+ this.reliableChannel = null;
1744
+ }
1745
+ if (this.lossyChannel) {
1746
+ this.lossyChannel.close();
1747
+ this.lossyChannel = null;
1748
+ }
1749
+ this.removeAllListeners();
1750
+ }
1751
+ /**
1752
+ * Check if both data channels are open and ready to send data.
1753
+ * @returns true if both channels are open and ready, false otherwise
1754
+ */
1755
+ getChannelsReadiness() {
1756
+ return this.reliableChannel?.status === "open" && this.lossyChannel?.status === "open";
1757
+ }
1758
+ createChannels() {
1759
+ this.createChannel("reliable");
1760
+ this.createChannel("lossy");
1761
+ }
1762
+ /**
1763
+ * Create a single data channel.
1764
+ * @private
1765
+ */
1766
+ createChannel(type) {
1767
+ const channel = this.getOrCreateChannelWrapper(type);
1768
+ if (channel.status !== "init") return;
1769
+ const label = type;
1770
+ const config = type === "reliable" ? { ordered: true } : { ordered: false, maxRetransmits: 0 };
1771
+ const rtcChannel = this.createDataChannel(label, config);
1772
+ channel.setOnOpen(() => this.onChannelOpen(type));
1773
+ channel.setOnError((error) => this.onChannelError(type, error));
1774
+ channel.setChannel(rtcChannel);
1775
+ this.logger.warn(`Created ${type} data channel`);
1776
+ }
1777
+ /**
1778
+ * Called when a data channel opens.
1779
+ * Emits channelOpen event and checks if both channels are open.
1780
+ * @private
1781
+ */
1782
+ onChannelOpen(type) {
1783
+ this.logger.warn(`Data channel ${type} opened`);
1784
+ this.emit("channelOpen", type);
1785
+ const bothReady = this.reliableChannel && this.reliableChannel.status === "open" && this.lossyChannel && this.lossyChannel.status === "open";
1786
+ if (bothReady) {
1787
+ this.logger.warn("All data publishers ready");
1788
+ this.emit("ready");
1789
+ }
1790
+ }
1791
+ /**
1792
+ * Called when a data channel errors.
1793
+ * @private
1794
+ */
1795
+ onChannelError(type, error) {
1796
+ this.logger.error(`Data channel ${type} error:`, error);
1797
+ this.emit("error", type, error);
1798
+ }
1799
+ /**
1800
+ * Handle data received from a channel and emit the dataReceived event.
1801
+ * @param type - The type of channel the data was received on
1802
+ * @param data - The received binary data
1803
+ * @private
1804
+ */
1805
+ onDataReceived(type, data) {
1806
+ const payload = {
1807
+ channelType: type,
1808
+ data
1809
+ };
1810
+ this.emit("data", payload);
1811
+ }
1812
+ /**
1813
+ * Get existing channel wrapper or create a new one (without RTCDataChannel).
1814
+ * @param type - The type of channel
1815
+ * @returns The DataChannel wrapper instance
1816
+ * @private
1817
+ */
1818
+ getOrCreateChannelWrapper(type) {
1819
+ if (type === "reliable") {
1820
+ if (!this.reliableChannel) {
1821
+ this.reliableChannel = new DataChannel(type, this.logger);
1822
+ this.reliableChannel.setCallback((data) => this.onDataReceived(type, data));
1823
+ }
1824
+ return this.reliableChannel;
1825
+ } else {
1826
+ if (!this.lossyChannel) {
1827
+ this.lossyChannel = new DataChannel(type, this.logger);
1828
+ this.lossyChannel.setCallback((data) => this.onDataReceived(type, data));
1829
+ }
1830
+ return this.lossyChannel;
1831
+ }
1832
+ }
1530
1833
  };
1531
1834
 
1532
1835
  // src/deferred.ts
@@ -3609,9 +3912,9 @@ function deserializeServerMediaEvent(serializedMediaEvent) {
3609
3912
  }
3610
3913
 
3611
3914
  // src/internal.ts
3612
- import EventEmitter from "events";
3915
+ import EventEmitter2 from "events";
3613
3916
  var isTrackKind = (kind) => kind === "audio" || kind === "video";
3614
- var TrackContextImpl = class extends EventEmitter {
3917
+ var TrackContextImpl = class extends EventEmitter2 {
3615
3918
  constructor(endpoint, trackId, metadata, simulcastConfig = { enabled: false, enabledVariants: [], disabledVariants: [] }) {
3616
3919
  super();
3617
3920
  this.endpoint = endpoint;
@@ -4422,11 +4725,12 @@ var Remote = class {
4422
4725
  };
4423
4726
 
4424
4727
  // src/webRTCEndpoint.ts
4425
- var WebRTCEndpoint = class extends EventEmitter2 {
4728
+ var WebRTCEndpoint = class extends EventEmitter3 {
4426
4729
  localTrackManager;
4427
4730
  remote;
4428
4731
  local;
4429
4732
  commandsQueue;
4733
+ dataChannelManager;
4430
4734
  proposedIceServers = [];
4431
4735
  logger;
4432
4736
  bandwidthEstimation = BigInt(0);
@@ -4443,6 +4747,25 @@ var WebRTCEndpoint = class extends EventEmitter2 {
4443
4747
  this.local = new Local(emit, sendEvent);
4444
4748
  this.localTrackManager = new LocalTrackManager(this.local, sendEvent);
4445
4749
  this.commandsQueue = new CommandsQueue(this.localTrackManager);
4750
+ const createDataChannelFn = (label, init) => {
4751
+ if (!this.connectionManager) {
4752
+ this.connectionManager = new ConnectionManager(this.proposedIceServers);
4753
+ }
4754
+ return this.connectionManager.createDataChannel(label, init);
4755
+ };
4756
+ const triggerRenegotiationFn = () => {
4757
+ this.sendMediaEvent({ renegotiateTracks: MediaEvent_RenegotiateTracks.create() });
4758
+ };
4759
+ this.dataChannelManager = new DataChannelManager(createDataChannelFn, triggerRenegotiationFn, this.logger);
4760
+ this.dataChannelManager.on("ready", () => {
4761
+ this.emit("dataChannelsReady");
4762
+ });
4763
+ this.dataChannelManager.on("data", (payload) => {
4764
+ this.emit("dataChannelPayload", payload);
4765
+ });
4766
+ this.dataChannelManager.on("error", (_, event) => {
4767
+ this.emit("dataChannelsError", new Error(`Data channel error event: ${event.type}`));
4768
+ });
4446
4769
  }
4447
4770
  /**
4448
4771
  * Tries to connect to the RTC Engine. If user is successfully connected then {@link WebRTCEndpointEvents.connected}
@@ -4545,6 +4868,9 @@ var WebRTCEndpoint = class extends EventEmitter2 {
4545
4868
  getBandwidthEstimation() {
4546
4869
  return this.bandwidthEstimation;
4547
4870
  }
4871
+ getDataChannelsReadiness() {
4872
+ return this.dataChannelManager?.getChannelsReadiness() ?? false;
4873
+ }
4548
4874
  handleMediaEvent = async (event) => {
4549
4875
  if (event.offerData) {
4550
4876
  await this.onOfferData(event.offerData);
@@ -4913,6 +5239,58 @@ var WebRTCEndpoint = class extends EventEmitter2 {
4913
5239
  this.emit("disconnectRequested", {});
4914
5240
  this.cleanUp();
4915
5241
  };
5242
+ /**
5243
+ * Create both reliable and lossy data channel.
5244
+ * This method must be called before publishData() can be used.
5245
+ * Emits the 'dataChannelsReady' event when both channels are open and ready.
5246
+ *
5247
+ * @example
5248
+ * ```ts
5249
+ * const webrtc = new WebRTCEndpoint();
5250
+ *
5251
+ * webrtc.on('dataChannelsReady', () => {
5252
+ * console.log('Data channels ready, can now send data');
5253
+ * webrtc.publishData(new TextEncoder().encode('Hello'), { reliable: true });
5254
+ * });
5255
+ *
5256
+ * webrtc.connectDataChannels();
5257
+ * ```
5258
+ */
5259
+ connectDataChannels = () => {
5260
+ return this.dataChannelManager.connect();
5261
+ };
5262
+ /**
5263
+ * Publish data through a data channel.
5264
+ * The data channels must be created first by calling connectDataChannels() or enabling negotiateOnConnect.
5265
+ * Throws an error if the channel doesn't exist or isn't ready yet.
5266
+ *
5267
+ * @param data - The data to send as Uint8Array
5268
+ * @param options - Options specifying which channel to use (reliable or lossy)
5269
+ * @throws Error if the channel doesn't exist or isn't ready
5270
+ *
5271
+ * @example
5272
+ * ```ts
5273
+ * // Subscribe to incoming data
5274
+ * webrtc.on('dataReceived', ({ channelType, data }) => {
5275
+ * console.log(`Received on ${channelType}:`, new TextDecoder().decode(data));
5276
+ * });
5277
+ *
5278
+ * webrtc.on('dataChannelsReady', () => {
5279
+ * // Send reliable data
5280
+ * const data = new TextEncoder().encode('Hello World');
5281
+ * webrtc.publishData(data, { reliable: true });
5282
+ *
5283
+ * // Send lossy data for low-latency updates
5284
+ * const gameState = new Uint8Array([1, 2, 3, 4, 5]);
5285
+ * webrtc.publishData(gameState, { reliable: false });
5286
+ * });
5287
+ *
5288
+ * webrtc.connectDataChannels();
5289
+ * ```
5290
+ */
5291
+ publishData = (data, options) => {
5292
+ this.dataChannelManager.publishData(data, options);
5293
+ };
4916
5294
  /**
4917
5295
  * Cleans up {@link WebRTCEndpoint} instance.
4918
5296
  */
@@ -4922,6 +5300,7 @@ var WebRTCEndpoint = class extends EventEmitter2 {
4922
5300
  this.connectionManager?.getConnection().close();
4923
5301
  this.commandsQueue.cleanUp();
4924
5302
  this.localTrackManager.cleanUp();
5303
+ this.dataChannelManager?.cleanup();
4925
5304
  }
4926
5305
  this.connectionManager = void 0;
4927
5306
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishjam-cloud/webrtc-client",
3
- "version": "0.25.0-rc.2",
3
+ "version": "0.25.0",
4
4
  "description": "Typescript client library for ExWebRTC/WebRTC endpoint in Membrane RTC Engine",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Fishjam Team",
@@ -75,6 +75,5 @@
75
75
  "*.(js|ts|tsx)": [
76
76
  "yarn lint"
77
77
  ]
78
- },
79
- "stableVersion": "0.24.0"
78
+ }
80
79
  }