@airtame/dove
TypeScript icon, indicating that this package has built-in type declarations

3.35.4 • Public • Published

This package contains message definitions for the different entitites in the ARC project.

It serves as both a contract, definition and implementation for the communication between arc-control and the Desktop Application, as well as between arc-screen and the DisplayManager.

Usage

In your project

To start, make sure that your project is setup to use Airtame's private NPM repository:

echo @airtame:registry=https://gitlab.com/api/v4/packages/npm/ >> .npmrc

And the you can simply install the library:

npm install --save @airtame/dove

Note:

You will need to set up your NPM config to log onto our Gitlab instance:

npm config set '//gitlab.com/api/v4/packages/npm/:_authToken' "<your token>"

But you should replace your token with a token that you generate here: https://gitlab.com/profile/personal_access_tokens.

Your token should have read_api and read_repository rights to work.

API

Four core Messenger classes are available, which serves, in pairs, to provide means of message passing between the different entitites in ARC. The messages between messengers are passed by using [postMessage()] on a Window-like object.

Predefined typed messages can be passed between the classes as shown below. The available message types are specified in Messages.ts.

DisplayManagerMessenger ↔︎ ScreenMessenger

DesktopAppMessenger ↔︎ ControlMessenger

ZoomScreenMessenger ↔︎ ZoomIframeMessenger

All Messenger classes provides the same core api.

addRequestListener(listener: (msg: ReceiveRequestType, next: NextFunction) => Promise<SendRequestType>): void

Adds a handler for the request.

The handler must return a Promise which is resolved when the request is successfully handled, rejected if an error occurs or call next() if the message received is not handled by this current handler.

removeRequestListener(listener: (msg: ReceiveRequestType, next: NextFunction) => Promise<SendRequestType>): void

Removes the listener from the list.

addNotificationListener(listener: (msg: ReceiveNotificationType) => void): void

Adds a listener for notifications. This will be called for each notifications.

removeNotificationListener(listener: (msg: ReceiveNotificationType) => void): void

Removes the listener.

sendRequest( msg: SendMessageType, timeout = DEFAULT_REQUEST_TIMEOUT): Promise<SendMessageType>

Sends a request to a receiving messenger, and returns a promise which either resolves or rejects, depending on whether the receiver handled the message or not.

If the call times out, it means that the request was either not handled, or took too long to be processed.

sendNotification(msg: SendNotificationType): void

Dispatches a message without waiting for a confirmation of receival.

Examples

Create 2 messengers and send/handle/reply a message

