@azure/communication-signaling
Version:
Azure Communication Signaling Client
297 lines (281 loc) • 9.63 kB
text/typescript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import {
createTrouterService,
ITrouterServiceBase,
ITrouterServiceConfig,
TrouterState,
StateChangedListener,
UserActivityState,
} from "@skype/tstrouter";
import { toMessageHandler, toLogProvider, toTelemetrySender } from "./TrouterUtils";
import { defaultTelemetrySettings, createSettings } from "./TrouterSettings";
import {
ChatEventId,
BaseChatEvent,
BaseChatMessageEvent,
ChatMessageReceivedEvent,
ChatMessageEditedEvent,
ChatMessageDeletedEvent,
ReadReceiptReceivedEvent,
TypingIndicatorReceivedEvent,
BaseChatThreadEvent,
ChatParticipant,
ChatAttachment,
ChatAttachmentType,
ChatThreadProperties,
ChatThreadCreatedEvent,
ChatThreadDeletedEvent,
ChatThreadPropertiesUpdatedEvent,
ParticipantsAddedEvent,
ParticipantsRemovedEvent,
ChatRetentionPolicy,
NoneRetentionPolicy,
ThreadCreationDateRetentionPolicy,
} from "./events/chat";
import {
CommunicationIdentifier,
CommunicationUserIdentifier,
PhoneNumberIdentifier,
MicrosoftTeamsUserIdentifier,
TeamsExtensionUserIdentifier,
UnknownIdentifier,
CommunicationIdentifierKind,
CommunicationUserKind,
PhoneNumberKind,
MicrosoftTeamsUserKind,
TeamsExtensionUserKind,
MicrosoftTeamsAppKind,
MicrosoftTeamsAppIdentifier,
UnknownIdentifierKind,
} from "./events/identifierModels";
import { MAX_NUMBER_OF_TOKEN_FETCH_RETRIES } from "./constants";
import { AzureLogger } from "@azure/logger";
import { AbortSignalLike } from "@azure/abort-controller";
import { AccessToken } from "@azure/core-auth";
import { AdditionalPolicyConfig } from "@azure/core-client";
import { UserAgentPolicyOptions } from "@azure/core-rest-pipeline";
export enum ConnectionState {
Unknown = 0,
Connected = 2,
Disconnected = 3,
Switching = 9,
}
export interface SignalingClientOptions {
registrationTimeInMs?: number;
resourceEndpoint?: string;
gatewayApiVersion?: string;
additionalPolicies?: AdditionalPolicyConfig[];
userAgentOptions?: UserAgentPolicyOptions;
}
export {
ChatEventId,
BaseChatEvent,
BaseChatMessageEvent,
ChatAttachment,
ChatAttachmentType,
ChatMessageReceivedEvent,
ChatMessageEditedEvent,
ChatMessageDeletedEvent,
ReadReceiptReceivedEvent,
TypingIndicatorReceivedEvent,
BaseChatThreadEvent,
ChatParticipant,
ChatThreadProperties,
ChatThreadCreatedEvent,
ChatThreadDeletedEvent,
ChatThreadPropertiesUpdatedEvent,
ParticipantsAddedEvent,
ParticipantsRemovedEvent,
CommunicationIdentifier,
CommunicationUserIdentifier,
PhoneNumberIdentifier,
MicrosoftTeamsUserIdentifier,
TeamsExtensionUserIdentifier,
UnknownIdentifier,
CommunicationIdentifierKind,
CommunicationUserKind,
PhoneNumberKind,
MicrosoftTeamsUserKind,
TeamsExtensionUserKind,
MicrosoftTeamsAppKind,
MicrosoftTeamsAppIdentifier,
UnknownIdentifierKind,
ChatRetentionPolicy,
NoneRetentionPolicy,
ThreadCreationDateRetentionPolicy,
};
/**
* Options for `CommunicationTokenCredential`'s `getToken` function.
*/
export interface CommunicationGetTokenOptions {
/**
* An implementation of `AbortSignalLike` to cancel the operation.
*/
abortSignal?: AbortSignalLike;
}
/**
* The Azure Communication Services token credential.
*/
export interface CommunicationTokenCredential {
/**
* Gets an `AccessToken` for the user. Throws if already disposed.
* @param options - Additional options.
*/
getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken>;
}
export interface SignalingClient {
/**
* Start the realtime connection.
*/
start(): void;
/**
* Stop the realtime connection and unsubscribe all event handlers.
*/
stop(isTokenExpired?: boolean): void;
/**
* Listen to connectionChanged events.
*/
on(event: "connectionChanged", listener: (state: ConnectionState) => void): void;
/**
* Listen to chatMessageReceived events.
*/
on(event: "chatMessageReceived", listener: (payload: ChatMessageReceivedEvent) => void): void;
/**
* Listen to typingIndicatorReceived events.
*/
on(
event: "typingIndicatorReceived",
listener: (payload: TypingIndicatorReceivedEvent) => void
): void;
/**
* Listen to readReceiptReceived events.
*/
on(event: "readReceiptReceived", listener: (payload: ReadReceiptReceivedEvent) => void): void;
/**
* Listen to chatMessageEdited events.
*/
on(event: "chatMessageEdited", listener: (payload: ChatMessageEditedEvent) => void): void;
/**
* Listen to chatMessageDeleted events.
*/
on(event: "chatMessageDeleted", listener: (payload: ChatMessageDeletedEvent) => void): void;
/**
* Listen to chatThreadCreated events.
*/
on(event: "chatThreadCreated", listener: (payload: ChatThreadCreatedEvent) => void): void;
/**
* Listen to chatThreadPropertiesUpdated events.
*/
on(
event: "chatThreadPropertiesUpdated",
listener: (payload: ChatThreadPropertiesUpdatedEvent) => void
): void;
/**
* Listen to chatThreadDeleted events.
*/
on(event: "chatThreadDeleted", listener: (payload: ChatThreadDeletedEvent) => void): void;
/**
* Listen to participantsAdded events.
*/
on(event: "participantsAdded", listener: (payload: ParticipantsAddedEvent) => void): void;
/**
* Listen to participantsRemoved events.
*/
on(event: "participantsRemoved", listener: (payload: ParticipantsRemovedEvent) => void): void;
}
export class CommunicationSignalingClient implements SignalingClient {
private readonly trouter: ITrouterServiceBase;
private config: ITrouterServiceConfig;
private stateChangedListener: StateChangedListener = null;
private tokenFetchRetries: number = 0;
private resourceEndpoint: string;
private gatewayApiVersion: string;
constructor(
private readonly credential: CommunicationTokenCredential,
private readonly logger: AzureLogger,
private readonly options?: SignalingClientOptions
) {
this.trouter = createTrouterService(toLogProvider(logger));
}
public async start(): Promise<void> {
this.resourceEndpoint = this.options?.resourceEndpoint;
if (this.resourceEndpoint === undefined) {
throw new Error("'endpoint' cannot be null");
}
this.gatewayApiVersion = this.options?.gatewayApiVersion || "2024-03-07";
if (this.config === undefined) {
this.config = {
trouterSettings: await createSettings(this.credential, this.options),
skypeTokenProvider: async (forceRefresh: boolean) => {
if (forceRefresh) {
this.tokenFetchRetries += 1;
if (this.tokenFetchRetries > MAX_NUMBER_OF_TOKEN_FETCH_RETRIES) {
await this.stop(true);
throw new Error(
`Access token is expired and failed to fetch a valid one after ${MAX_NUMBER_OF_TOKEN_FETCH_RETRIES} retries`
);
}
} else {
this.tokenFetchRetries = 0;
}
return Promise.resolve((await this.credential.getToken()).token);
},
telemetryConfig: {
eventLogger: toTelemetrySender(this.logger),
settings: defaultTelemetrySettings,
},
};
}
this.trouter.start(this.config);
this.trouter.setUserActivityState(UserActivityState.Active);
}
public async stop(isTokenExpired?: boolean): Promise<void> {
this.trouter.offStateChanged(this.stateChangedListener);
this.trouter.clearMessageHandlers();
this.trouter.stop(isTokenExpired ?? this.tokenFetchRetries > MAX_NUMBER_OF_TOKEN_FETCH_RETRIES);
}
public on(event: "connectionChanged", listener: (state: ConnectionState) => void): void;
public on(
event: "chatMessageReceived",
listener: (payload: ChatMessageReceivedEvent) => void
): void;
public on(
event: "typingIndicatorReceived",
listener: (payload: TypingIndicatorReceivedEvent) => void
): void;
public on(
event: "readReceiptReceived",
listener: (payload: ReadReceiptReceivedEvent) => void
): void;
public on(event: "chatMessageEdited", listener: (payload: ChatMessageEditedEvent) => void): void;
public on(
event: "chatMessageDeleted",
listener: (payload: ChatMessageDeletedEvent) => void
): void;
public on(event: "chatThreadCreated", listener: (payload: ChatThreadCreatedEvent) => void): void;
public on(
event: "chatThreadPropertiesUpdated",
listener: (payload: ChatThreadPropertiesUpdatedEvent) => void
): void;
public on(event: "chatThreadDeleted", listener: (payload: ChatThreadDeletedEvent) => void): void;
public on(event: "participantsAdded", listener: (payload: ParticipantsAddedEvent) => void): void;
public on(
event: "participantsRemoved",
listener: (payload: ParticipantsRemovedEvent) => void
): void;
public on(
event: ChatEventId | "connectionChanged",
listener: (genericPayload: any) => void
): void {
if (event === "connectionChanged") {
this.trouter.offStateChanged(this.stateChangedListener);
this.stateChangedListener = (state: TrouterState, _url: string) => listener(state);
this.trouter.onStateChanged(this.stateChangedListener);
return;
}
this.trouter.registerMessageHandler(
toMessageHandler(event, listener, this.resourceEndpoint, this.gatewayApiVersion)
);
}
}