diff --git a/README.md b/README.md index 994fcf5..2fc358a 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,25 @@ const sfu = new SfuClient(); sfu.disconnect(); sfu.state === SFU_CLIENT_STATE.DISCONNECTED; // true ``` +- broadcast() + ```js + // in the sender's client + sfu.broadcast("hello"); + ``` + ```js + // in the clients of other members of that channel + sfu.addEventListener("update", ({ detail: { name, payload } }) => { + switch (name) { + case "broadcast": + { + const { senderId, message } = payload; + console.log(`${senderId} says: "${message}"`); // 87 says "hello" + } + return; + // ... + } + }); + ``` - updateUpload() ```js const audioStream = await window.navigator.mediaDevices.getUserMedia({ diff --git a/src/client.js b/src/client.js index 30cc66c..a6ddbab 100644 --- a/src/client.js +++ b/src/client.js @@ -154,6 +154,19 @@ export class SfuClient extends EventTarget { return this._state; } + /** + * @param message any JSON serializable object + */ + broadcast(message) { + this._bus.send( + { + name: CLIENT_MESSAGE.BROADCAST, + payload: message, + }, + { batch: true } + ); + } + /** * @param {string} url * @param {string} jsonWebToken @@ -508,7 +521,7 @@ export class SfuClient extends EventTarget { /** * dispatches an event, intended for the client * - * @param { "disconnect" | "info_change" | "track" | "error"} name + * @param { "disconnect" | "info_change" | "track" | "error" | "broadcast"} name * @param [payload] * @fires SfuClient#update */ @@ -557,6 +570,9 @@ export class SfuClient extends EventTarget { */ async _handleMessage({ name, payload }) { switch (name) { + case SERVER_MESSAGE.BROADCAST: + this._updateClient("broadcast", payload); + break; case SERVER_MESSAGE.SESSION_LEAVE: { const { sessionId } = payload; diff --git a/src/models/session.js b/src/models/session.js index 00f1ffe..f5a777d 100644 --- a/src/models/session.js +++ b/src/models/session.js @@ -507,6 +507,17 @@ export class Session extends EventEmitter { */ async _handleMessage({ name, payload }) { switch (name) { + case CLIENT_MESSAGE.BROADCAST: + { + this._broadcast({ + name: SERVER_MESSAGE.BROADCAST, + payload: { + senderId: this.id, + message: payload, + }, + }); + } + break; case CLIENT_MESSAGE.CONSUMPTION_CHANGE: { /** @type {{ sessionId: number, states: Object }} */ diff --git a/src/shared/enums.js b/src/shared/enums.js index 615ea18..031f4db 100644 --- a/src/shared/enums.js +++ b/src/shared/enums.js @@ -22,6 +22,8 @@ export const SERVER_REQUEST = { }; export const SERVER_MESSAGE = { + /** Signals that the server wants to send a message to all the other members of that channel */ + BROADCAST: "BROADCAST", /** Signals the clients that one of the session in their channel has left. */ SESSION_LEAVE: "SESSION_LEAVE", /** Signals the clients that the info (talking, mute,...) of one of the session in their channel has changed. */ @@ -38,6 +40,8 @@ export const CLIENT_REQUEST = { }; export const CLIENT_MESSAGE = { + /** Signals that the client wants to send a message to all the other members of that channel */ + BROADCAST: "BROADCAST", /** Signals that the client wants to change how it consumes a track (like pausing or ending the download) */ CONSUMPTION_CHANGE: "CONSUMPTION_CHANGE", /** Signals that the info (talking, mute,...) of this client has changed. */ diff --git a/tests/network.test.js b/tests/network.test.js index 3590bc8..1cc385b 100644 --- a/tests/network.test.js +++ b/tests/network.test.js @@ -263,4 +263,21 @@ describe("Full network", () => { const [closeEvent] = await closeProm; expect(closeEvent.code).toBe(SESSION_CLOSE_CODE.P_TIMEOUT); }); + test("A client can broadcast arbitrary messages to other clients on a channel that does not have webRTC", async () => { + const channelUUID = await network.getChannelUUID(false); + const user1 = await network.connect(channelUUID, 1); + const user2 = await network.connect(channelUUID, 2); + const sender = await network.connect(channelUUID, 3); + const message = "hello"; + sender.sfuClient.broadcast(message); + const prom1 = once(user1.sfuClient, "update"); + const prom2 = once(user2.sfuClient, "update"); + const [[event1], [event2]] = await Promise.all([prom1, prom2]); + expect(event1.detail.name).toEqual("broadcast"); + expect(event2.detail.name).toEqual("broadcast"); + expect(event1.detail.payload.senderId).toBe(sender.session.id); + expect(event2.detail.payload.senderId).toBe(sender.session.id); + expect(event1.detail.payload.message).toBe(message); + expect(event2.detail.payload.message).toBe(message); + }); });