This example will be demonstrated with the (display-manager-utils)[https://gitlab.com/airtame/device/display-manager-utils] library but it could be replaced by anything else.

/***********************/
/* DESKTOP APPLICATION */
/***********************/
import {
  DesktopAppMessage,
  DesktopAppMessenger,
  ControlMessage,
  ControlMessageName,
  DesktopAppMessageName,
} from '@airtame/dove';
import {
  AutoReadyMessagingProxy,
  PageEndpoint,
  ProxyEnvelope,
  Sender,
} from '@airtame/display-manager-utils';

const dmEndpoint = new PageEndpoint<
  ProxyEnvelope<DesktopAppMessage, ControlMessage>
>({
  sendWindow: window,
  receiveWindow: window,
  name: Sender.ContentScript,
  peerName: Sender.Page,
});
const proxy = new AutoReadyMessagingProxy<DesktopAppMessage, ControlMessage>({
  endpoint: dmEndpoint,
});
const desktopMessenger = new DesktopAppMessenger(proxy);

try {
  const message = await desktopMessenger.sendRequest({
    name: DesktopAppMessageName.CALL_INFO_REQUEST,
  });

  if (message.name === ControlMessageName.CALL_INFO_RESPONSE) {
    if (message.isInCall) {
      await this.leaveCall();
    } else {
      await this.endCall();
    }
  }
} catch (err) {
  logger.error('Failed to get call info', { err });
} finally {
  this.stop();
}

/***********/
/* CONTROL */
/***********/
import {
  ControlMessenger,
  ControlMessage,
  DesktopAppMessage,
  NextFunction,
} from '@airtame/dove';
import {
  AutoReadyMessagingProxy,
  PageEndpoint,
  ProxyEnvelope,
  Sender,
} from '@airtame/display-manager-utils';

const endpoint = new PageEndpoint<
  ProxyEnvelope<ControlMessage, DesktopAppMessage>
>({
  sendWindow: window.parent,
  receiveWindow: window,
  name: Sender.Page,
  peerName: Sender.ContentScript,
});
const proxy = new AutoReadyMessagingProxy<ControlMessage, DesktopAppMessage>({
  endpoint,
});
controlMessenger = new ControlMessenger(proxy);

const handleRequest = (
  message: DesktopAppMessage,
  next: NextFunction
): Promise<ControlMessage> => {
  if (message.name === DesktopAppMessageName.CALL_INFO_REQUEST_v1) {
    return Promise.resolve({
      name: ControlMessageName.CALL_INFO_RESPONSE,
      isInCall: meetingJoined !== -1 && meetingLeft === -1,
    });
  }

  next();
};

controlMessenger.addRequestListener(handleRequest);

Examples in tests

A few tests are included which showcases usage of the various Messenger classes:

Versioning of messages

The versioning of messages is relatively simple: we will have the versioning made via new messages (see adr).

That means that for if a message need to be upgraded, we simply create a new message.

Here is an example:

// You are using the message to send the SDP offer:
type ControlMessages = {
  name: ControlMessageName.SDP_OFFER_v1;
  description: RTCSessionDescriptionJSON;
};

// All is well but now there is a new field that is useful in the new version that
// would allow you to tell the peer if this is a renewal or not. For that you need
// to create a new version of the message:
type ControlMessages =
  | {
      name: ControlMessageName.SDP_OFFER_v1;
      description: RTCSessionDescriptionJSON;
    }
  | {
      name: ControlMessageName.SDP_OFFER_v2;
      description: RTCSessionDescriptionJSON;
      renewal: boolean;
    };

// In the handler, in an effort to keep backward compatibility you could have:
desktopAppMessenger.addRequestListener(
  (message: ControlMessage, next: NextFunction): Promise<DesktopAppMessage> => {
    switch (message.name) {
      case ControlMessageName.SDP_OFFER_v2: {
        if (!this.isActive) {
          return Promise.reject();
        }
        const { description, renewal } = message;
        return this.handleSDPOffer(description, renewal).then(
          (descriptionResp) => {
            const replyMessage: DesktopAppMessage = {
              name: DesktopAppMessageName.SDP_ANSWER_v2,
              description: descriptionResp,
              renewal,
            };
            return Promise.resolve(replyMessage);
          }
        );
      }

      case ControlMessageName.SDP_OFFER_v1: {
        if (!this.isActive) {
          return Promise.reject();
        }
        const { description } = message;
        return this.handleSDPOffer(
          description,
          true /* Imagine this would be our default in case*/
        ).then((descriptionResp) => {
          const replyMessage: DesktopAppMessage = {
            name: DesktopAppMessageName.SDP_ANSWER_v1,
            description: descriptionResp,
          };
          return Promise.resolve(replyMessage);
        });
      }

      default:
        next();
    }
  }
);

This would be the same for notifications and replies.

Build

To build the library:

# Install the dependencies
npm install

# Run the build script
npm run build

After that you have the artifacts in the dist/ folder.

Development

Use yalc package to publish and install local version of @airtame/dove to another local project.

To install yalc globally, run:

npm i -g yalc

To publish, first build the changes, then run:

npx yalc publish

Then inside another project run:

npx yalc install @airtame/dove

Release

To create a release, you need to update related files, merge changes into master branch and create a tag from master named as version number prefixed with v as follows: v1.2.3.

To update related files run the following command:

npm run release:prepare [major | minor | patch]

Then push to remote, review and merge the changes.

After that switch back to master, pull the latest changes, and create a tag:

git checkout master
git pull --rebase
git tag v1.2.3
git push origin v1.2.3

Architecture Decision Records

  1. Record architecture decisions
  2. Message versioning

Readme

Keywords

none

Package Sidebar

Install

npm i @airtame/dove

Weekly Downloads

12

Version

3.35.4

License

none

Unpacked Size

132 kB

Total Files

82

Last publish

Collaborators

  • rene-airtame
  • airtame-ops