Skip to content

Commit 2ce5a3b

Browse files
authored
Merge pull request #58 from oslabs-beta/feature/raisa-conference-room
Feature/raisa conference room
2 parents e8088e1 + 2587513 commit 2ce5a3b

File tree

5 files changed

+81
-98
lines changed

5 files changed

+81
-98
lines changed

.github/workflows/rtconnect-test.yml

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
1+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node and various OS (Windows, Ubuntu)
22
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
33

44
name: RTConnect CI
@@ -29,21 +29,4 @@ jobs:
2929
- run: npm run lint
3030
- run: npm test
3131
# env:
32-
# CI: true
33-
34-
35-
# slackNotification:
36-
# name: Slack CI status - notify on failure
37-
# runs-on: ubuntu-latest
38-
# steps:
39-
# - name: Slack Notify on Failure
40-
# if: ${{ failure() }}
41-
# id: slack
42-
# uses: slackapi/slack-github-action@v1.24.0
43-
# with:
44-
# channel-id: 'C067F896WG5'
45-
# slack-message: "Github CI Result: ${{ job.status }}\nGithub PR/Commit URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
46-
# env:
47-
# SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
48-
# # SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
49-
# # SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
32+
# CI: true

.github/workflows/slack-notify.yml

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
name: Slack Notification of CI Status
22

33
on:
4-
push:
5-
branches: ["main", "feature/raisa-cicd"]
6-
pull_request:
7-
branches: ["main"]
8-
4+
push:
5+
branches: ["main", "feature/raisa-cicd"]
6+
pull_request:
7+
branches: ["main"]
8+
env:
9+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} # This works BUT it shows up as problem for some unknown reason ("Context access might be invalid: NPM_TOKEN") and there should not be any errors
10+
# SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
911
jobs:
10-
slackNotification:
11-
name: Slack CI status - notify on failure
12-
runs-on: ubuntu-latest
13-
steps:
14-
- name: Slack Notify on Failure
15-
if: ${{ failure() }}
16-
id: slack
17-
uses: slackapi/slack-github-action@v1.24.0
18-
with:
19-
channel-id: 'C067F896WG5'
20-
slack-message: "Github CI Result: ${{ job.status }}\nGithub PR/Commit URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
21-
env:
22-
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} # This works BUT it shows up as problem for some unknown reason ("Context access might be invalid: NPM_TOKEN") and there should not be any errors
23-
# SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
24-
12+
slackNotification:
13+
runs-on: ubuntu-latest
14+
name: Slack CI status - notify on failure
15+
steps:
16+
- name: Slack Notify on Failure
17+
if: ${{ failure() }}
18+
id: slack
19+
uses: slackapi/slack-github-action@v1.24.0
20+
with:
21+
channel-id: 'C067F896WG5'
22+
slack-message: "Github CI Result: ${{ job.status }}\nGithub PR/Commit URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
2523

2624
# https://github.com/slackapi/slack-github-action

lib/__tests__/unit/server.unit.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Testing the SignalingChannel class', () => {
2222
});
2323

2424
it('Empty hashmap of users is initialized', () => {
25-
expect(sc.users.size).toBe(0);
25+
expect(sc.peers.size).toBe(0);
2626
});
2727
});
2828

