From c49d836d177f8cc5ad9417aa6834a5cf6dcd2b8f Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 11 Mar 2024 12:39:26 -0700 Subject: [PATCH 1/2] fix(predictions): Serialize the dispatch of web socket events (#3558) --- .../Service/FaceLivenessSession.swift | 65 ++++++++++--------- .../FaceLivenessSessionRepresentable.swift | 2 +- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSession.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSession.swift index 7e2adf5064..fa0ca0df9a 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSession.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSession.swift @@ -17,6 +17,11 @@ public final class FaceLivenessSession: LivenessService { let baseURL: URL var serverEventListeners: [LivenessEventKind.Server: (FaceLivenessSession.SessionConfiguration) -> Void] = [:] var onComplete: (ServerDisconnection) -> Void = { _ in } + + private let livenessServiceDispatchQueue = DispatchQueue( + label: "com.amazon.aws.amplify.liveness.service", + target: .global() + ) init( websocket: WebSocketSession, @@ -77,36 +82,38 @@ public final class FaceLivenessSession: LivenessService { public func send( _ event: LivenessEvent, - eventDate: () -> Date = Date.init + eventDate: @escaping () -> Date = Date.init ) { - let encodedPayload = eventStreamEncoder.encode( - payload: event.payload, - headers: [ - ":content-type": .string("application/json"), - ":event-type": .string(event.eventTypeHeader), - ":message-type": .string("event") - ] - ) - - let eventDate = eventDate() - - let signedPayload = signer.signWithPreviousSignature( - payload: encodedPayload, - dateHeader: (key: ":date", value: eventDate) - ) - - let encodedEvent = eventStreamEncoder.encode( - payload: encodedPayload, - headers: [ - ":date": .timestamp(eventDate), - ":chunk-signature": .data(signedPayload) - ] - ) - - websocket.send( - message: .data(encodedEvent), - onError: { _ in } - ) + livenessServiceDispatchQueue.async { + let encodedPayload = self.eventStreamEncoder.encode( + payload: event.payload, + headers: [ + ":content-type": .string("application/json"), + ":event-type": .string(event.eventTypeHeader), + ":message-type": .string("event") + ] + ) + + let eventDate = eventDate() + + let signedPayload = self.signer.signWithPreviousSignature( + payload: encodedPayload, + dateHeader: (key: ":date", value: eventDate) + ) + + let encodedEvent = self.eventStreamEncoder.encode( + payload: encodedPayload, + headers: [ + ":date": .timestamp(eventDate), + ":chunk-signature": .data(signedPayload) + ] + ) + + self.websocket.send( + message: .data(encodedEvent), + onError: { _ in } + ) + } } private func fallbackDecoding(_ message: EventStream.Message) -> Bool { diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSessionRepresentable.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSessionRepresentable.swift index 92001bc980..896ef5769b 100644 --- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSessionRepresentable.swift +++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Liveness/Service/FaceLivenessSessionRepresentable.swift @@ -12,7 +12,7 @@ import Amplify public protocol LivenessService { func send( _ event: LivenessEvent, - eventDate: () -> Date + eventDate: @escaping () -> Date ) var onServiceException: (FaceLivenessSessionError) -> Void { get set } From 9f8e6904e5be1e47fd0ade18f4ceab55eae53e5f Mon Sep 17 00:00:00 2001 From: Tomasz Trela Date: Mon, 11 Mar 2024 20:41:37 +0100 Subject: [PATCH 2/2] fix(DataStore): created and deleted model on one device appears as created on the other (#3554) * fix(DataStore): created and deleted model on one device appears as created on the other (#3553) * fix(DataStore): created and deleted model on one device appears as created on the other #3553 Co-authored-by: Di Wu * fix(DataStore): created and deleted model on one device appears as created on the other #3553 --------- Co-authored-by: Di Wu --- .../RemoteSyncReconciler.swift | 35 +++++++++++++------ .../RemoteSyncReconcilerTests.swift | 27 ++++++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/RemoteSyncReconciler.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/RemoteSyncReconciler.swift index 1bfc0852b1..df95b21a8a 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/RemoteSyncReconciler.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/RemoteSyncReconciler.swift @@ -33,26 +33,41 @@ struct RemoteSyncReconciler { } } - /// Reconciles the incoming `remoteModels` against the local metadata to get the disposition /// + /// GroupBy the remoteModels by model identifier and apply only the latest version of the remoteModel + /// /// - Parameters: /// - remoteModels: models retrieved from the remote store /// - localMetadatas: metadata retrieved from the local store /// - Returns: disposition of models to apply locally - static func getDispositions(_ remoteModels: [RemoteModel], - localMetadatas: [LocalMetadata]) -> [Disposition] { - guard !remoteModels.isEmpty else { + static func getDispositions( + _ remoteModels: [RemoteModel], + localMetadatas: [LocalMetadata] + ) -> [Disposition] { + let remoteModelsGroupByIdentifier = remoteModels.reduce([String: [RemoteModel]]()) { + $0.merging([ + $1.model.identifier: ($0[$1.model.identifier] ?? []) + [$1] + ], uniquingKeysWith: { $1 }) + } + + let optimizedRemoteModels = remoteModelsGroupByIdentifier.values.compactMap { + $0.sorted(by: { $0.syncMetadata.version > $1.syncMetadata.version }).first + } + + guard !optimizedRemoteModels.isEmpty else { return [] } - + guard !localMetadatas.isEmpty else { - return remoteModels.compactMap { getDisposition($0, localMetadata: nil) } + return optimizedRemoteModels.compactMap { getDisposition($0, localMetadata: nil) } } - - let metadataBymodelId = localMetadatas.reduce(into: [:]) { $0[$1.modelId] = $1 } - let dispositions = remoteModels.compactMap { getDisposition($0, localMetadata: metadataBymodelId[$0.model.identifier]) } - + + let metadataByModelId = localMetadatas.reduce(into: [:]) { $0[$1.modelId] = $1 } + let dispositions = optimizedRemoteModels.compactMap { + getDisposition($0, localMetadata: metadataByModelId[$0.model.identifier]) + } + return dispositions } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/RemoteSyncReconcilerTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/RemoteSyncReconcilerTests.swift index 612947d04d..1584ed3897 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/RemoteSyncReconcilerTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/RemoteSyncReconcilerTests.swift @@ -179,6 +179,33 @@ class RemoteSyncReconcilerTests: XCTestCase { } waitForExpectations(timeout: 1) } + + func testGetDispositions_emptyLocal_singleModelAddedAndDeleted() { + let sameId = UUID().uuidString + + let remoteModels = [ + makeRemoteModel(modelId: sameId, deleted: false, version: 1), + makeRemoteModel(modelId: sameId, deleted: true, version: 2) + ] + + let dispositions = RemoteSyncReconciler.getDispositions(remoteModels, localMetadatas: []) + + XCTAssertTrue(dispositions.isEmpty) + } + + func testGetDispositions_emptyLocal_oneModelAdded_SecondModelAddedAndDeleted() { + let sameId = UUID().uuidString + + let remoteModels = [ + makeRemoteModel(deleted: false, version: 1), + makeRemoteModel(modelId: sameId, deleted: false, version: 1), + makeRemoteModel(modelId: sameId, deleted: true, version: 2) + ] + + let dispositions = RemoteSyncReconciler.getDispositions(remoteModels, localMetadatas: []) + + XCTAssertTrue(dispositions.count == 1) + } // MARK: - Utilities