# Postduif ###### tags: `Semester1` Welcome to Postduif! Postduif is an easy-to-use privacy-minded web-based app like discord, where you can call and chat with your friends! This document describes the use cases and implementation in detail. ## How to run our project Run the app: ``` $ npm ci $ npm run copy-files-web $ npm run tsc-web $ npm run dev Then go to "Postduif.M1". ``` Run the tests: ``` $ npm ci $ npm run copy-files-web $ npm run tsc-web $ npx jest ``` <!-- Localtunnel? other tool? node server? Node server: ``` In first terminal: $ npm ci $ npm run copy-files-web $ npm run tsc-web $ npx http-server -c1 dist/webroot -o web/Postduif.M1 In second terminal: $ npx localtunnel --port 8080 ``` !--> <!-- ## Structure project - in progress ... --> ## Basic idea Let's imagine we have a group of 4 friends: Alice, Bob, Charles and Dino. They want to do pair programming. To do so, they need a virtual room where they can chat together, and they need a tool to be able to call the friend they are programming with. To do so, they decide in advance a room name (e.g. "penocw") and a room password (e.g. "papegaai"). They then surf to (e.g.) postduif.com. Alice is the first one to connect. She arrives on the home webpage, see figure 1. In the toolbar above, she clicks on "Make or join a room". She now arrives on the "room form page", see figure 2. The room doesn't exist yet, so she makes one: she gives the name of the room ("penocw"), the password of the room ("papegaai"), and the username she wants to use (e.g. "Alice"). Alice then clicks on the submit button, and arrives on the room page postduif.com/room/{roomID}, see figure 3. On this page, there is a chat for all users of the page. There is also a list of users which you can call. If a user is free (if he is not in a call yet), you can call him (if you are free too), otherwise the button to call him is disable. If a user gets called, a prompt window appears and he may accept or reject the call simply by clicking on "ok" or "cancel". There is a stop button to stop the call, and a leave button to leave the page. <!-- ![](https://i.imgur.com/1kD73vQ.png) --> **Figure 1:** ![](https://i.imgur.com/IDyXqWG.png) **Figure 2:** ![](https://i.imgur.com/dEqtAt7.png) **Figure 3:** ![](https://i.imgur.com/eceMTl1.png) ## The database In-memory database, supported by the REST API. ### The room object A room object has a name, a password, a list of users and list of messages. ``` Fields of room object: * name (string) * password (string) * list users (Map<string, User>) * list messages (Array<Message>) ``` A room is created when a user gives a room name and a room password and the room with this name does not exist yet. The user list of the room then contains one user. If, at some point, the user list is empty, then the room and all its data is deleted. Questions: * How to safely store the password? Remarks: * There can not be 2 rooms with the same name. ### The user object A user object has a name, a status, a caller, a SDP field, a candidates field, and a role (polite or impolite). ``` Fields of user object: * name (string) * status (string: free, waiting, beingCalled, calling or closing) * caller (string | null) * SDP (string | null) * candidates (string | null) * isPolite (boolean) ``` When a user makes/joins a room, a user object is created with the given username. The status is set to 'free' (default), the SDP and candidates are set to null, as well as the caller. The isPolite value is set to true. ## Make or join a room When a random user arrives on the "room form page", he gives a username, and he may make or join a room. The is one single form, for both making a room and joining a room. The user gives the room name, room password and his username. If the room doesn't exist yet, one is created and the user joins the newly created room (a room object is made with the given name, password, and the single user into the users list; the user is redirected to the room page). If it already exists, the user joins the old room (the users list of this room is updated and the user is redirected to the room page). Advantage of having one single form: if the user doesn't know if his friends are already connected, and so if he doesn't know if the room already exists, he doesn't have to first try the join from, and then the make form. ## The room page You can see a screenshot of the room page in figure 3 above. There is single chat for all users of the room. There is a list of users on the right, with one call button for each user. If a user is in a call process, all the call buttons on his page are disabled. If the user is free, the buttons of the other free users will be enabled. There is a stop call button that is enabled when the user is in a calling process (status is not "free", but "waiting", "calling", "beingCalled" or "closing"). There is a leave button to leave the page. When a user in on a room page, the information about the room and all the users is reloaded every 5 seconds to take changes into account: * are there new users in the room? * are there new messages in the chat? * is the status of the users changed (relevant for availability of the call buttons)? * is someone trying to call the user? ## The security problem How to store the password? How to make all of this safe? Secure storage of the chat? The biggest shortcomming of our app in its current state is **Security**. For example, we have this problem: we only need the name of the room and the username of one of the users in the room to know the url, go to it and take the place of that user in the room. Because the url (e.g. https:Postduif.com/room/workplace?username=john) of the room page only depends on the roomname and the username, and nothing prevents random users to that url. We chose to tackle these security issues later, as a working implementation is our first priority. Proposition for later: add some number into the url that is a function of the room name and the username. The server knows this function and so what the number must be, bu a user only knows this number when he is redirected to the url after giving the password of the room. A user that only knows the room name but not the funtion (only the server knows it) doesn't know the url and cannot join the room. ## Funtions / processes ### Functions associated with the home page The only event on this page is when the submit button is activated. The submited data is then handled in the koa server. First check that the data is valid using Zod. The program then checks if there already is a room with the given room name and room password. If the room already exists: We check that there is no user with the same username in the room yet. If so, the user is redirected to the room page and he is added to the user list of the room. If the room doesn't exist yet: A new room is created and the user is redirected to it. ### Functions associated with the room page The page must be reloaded every 5 sec for incoming calls, new messages, new users on page, ... Every time the page is reloaded: - the chat is updated - the user list and their status is updated - function handleIncomingCall if any (use status to see if there is an incoming call) <!-- * Processes when a room is joined: User is added to the users list. --> * Processes when someone starts a call: Alice wants to call Bob. She clicks on the button to call Bob. If her status or the status of Bob isn't "free", a error prompts in the window of Alice and nothing happens. We suppose for now that two users will not call another one at the same time. - The status of Alice (Alice.status) is set to "waiting" (Alice is waiting for an answer from Bob) - The status of Bob (Bob.status) is set to "beingCalled" (someone tries to call Bob and waits for an answer) - Alice.caller is set to Bob, and Bob.caller is set to Alice - Alice.isPolite is set to false, and Bob.isPolite is set to true - The microphone begins to catch a stream - Alice makes a perfect negotiation RTCPeerConnection - The streams from the microphone are added to the RTCPeerConnection - This will make the SDP and Candidates and start the negotiation process in the PerfectNegotationPeerConnection class - The SDP attribute of the user Alice will be set to these SDP and Candidates - Now Alice waits <!-- ![](https://i.imgur.com/6DXH6qR.png) --> * Processes when someone is being called: The webpage of Bob is reloaded every 5 seconds. Each time the page is loaded, it checks the status of Bob. If Bob.status is "beingCalled", then the function "handleIncomingCall" is called. A window prompts and asks Bob if he wants to accept or reject the call. The following diagram shows what happens when Alice calls Bob and Bob accepts the call. The second diagram then shows what happens if Bob rejects the call. **Sequence diagram - accept call:** ![](https://i.imgur.com/cy4opFy.png) <!-- Sequence diagram - ACCEPT CALL --> ```mermaid %%{init: {'theme': 'base', 'actorMargin': 500 , 'themeVariables': { 'primaryColor': '#abcdef', 'edgeLabelBackground':'#ffffee', 'noteBkgColor': '#fff0f0'}}}%% sequenceDiagram actor Alice participant server actor Bob note left of Alice : Alice.status = free note right of Bob : Bob.status = free Alice ->> server : Alice calls Bob <br/> PUT Note over Alice,server : Open mic --> get stream <br/> Alice.status = waiting <br/> Bob.status = beingCalled <br/> Alice.caller = Bob <br/> Bob.caller = Alice <br/> Alice.isPolite = false <br/> Bob.isPolite = true <br/> Set up peerconnection <br/> Add tracks <br/> Set SDP <br/> Set Candidates loop polling each 5 s Bob ->> server : refresh() <br/> GET server ->> Bob : Bob.status = beingCalled ! end rect rgb(0, 255, 0) server ->> Bob : Confirm: accept/reject call Bob ->> server : Accept confirm ! <br/> PUT Note over server, Bob : Open mic --> get stream <br/> GET Alice.SDP <br/> GET Alice.candidates <br/> Handle SDP from Alice <br/> Handle candidates from Alice <br/> Remove Alice.SDP <br/> Bob.status = calling <br/> Set up peerconnection <br/> Add tracks <br/> Set SDP <br/> Set Candidates end loop polling each 5 s Alice ->> server : refresh() <br/> GET server ->> Alice: Bob.status = calling ! end Alice ->> server : Update <br/> PUT Note over Alice, server: GET Bob.SDP <br/> GET Bob.candidates <br/> Handle SDP from Bob <br/> Handle candidates from Bob <br/> Remove Bob.SDP <br/> Alice.status = calling <br/> note left of Alice : Alice.status = calling note right of Bob : Bob.status = calling ``` **Sequence diagram - reject call:** ![](https://i.imgur.com/XYnqUoA.png) <!-- Sequence diagram - REJET CALL --> ```mermaid %%{init: {'theme': 'base', 'actorMargin': 500 , 'themeVariables': { 'primaryColor': '#abcdef', 'edgeLabelBackground':'#ffffee', 'noteBkgColor': '#fff0f0'}}}%% sequenceDiagram actor Alice participant server actor Bob note left of Alice : Alice.status = free note right of Bob : Bob.status = free Alice ->> server : Alice calls Bob <br/> PUT Note over Alice,server : Open mic --> get stream <br/> Alice.status = waiting <br/> Bob.status = beingCalled <br/> Alice.caller = Bob <br/> Bob.caller = Alice <br/> Alice.isPolite = false <br/> Bob.isPolite = true <br/> Set up peerconnection <br/> Add tracks <br/> Set SDP <br/> Set Candidates loop polling each 5 s Bob ->> server : refresh() <br/> GET server ->> Bob : Bob.status = beingCalled ! end rect rgb(255, 0, 0) server ->> Bob : Confirm: accept/reject call Bob ->> server : Rejet confirm ! <br/> PUT Note over server, Bob : Alice.status = closing <br/> Bob.status = free <br/> Alice.caller = null <br/> Bob.caller = null <br/> Alice.isPolite = true <br/> Bob.isPolite = true <br/> Alice.SDP = null <br/>Bob.SDP = null <br/> Alice.candidates = null <br/> Bob.candidates = null <br/> Kill Bob's peerconnection end loop polling each 5 s Alice ->> server : refresh() <br/> GET server ->> Alice: Alice.status = closing ! end Alice ->> server : Update <br/> PUT Note over Alice, server: <br/> Alice.status = free <br/> Kill Alice's peerconnection <br/> note left of Alice : Alice.status = free note right of Bob : Bob.status = free ``` * Processes when someone stops a call: Similar to what happens when someone rejects a call. Imagine Alice and Bob were calling, and Alice clicks on "stop call". The RTCPeerConnection of Alice is closed, and the data of both users is set to default (status = free, SDP=candidates=caller=null, isPolite = true), BUT the status of Bob is set to "closing" instead of "free" (so he knows that the call has been stopped and so that he must close his mic - stop the tracks - and close the RTCPeerConnection on his side too). The following diagram shows what happens when Alice stops the call. **Sequence diagram - stop call:** ![](https://i.imgur.com/5kNbRDq.png) <!-- Sequence diagram - STOP CALL --> ```mermaid %%{init: {'theme': 'base', 'actorMargin': 500 , 'themeVariables': { 'primaryColor': '#abcdef', 'edgeLabelBackground':'#ffffee', 'noteBkgColor': '#fff0f0'}}}%% sequenceDiagram actor Alice participant server actor Bob note left of Alice : Alice.status = calling note right of Bob : Bob.status = calling Alice ->> server : Stop Call <br/> PUT Note over Alice,server : Close mic - stop tracks <br/> Bob.status = closing <br/> Alice.status = free <br/> Bob.caller = null <br/> Alice.caller = null <br/> Bob.isPolite = true <br/> Alice.isPolite = true <br/> Bob.SDP = null <br/>Alice.SDP = null <br/> Bob.candidates = null <br/> Alice.candidates = null <br/> Kill Alice's peerconnection loop polling each 5 s Bob ->> server : refresh() <br/> GET server ->> Bob : Bob.status = closing ! end Bob ->> server : Update <br/> PUT Note over Bob, server: <br/> Close mic - stop tracks <br/> Bob.status = free <br/> Kill Bob's peerconnection <br/> note left of Alice : Alice.status = free note right of Bob : Bob.status = free ``` * Processes when someone leaves the page: User is deleted from the users list. Check if the users list is empty. If empty: delete room and all its objects.