lib/server/server.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,19 @@ const { OFFER, ANSWER, ICECANDIDATE, LOGIN, LEAVE } = actions;
88
* @class
99
* @classdesc The SignalingChannel class, which utilizes WebSockets in order to facillitate communication between clients connected to the WebSocket server.
1010
* @prop { WebsocketServer } websocketServer - a simple WebSocket server
11-
* @prop { Map } users - object containing key-value pairs consisting of users' names and their corresponding WebSocket in the following fashion { username1: socket1, username2: socket2, ... , usernameN: socketN }
11+
* @prop { Map } peers - object containing key-value pairs consisting of peers' names and their corresponding WebSocket in the following fashion { username1: socket1, username2: socket2, ... , usernameN: socketN }
1212
*/
13-
1413
class SignalingChannel {
1514
webSocketServer: WebSocketServer;
16-
users: Map<string, WebSocket>;
15+
peers: Map<string, WebSocket>;
1716

1817
/**
1918
* @constructor constructing a websocket server with an http/https object or port passed in upon instantiating SignalingChannel
2019
* @param {Server} server - pass in a server (http or https) or pass in a port (this port cannot be the same as the application port and it has to listen on the same port)
2120
*/
2221
constructor(server: Server | httpsServer | number) {
2322
this.webSocketServer = typeof server === 'number' ? new WebSocket.Server({ port: server }) : new WebSocket.Server({ server: server });
24-
this.users = new Map();
23+
this.peers = new Map();
2524
// this.rooms = new Map(); //focus on later when constructing 2+ video conferencing functionality, SFU topology
2625
}
2726

@@ -36,14 +35,14 @@ class SignalingChannel {
3635
this.webSocketServer.on('connection', (socket) => {
3736
console.log('A user has connected to the websocket server.');
3837

39-
// when a client closes their browser or connection to the websocket server (onclose), their socket gets terminated and they are removed from the map of users
38+
// when a client closes their browser or connection to the websocket server (onclose), their socket gets terminated and they are removed from the map of peers
4039
// lastly a new user list is sent out to all clients connected to the websocket server.
4140
socket.on('close', () => {
42-
const userToDelete = this.getByValue(this.users, socket);
43-
this.users.delete(userToDelete);
41+
const userToDelete = this.getByValue(this.peers, socket);
42+
this.peers.delete(userToDelete);
4443
socket.terminate();
4544

46-
const userList = { ACTION_TYPE: LOGIN, payload: Array.from(this.users.keys()) };
45+
const userList = { ACTION_TYPE: LOGIN, payload: Array.from(this.peers.keys()) };
4746
this.webSocketServer.clients.forEach(client => client.send(JSON.stringify(userList)));
4847
});
4948

@@ -67,11 +66,11 @@ class SignalingChannel {
6766
this.transmit(data);
6867
break;
6968
case LOGIN:
70-
this.users.set(data.payload, socket);
69+
this.peers.set(data.payload, socket);
7170
this.webSocketServer.clients.forEach(client => client.send(JSON.stringify(
7271
{
7372
ACTION_TYPE: LOGIN,
74-
payload: Array.from(this.users.keys())
73+
payload: Array.from(this.peers.keys())
7574
})));
7675
break;
7776
case LEAVE:
@@ -90,7 +89,7 @@ class SignalingChannel {
9089
* @param {object} data
9190
*/
9291
transmit(data: { ACTION_TYPE: string, receiver: string }): void {
93-
this.users.get(data.receiver)?.send(JSON.stringify(data));
92+
this.peers.get(data.receiver)?.send(JSON.stringify(data));
9493
}
9594

9695
/**

lib/src/components/VideoCall.tsx

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface icePayObj extends payloadObj {
3838
3939
* @desc Wrapper component containing the logic necessary for peer connections using WebRTC APIs (RTCPeerConnect API + MediaSession API) and WebSockets.
4040
*
41-
* ws, localVideo, remoteVideo, peerRef, localStreamRef, otherUser, senders are all mutable ref objects that are created using the useRef hook. The useRef hook allows you to persist values between renders and it is used to store a mutable value that does NOT cause a re-render when updated.
41+
* ws, localVideoRef, remoteVideo, peerRef, localStreamRef, otherUser, senders are all mutable ref objects that are created using the useRef hook. The useRef hook allows you to persist values between renders and it is used to store a mutable value that does NOT cause a re-render when updated.
4242
*
4343
* The WebSocket connection (ws.current) is established using the useEffect hook and once the component mounts, the Socket component is rendered. The Socket component adds event listeners that handle the offer-answer model and the exchange of SDP objects between peers and the socket.
4444
*
@@ -49,6 +49,32 @@ interface icePayObj extends payloadObj {
4949
*
5050
* @returns A component that renders two VideoComponents,
5151
*/
52+
53+
/**
54+
* A diagram of the WebRTC Connection logic
55+
* Peer A Stun Signaling Channel(WebSockets) Peer B Step
56+
* |------>| | | Who Am I? + RTCPeerConnection(configuration) this contains methods to connect to a remote Peer
57+
* |<------| | | Symmetric NAT (your ip that you can be connected to)
58+
* |-------------------------->|------------------>| Calling Peer B, Offer SDP is generated and sent over WebSocket
59+
* |-------------------------->|------------------>| ICE Candidates are also being trickled in, where and what IP:PORT can Peer B connect to Peer A
60+
* | |<------------------|-------------------| Who Am I? PeerB this time!
61+
* | |-------------------|------------------>| Peer B's NAT
62+
* |<--------------------------|-------------------| Accepting Peer A's call, sending Answer SDP
63+
* |<--------------------------|-------------------| Peer B's ICE Candidates are now being trickled in to peer A for connectivity.
64+
* |-------------------------->|------------------>| ICE Candidates from Peer A, these steps repeat and are only necessary if Peer B can't connect to the
65+
* | | | | earlier candidates sent.
66+
* |<--------------------------|-------------------| ICE Candidate trickling from Peer B, could also take a second if there's a firewall to be
67+
* | | | | circumvented.
68+
* | | | | Connected! Peer to Peer connection is made and now both users are streaming data to eachother!
69+
*
70+
* If Peer A starts a call their order of functions being invoked is... handleOffer --> callUser --> createPeer --> peerRef.current.negotiationNeeded event (handleNegotiationNeededEvent) --> ^send Offer SDP^ --> start ICE trickle, handleIceCandidateEvent --> ^receive Answer^ SDP --> handleIceCandidateMsg --> once connected, handleTrackEvent
71+
* If Peer B receives a call then we invoke... ^Receive Offer SDP^ --> handleReceiveCall --> createPeer --> ^send Answer SDP^ --> handleIceCandidateMsg --> handleIceCandidateEvent --> once connected, handleTrackEvent
72+
*
73+
* Note: Media is attached to the Peer Connection and sent along with the offers/answers to describe what media each client has.
74+
*
75+
* @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTrack
76+
*/
77+
5278
const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { controls: boolean, style: { width: string, height: string }}}): JSX.Element => {
5379

5480
const [username, setUsername] = useState<string>('');
@@ -65,10 +91,10 @@ const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { control
6591
const ws = useRef<WebSocket>(null!);
6692

6793
/**
68-
* @type {mutable ref object} localVideo - video element of the local user. It will not be null or undefined.
69-
* @property {HTMLVideoElement} localVideo.current
94+
* @type {mutable ref object} localVideoRef - video element of the local user. It will not be null or undefined.
95+
* @property {HTMLVideoElement} localVideoRef.current
7096
*/
71-
const localVideo = useRef<HTMLVideoElement>(null!);
97+
const localVideoRef = useRef<HTMLVideoElement>(null!);
7298

7399
/**
74100
* @type {mutable ref object} remoteVideo - video stream of the remote user. It cannot be null or undefined.
@@ -110,30 +136,27 @@ const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { control
110136
openUserMedia();
111137
},[]);
112138

139+
113140
/**
114-
* A diagram of the WebRTC Connection logic
115-
* Peer A Stun Signaling Channel(WebSockets) Peer B Step
116-
* |------>| | | Who Am I? + RTCPeerConnection(configuration) this contains methods to connect to a remote Peer
117-
* |<------| | | Symmetric NAT (your ip that you can be connected to)
118-
* |-------------------------->|------------------>| Calling Peer B, Offer SDP is generated and sent over WebSocket
119-
* |-------------------------->|------------------>| ICE Candidates are also being trickled in, where and what IP:PORT can Peer B connect to Peer A
120-
* | |<------------------|-------------------| Who Am I? PeerB this time!
121-
* | |-------------------|------------------>| Peer B's NAT
122-
* |<--------------------------|-------------------| Accepting Peer A's call, sending Answer SDP
123-
* |<--------------------------|-------------------| Peer B's ICE Candidates are now being trickled in to peer A for connectivity.
124-
* |-------------------------->|------------------>| ICE Candidates from Peer A, these steps repeat and are only necessary if Peer B can't connect to the
125-
* | | | | earlier candidates sent.
126-
* |<--------------------------|-------------------| ICE Candidate trickling from Peer B, could also take a second if there's a firewall to be
127-
* | | | | circumvented.
128-
* | | | | Connected! Peer to Peer connection is made and now both users are streaming data to eachother!
129-
*
130-
* If Peer A starts a call their order of functions being invoked is... handleOffer --> callUser --> createPeer --> peerRef.current.negotiationNeeded event (handleNegotiationNeededEvent) --> ^send Offer SDP^ --> start ICE trickle, handleIceCandidateEvent --> ^receive Answer^ SDP --> handleIceCandidateMsg --> once connected, handleTrackEvent
131-
* If Peer B receives a call then we invoke... ^Receive Offer SDP^ --> handleReceiveCall --> createPeer --> ^send Answer SDP^ --> handleIceCandidateMsg --> handleIceCandidateEvent --> once connected, handleTrackEvent
141+
* @async
142+
* @function openUserMedia is invoked in the useEffect Hook after WebSocket connection is established.
143+
* @desc If the localVideoRef.current property exists, openUserMedia invokes the MediaDevices interface getUserMedia() method to prompt the clients for audio and video permission.
132144
*
133-
* Note: Media is attached to the Peer Connection and sent along with the offers/answers to describe what media each client has.
145+
* If clients grant permissions, getUserMedia() uses the video and audio constraints to assign the local MediaStream from the clients' cameras/microphones to the local <video> element.
134146
*
135-
* @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTrack
147+
* @param {void}
148+
* @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
136149
*/
150+
const openUserMedia = async (): Promise<void> => {
151+
try {
152+
if (localVideoRef.current){
153+
localStreamRef.current = localVideoRef.current.srcObject = await navigator.mediaDevices.getUserMedia(constraints);
154+
}
155+
} catch (error) {
156+
console.log('Error in openUserMedia: ', error);
157+
}
158+
};
159+
137160

138161
/**
139162
* @func handleUsername
@@ -182,26 +205,6 @@ const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { control
182205
setUsers(userList);
183206
};
184207

185-
/**
186-
* @async
187-
* @function openUserMedia is invoked in the useEffect Hook after WebSocket connection is established.
188-
* @desc If the localVideo.current property exists, openUserMedia invokes the MediaDevices interface getUserMedia() method to prompt the clients for audio and video permission.
189-
*
190-
* If clients grant permissions, getUserMedia() uses the video and audio constraints to assign the local MediaStream from the clients' cameras/microphones to the local <video> element.
191-
*
192-
* @param {void}
193-
* @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
194-
*/
195-
const openUserMedia = async (): Promise<void> => {
196-
try {
197-
if (localVideo.current){
198-
localStreamRef.current = localVideo.current.srcObject = await navigator.mediaDevices.getUserMedia(constraints);
199-
}
200-
} catch (error) {
201-
console.log('Error in openUserMedia: ', error);
202-
}
203-
};
204-
205208
/**
206209
* @function callUser - Constructs a new RTCPeerConnection object using the createPeer function and then adds the local client's (Peer A/caller) media tracks to peer connection ref object.
207210
* @param {string} userID the remote client's (Peer B/callee) username
@@ -413,13 +416,13 @@ const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { control
413416
senders.current
414417
?.find(sender => sender.track?.kind === 'video')
415418
?.replaceTrack(screenTrack);
416-
localVideo.current.srcObject = stream; // changing local video to display what is being screen shared to the other peer
419+
localVideoRef.current.srcObject = stream; // changing local video to display what is being screen shared to the other peer
417420

418421
screenTrack.onended = function() { // ended event is fired when playback or streaming has stopped because the end of the media was reached or because no further data is available
419422
senders.current
420423
?.find(sender => sender.track?.kind === 'video')
421424
?.replaceTrack(localStreamRef.current.getTracks()[1]); //
422-
localVideo.current.srcObject = localStreamRef.current; // changing local video displayed back to webcam
425+
localVideoRef.current.srcObject = localStreamRef.current; // changing local video displayed back to webcam
423426
};
424427
});
425428
}
@@ -573,7 +576,7 @@ const VideoCall = ({ URL, mediaOptions }: { URL: string, mediaOptions: { control
573576
>
574577

575578
<VideoComponent
576-
video={localVideo}
579+
video={localVideoRef}
577580
mediaOptions={mediaOptions}
578581
/>
579582

0 commit comments

Comments
 (0)