@airtame/dove
v3.35.4
Published
Message definitions and utilities for entities in the arc project.
Downloads
42
Readme
Dove
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