@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
224 lines • 11.4 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { CallSubscriber } from './CallSubscriber';
import { convertSdkCallToDeclarativeCall, convertSdkIncomingCallToDeclarativeIncomingCall } from './Converter';
import { IncomingCallSubscriber } from './IncomingCallSubscriber';
import { disposeAllViews, disposeAllViewsFromCall } from './StreamUtils';
import { _isTeamsIncomingCall } from './TypeGuards';
import { incomingCallDeclaratify } from './IncomingCallDeclarative';
import { teamsIncomingCallDeclaratify } from './TeamsIncomingCallDeclarative';
/**
* ProxyCallAgent proxies CallAgent and saves any returned state in the given context. It will subscribe to all state
* updates in the CallAgent and in the contained Calls and RemoteParticipants. When dispose is called it will
* unsubscribe from all state updates.
*/
export class ProxyCallAgentCommon {
constructor(context, internalContext) {
// Unsubscribe is called when CallAgent is disposed. This should mean no more updating of existing call but we don't
// remove any existing state.
this.unregisterSubscriber = () => {
for (const [_, callSubscriber] of this._callSubscribers.entries()) {
callSubscriber.unsubscribe();
}
this._callSubscribers.clear();
for (const [_, incomingCallSubscriber] of this._incomingCallSubscribers.entries()) {
incomingCallSubscriber.unsubscribe();
}
this._incomingCallSubscribers.clear();
this._incomingCalls.clear();
for (const [_, declarativeCall] of this._declarativeCalls.entries()) {
declarativeCall.unsubscribe();
}
this._declarativeCalls.clear();
};
this.callsUpdated = (event) => {
const addedStatefulCall = [];
for (const call of event.added) {
const statefulCall = this.addCall(call);
addedStatefulCall.push(statefulCall);
}
const removedStatefulCall = [];
for (const call of event.removed) {
disposeAllViewsFromCall(this._context, this._internalContext, call.id);
const callSubscriber = this._callSubscribers.get(call);
if (callSubscriber) {
callSubscriber.unsubscribe();
this._callSubscribers.delete(call);
}
this._context.setCallEnded(call.id, call.callEndReason);
const declarativeCall = this._declarativeCalls.get(call);
if (declarativeCall) {
declarativeCall.unsubscribe();
removedStatefulCall.push(declarativeCall);
this._declarativeCalls.delete(call);
}
else {
removedStatefulCall.push(this.callDeclaratify(call, this._context));
}
}
for (const externalCallsUpdatedListener of this._externalCallsUpdatedListeners) {
externalCallsUpdatedListener({
added: addedStatefulCall,
removed: removedStatefulCall
});
}
};
this.setIncomingCallEnded = (incomingCallId, callEndReason) => {
const incomingCallSubscriber = this._incomingCallSubscribers.get(incomingCallId);
if (incomingCallSubscriber) {
incomingCallSubscriber.unsubscribe();
this._incomingCallSubscribers.delete(incomingCallId);
}
this._incomingCalls.delete(incomingCallId);
this._context.setIncomingCallEnded(incomingCallId, callEndReason);
};
this.incomingCall = ({ incomingCall }) => {
// Make sure to not subscribe to the incoming call if we are already subscribed to it.
if (!this._incomingCallSubscribers.has(incomingCall.id)) {
this._incomingCallSubscribers.set(incomingCall.id, new IncomingCallSubscriber(incomingCall, this.setIncomingCallEnded));
}
if (_isTeamsIncomingCall(incomingCall)) {
this._incomingCalls.set(incomingCall.id, teamsIncomingCallDeclaratify(incomingCall, this._context));
}
else {
this._incomingCalls.set(incomingCall.id, incomingCallDeclaratify(incomingCall, this._context));
}
this._context.setIncomingCall(convertSdkIncomingCallToDeclarativeIncomingCall(incomingCall));
};
this.addCall = (call) => {
var _a;
(_a = this._callSubscribers.get(call)) === null || _a === void 0 ? void 0 : _a.unsubscribe();
// For API extentions we need to have the call in the state when we are subscribing as we may want to update the
// state during the subscription process in the subscriber so we add the call to state before subscribing.
this._context.setCall(convertSdkCallToDeclarativeCall(call));
this._callSubscribers.set(call, new CallSubscriber(call, this._context, this._internalContext));
return this.getOrCreateDeclarativeCall(call);
};
this.getOrCreateDeclarativeCall = (call) => {
const declarativeCall = this._declarativeCalls.get(call);
if (declarativeCall) {
return declarativeCall;
}
const newDeclarativeCall = this.callDeclaratify(call, this._context);
this._declarativeCalls.set(call, newDeclarativeCall);
return newDeclarativeCall;
};
this._context = context;
this._internalContext = internalContext;
this._callSubscribers = new Map();
this._incomingCallSubscribers = new Map();
this._incomingCalls = new Map();
this._declarativeCalls = new Map();
this._externalCallsUpdatedListeners = new Set();
}
/*
* We can't directly override get function because it is proxied,
* Add a getCommon function and call it in child class
*/
getCommon(target, prop
//eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
switch (prop) {
case 'startCall':
{
return this._context.withErrorTeedToState((...args) => {
const call = this.startCall(target, args);
this.addCall(call);
return this.getOrCreateDeclarativeCall(call);
}, 'CallAgent.startCall');
}
case 'join':
{
return this._context.withErrorTeedToState((...args) => {
const call = this.joinCall(target, args);
this.addCall(call);
return this.getOrCreateDeclarativeCall(call);
}, 'CallAgent.join');
}
case 'calls':
{
return Array.from(this._declarativeCalls.values());
}
case 'on':
{
return (...args) => {
// typescript is not smart enough to handle multiple overloads and pull the correct type here so force casting args
const event = args[0];
const isCallsUpdated = event === 'callsUpdated';
if (isCallsUpdated) {
const listener = args[1];
this._externalCallsUpdatedListeners.add(listener);
}
else {
this.agentSubscribe(target, args);
}
};
}
case 'off':
{
return (...args) => {
// typescript is not smart enough to handle multiple overloads and pull the correct type here so force casting args
const event = args[0];
const isCallsUpdated = event === 'callsUpdated';
if (isCallsUpdated) {
const listener = args[1];
this._externalCallsUpdatedListeners.delete(listener);
}
else {
this.agentUnsubscribe(target, args);
}
};
}
case 'dispose':
{
// Wrapping CallAgent.dispose in a callback type (): Promise<void> to accomodate the change of CallAgent.dispose
// in calling beta version 1.8.0-beta.1 from callback type (): Promise<void> to (): void
const callAgentDisposeAsyncCallbackWrapper = () => __awaiter(this, void 0, void 0, function* () {
yield target.dispose();
return Promise.resolve();
});
return () => {
return callAgentDisposeAsyncCallbackWrapper().then(() => {
this.unsubscribe();
});
};
}
/**
* This attribute is a special case and doesn't exist on the CallAgent interface.
* We need this to be able to return a declarative incoming call object using the call agent.
* In a standard headless SDK usage, the right way to get an incoming call is to use the `incomingCall` event.
* However, using the declarative layer, the ideal usage would be to:
* 1. subscribe to the `onStateChange` event
* 2. Get the incoming call from the new state and it's ID
* 3. Use `callAgent.incomingCalls` and filter an incoming call ID to get a declarative incoming call object
*/
case 'incomingCalls':
{
return Array.from(this._incomingCalls.values());
}
default:
return Reflect.get(target, prop);
}
}
}
/**
* @private
*/
export const clearCallRelatedState = (context, internalContext) => {
// Make sure there are no existing call data if creating a new CallAgentDeclarative (if creating a new
// CallAgentDeclarative after disposing of the hold one will mean context have old call state). TODO: should we stop
// rendering when the previous callAgent is disposed?
disposeAllViews(context, internalContext);
context.clearCallRelatedState();
internalContext.clearCallRelatedState();
};
//# sourceMappingURL=CallAgentDeclarativeCommon.js.map