How I integrated zoom in a meteor app
Tags: webdev, zoom, meteor
Published: 2020-12-04 09:43:49

Integrating zoom in a meteor application or any web application isn't hard by any means, nevertheless I needed a week or so to figure out everything from reading countless pages of documentation, forum articles, code samples, and also from a lot of trial and error.
So here I put a step by step guide to how I did it.
*this tutorial is not limited to a meteor project, the steps will apply to any web application.*

###I. Get your credentials

####1. Create a zoom account:
First of all, you need to have a zoom account, you can create it from here [zoom](https://zoom.us/signup).

####2. Creating a Jwt application
JWT or Json Web Token is an Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims.
You will need it to make calls to the zoom API to create,update,end... meetings.
* Go to [zoom marketplace](https://marketplace.zoom.us/), and go to Developer > Build app:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jkmdvoc572mew350gydf.png)
* create a JWT app, and complete your informations to get your api key and secret:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eiyha2tdvke9ta9xugv6.png)
You must keep your api key and secret in the backend, They are used to generate Jwt tokens and signatures.

###II. On the server side
####1. generate Jwt token
Add this method to generate the token
```js
import { Meteor } from "meteor/meteor";
import { JWT } from "jose";

const { ZOOM_JWT_API_KEY, ZOOM_JWT_API_SECRET } = Meteor.settings.private;

export const generateJWTToken = () => {
    const header = { alg: "HS256", typ: "JWT" };
    const payload = { iss: ZOOM_JWT_API_KEY, exp: new Date().getTime() + 30 * 60 * 1000 };

    return JWT.sign(payload, ZOOM_JWT_API_SECRET, { header });
};
```
####2. create a meeting:
```js
   // prepare the request body
        const data = {
            topic: "a topic for the meeting",
            type: 1, // this is an instant meeting
            duration: 90,
            password: Random.secret(9), // a passphrase to secure access to the meeting
            agenda: "description",
            settings: { // you can override settings as you wish
                host_video: true,
                participant_video: false,
                cn_meeting: false,
                in_meeting: false,
                waiting_room: false,
                join_before_host: false,
                mute_upon_entry: true,
                watermark: false,
                use_pmi: false,
                approval_type: 0,
                registration_type: 1,
                audio: "voip",
                auto_recording: false,
                show_share_button: true,
            },
        };

        // prepare the request headers
        const zoomHeaders = {
            Authorization: `Bearer ${ generateJWTToken() }`,
        };

        const result = HTTP.post("https://api.zoom.us/v2/users/me/meetings", { data, headers: zoomHeaders });
```
The result returned will contain the meeting id and password, you need to store them somewhere.

####3. generate the signature to join a meeting:
Add this method to your backend when a user want to join a meeting he needs to call it to generate a signature
```js
const { ZOOM_JWT_API_KEY, ZOOM_JWT_API_SECRET } = Meteor.settings.private;

const generateSignature = (meetingNumber: number, role: number) => {
    const timestamp = new Date().getTime() - 30000;
    const msg = Buffer.from(ZOOM_JWT_API_KEY + meetingNumber + timestamp + role).toString("base64");
    const hash = crypto.createHmac("sha256", ZOOM_JWT_API_SECRET).update(msg).digest("base64");
    const signature = Buffer.from(`${ ZOOM_JWT_API_KEY }.${ meetingNumber }.${ timestamp }.${ role }.${ hash }`).toString("base64");

    return signature;
};
```
**meetingNumber**: is the meeting id.
**role**: is an integer that take the values 0 for a participant and 1 for a host.

The server will also need to return this informations:
```js
{
            signature, //using generateSignature
            meetingNumber: id,
            password, //returned from create meeting
            userName: `${ lastName } ${ firstName }`,
            role,
            apiKey: ZOOM_JWT_API_KEY,
}
```
####4. End the meeting:
This is similar to creating a meeting:
```js
    const data = {
            action: "end",
    };


    const zoomHeaders = {
            Authorization: `Bearer ${ generateJWTToken() }`,
    };

    const result = HTTP.put(`https://api.zoom.us/v2/meetings/${ meeting.id }/status`, { data, headers: zoomHeaders });

    console.error(result);
```

###III. On the client side:
####1. add zoom web-sdk
* Using CDN
```html
    <!-- CSS -->
    <link type="text/css" rel="stylesheet" href="https://source.zoom.us/1.8.1/css/bootstrap.css" />
    <link type="text/css" rel="stylesheet" href="https://source.zoom.us/1.8.1/css/react-select.css" />

    <!-- JS -->
    <script src="https://source.zoom.us/1.8.1/lib/vendor/react.min.js"></script>
    <script src="https://source.zoom.us/1.8.1/lib/vendor/react-dom.min.js"></script>
    <script src="https://source.zoom.us/1.8.1/lib/vendor/redux.min.js"></script>
    <script src="https://source.zoom.us/1.8.1/lib/vendor/redux-thunk.min.js"></script>
    <script src="https://source.zoom.us/1.8.1/lib/vendor/jquery.min.js"></script>
    <script src="https://source.zoom.us/1.8.1/lib/vendor/lodash.min.js"></script>
    <script src="https://source.zoom.us/zoom-meeting-1.8.1.min.js"></script>
```
This may varry if you are in China or India
more details in this [page](https://marketplace.zoom.us/docs/sdk/native-sdks/web/build). 

* Using a package manager
````
npm i @zoomus/websdk
```

####2. Joining a meeting
* Config
You will need this config returned from the serve:
```js
    var meetingConfig = {
      apiKey: tmpArgs.apiKey, // required
      meetingNumber: tmpArgs.meetingNumber, // required
      userName: tmpArgs.userName, // required
      passWord: tmpArgs.password, // required
      leaveUrl: "/index.html", //if a user quite or couldn't join the meeting he will be redirected to this Url
      userEmail: tmpArgs.email, // optional
      lang: tmpArgs.lang, // optional
      signature: tmpArgs.signature, // required
    };
```

* Prepare dependencies:
```js
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
```

* Join:
```js
    ZoomMtg.init({
        leaveUrl: meetingConfig.leaveUrl,
        webEndpoint: meetingConfig.webEndpoint,
        isSupportAV: true, //optional,
        isSupportChat: false, //optional,
        isSupportQA: false, //optional,
        isSupportCC: false, //optional,
        screenShare: true, //optional,
        sharingMode: 'both',
        disableInvite: true, //optional
        disableRecord: false, //optional
        audioPanelAlwaysOpen: false,
        success: function () {
          console.log(meetingConfig);
          $.i18n.reload(meetingConfig.lang);
          ZoomMtg.join({
            meetingNumber: meetingConfig.meetingNumber,
            userName: meetingConfig.userName,
            signature: signature,
            apiKey: meetingConfig.apiKey,
            userEmail: meetingConfig.userEmail,
            passWord: meetingConfig.passWord,
            success: function (res) {
              console.log("join meeting success");
              console.log("get attendeelist");
              ZoomMtg.getAttendeeslist({});
              ZoomMtg.getCurrentUser({
                success: function (res) {
                  console.log("success getCurrentUser", res.result.currentUser);
                },
              });
            },
            error: function (res) {
              console.error(res);
            },
          });
        },
        error: function (res) {
          console.error(res);
        },
      });
```

####3. Add events to log user activities
```js
ZoomMtg.inMeetingServiceListener('onUserJoin', function (data) {
        console.log('inMeetingServiceListener onUserJoin', "*", data);
});

ZoomMtg.inMeetingServiceListener('onUserLeave', function (data) {
        console.log('inMeetingServiceListener onUserLeave', "*", data);
});

ZoomMtg.inMeetingServiceListener('onUserIsInWaitingRoom', function (data) {
        console.log('inMeetingServiceListener onUserIsInWaitingRoom', data);
});

ZoomMtg.inMeetingServiceListener('onMeetingStatus', function (data) {
        console.log('inMeetingServiceListener onMeetingStatus', data);
});
```

###IV. Conclusion
Now you should have a working zoom integration. I hope this article was useful.
Thanks for reading, and I will be in the comments for your suggestions, remarks, advice...