DEV Community

Cover image for How I integrated zoom in a meteor app
medelmourabite
medelmourabite

Posted on

How I integrated zoom in a meteor app

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.

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, and go to Developer > Build app: Alt Text
  • create a JWT app, and complete your informations to get your api key and secret: Alt Text 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

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 });
};
Enter fullscreen mode Exit fullscreen mode

2. create a meeting:

   // 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 });
Enter fullscreen mode Exit fullscreen mode

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

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;
};
Enter fullscreen mode Exit fullscreen mode

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:

{
            signature, //using generateSignature
            meetingNumber: id,
            password, //returned from create meeting
            userName: `${ lastName } ${ firstName }`,
            role,
            apiKey: ZOOM_JWT_API_KEY,
}
Enter fullscreen mode Exit fullscreen mode

4. End the meeting:

This is similar to creating a meeting:

    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);
Enter fullscreen mode Exit fullscreen mode

III. On the client side:

1. add zoom web-sdk

  • Using CDN
    <!-- 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>
Enter fullscreen mode Exit fullscreen mode

This may varry if you are in China or India
more details in this page.

  • 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...

Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
tepxgit profile image
tepxlearning • Edited

Hi I am having error

CREATE JSMEDIASKD INSTANCE <<<<<<<<
modules.js?hash=948fe0106519dbd4deb8ed2c556365a10a1d632f:26131 Uncaught ReferenceError: JsMediaSDK_Instance is not defined







It was working in a non meteor environment.
Any Idea on this? Thanks

Collapse
 
medelmourabite profile image
medelmourabite

Hello, make sure you test it on chrome and in a normal tab, in my experience it doesn't load some libraries in incognito mode.
Also don't forget to call
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
And if nothing works, call this before preLoadWasm
ZoomMtg.setZoomJSLib("source.zoom.us/1.8.1/lib", "/av");
Here you can find a working example
github.com/medelmourabite/ttp-zoom...

Collapse
 
tepxgit profile image
tepxlearning

Hi Do you have a working sample on a meteor environment?