Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChatSession.onConnectionEstablished() is fired twice after invoking ChatSession.connect() #124

Closed
marcogrcr opened this issue Feb 24, 2023 · 3 comments
Labels
⌛️ In Progress Team is currently working on fix

Comments

@marcogrcr
Copy link
Contributor

marcogrcr commented Feb 24, 2023

Steps to reproduce:

import "amazon-connect-chatjs"; // v1.3.1
import {
  ConnectClient,
  StartChatContactCommand,
} from "@aws-sdk/client-connect"; // v3.254.0

// start a chat contact
const client = new ConnectClient({
  region: "us-east-1",
  credentials: {
    accessKeyId: "...",
    secretAccessKey: "...",
    sessionToken: "...",
  },
});

const { ContactId, ParticipantId, ParticipantToken } = await client.send(
  new StartChatContactCommand({
    InstanceId: "...",
    ContactFlowId: "...",
    ParticipantDetails: { DisplayName: "Customer" },
  })
);

// create chat session
const session = connect.ChatSession.create({
  chatDetails: {
    contactId: ContactId,
    participantId: ParticipantId,
    participantToken: ParticipantToken,
  },
  options: { region: "us-east-1" },
  type: connect.ChatSession.SessionTypes.CUSTOMER,
});

// subscribe to `onConnectionEstablished` events
session.onConnectionEstablished((event) => {
  console.log("Connection established:", event);
});

// connect to chat
await session.connect();

Expected result:

The onConnectionEstablished listener gets invoked once:

Connection established: {
  chatDetails: { ... },
  data: {}
}

Actual result:

The onConnectionEstablished listener gets invoked twice with different event payloads:

Connection established: {
  chatDetails: { ... },
  connectCalled: true,
  connectSuccess: true,
  metadata: null,
  _debug: { webSocketStatus: "Starting" }
}

Connection established: {
  chatDetails: { ... },
  data: {}
}

Analysis:

This occurs because ChatController actually dispatches the event twice from two different places:

  1. As part of the ChatController.prototype.connect() call:

connect(args={}) {
this.sessionMetadata = args.metadata || null;
this.argsValidator.validateConnectChat(args);
const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()
.then(
(connectionDetails) =>
this._initConnectionHelper(connectionDetailsProvider, connectionDetails)
)
.then(response => this._onConnectSuccess(response, connectionDetailsProvider))

_onConnectSuccess(response, connectionDetailsProvider) {
this._sendInternalLogToServer(this.logger.info("Connect successful!"));
console.warn("onConnectionSuccess response", response);
const responseObject = {
_debug: response,
connectSuccess: true,
connectCalled: true,
metadata: this.sessionMetadata
};
const eventData = Object.assign({
chatDetails: this.getChatDetails()
}, responseObject);
this.pubsub.triggerAsync(CHAT_EVENTS.CONNECTION_ESTABLISHED, eventData);

  1. Via an event listener of LpcConnectionHelper.prototype.handleConnectionGain():

connect(args={}) {
this.sessionMetadata = args.metadata || null;
this.argsValidator.validateConnectChat(args);
const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()
.then(
(connectionDetails) =>
this._initConnectionHelper(connectionDetailsProvider, connectionDetails)

_initConnectionHelper(connectionDetailsProvider, connectionDetails) {
this.connectionHelper = new LpcConnectionHelper(
this.contactId,
this.initialContactId,
connectionDetailsProvider,
this.websocketManager,
this.logMetaData,
connectionDetails
);
this.connectionHelper.onEnded(this._handleEndedConnection.bind(this));
this.connectionHelper.onConnectionLost(this._handleLostConnection.bind(this));
this.connectionHelper.onConnectionGain(this._handleGainedConnection.bind(this));

_handleGainedConnection(eventData) {
this._forwardChatEvent(CHAT_EVENTS.CONNECTION_ESTABLISHED, {
data: eventData,
chatDetails: this.getChatDetails()
});
}

_forwardChatEvent(eventName, eventData) {
this.pubsub.triggerAsync(eventName, eventData);
}

Having duplicate event invocations can be problematic when the user assumes it will be invoked only once and the handler implementation is not idempotent (i.e. invoking it again may have undesired side-effects).

Proposed fix:

The event dispatch that is part of ChatController.prototype.connect() should be removed, so that only the LpcConnectionHelper.prototype.handleConnectionGain() remains. This is because the latter event will be triggered whenever the connection is lost and later regained.

@marcogrcr marcogrcr changed the title ChatSession.onConnectionEstablished() ChatSession.onConnectionEstablished() is fired twice after invoking ChatSession.connect() Feb 24, 2023
@spencerlepine spencerlepine added the ⌛️ In Progress Team is currently working on fix label Mar 15, 2023
@spencerlepine spencerlepine added 🗒️ In Backlog Reviewed by team, added to backlog and removed ⌛️ In Progress Team is currently working on fix labels May 19, 2023
@spencerlepine spencerlepine added ⌛️ In Progress Team is currently working on fix and removed 🗒️ In Backlog Reviewed by team, added to backlog labels Oct 2, 2023
@sseidel16
Copy link
Contributor

sseidel16 commented Aug 6, 2024

The code fix does not fix the described issue of CHAT_EVENTS.CONNECTION_ESTABLISHED being fired twice. There is still an event fired from the connect call, and one from LpcConnectionHelper. Perhaps this is desired behavior, but that should be noted. As it stands, onConnectionEstablished is still called twice when connect is called a single time.

This is particularly interesting because when using amazon-connect-chat-interface, the duplicated call to onConnectionEstablished causes the transcript API to be called twice, which could seriously affect rate limits.

@marcogrcr
Copy link
Contributor Author

@sseidel16 You are correct. I believe this may have been the result of a mixup. Looks like PR #196 resolved #123 instead of this issue.

@sseidel16
Copy link
Contributor

Another interesting note:

The proposed fix in the initial issue is likely not feasible because, when used in conjunction with streams, the websocket can be already connected when connect is called (wsm has already connected). In that scenario, onConnectionEstablished is only called by connect, and therefore only invoked once. It seems more reasonable to only emit a connection established event after connect if the websocket has already been started (this can be seen in the response from LpcConnectionHelper start).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⌛️ In Progress Team is currently working on fix
Projects
None yet
Development

No branches or pull requests

3 participants