getSessionIdToken(@RequestBody String sessionNameParam,
- HttpSession httpSession) throws ParseException {
- // Check the user is logged ...
-
- JSONObject sessionJSON = (JSONObject) new JSONParser().parse(sessionNameParam);
-
- // The video-call to connect ("TUTORIAL")
- String sessionName = (String) sessionJSON.get("session");
-
- // Role associated to this user
- OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
-
- // Optional data to be passed to other users when this user connects to the video-call
- // In this case, a JSON with the value we stored in the HttpSession object on login
- String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
-
- // Build tokenOptions object with the serverData and the role
- TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
-
- JSONObject responseJson = new JSONObject();
- ```
-
- Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
- ```java
- if (this.mapSessions.get(sessionName) != null) { ...
- ```
- In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
-
- ```java
- else {
- // New session: return a new sessionId and a new token
- try {
-
- // Create a new OpenVidu Session
- Session session = this.openVidu.createSession();
- // Get the sessionId
- String sessionId = session.getSessionId();
- // Generate a new token with the recently created tokenOptions
- String token = session.generateToken(tokenOptions);
-
- // Store the session and the token in our collections
- this.mapSessions.put(sessionName, session);
- this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
- this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
-
- // Prepare the response with the sessionId and the token
- responseJson.put(0, sessionId);
- responseJson.put(1, token);
-
- // Return the response to the client
- return new ResponseEntity<>(responseJson, HttpStatus.OK);
-
- } catch (Exception e) {
- // If error generate an error message and return it to client
- return getErrorResponse(e);
- }
- }
- ```
- We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
-
- ```javascript
- // --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
-
- OV = new OpenVidu();
- session = OV.initSession(sessionId);
-
-
- // --- 2) Specify the actions when events take place ---
-
- // On every new Stream received...
- session.on('streamCreated', function (event) {
-
- // Subscribe to the Stream to receive it
- // HTML video will be appended to element with 'subscriber' id
- var subscriber = session.subscribe(event.stream, 'subscriber');
-
- // When the HTML video has been appended to DOM...
- subscriber.on('videoElementCreated', function (event) {
-
- // Add a new element for the user's name and nickname just below its video
- appendUserData(event.element, subscriber.stream.connection);
- });
+```javascript
+function logIn() {
+ var user = $("#user").val(); // Username
+ var pass = $("#pass").val(); // Password
+ var jsonBody = JSON.stringify({ // Body of POST request
+ 'user': user,
+ 'pass': pass
});
-
- // On every Stream destroyed...
- session.on('streamDestroyed', function (event) {
- // Delete the HTML element with the user's name and nickname
- removeUserData(event.stream.connection);
+
+ httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
+ function successCallback(response){ // Send POST request
+ console.warn(userName + ' login');
+ // HTML shows logged-in page ...
});
+}
+```
+
+`LoginController.java` checks the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object):
+
+```java
+@RequestMapping(value = "/api-login/login", method = RequestMethod.POST)
+public ResponseEntity login(@RequestBody String userPass, HttpSession httpSession)
+ throws ParseException {
+
+ // Retrieve params from POST body
+ JSONObject userPassJson = (JSONObject) new JSONParser().parse(userPass);
+ String user = (String) userPassJson.get("user");
+ String pass = (String) userPassJson.get("pass");
+
+ if (login(user, pass)){ // Correct user-pass
+ // Validate session and return OK
+ // Value stored in HttpSession allows us to identify the user in future requests
+ httpSession.setAttribute("loggedUser", user);
+ return new ResponseEntity<>(HttpStatus.OK);
+ } else { // Wrong user-pass
+ // Invalidate session and return error
+ httpSession.invalidate();
+ return new ResponseEntity<>("User/Pass incorrect", HttpStatus.UNAUTHORIZED);
+ }
+}
+```
+
+---
+
+### 2) User connects to "TUTORIAL" video-call
+
+HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
+
+
+
+
+
+`app.js` will execute `joinSession()` method, which starts like this:
+
+```javascript
+function joinSession() {
+ getSessionIdAndToken(function () { ...
+```
+So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
+
+```javascript
+function getSessionIdAndToken(callback) {
+ sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
+ var jsonBody = JSON.stringify({ // Body of POST request
+ 'session': sessionName
+ });
+
+ // Send POST request
+ httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
+ 'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
+ sessionId = response[0]; // Get sessionId from response
+ token = response[1]; // Get token from response
+ callback(); // Continue the join operation
+ });
+}
+```
+Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `SessionController.java`. First of all there are some important attributes in this class we must mention:
+
+```java
+// OpenVidu object to ask openvidu-server for sessionId and token
+private OpenVidu openVidu;
+
+// Collection to pair session names and OpenVidu Session objects
+private Map mapSessions = new ConcurrentHashMap<>();
+// Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
+private Map> mapSessionIdsTokens = new ConcurrentHashMap<>();
+
+// URL where our OpenVidu server is listening
+private String OPENVIDU_URL;
+// Secret shared with our OpenVidu server
+private String SECRET;
+```
+
+Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
+
+```java
+@RequestMapping(value = "/api-sessions/get-sessionid-token", method = RequestMethod.POST)
+public ResponseEntity getSessionIdToken(@RequestBody String sessionNameParam,
+ HttpSession httpSession) throws ParseException {
+ // Check the user is logged ...
+
+ JSONObject sessionJSON = (JSONObject) new JSONParser().parse(sessionNameParam);
+ // The video-call to connect ("TUTORIAL")
+ String sessionName = (String) sessionJSON.get("session");
- // --- 3) Connect to the session passing the retrieved token and some more data from
- // the client (in this case a JSON with the nickname chosen by the user) ---
+ // Role associated to this user
+ OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
- session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
+ // Optional data to be passed to other users when this user connects to the video-call
+ // In this case, a JSON with the value we stored in the HttpSession object on login
+ String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
+
+ // Build tokenOptions object with the serverData and the role
+ TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
+
+ JSONObject responseJson = new JSONObject();
+```
+
+Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
+```java
+if (this.mapSessions.get(sessionName) != null) { ...
+```
+In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
+
+```java
+else {
+ // New session: return a new sessionId and a new token
+ try {
- // If the connection is successful, initialize a publisher and publish to the session
- if (!err) {
+ // Create a new OpenVidu Session
+ Session session = this.openVidu.createSession();
+ // Get the sessionId
+ String sessionId = session.getSessionId();
+ // Generate a new token with the recently created tokenOptions
+ String token = session.generateToken(tokenOptions);
+
+ // Store the session and the token in our collections
+ this.mapSessions.put(sessionName, session);
+ this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
+ this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
+
+ // Prepare the response with the sessionId and the token
+ responseJson.put(0, sessionId);
+ responseJson.put(1, token);
+
+ // Return the response to the client
+ return new ResponseEntity<>(responseJson, HttpStatus.OK);
+
+ } catch (Exception e) {
+ // If error generate an error message and return it to client
+ return getErrorResponse(e);
+ }
+}
+```
+We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
+
+```javascript
+// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
+
+OV = new OpenVidu();
+session = OV.initSession(sessionId);
+
+
+// --- 2) Specify the actions when events take place ---
+
+// On every new Stream received...
+session.on('streamCreated', function (event) {
+
+ // Subscribe to the Stream to receive it
+ // HTML video will be appended to element with 'subscriber' id
+ var subscriber = session.subscribe(event.stream, 'subscriber');
- // Here we check somehow if the user has at least 'PUBLISHER' role before
- // trying to publish its stream. Even if someone modified the client's code and
- // published the stream, it won't work if the token sent in Session.connect
- // method doesn't belong to a 'PUBLIHSER' role
- if (isPublisher()) {
+ // When the HTML video has been appended to DOM...
+ subscriber.on('videoElementCreated', function (event) {
- // --- 4) Get your own camera stream and publish it ---
-
- var publisher = OV.initPublisher('publisher', {
- audio: true,
- video: true,
- quality: 'MEDIUM'
- });
-
-
- // --- 5) Publish your stream ---
-
- session.publish(publisher);
-
- } else {
- console.warn('You don\'t have permissions to publish');
- }
+ // Add a new element for the user's name and nickname just below its video
+ appendUserData(event.element, subscriber.stream.connection);
+ });
+});
+
+// On every Stream destroyed...
+session.on('streamDestroyed', function (event) {
+ // Delete the HTML element with the user's name and nickname
+ removeUserData(event.stream.connection);
+});
+
+
+// --- 3) Connect to the session passing the retrieved token and some more data from
+// the client (in this case a JSON with the nickname chosen by the user) ---
+
+session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
+
+ // If the connection is successful, initialize a publisher and publish to the session
+ if (!err) {
+
+ // Here we check somehow if the user has at least 'PUBLISHER' role before
+ // trying to publish its stream. Even if someone modified the client's code and
+ // published the stream, it won't work if the token sent in Session.connect
+ // method doesn't belong to a 'PUBLIHSER' role
+ if (isPublisher()) {
+
+ // --- 4) Get your own camera stream and publish it ---
+
+ var publisher = OV.initPublisher('publisher', {
+ audio: true,
+ video: true,
+ quality: 'MEDIUM'
+ });
+
+
+ // --- 5) Publish your stream ---
+
+ session.publish(publisher);
+
} else {
- console.warn('Error connecting to the session:', error.code, error.message);
+ console.warn('You don\'t have permissions to publish');
}
+ } else {
+ console.warn('Error connecting to the session:', error.code, error.message);
+ }
+
+ // HTML shows session page ...
+
+});
+```
+The user will now see its own video on the page. The connection to the session has completed!
+
+---
+
+### 3) Another user connects to the video-call
+
+The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
+
+```java
+if (this.mapSessions.get(sessionName) != null) {
+ // Session already exists: return existing sessionId and a new token
+ try {
+
+ // Get the existing sessionId from our collection with
+ // the sessionName param ("TUTORIAL")
+ String sessionId = this.mapSessions.get(sessionName).getSessionId();
+ // Generate a new token with the recently created tokenOptions
+ String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
- // HTML shows session page ...
+ // Update our collection storing the new token
+ this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
+ // Prepare the response with the sessionId and the token
+ responseJson.put(0, sessionId);
+ responseJson.put(1, token);
+
+ // Return the response to the client
+ return new ResponseEntity<>(responseJson, HttpStatus.OK);
+
+ } catch (Exception e) {
+ // If error generate an error message and return it to client
+ return getErrorResponse(e);
+ }
+}
+```
+The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
+
+---
+
+### 4) Users leave the video-call
+
+After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
+For the POST operation, in `app.js` we run:
+
+```javascript
+function removeUser() {
+ // Body of POST request with the name of the session and the token of the leaving user
+ var jsonBody = JSON.stringify({
+ 'sessionName': sessionName,
+ 'token': token
});
- ```
- The user will now see its own video on the page. The connection to the session has completed!
+ // Send POST request
+ httpRequest('POST', '/api-sessions/remove-user', jsonBody,
+ 'User couldn\'t be removed from session', function successCallback(response) {
+ console.warn(userName + ' correctly removed from session ' + sessionName);
+ });
+}
+```
+And in `SessionController.java` we update the collections:
-3. **Another user connects to the video-call**
+```java
+@RequestMapping(value = "/api-sessions/remove-user", method = RequestMethod.POST)
+public ResponseEntity removeUser(@RequestBody String sessionNameToken,
+ HttpSession httpSession) throws Exception {
+ // Check the user is logged ...
- The process would be exactly the same as before until `SessionController.java` executes `getSessionIdAndToken()` method. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
+ // Retrieve the params from BODY
+ JSONObject sessionNameTokenJSON = (JSONObject) new JSONParser().parse(sessionNameToken);
+ String sessionName = (String) sessionNameTokenJSON.get("sessionName");
+ String token = (String) sessionNameTokenJSON.get("token");
- ```java
+ // If the session exists ("TUTORIAL" in this case)
if (this.mapSessions.get(sessionName) != null) {
- // Session already exists: return existing sessionId and a new token
- try {
-
- // Get the existing sessionId from our collection with
- // the sessionName param ("TUTORIAL")
- String sessionId = this.mapSessions.get(sessionName).getSessionId();
- // Generate a new token with the recently created tokenOptions
- String token = this.mapSessions.get(sessionName).generateToken(tokenOptions);
-
- // Update our collection storing the new token
- this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
-
- // Prepare the response with the sessionId and the token
- responseJson.put(0, sessionId);
- responseJson.put(1, token);
-
- // Return the response to the client
- return new ResponseEntity<>(responseJson, HttpStatus.OK);
-
- } catch (Exception e) {
- // If error generate an error message and return it to client
- return getErrorResponse(e);
- }
- }
- ```
- The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
+ String sessionId = this.mapSessions.get(sessionName).getSessionId();
-4. **Users leave the video-call**
-
- After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
- For the POST operation, in `app.js` we run:
-
- ```javascript
- function removeUser() {
- // Body of POST request with the name of the session and the token of the leaving user
- var jsonBody = JSON.stringify({
- 'sessionName': sessionName,
- 'token': token
- });
-
- // Send POST request
- httpRequest('POST', '/api-sessions/remove-user', jsonBody,
- 'User couldn\'t be removed from session', function successCallback(response) {
- console.warn(userName + ' correctly removed from session ' + sessionName);
- });
- }
- ```
- And in `SessionController.java` we update the collections:
-
- ```java
- @RequestMapping(value = "/api-sessions/remove-user", method = RequestMethod.POST)
- public ResponseEntity removeUser(@RequestBody String sessionNameToken,
- HttpSession httpSession) throws Exception {
- // Check the user is logged ...
-
- // Retrieve the params from BODY
- JSONObject sessionNameTokenJSON = (JSONObject) new JSONParser().parse(sessionNameToken);
- String sessionName = (String) sessionNameTokenJSON.get("sessionName");
- String token = (String) sessionNameTokenJSON.get("token");
-
- // If the session exists ("TUTORIAL" in this case)
- if (this.mapSessions.get(sessionName) != null) {
- String sessionId = this.mapSessions.get(sessionName).getSessionId();
-
- if (this.mapSessionIdsTokens.containsKey(sessionId)) {
- // If the token exists
- if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
- // Token has been removed
- if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
- // Last user left: session "TUTORIAL" must be removed
- this.mapSessions.remove(sessionName);
- }
- return new ResponseEntity<>(HttpStatus.OK);
- } else {
- // The TOKEN wasn't valid
- return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ if (this.mapSessionIdsTokens.containsKey(sessionId)) {
+ // If the token exists
+ if (this.mapSessionIdsTokens.get(sessionId).remove(token) != null) {
+ // Token has been removed
+ if (this.mapSessionIdsTokens.get(sessionId).isEmpty()) {
+ // Last user left: session "TUTORIAL" must be removed
+ this.mapSessions.remove(sessionName);
}
+ return new ResponseEntity<>(HttpStatus.OK);
} else {
- // The SESSIONID wasn't valid
+ // The TOKEN wasn't valid
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
} else {
- // The SESSION does not exist
+ // The SESSIONID wasn't valid
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
+ } else {
+ // The SESSION does not exist
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
- ```
- When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
+}
+```
+When the last user leaves the session `this.mapSessions.remove(sessionName);` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
---
diff --git a/openvidu-js-node/README.md b/openvidu-js-node/README.md
index 922382c4..05cde09a 100644
--- a/openvidu-js-node/README.md
+++ b/openvidu-js-node/README.md
@@ -70,261 +70,162 @@ This is a very basic web application with a pretty simple vanilla JS/HTML/CSS fr
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
-1. **User logs in**
+---
- We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
-
- ```javascript
- function httpRequest(method, url, body, errorMsg, callback)
- ```
-
- Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
+### 1) User logs in
- `index.html` will first show a form to log in:
+We have implemented a method for making HTTP requests to the backend, as we will need to make at least three of them: one for logging in, one for getting the sessionId and a valid token from openvidu-server and a one for letting know our backend when any user leaves the video-call. The header of the method looks like this:
-
-
-
+```javascript
+function httpRequest(method, url, body, errorMsg, callback)
+```
- `app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
-
- ```javascript
- function logIn() {
- var user = $("#user").val(); // Username
- var pass = $("#pass").val(); // Password
- var jsonBody = JSON.stringify({ // Body of POST request
- 'user': user,
- 'pass': pass
- });
-
- httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
- function successCallback(response){ // Send POST request
- console.warn(userName + ' login');
- // HTML shows logged-in page ...
- });
- }
- ```
+Where `method` is whether "POST" or "GET", `url` the path of the REST operation, `body` the data to be passed, `errorMsg` the output error message if something goes wrong and `callback` the function to execute in case of success. As mentioned above, we need to call this method three times for each user that LOGS IN 🡒 CONNECTS TO A VIDEO-CALL 🡒 LEAVES THE VIDEO-CALL.
- `server.js` at `/api-login/login` checks the params are correct and if so sets an active session for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object):
+`index.html` will first show a form to log in:
- ```javascript
- app.post('/api-login/login', function (req, res) {
-
- // Retrieve params from POST body
- var user = req.body.user;
- var pass = req.body.pass;
-
- if (login(user, pass)) { // Correct user-pass
- // Validate session and return OK
- // Value stored in req.session allows us to identify the user in future requests
- req.session.loggedUser = user;
- res.status(200).send();
- } else { // Wrong user-pass
- // Invalidate session and return error
- req.session.destroy();
- res.status(401).send('User/Pass incorrect');
- }
+
+
+
+
+`app.js` sends an HTTP request to "/api-login/login" passing the username and the password retrieved from the HTML form whenever "Log in" button is clicked:
+
+```javascript
+function logIn() {
+ var user = $("#user").val(); // Username
+ var pass = $("#pass").val(); // Password
+ var jsonBody = JSON.stringify({ // Body of POST request
+ 'user': user,
+ 'pass': pass
});
- ```
-2. **User connects to "TUTORIAL" video-call**
-
- HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
-
-
-
-
-
- `app.js` will execute `joinSession()` method, which starts like this:
-
- ```javascript
- function joinSession() {
- getSessionIdAndToken(function () { ...
- ```
- So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
-
- ```javascript
- function getSessionIdAndToken(callback) {
- sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
- var jsonBody = JSON.stringify({ // Body of POST request
- 'session': sessionName
- });
-
- // Send POST request
- httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
- 'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
- sessionId = response[0]; // Get sessionId from response
- token = response[1]; // Get token from response
- callback(); // Continue the join operation
- });
- }
- ```
- Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `server.js` controller at `/api-sessions/get-sessionid-token`. First of all there are some important attributes in this class we must mention:
-
- ```javascript
- // Environment variable: URL where our OpenVidu server is listening
- var OPENVIDU_URL = process.argv[2];
- // Environment variable: secret shared with our OpenVidu server
- var OPENVIDU_SECRET = process.argv[3];
-
- // OpenVidu object to ask openvidu-server for sessionId and token
- var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
-
- // Collection to pair session names and OpenVidu Session objects
- var mapSessionNameSession = {};
- // Collection to pair sessionId's (identifiers of Session objects) and tokens
- var mapSessionIdTokens = {};
- ```
-
- Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
-
-
- ```javascript
- app.post('/api-sessions/get-sessionid-token', function (req, res) {
- // Check the user is logged ...
-
- // The video-call to connect ("TUTORIAL")
- var sessionName = req.body.session;
-
- // Role associated to this user
- var role = users.find(u => (u.user === req.session.loggedUser)).role;
-
- // Optional data to be passed to other users when this user connects to the video-call
- // In this case, a JSON with the value we stored in the req.session object on login
- var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
-
- // Build tokenOptions object with the serverData and the role
- var tokenOptions = new TokenOptions.Builder()
- .data(serverData)
- .role(role)
- .build();
- ```
-
- Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
- ```javascript
- if (mapSessionNameSession[sessionName]) { ...
- ```
- In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
-
- ```javascript
- else { // New session: return a new sessionId and a new token
- // Create a new OpenVidu Session
- var mySession = OV.createSession();
-
- // Get the sessionId asynchronously
- mySession.getSessionId(function (sessionId) {
-
- // Store the new Session in the collection of Sessions
- mapSessionNameSession[sessionName] = mySession;
- // Store a new empty array in the collection of tokens
- mapSessionIdTokens[sessionId] = [];
-
- // Generate a new token asynchronously with the recently created tokenOptions
- mySession.generateToken(tokenOptions, function (token) {
-
- // Store the new token in the collection of tokens
- mapSessionIdTokens[sessionId].push(token);
-
- // Return the sessionId and token to the client
- res.status(200).send({
- 0: sessionId,
- 1: token
- });
- });
- });
- }
- ```
- We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
-
- ```javascript
- // --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
-
- OV = new OpenVidu();
- session = OV.initSession(sessionId);
-
-
- // --- 2) Specify the actions when events take place ---
-
- // On every new Stream received...
- session.on('streamCreated', function (event) {
-
- // Subscribe to the Stream to receive it
- // HTML video will be appended to element with 'subscriber' id
- var subscriber = session.subscribe(event.stream, 'subscriber');
-
- // When the HTML video has been appended to DOM...
- subscriber.on('videoElementCreated', function (event) {
-
- // Add a new element for the user's name and nickname just below its video
- appendUserData(event.element, subscriber.stream.connection);
- });
+ httpRequest('POST', '/api-login/login', jsonBody, 'Login WRONG',
+ function successCallback(response){ // Send POST request
+ console.warn(userName + ' login');
+ // HTML shows logged-in page ...
});
-
- // On every Stream destroyed...
- session.on('streamDestroyed', function (event) {
- // Delete the HTML element with the user's name and nickname
- removeUserData(event.stream.connection);
+}
+```
+
+`server.js` at `/api-login/login` checks the params are correct and if so sets an active session for the newly logged user (adding a _loggedUser_ property with its username in the _req.session_ object):
+
+```javascript
+app.post('/api-login/login', function (req, res) {
+
+ // Retrieve params from POST body
+ var user = req.body.user;
+ var pass = req.body.pass;
+
+ if (login(user, pass)) { // Correct user-pass
+ // Validate session and return OK
+ // Value stored in req.session allows us to identify the user in future requests
+ req.session.loggedUser = user;
+ res.status(200).send();
+ } else { // Wrong user-pass
+ // Invalidate session and return error
+ req.session.destroy();
+ res.status(401).send('User/Pass incorrect');
+ }
+});
+```
+
+---
+
+### 2) User connects to "TUTORIAL" video-call
+
+HTML will display now the user has logged in a different form, asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field and press "Join!" button:
+
+
+
+
+
+`app.js` will execute `joinSession()` method, which starts like this:
+
+```javascript
+function joinSession() {
+ getSessionIdAndToken(function () { ...
+```
+So the first thing to do here is to retrieve a _sessionId_ and a _token_ from our backend. Only when we have them available in the browser we will continue with the _join_ operation. Let's see what `getSessionIdAndToken()` looks like:
+
+```javascript
+function getSessionIdAndToken(callback) {
+ sessionName = $("#sessionName").val(); // Video-call to connect ("TUTORIAL")
+ var jsonBody = JSON.stringify({ // Body of POST request
+ 'session': sessionName
});
-
-
- // --- 3) Connect to the session passing the retrieved token and some more data from
- // the client (in this case a JSON with the nickname chosen by the user) ---
-
- session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
-
- // If the connection is successful, initialize a publisher and publish to the session
- if (!err) {
-
- // Here we check somehow if the user has at least 'PUBLISHER' role before
- // trying to publish its stream. Even if someone modified the client's code and
- // published the stream, it won't work if the token sent in Session.connect
- // method doesn't belong to a 'PUBLIHSER' role
- if (isPublisher()) {
-
- // --- 4) Get your own camera stream and publish it ---
-
- var publisher = OV.initPublisher('publisher', {
- audio: true,
- video: true,
- quality: 'MEDIUM'
- });
-
-
- // --- 5) Publish your stream ---
-
- session.publish(publisher);
-
- } else {
- console.warn('You don\'t have permissions to publish');
- }
- } else {
- console.warn('Error connecting to the session:', error.code, error.message);
- }
-
- // HTML shows session page ...
-
+
+ // Send POST request
+ httpRequest('POST', '/api-sessions/get-sessionid-token', jsonBody,
+ 'Request of SESSIONID and TOKEN gone WRONG:', function successCallback(response){
+ sessionId = response[0]; // Get sessionId from response
+ token = response[1]; // Get token from response
+ callback(); // Continue the join operation
});
- ```
- The user will now see its own video on the page. The connection to the session has completed!
+}
+```
+Here is the second time we must call our `httpRequest()` method, sending the session we want to connect ("TUTORIAL") and waiting to get a _sessionId_ and a _token_ as response. The interesting part here is in `server.js` controller at `/api-sessions/get-sessionid-token`. First of all there are some important attributes in this class we must mention:
+
+```javascript
+// Environment variable: URL where our OpenVidu server is listening
+var OPENVIDU_URL = process.argv[2];
+// Environment variable: secret shared with our OpenVidu server
+var OPENVIDU_SECRET = process.argv[3];
+
+// OpenVidu object to ask openvidu-server for sessionId and token
+var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
+
+// Collection to pair session names and OpenVidu Session objects
+var mapSessionNameSession = {};
+// Collection to pair sessionId's (identifiers of Session objects) and tokens
+var mapSessionIdTokens = {};
+```
+
+Rest controller method begins retrieving the param send by the client, which in this case is the video-call name ("TUTORIAL"), as well as preparing a param we will need a little further on: `tokenOptions`.
-3. **Another user connects to the video-call**
+```javascript
+app.post('/api-sessions/get-sessionid-token', function (req, res) {
+ // Check the user is logged ...
- The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
+ // The video-call to connect ("TUTORIAL")
+ var sessionName = req.body.session;
- ```javascript
- if (mapSessionNameSession[sessionName]) {
- // Session already exists: return existing sessionId and a new token
+ // Role associated to this user
+ var role = users.find(u => (u.user === req.session.loggedUser)).role;
+
+ // Optional data to be passed to other users when this user connects to the video-call
+ // In this case, a JSON with the value we stored in the req.session object on login
+ var serverData = '{"serverData": "' + req.session.loggedUser + '"}';
+
+ // Build tokenOptions object with the serverData and the role
+ var tokenOptions = new TokenOptions.Builder()
+ .data(serverData)
+ .role(role)
+ .build();
+```
+
+Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
+```javascript
+if (mapSessionNameSession[sessionName]) { ...
+```
+In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
+
+```javascript
+else { // New session: return a new sessionId and a new token
+ // Create a new OpenVidu Session
+ var mySession = OV.createSession();
+
+ // Get the sessionId asynchronously
+ mySession.getSessionId(function (sessionId) {
- // Get the existing Session from the collection
- var mySession = mapSessionNameSession[sessionName];
+ // Store the new Session in the collection of Sessions
+ mapSessionNameSession[sessionName] = mySession;
+ // Store a new empty array in the collection of tokens
+ mapSessionIdTokens[sessionId] = [];
// Generate a new token asynchronously with the recently created tokenOptions
mySession.generateToken(tokenOptions, function (token) {
- // Get the existing sessionId
- var sessionId = mySession.getSessionId();
-
// Store the new token in the collection of tokens
mapSessionIdTokens[sessionId].push(token);
@@ -334,68 +235,174 @@ Let's describe the code following this scenario: a user logs in to the app and c
1: token
});
});
- }
- ```
- The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
+ });
+}
+```
+We are almost there! Now in `app.js` we can init a new Session with _sessionId_ and connect to it with _token_:
-4. **Users leave the video-call**
+```javascript
+// --- 1) Get an OpenVidu object and init a session with the retrieved sessionId ---
- After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
- For the POST operation, in `app.js` we run:
+OV = new OpenVidu();
+session = OV.initSession(sessionId);
- ```javascript
- function removeUser() {
- // Body of POST request with the name of the session and the token of the leaving user
- var jsonBody = JSON.stringify({
- 'sessionName': sessionName,
- 'token': token
- });
+
+// --- 2) Specify the actions when events take place ---
+
+// On every new Stream received...
+session.on('streamCreated', function (event) {
+
+ // Subscribe to the Stream to receive it
+ // HTML video will be appended to element with 'subscriber' id
+ var subscriber = session.subscribe(event.stream, 'subscriber');
- // Send POST request
- httpRequest('POST', '/api-sessions/remove-user', jsonBody,
- 'User couldn\'t be removed from session', function successCallback(response) {
- console.warn(userName + ' correctly removed from session ' + sessionName);
- });
- }
- ```
- And in `server.js` we update the collections in `/api-sessions/remove-user`:
+ // When the HTML video has been appended to DOM...
+ subscriber.on('videoElementCreated', function (event) {
- ```javascript
- app.post('/api-sessions/remove-user', function (req, res) {
- // Check the user is logged ...
-
- // Retrieve params from POST body
- var sessionName = req.body.sessionName;
- var token = req.body.token;
-
- // If the session exists ("TUTORIAL" in this case)
- var mySession = mapSessionNameSession[sessionName];
- if (mySession) {
- var tokens = mapSessionIdTokens[mySession.getSessionId()];
- if (tokens) {
- var index = tokens.indexOf(token);
-
- // If the token exists
- if (index !== -1) {
- // Token removed!
- tokens.splice(index, 1);
- } else {
- res.status(500).send('The TOKEN wasn\'t valid');
- }
- if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
- // Last user left: session "TUTORIAL" must be removed
- delete mapSessionNameSession[sessionName];
- }
- res.status(200).send();
- } else {
- res.status(500).send('The SESSIONID wasn\'t valid');
- }
+ // Add a new element for the user's name and nickname just below its video
+ appendUserData(event.element, subscriber.stream.connection);
+ });
+});
+
+// On every Stream destroyed...
+session.on('streamDestroyed', function (event) {
+ // Delete the HTML element with the user's name and nickname
+ removeUserData(event.stream.connection);
+});
+
+
+// --- 3) Connect to the session passing the retrieved token and some more data from
+// the client (in this case a JSON with the nickname chosen by the user) ---
+
+session.connect(token, '{"clientData": "' + $("#participantName").val() + '"}', function (err) {
+
+ // If the connection is successful, initialize a publisher and publish to the session
+ if (!err) {
+
+ // Here we check somehow if the user has at least 'PUBLISHER' role before
+ // trying to publish its stream. Even if someone modified the client's code and
+ // published the stream, it won't work if the token sent in Session.connect
+ // method doesn't belong to a 'PUBLIHSER' role
+ if (isPublisher()) {
+
+ // --- 4) Get your own camera stream and publish it ---
+
+ var publisher = OV.initPublisher('publisher', {
+ audio: true,
+ video: true,
+ quality: 'MEDIUM'
+ });
+
+
+ // --- 5) Publish your stream ---
+
+ session.publish(publisher);
+
} else {
- res.status(500).send('The SESSION does not exist');
+ console.warn('You don\'t have permissions to publish');
}
+ } else {
+ console.warn('Error connecting to the session:', error.code, error.message);
}
- ```
- When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
+
+ // HTML shows session page ...
+
+});
+```
+The user will now see its own video on the page. The connection to the session has completed!
+
+---
+
+### 3) Another user connects to the video-call
+
+The process would be exactly the same as before until `server.js` executes controller at `/api-sessions/get-sessionid-token`. Now session 'TUTORIAL' already exists, so in the _if-else_ statement the _if_ branch would be the one executed:
+
+```javascript
+if (mapSessionNameSession[sessionName]) {
+ // Session already exists: return existing sessionId and a new token
+
+ // Get the existing Session from the collection
+ var mySession = mapSessionNameSession[sessionName];
+
+ // Generate a new token asynchronously with the recently created tokenOptions
+ mySession.generateToken(tokenOptions, function (token) {
+
+ // Get the existing sessionId
+ var sessionId = mySession.getSessionId();
+
+ // Store the new token in the collection of tokens
+ mapSessionIdTokens[sessionId].push(token);
+
+ // Return the sessionId and token to the client
+ res.status(200).send({
+ 0: sessionId,
+ 1: token
+ });
+ });
+}
+```
+The code executed in `app.js` would also be the same. After the `Session.publish()` method has been succesful, both users will be seeing each other's video, as well as the username and the nickname below it.
+
+---
+
+### 4) Users leave the video-call
+
+After a while both users decide to leave the session. Apart from calling `leaveSession()` (and therefore `session.disconnect()`) to destroy the connection on openvidu-server, we need to run the last HTTP operation: we must let the backend know that certain user has left the session so it can update the collections with the active sessions and tokens. To sum up, `session.disconnect()` updates our openvidu-server and the POST operation updates our backend.
+For the POST operation, in `app.js` we run:
+
+```javascript
+function removeUser() {
+ // Body of POST request with the name of the session and the token of the leaving user
+ var jsonBody = JSON.stringify({
+ 'sessionName': sessionName,
+ 'token': token
+ });
+
+ // Send POST request
+ httpRequest('POST', '/api-sessions/remove-user', jsonBody,
+ 'User couldn\'t be removed from session', function successCallback(response) {
+ console.warn(userName + ' correctly removed from session ' + sessionName);
+ });
+}
+```
+And in `server.js` we update the collections in `/api-sessions/remove-user`:
+
+```javascript
+app.post('/api-sessions/remove-user', function (req, res) {
+ // Check the user is logged ...
+
+ // Retrieve params from POST body
+ var sessionName = req.body.sessionName;
+ var token = req.body.token;
+
+ // If the session exists ("TUTORIAL" in this case)
+ var mySession = mapSessionNameSession[sessionName];
+ if (mySession) {
+ var tokens = mapSessionIdTokens[mySession.getSessionId()];
+ if (tokens) {
+ var index = tokens.indexOf(token);
+
+ // If the token exists
+ if (index !== -1) {
+ // Token removed!
+ tokens.splice(index, 1);
+ } else {
+ res.status(500).send('The TOKEN wasn\'t valid');
+ }
+ if (mapSessionIdTokens[mySession.getSessionId()].length == 0) {
+ // Last user left: session "TUTORIAL" must be removed
+ delete mapSessionNameSession[sessionName];
+ }
+ res.status(200).send();
+ } else {
+ res.status(500).send('The SESSIONID wasn\'t valid');
+ }
+ } else {
+ res.status(500).send('The SESSION does not exist');
+ }
+}
+```
+When the last user leaves the session `delete mapSessionNameSession[sessionName]` will be executed: this means the session is empty and that it is going to be closed. The _sessionId_ and all _token_ params associated to it will be invalidated.
---
diff --git a/openvidu-mvc-java/README.md b/openvidu-mvc-java/README.md
index db1861d3..cb42cb14 100644
--- a/openvidu-mvc-java/README.md
+++ b/openvidu-mvc-java/README.md
@@ -68,334 +68,342 @@ OpenVidu assumes you can identify your users so you can tell which users can con
Let's describe the code following this scenario: a user logs in to the app and connects to the video-call "TUTORIAL", where he publishes his webcam. A second user will connect to the same video-call just after that and publish its own webcam. Both of them will leave the call after a while.
-1. **User logs in**
+---
- At path `/` a login form will be displayed:
+### 1) User logs in
-
-
+At path `/` a login form will be displayed:
+
+
+
+
+
+The form will execute a POST operation to path `/dashboard` whenever "Log in" button is clicked, passing the username and the password:
+
+```html
+
- ```
-
- `LoginController.java` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object). Finally it returns `dashboard.html` template:
-
- ```java
- @RequestMapping(value = "/dashboard", method = { RequestMethod.GET, RequestMethod.POST })
- public String login(@RequestParam(name = "user", required = false) String user,
- @RequestParam(name = "pass", required = false) String pass,
- Model model, HttpSession httpSession) {
-
- // Check if the user is already logged in
- String userName = (String) httpSession.getAttribute("loggedUser");
- if (userName != null) {
- // User is already logged. Immediately return dashboard
- model.addAttribute("username", userName);
- return "dashboard";
- }
-
- // User wasn't logged and wants to
- if (login(user, pass)) { // Correct user-pass
-
- // Validate session and return OK
- // Value stored in HttpSession allows us to identify the user in future requests
- httpSession.setAttribute("loggedUser", user);
- model.addAttribute("username", user);
-
- // Return dashboard.html template
- return "dashboard";
-
- } else { // Wrong user-pass
- // Invalidate session and redirect to index.html
- httpSession.invalidate();
- return "redirect:/";
- }
- }
- ```
-
-2. **User connects to "TUTORIAL" video-call**
-
- `dashboard.html` template will display a form asking for the video-call to connect and the nickname the user wants to have in it. So our 'publisher1' user would write TUTORIAL in "Session" field:
-
-
-
+
+ Pass
+
+ Log in
+
+
+```
- The form will execute a POST operation to path `/session` whenever "Join!" button is clicked, passing the nickname and the session name:
+`LoginController.java` first checks if the user is already logged (maybe he has just refreshed `/dashboard` page), and if so it just redirects to the dashboard itself. If the user is actually logging in, the method checks that the params are correct and if so sets an _HttpSession_ for the newly logged user (adding a "loggedUser" attribute with its username in the HttpSession object). Finally it returns `dashboard.html` template:
- ```html
-
- ```
+```java
+@RequestMapping(value = "/dashboard", method = { RequestMethod.GET, RequestMethod.POST })
+public String login(@RequestParam(name = "user", required = false) String user,
+ @RequestParam(name = "pass", required = false) String pass,
+ Model model, HttpSession httpSession) {
- When `SessionController.java` receives a request at `/session` path is when things get interesting.
- First of all there are some important attributes in this class we must mention:
-
- ```java
- // OpenVidu object to ask openvidu-server for sessionId and token
- private OpenVidu openVidu;
-
- // Collection to pair session names and OpenVidu Session objects
- private Map mapSessions = new ConcurrentHashMap<>();
- // Collection to pair sessionId's and tokens (the inner Map pairs tokens and role associated)
- private Map> mapSessionIdsTokens = new ConcurrentHashMap<>();
-
- // URL where our OpenVidu server is listening
- private String OPENVIDU_URL;
- // Secret shared with our OpenVidu server
- private String SECRET;
- ```
-
- Rest controller method receives both params sent by the client (whatever nickname the user has chosen and "TUTORIAL" as the sessionName). First it prepares a param we will need a little further on: `tokenOptions`.
-
- ```java
- @RequestMapping(value = "/session", method = RequestMethod.POST)
- public String joinSession(@RequestParam(name = "data") String clientData,
- @RequestParam(name = "session-name") String sessionName,
- Model model, HttpSession httpSession) {
- // Check the user is logged ...
-
- // Role associated to this user
- OpenViduRole role = LoginController.users.get(httpSession.getAttribute("loggedUser")).role;
-
- // Optional data to be passed to other users when this user connects to the video-call
- // In this case, a JSON with the value we stored in the HttpSession object on login
- String serverData = "{\"serverData\": \"" + httpSession.getAttribute("loggedUser") + "\"}";
-
- // Build tokenOptions object with the serverData and the role
- TokenOptions tokenOptions = new TokenOptions.Builder().data(serverData).role(role).build();
- ```
-
- Just after that an _if-else_ statement comes into play: does the session "TUTORIAL" already exitsts?
- ```java
- if (this.mapSessions.get(sessionName) != null) { ...
- ```
- In this case it doesn't because 'publisher1' is the first user connecting to it. So we focus on the _else_ branch:
-
- ```java
- else {
- // New session: return a new sessionId and a new token
- try {
-
- // Create a new OpenVidu Session
- Session session = this.openVidu.createSession();
- // Get the sessionId
- String sessionId = session.getSessionId();
- // Generate a new token with the recently created tokenOptions
- String token = session.generateToken(tokenOptions);
-
- // Store the session and the token in our collections
- this.mapSessions.put(sessionName, session);
- this.mapSessionIdsTokens.put(sessionId, new ConcurrentHashMap<>());
- this.mapSessionIdsTokens.get(sessionId).put(token, OpenViduRole.PUBLISHER);
-
- // Add all the needed attributes to the template
- model.addAttribute("sessionId", sessionId);
- model.addAttribute("token", token);
- model.addAttribute("nickName", clientData);
- model.addAttribute("userName", httpSession.getAttribute("loggedUser"));
- model.addAttribute("sessionName", sessionName);
-
- // Return session.html template
- return "session";
-
- } catch (Exception e) {
- // If error just return dashboard.html template
- model.addAttribute("username", httpSession.getAttribute("loggedUser"));
- return "dashboard";
- }
+ // Check if the user is already logged in
+ String userName = (String) httpSession.getAttribute("loggedUser");
+ if (userName != null) {
+ // User is already logged. Immediately return dashboard
+ model.addAttribute("username", userName);
+ return "dashboard";
}
- ```
- We are almost there! Now in `session.html` JavaScript code (preceded by a tag `