Skip to content

Commit

Permalink
Previous session transcript fix , made initializer public (#46)
Browse files Browse the repository at this point in the history
* Previous session transcript fix , made initializer public
* Update Build.yml
* Update version of aws-ios-sdk in Package.swift
* updated logic for handling prev chat ended messages
  • Loading branch information
mrajatttt authored Jan 16, 2025
1 parent 9625091 commit 7c9433a
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 62 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,23 @@ jobs:
- name: Install xcpretty
run: gem install xcpretty

- name: Clean and Build SDK
run: |
echo "::group::Clean and Build SDK"
xcodebuild clean -project AmazonConnectChatIOS.xcodeproj \
-scheme AmazonConnectChatIOS \
-sdk iphonesimulator \
-configuration Debug \
build
echo "::endgroup::"
# - name: Clean and Build SDK
# run: |
# echo "::group::Clean and Build SDK"
# xcodebuild clean -project AmazonConnectChatIOS.xcodeproj \
# -scheme AmazonConnectChatIOS \
# -sdk iphonesimulator \
# -configuration Debug \
# build
# echo "::endgroup::"
- name: Run Unit Tests
run: |
echo "::group::Run Unit Tests"
xcodebuild test -project AmazonConnectChatIOS.xcodeproj \
-scheme AmazonConnectChatIOS \
xcodebuild test -scheme AmazonConnectChatIOS \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' \
-configuration Debug \
-enableCodeCoverage YES \
-testPlan AmazonConnectChatIOS.xctestplan \
2>&1 | xcpretty --color --simple
echo "::endgroup::"
26 changes: 13 additions & 13 deletions AmazonConnectChatIOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
7E58A6232BBDDBF900965327 /* ChatSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58A6222BBDDBF900965327 /* ChatSession.swift */; };
7E58A6272BBDDE4A00965327 /* AWSClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58A6262BBDDE4A00965327 /* AWSClient.swift */; };
7E58A6292BBE415700965327 /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E58A6282BBE415700965327 /* ChatService.swift */; };
7E78DCFD2C3C93DB00D5351A /* AWSConnectParticipant in Frameworks */ = {isa = PBXBuildFile; productRef = 7E78DCFC2C3C93DB00D5351A /* AWSConnectParticipant */; };
7E78DCFF2C3C93DB00D5351A /* AWSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 7E78DCFE2C3C93DB00D5351A /* AWSCore */; };
7E5D95ED2D399850005E20AC /* AWSConnectParticipant in Frameworks */ = {isa = PBXBuildFile; productRef = 7E5D95EC2D399850005E20AC /* AWSConnectParticipant */; };
7E5D95EF2D399850005E20AC /* AWSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 7E5D95EE2D399850005E20AC /* AWSCore */; };
7E91597B2BC0A92E00821C05 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E91597A2BC0A92E00821C05 /* Constants.swift */; };
7EAED4CB2CEDD4340091F65E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7EAED4CA2CEDD4340091F65E /* PrivacyInfo.xcprivacy */; };
7EC0A21B2BC79D7B00DB47F4 /* WebSocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC0A21A2BC79D7A00DB47F4 /* WebSocketManager.swift */; };
Expand Down Expand Up @@ -139,8 +139,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7E78DCFD2C3C93DB00D5351A /* AWSConnectParticipant in Frameworks */,
7E78DCFF2C3C93DB00D5351A /* AWSCore in Frameworks */,
7E5D95ED2D399850005E20AC /* AWSConnectParticipant in Frameworks */,
7E5D95EF2D399850005E20AC /* AWSCore in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -381,8 +381,8 @@
);
name = AmazonConnectChatIOS;
packageProductDependencies = (
7E78DCFC2C3C93DB00D5351A /* AWSConnectParticipant */,
7E78DCFE2C3C93DB00D5351A /* AWSCore */,
7E5D95EC2D399850005E20AC /* AWSConnectParticipant */,
7E5D95EE2D399850005E20AC /* AWSCore */,
);
productName = AmazonConnectChatIOS;
productReference = 7E58A5D42BB5F3DA00965327 /* AmazonConnectChatIOS.framework */;
Expand Down Expand Up @@ -434,7 +434,7 @@
);
mainGroup = 7E58A5CA2BB5F3DA00965327;
packageReferences = (
7E78DCFB2C3C93DB00D5351A /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */,
7E5D95EB2D399850005E20AC /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */,
);
productRefGroup = 7E58A5D52BB5F3DA00965327 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -806,25 +806,25 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
7E78DCFB2C3C93DB00D5351A /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */ = {
7E5D95EB2D399850005E20AC /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/aws-amplify/aws-sdk-ios-spm";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.36.3;
minimumVersion = 2.40.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
7E78DCFC2C3C93DB00D5351A /* AWSConnectParticipant */ = {
7E5D95EC2D399850005E20AC /* AWSConnectParticipant */ = {
isa = XCSwiftPackageProductDependency;
package = 7E78DCFB2C3C93DB00D5351A /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */;
package = 7E5D95EB2D399850005E20AC /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */;
productName = AWSConnectParticipant;
};
7E78DCFE2C3C93DB00D5351A /* AWSCore */ = {
7E5D95EE2D399850005E20AC /* AWSCore */ = {
isa = XCSwiftPackageProductDependency;
package = 7E78DCFB2C3C93DB00D5351A /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */;
package = 7E5D95EB2D399850005E20AC /* XCRemoteSwiftPackageReference "aws-sdk-ios-spm" */;
productName = AWSCore;
};
/* End XCSwiftPackageProductDependency section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MockWebsocketManager: WebsocketManagerProtocol {
SDKLogger.logger.logDebug("MockWebsocketManager: Connected to \(String(describing: wsUrl))")
}

func disconnect() {
func disconnect(reason: String?) {
// Simulate disconnection logic if needed
SDKLogger.logger.logDebug("MockWebsocketManager: Disconnected")
}
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/aws-amplify/aws-sdk-ios-spm", from: "2.36.4")
.package(url: "https://github.com/aws-amplify/aws-sdk-ios-spm", from: "2.40.0")
],
targets: [
.target(
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Models/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class Event: TranscriptItem, EventProtocol {
public var displayName: String?
public var eventDirection: MessageDirection?

init(text: String? = nil, timeStamp: String, contentType: String, messageId: String, displayName: String? = nil, participant: String? = nil, eventDirection: MessageDirection? = .Common, serializedContent: [String: Any]) {
public init(text: String? = nil, timeStamp: String, contentType: String, messageId: String, displayName: String? = nil, participant: String? = nil, eventDirection: MessageDirection? = .Common, serializedContent: [String: Any]) {
self.participant = participant
self.text = text
self.displayName = displayName
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class Message: TranscriptItem, MessageProtocol {
public var displayName: String?
@Published public var metadata: (any MetadataProtocol)?

init(participant: String, text: String, contentType: String, messageDirection: MessageDirection? = nil, timeStamp: String, attachmentId: String? = nil, messageId: String? = nil,
public init(participant: String, text: String, contentType: String, messageDirection: MessageDirection? = nil, timeStamp: String, attachmentId: String? = nil, messageId: String? = nil,
displayName: String? = nil, serializedContent: [String: Any], metadata: (any MetadataProtocol)? = nil) {
self.participant = participant
self.text = text
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Models/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Metadata: TranscriptItem, MetadataProtocol {
@Published public var status: MessageStatus?
@Published public var eventDirection: MessageDirection?

init(status: MessageStatus? = nil, messageId: String? = nil, timeStamp: String, contentType: String, eventDirection: MessageDirection? = .Common, serializedContent: [String: Any]) {
public init(status: MessageStatus? = nil, messageId: String? = nil, timeStamp: String, contentType: String, eventDirection: MessageDirection? = .Common, serializedContent: [String: Any]) {
self.status = status
self.eventDirection = eventDirection
super.init(timeStamp: timeStamp, contentType: contentType, id: messageId, serializedContent: serializedContent)
Expand Down
1 change: 1 addition & 0 deletions Sources/Core/Models/WebSocketErrorCodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ public enum WebSocketErrorCodes: Int {
case NETWORK_DISCONNECTED = -1005
case BAD_SERVER_RESPONSE = -1011
case SOFTWARE_ABORT = 53
case OPERATION_TIMED_OUT = 60
}
66 changes: 40 additions & 26 deletions Sources/Core/Network/WebSocketManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protocol WebsocketManagerProtocol {
var eventPublisher: PassthroughSubject<ChatEvent, Never> { get }
var transcriptPublisher: PassthroughSubject<TranscriptItem, Never> { get }
func connect(wsUrl: URL?, isReconnect: Bool?)
func disconnect()
func disconnect(reason: String?)
func formatAndProcessTranscriptItems(_ transcriptItems: [AWSConnectParticipantItem]) -> [TranscriptItem]
}

Expand All @@ -31,7 +31,8 @@ extension URLSessionWebSocketTask: WebSocketTask {}
class WebsocketManager: NSObject, WebsocketManagerProtocol {
var eventPublisher = PassthroughSubject<ChatEvent, Never>()
var transcriptPublisher = PassthroughSubject<TranscriptItem, Never>()

private var latestParticipantJoinedTimestamp: String?

private var session: URLSession?
private var wsUrl: URL?
var heartbeatManager: HeartbeatManager?
Expand All @@ -56,7 +57,7 @@ class WebsocketManager: NSObject, WebsocketManagerProtocol {
if let isReconnect {
self.isReconnectFlow = isReconnect
}
disconnect() // Ensure previous WebSocket tasks are properly closed
disconnect(reason: "Connecting...") // Ensure previous WebSocket tasks are properly closed

if let wsTask = self.websocketTask {
wsTask.cancel(with: .goingAway, reason: nil)
Expand Down Expand Up @@ -90,11 +91,11 @@ class WebsocketManager: NSObject, WebsocketManagerProtocol {
websocketTask?.receive { [weak self] result in
switch result {
case .failure(let error):
print("Failed to receive message: \(error)")
print("Failed to receive message, Websocket connection might be closed: \(error)")
case .success(let message):
switch message {
case .string(let text):
print("Received text in receiveMessage()")
print("Received text in receiveMessage() \(text)")
self?.handleWebsocketTextEvent(text: text)
case .data(_):
print("Received data from websocket")
Expand All @@ -111,7 +112,8 @@ class WebsocketManager: NSObject, WebsocketManagerProtocol {
if let nsError = error as? NSError {
switch (nsError.domain) {
case NSPOSIXErrorDomain:
if (nsError.code == WebSocketErrorCodes.SOFTWARE_ABORT.rawValue) {
if (nsError.code == WebSocketErrorCodes.SOFTWARE_ABORT.rawValue
|| nsError.code == WebSocketErrorCodes.OPERATION_TIMED_OUT.rawValue) {
NotificationCenter.default.post(name: .requestNewWsUrl, object: nil)
}
break
Expand All @@ -137,14 +139,16 @@ class WebsocketManager: NSObject, WebsocketManagerProtocol {
self.onError?(error)
}

func disconnect() {
websocketTask?.cancel(with: .goingAway, reason: nil)
func disconnect(reason: String?) {
resetHeartbeatManagers()
let reasonData = reason?.data(using: .utf8)
websocketTask?.cancel(with: .goingAway, reason: reasonData)
websocketTask = nil
}


func addNetworkNotificationObserver() {
NotificationCenter.default.addObserver(forName: .networkConnected, object: nil, queue: .main) { _ in

if (ChatSession.shared.isChatSessionActive()) {
NotificationCenter.default.post(name: .requestNewWsUrl, object: nil)
}
Expand Down Expand Up @@ -210,6 +214,12 @@ class WebsocketManager: NSObject, WebsocketManagerProtocol {
switch eventType {
case .joined:
// Handle participant joined event
// Update latestParticipantJoinedTimestamp
if let timestamp = innerJson["AbsoluteTime"] as? String {
if latestParticipantJoinedTimestamp == nil || timestamp > (latestParticipantJoinedTimestamp ?? "") {
latestParticipantJoinedTimestamp = timestamp
}
}
return handleParticipantEvent(innerJson, json)
case .left:
// Handle participant left event
Expand Down Expand Up @@ -306,23 +316,20 @@ extension WebsocketManager: URLSessionWebSocketDelegate {
}

func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
self.onDisconnected?()
self.isReconnectFlow = false
let reasonString = reason.flatMap { String(data: $0, encoding: .utf8) } ?? "No reason provided"
print("WebSocket closed with code: \(closeCode) and reason: \(reasonString)")
self.onDisconnected?()
self.isReconnectFlow = false
}

print("WebSocket connection closed")
}

// Foregrounded
// - see if websocket is connected, if it isn't we try reconnecting
// - Call get transcript

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("WebSocket connection completed with error.")
self.onDisconnected?()
if error != nil {
if let error = error {
print("WebSocket connection completed with error: \(error.localizedDescription)")
handleError(error)
} else {
print("WebSocket task completed successfully without errors.")
}

self.onDisconnected?()
}
}

Expand Down Expand Up @@ -378,7 +385,6 @@ extension WebsocketManager {
if let validItem = transcriptItem {
transcriptPublisher.send(validItem)
}

return transcriptItem
}

Expand Down Expand Up @@ -479,10 +485,18 @@ extension WebsocketManager {

func handleChatEnded(_ innerJson: [String: Any], _ serializedContent: [String: Any]) -> TranscriptItem? {
let time = innerJson["AbsoluteTime"] as! String
self.eventPublisher.send(.chatEnded)
let messageId = innerJson["Id"] as! String
resetHeartbeatManagers()

// Check if the event belongs to a previous transcript
let isOlderEvent = latestParticipantJoinedTimestamp != nil && time < latestParticipantJoinedTimestamp!

if !isOlderEvent {
// Current session event: Reset state and update session
resetHeartbeatManagers()
eventPublisher.send(.chatEnded)
ConnectionDetailsProvider.shared.setChatSessionState(isActive: false)
}

let messageId = innerJson["Id"] as! String
return Event(
timeStamp: time,
contentType: innerJson["ContentType"] as! String,
Expand Down
7 changes: 5 additions & 2 deletions Sources/Core/Service/ChatService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class ChatService : ChatServiceProtocol {
private func updateTranscriptDict(with item: TranscriptItem) {
switch item {
case let metadata as Metadata:
// metadata.id here refers to messageId attatched to a metadata
if let messageItem = transcriptDict[metadata.id] as? Message {
messageItem.metadata = metadata
transcriptDict[metadata.id] = messageItem
Expand Down Expand Up @@ -237,10 +238,12 @@ class ChatService : ChatServiceProtocol {

func disconnectChatSession(completion: @escaping (Bool, Error?) -> Void) {
if (!connectionDetailsProvider.isChatSessionActive()) {
self.websocketManager?.disconnect()
self.websocketManager?.disconnect(reason: "Session inactive")
self.clearSubscriptionsAndPublishers()
completion(true, nil)
return
}

guard let connectionDetails = connectionDetailsProvider.getConnectionDetails() else {
let error = NSError(domain: "ChatService", code: -1, userInfo: [NSLocalizedDescriptionKey: "No connection details available"])
completion(false, error)
Expand All @@ -254,7 +257,7 @@ class ChatService : ChatServiceProtocol {
case .success(_):
SDKLogger.logger.logDebug("Participant Disconnected")
self.eventPublisher.send(.chatEnded)
self.websocketManager?.disconnect()
self.websocketManager?.disconnect(reason: "Participant Disconnected")
self.clearSubscriptionsAndPublishers()
completion(true, nil)
case .failure(let error):
Expand Down
7 changes: 2 additions & 5 deletions Sources/Core/Service/ChatSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,13 @@ public class ChatSession: ChatSessionProtocol {
DispatchQueue.main.async {
switch event {
case .connectionEstablished:
ConnectionDetailsProvider.shared.setChatSessionState(isActive: true)
self?.onConnectionEstablished?()
case .connectionBroken:
self?.onConnectionBroken?()
case .connectionReEstablished:
self?.onConnectionReEstablished?()
case .chatEnded:
if (self != nil && ConnectionDetailsProvider.shared.isChatSessionActive() == true) {
ConnectionDetailsProvider.shared.setChatSessionState(isActive: false)
self?.onChatEnded?()
}
self?.onChatEnded?()
default:
break
}
Expand Down Expand Up @@ -195,6 +191,7 @@ public class ChatSession: ChatSessionProtocol {
if success {
self.onChatEnded?()
self.cleanupSubscriptions()
ConnectionDetailsProvider.shared.setChatSessionState(isActive: false)
completion(.success(()))
} else if let error = error {
SDKLogger.logger.logError("Error disconnecting chat session: \(error.localizedDescription)")
Expand Down
1 change: 1 addition & 0 deletions Sources/Core/Utils/HttpClient/HttpHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension HttpHeader {
case contentLength = "Content-Length"
case contentDisposition = "Content-Disposition"
case amzMetaAccountId = "x-amz-meta-account_id"
case amzExpctedBucketOwner = "x-amz-expected-bucket-owner"
}
}

Expand Down

0 comments on commit 7c9433a

Please sign in to comment.