@corto-ai/integrations-messenger
@corto-ai/integrations-messenger provides messaging utilities for communication between host applications and embedded integrations.
This package is currently in early development and is being published to npm to support internal/dev workflows.
Intended use
This package is intended for teams adding applications into the CORTO platform (for example, iframe-based integrations).
Status
- Early development
- API may change between dev releases
- Documentation will be expanded over time
Installation
npm install @corto-ai/integrations-messenger
Capabilities
- Handshake & identity -- every window (host or integration) registers itself with a unique
id. Nested windows automatically perform a handshake with their parent so both sides know who they're talking to before any action is sent. - Origin-checked messaging -- once the handshake completes, messages from a given source are only accepted if they come from the origin observed during that handshake, protecting against messages from unexpected origins.
- Request/response actions -- typed helpers for the actions below, each with a
send{{Action}}(caller side) andhandle{{Action}}(implementer side) pair that resolves once a matching response is received. - Streaming/pubsub notifications -- a channel-based notification action that can be sent or handled repeatedly, useful for pushing multiple updates over time rather than a single request/response.
- Automatic forwarding across nested iframes -- if a window has no handler registered for an action, the message is forwarded up/down the iframe tree automatically, so multi-level nesting doesn't require every level to implement every action.
- Optional debug logging -- pass
withLogging: truetoinitto log handshake and message activity to the console while integrating.
Usage
Initialization
Every window that uses this package -- host and embedded integrations alike -- must call init first:
import messenger from '@corto-ai/integrations-messenger';
await messenger.init({ id: 'my-integration-app' });
idis required and must be unique for the window callinginit.parentRefis optional and defaults towindow.parent. Only override it if the page isn't communicating with its direct parent.withLoggingis optional and enables console logging for debugging the handshake/messaging flow.- If the window is nested (its
parentRefisn'twindowitself),initalso kicks off the handshake with the parent.initresolves once setup is complete; the handshake with the parent finishes asynchronously.
Host applications that embed an integration in an iframe must also call registerChild with a reference to the iframe's contentWindow and the same id the child passed to init:
messenger.registerChild(iframe.contentWindow, 'my-integration-app');
registerChild listens for the handshake from that child and resolves once it completes. Call it before the iframe's own init call runs, or the handshake may be missed.
Actions
messenger.actions groups all supported actions by the entity/flow they affect:
| Group | Action | Description |
|---|---|---|
matter | sendOpenMatter / handleOpenMatter | Open a matter by matterId. |
matter | sendOpenChatSession / handleOpenChatSession | Open a specific chat session (sessionId) on a matter. |
matter | sendOpenAgentSession / handleOpenAgentSession | Open a specific agent session (sessionId) on a matter. |
matter | sendOpenDetails / handleOpenDetails | Open the details view for a matter. |
matter | sendOpenEdit / handleOpenEdit | Open the edit view for a matter. |
matter | sendOpenDocumentStatus / handleOpenDocumentStatus | Open the document status view for a matter. |
matter | sendOpenPrompt / handleOpenPrompt | Open a specific prompt (promptId) on a matter. |
matter | sendOpenPromptLibrary / handleOpenPromptLibrary | Open the prompt library for a matter. |
matter | sendPreviewDocument / handlePreviewDocument | Preview a document by documentId or presignedUrl. documentName is required. |
matter | sendSetFullscreen / handleSetFullscreen | Show/hide the matter nav and list. Clear automatically on navigation in the host application |
contact | sendOpenContact / handleOpenContact | Open a contact by contactId. |
contact | sendOpenEditContact / handleOpenEditContact | Open the edit view for a contact. |
settings | sendOpenSettings / handleOpenSettings | Open the host application's settings. |
branding | sendGetBranding / handleGetBranding | Request the active brand name and logo URL, or pass an optional brandName to request a specific brand instead of the active one. |
auth | sendExchangeTokenRequest / handleExchangeTokenRequest | Exchange a clientId for an auth token. |
auth | sendUserDataRequest / handleUserDataRequest | Request the current user's data from the host (currently returns userId). |
pubsub | sendNotificationRequest / handleNotificationRequest | Subscribe to (or publish) notifications on a channel, supporting multiple responses over time. |
pubsub | sendStatusRequest / handleStatusRequest | Subscribe to the host SignalR hub connection state (Connected, Reconnecting, Disconnected, and the other hub states). |
window | sendUpdateUrl / handleUpdateUrl | Update the url in the host application without navigating. Used to support routing/refreshing within an integration |
window | sendOpenModal / handleOpenModal | Opens a modal overlay that can be used to host integration iframes. Appends the provided path to the root target origin. |
window | sendCloseModal / handleCloseModal | Closes a modal overlay that was previously opened with the sendOpenModal command |
window | sendOpenConfirmation / handleOpenConfirmation | Opens a confirmation dialog. Supports custom title, message, confirmLabel, and cancelLabel, and returns whether the user confirmed. |
window | sendOpenToast / handleOpenToast | Open a toast message as either a success, error, info or warning type. Supports custom title, message, and override to detailed configuration. On click of the action button, a response will be returned. |
window | sendSetUnsavedChanges / handleSetUnsavedChanges | Sets a flag on the parent application. When true, if the user tries to navigate away or close the tab, we will pop up a confirmation modal which interupts the navigation and allows the user to cancel |
integrations | sendGetList / handleGetList | Returns a list of integration ids, label, and appNames that are integrated with the host app. Intended to be used in conjunction with integrations.sendOpen in order to trigger one integration from another |
integrations | sendOpen / handleOpen | Opens the provided integration. Prioritizes staying in the same context (ex/ global -> global, matter -> matter), and will fail when navigating from global -> matter if no matterId is provided. Any parameters passed via extraParams will be appended to the target integration's url. |
All matter, contact, and settings actions accept an optional newTab flag to indicate the host should open the target in a new tab, and matter actions also accept an optional matterId beyond what's called out above.
For each action, the send... function is intended for the embedded integration to call, to ask the host application to perform some action or return some data. The matching handle... function is intended to be implemented by the host application: it registers a callback that receives the request and returns the response to send back.
// in the host application
await messenger.init({ id: 'host' });
messenger.registerChild(iframe.contentWindow, 'child');
messenger.actions.matter.handleOpenChatSession(async ({ matterId, sessionId }) => {
try {
await someService.openSession(matterId, sessionId);
return { success: true };
} catch (err) {
return { success: false, error: err.message };
}
});
// in the embedded integration
await messenger.init({ id: 'child' });
const response = await messenger.actions.matter.sendOpenChatSession({
matterId: '123',
sessionId: 'abc'
});
if (response.success) {
// happy path
} else {
// sad path
}
Streaming actions (pubsub)
pubsub notifications differ from the other actions: instead of resolving once with a single response, sendNotificationRequest and handleNotificationRequest can fire their callback multiple times for the same channel, and both return a handle with a remove() function to stop listening.
// listen for notifications on a channel
const handle = messenger.actions.pubsub.sendNotificationRequest({ channel: 'documents' }, (notification) => {
console.log('received', notification.data);
});
// later, stop listening
handle.remove();
const statusHandle = messenger.actions.pubsub.sendStatusRequest(({ status }) => {
console.log(status);
});
statusHandle.remove();
Modal communication
As mentioned above, the window.sendOpenModal command allows integrations to trigger a modal overlay and display a custom modal from their application. Its worth noting though that this new modal will not be able to communicate with the parent application. Modals should instead communicate with their sibling integration (ie the integration which opened the modal) via the BroadcastChannelAPI. This way they can notify the sibling of whatever user interaction might be required, including closing the modal.
Forwarding
In order to support multiple levels of iframe nesting, the package inherently has a "forwarding" mechanic: for any action, if no handler is defined in a given window, the message continues up or down the iframe tree until a handler picks it up. This is enabled by default and requires no opt-in.
Adding a handler in a child application can shadow the same action's handling in a parent application, since forwarding only happens when a handler isn't defined. If you're not sure whether you should implement a handler for a given action, don't -- let it forward to the host.
Demo Repository
We have set up a demo repository with react/angular applications as an example of how to use this package.
Development
This library is maintained by CORTO and published as part of the regular package release workflow.
For more information, visit https://www.docorto.ai.
License
License details will be documented as this package moves toward stable releases.