diff --git a/resources/cloud-api.swagger b/resources/cloud-api.swagger index a62528739cd0e..0ceb0920abf4c 100644 --- a/resources/cloud-api.swagger +++ b/resources/cloud-api.swagger @@ -88,6 +88,29 @@ paths: type: array items: $ref: "#/definitions/PhoneNumberListAnswer" + /waiting-queue/golive: + post: + tags: + - waitingQueueGoLive + summary: Post a go live request + description: Mark the conference as live, notify all visitors waiting. + operationId: goLive + consumes: + - "application/json" + parameters: + - in: "body" + name: "body" + description: "Go Live Request" + required: true + schema: + "$ref": "#/definitions/GoLiveRequest" + responses: + '200': + description: Successful operation + '404': + description: Missing conference + '500': + description: Failed operation securityDefinitions: token: type: "apiKey" @@ -143,6 +166,12 @@ definitions: - {"countryCode":"US","tollFree":false,"formattedNumber":"+1 123-456-7890"} - {"countryCode":"UK","tollFree":true,"formattedNumber":"+44 123 456 7890"} + GoLiveRequest: + type: object + properties: + conference: + type: string externalDocs: description: "Find out more about the Jitsi Cloud API" url: "https://jitsi.org/CloudAPI" + \ No newline at end of file diff --git a/resources/waiting-queue/examples/main.css b/resources/waiting-queue/examples/main.css new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/resources/waiting-queue/examples/visitor.html b/resources/waiting-queue/examples/visitor.html new file mode 100644 index 0000000000000..9c514982b2f60 --- /dev/null +++ b/resources/waiting-queue/examples/visitor.html @@ -0,0 +1,45 @@ + + + + Conference WebSocket + + + + + + + + +
+
+
+
+
+ + + + + +
+
+
+
+
+
+ + + + + + + + +
Messages
+
+
+
+ + \ No newline at end of file diff --git a/resources/waiting-queue/examples/visitor.js b/resources/waiting-queue/examples/visitor.js new file mode 100644 index 0000000000000..b8d0b57e5a621 --- /dev/null +++ b/resources/waiting-queue/examples/visitor.js @@ -0,0 +1,63 @@ +const token = 'JWT_TOKEN_GOES_HERE' + +const stompClient = new StompJs.Client({ + brokerURL: 'ws://localhost:8060/waiting-queue/visitor/websocket', +}); + +stompClient.onWebSocketError = (error) => { + console.error('Error with websocket', error); +}; + +stompClient.onStompError = (frame) => { + console.error('Broker reported error: ' + frame.headers['message']); + console.error('Additional details: ' + frame.body); +}; + +function setConnected(connected) { + $("#connect").prop("disabled", connected); + $("#disconnect").prop("disabled", !connected); + if (connected) { + $("#conversation").show(); + } + else { + $("#conversation").hide(); + } + $("#messages").html(""); +} + +function connect(conference) { + console.log("Connecting to conference " + conference); + + headers = { + Authorization: 'Bearer ' + token + }; + + stompClient.connectHeaders = headers; + + stompClient.onConnect = (frame) => { + setConnected(true); + console.log('Connected: ' + frame); + + stompClient.subscribe('/secured/conference/visitor/topic.' + conference, (message) => { + showMessage(message.body); + }, headers); + }; + + stompClient.activate(); +} + +function disconnect() { + stompClient.deactivate(); + setConnected(false); + console.log("Disconnected"); +} + +function showMessage(message) { + $("#messages").append("" + message + ""); +} + +$(function () { + $("form").on('submit', (e) => e.preventDefault()); + $( "#connect" ).click(() => connect($("#conference").val())); + $( "#disconnect" ).click(() => disconnect()); +}); \ No newline at end of file diff --git a/resources/waiting-queue/img/waiting-queue-ds.png b/resources/waiting-queue/img/waiting-queue-ds.png new file mode 100644 index 0000000000000..6b0f4da45931f Binary files /dev/null and b/resources/waiting-queue/img/waiting-queue-ds.png differ diff --git a/resources/waiting-queue/img/waiting-queue-topics.png b/resources/waiting-queue/img/waiting-queue-topics.png new file mode 100644 index 0000000000000..4c68655d54ea6 Binary files /dev/null and b/resources/waiting-queue/img/waiting-queue-topics.png differ diff --git a/resources/waiting-queue/waiting-queue.md b/resources/waiting-queue/waiting-queue.md new file mode 100644 index 0000000000000..e481833a308cc --- /dev/null +++ b/resources/waiting-queue/waiting-queue.md @@ -0,0 +1,79 @@ +# Waiting queue + +Visitors queue service is used for managing the visitors queue for the 8x8 video meetings by keeping visitors websocket connections opened and when a moderator opens the meeting, the visitors are notified and allowed to join the meeting. +The moderators should be able to see the visitors count. + +## Authentication + +The JWT token is sent at least in the CONNECT STOMP message as connect header - see sample code: + +``` +headers = { + Authorization: 'Bearer ' + token + }; + + stompClient.connectHeaders = headers; + + stompClient.onConnect = (frame) => { + setConnected(true); + console.log('Connected: ' + frame); + + stompClient.subscribe('/secured/conference/visitor/topic.' + conference, (message) => { + showMessage(message.body); + }, headers); + }; +``` + +### Visitors + +This endpoint should accept only visitor's jaas tokens for a conference specified as param to the endpoint and the token to be valid for that room. The token for visitors contains: +``` +context: { + user: { + role: ‘visitor' + } +} +``` +It allows visitors to connect to the /visitors websocket and wait for the start message to be published on /secured/conference/visitor/topic.{conference} topic. + +### Moderators + +This endpoint should accept only moderator's jaas tokens for a conference specified as param to the endpoint and the token to be valid for that room. The token for moderator contains: +``` +context: { + user: { + moderator: true + } +} +``` +It allows moderators to connect to the /moderator websocket and wait for the status message to be published on /secured/conference/state/topic.{conference} topic (triggered every 15 seconds). + +## Flow + +The flow is depicted below: + +![Flow](img/waiting-queue-ds.png) + +## Topics + +The topics used: + +![Topics](img/waiting-queue-topics.png) + +## API + +| Endpoint | Type | Auth | Use | +|----------|:-------------:|------:|------:| +| WS /visitor | WebSocket/STOMP | require client token for conference | Visitors open a websocket and wait to receive a message. Message format is not very important, since we’re starting with a single message – “ready to join”. But keep it extensible. If a conference is already live when a visitor opens the ws, immediately send a notification | +| WS /state | WebSocket/STOMP | require client token for conference | Moderators use it to get the number of visitors waiting. Service sends updates for the number of visitors. To reduce traffic send updates at a minimum period and only if the count changed | +| POST /golive | REST | require a server-to-server token for conference | Our backend calls it anytime the visitorsLive state for a conference changes from “false” to “true”, including when a conference is created with visitorsLive=true | + +> +> Note: CONNECT and MESSAGE STOMP frames expect an additional header for Authorization +> + +More on [STOMP](https://stomp.github.io/stomp-specification-1.2.html). + +## Sample code + +There is sample code showing how to handle the visitor case [here](./examples/visitor.js). \ No newline at end of file