import * as signalR from '@microsoft/signalr'; import { Subject } from 'rxjs'; import { IClientApp, TClientAppStatus } from '../eva-sdk-redux'; import { IHubEventDisplayOrderUpdated, IHubEventFeedbackReceived, IHubCustomerFacingDisplayJoinRequest, IHubPushDisplayOrderToDeviceID } from './cfd-hub'; import { IHubEventClientAppRegistered, IHubEventClientAppMessage, IHubClientAppJoinRequest, IHubEventJoinedChannelMessage, IHubEventLeftChannelMessage, IClientAppMessage } from './client-app-hub'; import { IHubEventSessionChanged, IHubEventBarcode, IHubEventSessionPushedToDeviceID, IHubDeviceJoinRequest, IHubPushSessionToDeviceID } from './device-hub'; import { IHubEventGlobalBlueMessage, IHubGlobalBlueJoinRequest } from './global-blue-hub'; import { IHubOrderJoinRequest, IHubEventOrderUpdated, IHubEventSubscribedOrderAdditionalInformation } from './order-hub'; import { IHubEventPaymentTransactionUpdated, IHubEventPaymentTransactionCreated, IHubPaymentJoinRequest } from './payment-hub'; import { IHubEventUserTask, IHubUserTaskJoinRequest } from './user-task-hub'; import { IHubEventMagicLinkConfirmed, IHubMagicLinkJoinRequest } from './magic-link-hub'; import { IHubEventElevationBarcodeConfirmed, IHubElevationBarcodeJoinRequest } from './elevation-barcode-link-hub'; /** * Hub definitions based on GetApplicationConfiguration response * * @export * @interface IEvaSignalRHub */ export interface IEvaSignalRHub { Name: string; Path: string; } /** * A single hub connection * * @export * @interface IHubConnection */ export interface IHubConnection { hub: IEvaSignalRHub; connection: signalR.HubConnection; uri: string; subjectId: string | number; token?: string; reconnect_count: number; subscribe?: () => Promise; channels?: string[]; } /** * The hub event as emitted on the event$ observable * * @export * @interface IHubEvent */ export interface IHubEvent { hub: string; subjectId: string | number; method: 'SessionChanged' | 'OrderUpdated' | 'SubscribedOrderAdditionalInformation' | 'BarcodeScanned' | 'SessionPushedToDeviceID' | 'UserTaskCreated' | 'UserTaskUpdated' | 'DisplayOrderUpdated' | 'FeedbackReceived' | 'PaymentTransactionUpdated' | 'PaymentTransactionCreated' | 'Registered' | 'Unregistered' | 'ReceiveMessage' | 'JoinedChannel' | 'LeftChannel' | 'GlobalBlueEvent:showForm' | 'GlobalBlueEvent:hideForm' | 'GlobalBlueEvent:issueSuccessful' | 'GlobalBlueEvent:issueFailed' | 'MagicLink:Confirmed' | 'ElevationBarcode:Confirmed' | 'JoinHub' | 'LeaveHub'; data: IHubEventSessionChanged | IHubEventOrderUpdated | IHubEventSubscribedOrderAdditionalInformation | IHubEventBarcode | IHubEventSessionPushedToDeviceID | IHubEventUserTask | IHubEventDisplayOrderUpdated | IHubEventFeedbackReceived | IHubEventPaymentTransactionUpdated | IHubEventPaymentTransactionCreated | IHubEventClientAppRegistered | IHubEventClientAppMessage | IHubEventGlobalBlueMessage | IHubEventMagicLinkConfirmed | IHubEventElevationBarcodeConfirmed | IHubEventJoin | IHubEventLeave; } /** * Join hub event * * @export * @interface IHubEventJoin */ export interface IHubEventJoin { DeviceID?: string; OrderID?: number; SessionID?: string; SubscriberName: string; } /** * Leave hub event * * @export * @interface IHubEventLeave */ export interface IHubEventLeave { DeviceID?: string; OrderID?: number; SessionID?: string; SubscriberName: string; } /** * A signalr hub activity message * * @export * @interface IConnectionActivity */ export interface IConnectionActivity { type: 'close' | 'reconnecting' | 'reconnected' | 'send' | 'receive'; hubName: string; ts: Date; payload?: string; } /** * The information we collect from every peer client that sends us their status (ping) * * @export * @interface IPeerClientStatus */ export interface IPeerClientStatus { clientApp: IClientApp; lastSeen: Date; channels: string[]; status: TClientAppStatus; } /** * The information about a single channels clients * * @export * @interface IChannelStatus */ export interface IChannelStatus { channelName: string; clients: { app: IClientApp; lastSeen: Date; status: 'active' | 'stale'; }[]; } /** * Connection options for joining a hub * * @export * @interface IHubConnectionOptions */ export interface IHubConnectionOptions { /** * A specific application token to use for this signalr connection * By default the current application token from the settings will be used. * The setting will be updated whenever a new EVA-App-Token is returned. * * @type {string} */ applicationToken?: string; /** * Can be used to set the `EVA-Requested-OrganizationUnitID` header. * This is used to select a specific organization unit when making calls without * a specific user token by ID. * * @type {number} */ requestedOrganizationUnitID?: number; /** * Can be used to set the `EVA-Requested-OrganizationUnit-Query` header. * This is used to select a specific organization unit when making calls without * a specific user token using an exact match query on name and backend id. * * * @type {{ Name: string; BackendID: string; }} */ requestedOrganizationUnitQuery?: { Name: string; BackendID: string; }; /** * The Global Blue group id as returned from GlobalBlueStartTaxFree. * Required for connecting to the Global Blue hub * * @type {string} */ globalBlueGroupID?: string; } /** * Shared interface for all join hub requests * * @export * @interface IHubJoinRequest */ export interface IHubJoinRequestBase { hubName?: string; options?: IHubConnectionOptions; subscriberName?: string; token: string; } /** * The realtime communicator handles the SignalR hubs and their connections * * @export * @class RealtimeCommunicator */ export declare class RealtimeCommunicator { /** * The current list of EVA SignalR hubs * * @private * @type {EVA.Core.GetApplicationConfigurationResponse} * @memberof RealtimeCommunicator */ hubs: IEvaSignalRHub[]; /** * All the connection grouped by hub * * @type {{[hubName: string]: {[id: string]: IHubConnection } }} * @memberof RealtimeCommunicator */ connection: { [hubName: string]: IHubConnection[]; }; /** * The combined realtime event feed from all hubs as an observable * * @type {Subject} * @memberof RealtimeCommunicator */ event$: Subject; /** * Debug-able activity stream for SignalR * * @type {Subject} */ activity$: Subject; /** * The subscription listener for application configuration changes * * @private * @type {(Subscription|undefined)} * @memberof RealtimeCommunicator */ private configurationSubscription; /** * Creates an instance of RealtimeCommunicator * The realtime communicator uses SignalR to connect to a number of hubs supplied * by EVA. * The getApplicationConfiguration response will contain the required urls * * @memberof RealtimeCommunicator */ constructor(); /** * Start listening to the getApplicationConfig * Will resolve its promise when first application config is received * * @memberof RealtimeCommunicator */ start(): Promise; /** * Stop listening for application configuration changes and disconnect all hubs * * @memberof RealtimeCommunicator */ stop(): Promise; /** * Helper method to build signalr connection headers * * @param {IHubJoinRequestBase} joinRequest */ protected buildHeaders(joinRequest: IHubJoinRequestBase): signalR.MessageHeaders; /** * Helper method to build signalr connection query parameters * * @protected * @param {IHubJoinRequestBase} joinRequest * @returns */ protected buildQueryParams(joinRequest: IHubJoinRequestBase): string[]; /** * Disconnect all existing hub connections * * @returns * @memberof RealtimeCommunicator */ disconnectAll(): Promise; /** * Start listening to device hub events for a specific device * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubDeviceJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinDeviceHub(subscription: IHubDeviceJoinRequest): Promise; /** * Stop listening to device hub events for a specific device * * @param {string} deviceId The device to stop listening to * @param {string} [hub='DeviceHub'] The hub name * @memberof RealtimeCommunicator */ leaveDeviceHub(subscription: IHubDeviceJoinRequest): Promise; /** * Send a scanned barcode to a device hub * * @param {IEventBarcodeScanned} scanEvent * @param {string} [hub='DeviceHub'] * @returns * @memberof RealtimeCommunicator */ sendScannedBarcode(scanEvent: IHubEventBarcode, hubName?: string): Promise; /** * Transfers a session to another device * * @param {IHubPushSessionToDeviceID} transferSessionEvent * @param {string} [hubName='DeviceHub'] * @returns * @memberof RealtimeCommunicator */ sendSessionToDevice(transferSessionEvent: IHubPushSessionToDeviceID, hubName?: string): Promise; /** * Start listening to order hub events for a specific order * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubOrderJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinOrderHub(subscription: IHubOrderJoinRequest): Promise; /** * Stop listening to order hub events for a specific order * * @param {IHubOrderJoinRequest} subscription The subscription details * @memberof RealtimeCommunicator */ leaveOrderHub(subscription: IHubOrderJoinRequest): Promise; /** * Start listening to user task hub events * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubUserTaskJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinUserTaskHub(subscription: IHubUserTaskJoinRequest): Promise; /** * Stop listening to user task hub events for a specific device * * @param {string} [hub='UserTaskHub'] The hub name * @memberof RealtimeCommunicator */ leaveUserTaskHub(subscription: IHubUserTaskJoinRequest): Promise; /** * Start listening to CFD hub events * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubCustomerFacingDisplayJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinCustomerFacingDisplayHub(subscription: IHubCustomerFacingDisplayJoinRequest): Promise; /** * Stop listening to CFD hub events for a specific device * * @param {string} [hub='CustomerFacingDisplayHub'] The hub name * @memberof RealtimeCommunicator */ leaveCustomerFacingDisplayHub(subscription: IHubCustomerFacingDisplayJoinRequest): Promise; /** * Push an update for the displayed order to the customer facing display * * @param {IHubPushDisplayOrderToDeviceID} updateDisplayOrderEvent * @param {string} [hubName='CustomerFacingDisplayHub'] * @returns * @memberof RealtimeCommunicator */ pushDisplayOrderToDevice(updateDisplayOrderEvent: IHubPushDisplayOrderToDeviceID, hubName?: string): Promise; /** * Start listening to updates for a payment transaction * * @param {IHubPaymentJoinRequest} subscription * @returns */ joinPaymentHub(subscription: IHubPaymentJoinRequest): Promise; /** * Stop listening to payment transaction update events * * @param {IHubPaymentJoinRequest} subscription * @returns */ leavePaymentHub(subscription: IHubPaymentJoinRequest): Promise; /** * The details of the registered client application * * @private * @type {(IClientApp | undefined)} */ private clientApp; /** * This is the special purpose channel used for client status notifications * * @private */ private clientAppStatusChannel; /** * The list of all channels we are connected to and a unix timestamp * of when we joined it * * @private * @type {{[channelName: string]: IChannelStatus}} */ private channels; /** * The list of seen peers from ping responses * * @private * @type {{[UUID: string]: IPeerClientStatus }} */ private peers; /** * The current peers exposed as an observable stream * * @private * @type {BehaviorSubject} */ private _peers$; peers$: import("rxjs").Observable; /** * Used to mark peers that haven't sent a message recently * * @private */ private idleThreshold; /** * Used to mark peers that have missed multiple heartbeats as disconnected * * @private */ private disconnectedThreshold; /** * Used to remove peers that haven't sent a heartbeat in a while * * @private */ private staleThreshold; /** * The interval at which the client sends an update about its peers * * @private */ private heartbeatInterval; /** * The interval at which the client sends a status update * * @private */ private clientStatusInterval; /** * The date the last client status update was sent at * * @private */ private lastSentClientStatusAt; /** * The interval timer used to send the heartbeat message * * @private * @type {(NodeJS.Timer | undefined)} */ private heartbeatTimer; /** * Client status updates need to be debounced to prevent update * message overload on reconnects * * @private * @type {(NodeJS.Timer | undefined)} */ private debouncedStatusTimer; /** * Sends the status of this client to the others * Will be sent on request when a Ping is received and at an interval * * @private */ private sendClientStatus; /** * Removes all clients from the peers list that haven't been seen in a while */ purgeStalePeers(): void; /** * Retrieves the names of the currently connected channels * * @return {*} {string[]} */ getChannels(): string[]; /** * Retrieves the list of all other clients currently connected to the * client app hub. * * @return {*} {IPeerClientStatus[]} */ getPeers(): IPeerClientStatus[]; /** * Retrieves the connected clients for a specific channel * * @param {string} channelName * @return {*} IChannelStatus */ getChannelStatus(channelName: string): IChannelStatus | undefined; /** * Start listening to updates from the client app hub * * @param {IHubClientAppJoinRequest} subscription * @returns */ joinClientAppHub(subscription: IHubClientAppJoinRequest): Promise; /** * Stop listening to client app update events * * @param {IHubPaymentJoinRequest} subscription * @returns */ leaveClientAppHub(subscription: IHubClientAppJoinRequest): Promise; /** * Join a channel on a client app hub connection * * @param {IHubEventJoinedChannelMessage} joinRequest * @param {string} [hubName='ClientAppHub'] * @returns */ joinChannel(joinRequest: IHubEventJoinedChannelMessage, hubName?: string): Promise; /** * Leave a channel on a client app hub connection * * @param {IHubEventJoinedChannelMessage} joinRequest * @param {string} [hubName='ClientAppHub'] * @returns */ leaveChannel(leaveRequest: IHubEventLeftChannelMessage, hubName?: string): Promise; /** * Push a message to all apps registered on the same hub * * @param {IClientAppMessage} appMessage * @param {string} [hubName='CustomerFacingDisplayHub'] * @returns * @memberof RealtimeCommunicator */ sendAppMessage(appMessage: IClientAppMessage, hubName?: string): Promise; /** * Manually triggers a ping to see which other clients are currently connected to the hub * * @returns */ ping(uuid: string): Promise; /** * The timer for reconnection attempts * * @private * @type {(NodeJS.Timer|undefined)} */ private reconnectTimer; /** * Used to track any in flight reconnect attempts so we don't end up creating multiple * * @private * @type {Promise} */ private pendingReconnectAttempt; /** * Helper method to try to reconnect to the SignalR server * We will use the reconnect count and multiply it with a small interval * which will effectively increment it for each attempt * Max delay is set to 10 minutes * * @private * @param {IHubConnection} hubDetails */ private reconnectAttempt; /** * Force an immediate reconnection attempt. Can be useful for clients coming * out of a sleep or apps being resumed. ClientApp hub is usually the one * you want to immediately reconnect to */ reconnect(hubName?: string): Promise; /** * Start listening to Global Blue hub events for a group identifier * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubGlobalBlueJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinGlobalBlueHub(subscription: IHubGlobalBlueJoinRequest): Promise; /** * Stop listening to Global Blue hub events * * @param {IHubGlobalBlueJoinRequest} subscription The original join request which we need for the group id * @returns */ leaveGlobalBlueHub(subscription: IHubGlobalBlueJoinRequest): Promise; /** * Start listening to magic link hub events for a specific RequestToken * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubMagicLinkJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinMagicLinkHub(subscription: IHubMagicLinkJoinRequest): Promise; /** * Stop listening to magic link hub events * * @param {IHubMagicLinkJoinRequest} subscription The subscription details * @memberof RealtimeCommunicator */ leaveMagicLinkHub(subscription: IHubMagicLinkJoinRequest): Promise; /** * Start listening to elevation barcode hub events for a specific RequestToken * You can either supply a callback(s) here or listen to the event$ observable * * @param {IHubElevationBarcodeJoinRequest} subscription The subscription details * @returns * @memberof RealtimeCommunicator */ joinElevationBarcodeHub(subscription: IHubElevationBarcodeJoinRequest): Promise; /** * Stop listening to elevation barcode hub events * * @param {IHubElevationBarcodeJoinRequest} subscription The subscription details * @memberof RealtimeCommunicator */ leaveElevationBarcodeHub(subscription: IHubElevationBarcodeJoinRequest): Promise; } export declare const communicator: RealtimeCommunicator;