From a65984883d2a9f46b6990d790e798e3ff8978ed6 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 9 Nov 2023 20:10:34 -0500 Subject: [PATCH 01/10] fix: refactor keychain errors (#3354) * fix: refactor keychain errors * fix: debug dictionary not properly formatted. * making keychain store error as Auth convertible * Update AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/RefreshSessionState+Debug.swift Co-authored-by: Ian Saultz <52051793+atierian@users.noreply.github.com> * worked on review comment * worked on comments to narrow the keychain status list --------- Co-authored-by: Ian Saultz <52051793+atierian@users.noreply.github.com> --- .../States/DebugInfo/AuthState+Debug.swift | 3 +- .../DebugInfo/AuthenticationState+Debug.swift | 2 +- .../DebugInfo/AuthorizationState+Debug.swift | 2 +- .../CredentialStoreState+Debug.swift | 46 +++++++++------- .../DebugInfo/CustomSignInState+Debug.swift | 4 +- .../DebugInfo/DeleteUserState+Debug.swift | 2 +- .../DebugInfo/DeviceSRPState+Debug.swift | 4 +- .../FetchAuthSessionState+Debug.swift | 4 +- .../DebugInfo/HostedUISignInState+Debug.swift | 2 +- .../DebugInfo/MigrateSignInState+Debug.swift | 4 +- .../DebugInfo/RefreshSessionState+Debug.swift | 4 +- .../DebugInfo/SRPSignInState+Debug.swift | 4 +- .../SignInChallengeState+Debug.swift | 11 ++-- .../States/DebugInfo/SignInState+Debug.swift | 14 +++-- .../SignInTOTPSetupState+Debug.swift | 14 ++--- .../States/DebugInfo/SignOutState+Debug.swift | 4 +- .../KeychainStoreError+AuthConvertible.swift | 30 +++++++++++ .../Keychain/KeychainStatus.swift | 54 +++++++++++++++++++ .../Keychain/KeychainStoreError.swift | 23 +++++++- 19 files changed, 174 insertions(+), 57 deletions(-) create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/KeychainStoreError+AuthConvertible.swift create mode 100644 AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStatus.swift diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthState+Debug.swift index 0bb1789d82..56a8c92f92 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthState+Debug.swift @@ -11,7 +11,7 @@ extension AuthState: CustomDebugStringConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notConfigured: @@ -36,6 +36,5 @@ extension AuthState: CustomDebugStringConvertible { var debugDescription: String { return (debugDictionary as AnyObject).description - } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthenticationState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthenticationState+Debug.swift index 9e72588f35..451f8e36b5 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthenticationState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthenticationState+Debug.swift @@ -8,7 +8,7 @@ extension AuthenticationState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notConfigured: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthorizationState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthorizationState+Debug.swift index 3b93ef7e89..d66207a120 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthorizationState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/AuthorizationState+Debug.swift @@ -9,7 +9,7 @@ import Foundation extension AuthorizationState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notConfigured, diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CredentialStoreState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CredentialStoreState+Debug.swift index 8554aa4106..b12c8ec64f 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CredentialStoreState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CredentialStoreState+Debug.swift @@ -7,31 +7,37 @@ import Foundation -extension CredentialStoreState { +extension CredentialStoreState: CustomDebugStringConvertible { var debugDictionary: [String: Any] { - let stateTypeDictionary: [String: Any] = ["CredentialStoreState": type] - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { - case .notConfigured: - additionalMetadataDictionary = [:] - case .migratingLegacyStore: - additionalMetadataDictionary = [:] - case .loadingStoredCredentials: - additionalMetadataDictionary = [:] - case .clearingCredentials: + case .notConfigured, + .migratingLegacyStore, + .loadingStoredCredentials, + .storingCredentials, + .clearingCredentials, + .idle: additionalMetadataDictionary = [:] case .clearedCredential(let dataType): - additionalMetadataDictionary = ["StoreDataType": dataType] - case .storingCredentials: - additionalMetadataDictionary = [:] - case .success: - additionalMetadataDictionary = [:] - case .error: - additionalMetadataDictionary = [:] - case .idle: - additionalMetadataDictionary = [:] + additionalMetadataDictionary = [ + "clearedDataType": dataType + ] + case .success(let data): + additionalMetadataDictionary = [ + "savedData": data + ] + case .error(let error): + additionalMetadataDictionary = [ + "errorType": error + ] } - return stateTypeDictionary.merging(additionalMetadataDictionary, uniquingKeysWith: { $1 }) + return [type: additionalMetadataDictionary] } + + + var debugDescription: String { + return (debugDictionary as AnyObject).description + } + } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CustomSignInState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CustomSignInState+Debug.swift index dc86bab26a..316bf2daa2 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CustomSignInState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/CustomSignInState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension CustomSignInState { +extension CustomSignInState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeleteUserState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeleteUserState+Debug.swift index f5c7ad7af8..f2a0c1dc88 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeleteUserState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeleteUserState+Debug.swift @@ -8,7 +8,7 @@ extension DeleteUserState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeviceSRPState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeviceSRPState+Debug.swift index eeb8699110..3b0e80913d 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeviceSRPState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/DeviceSRPState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension DeviceSRPState { +extension DeviceSRPState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/FetchAuthSessionState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/FetchAuthSessionState+Debug.swift index af0f0c1ccc..57c7f9646a 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/FetchAuthSessionState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/FetchAuthSessionState+Debug.swift @@ -7,10 +7,10 @@ import Foundation -extension FetchAuthSessionState { +extension FetchAuthSessionState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/HostedUISignInState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/HostedUISignInState+Debug.swift index b06aa7cde7..82b14798f7 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/HostedUISignInState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/HostedUISignInState+Debug.swift @@ -8,7 +8,7 @@ extension HostedUISignInState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/MigrateSignInState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/MigrateSignInState+Debug.swift index 24e058e53e..0b7571271b 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/MigrateSignInState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/MigrateSignInState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension MigrateSignInState { +extension MigrateSignInState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/RefreshSessionState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/RefreshSessionState+Debug.swift index 6535b4e507..fedc330c46 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/RefreshSessionState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/RefreshSessionState+Debug.swift @@ -7,10 +7,10 @@ import Foundation -extension RefreshSessionState { +extension RefreshSessionState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .fetchingAuthSessionWithUserPool(let state, _): additionalMetadataDictionary = ["fetchingSession": state.debugDictionary] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SRPSignInState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SRPSignInState+Debug.swift index 4824450a94..f61b26644b 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SRPSignInState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SRPSignInState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension SRPSignInState { +extension SRPSignInState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift index 287bea12e0..7ded00a585 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift @@ -7,18 +7,21 @@ import Foundation -extension SignInChallengeState { +extension SignInChallengeState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .waitingForAnswer(let respondAuthChallenge, _), .verifying(let respondAuthChallenge, _, _): additionalMetadataDictionary = respondAuthChallenge.debugDictionary case .error(let respondAuthChallenge, _, let error): - additionalMetadataDictionary = respondAuthChallenge.debugDictionary - additionalMetadataDictionary["error"] = error + additionalMetadataDictionary = respondAuthChallenge.debugDictionary.merging( + [ + "error": error + ], + uniquingKeysWith: {$1}) default: additionalMetadataDictionary = [:] } return [type: additionalMetadataDictionary] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInState+Debug.swift index 63e91680db..51d86499fe 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension SignInState { +extension SignInState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { @@ -22,9 +22,13 @@ extension SignInState { case .signingInWithHostedUI(let substate): additionalMetadataDictionary = substate.debugDictionary case .resolvingChallenge(let challengeState, let challengeType, let signInMethod): - additionalMetadataDictionary = challengeState.debugDictionary - additionalMetadataDictionary["challengeType"] = challengeType - additionalMetadataDictionary["signInMethod"] = signInMethod + + additionalMetadataDictionary = challengeState.debugDictionary.merging( + [ + "challengeType": challengeType, + "signInMethod": signInMethod + ], + uniquingKeysWith: {$1}) case .notStarted: additionalMetadataDictionary = [:] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInTOTPSetupState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInTOTPSetupState+Debug.swift index e071f9a646..53b796378f 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInTOTPSetupState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignInTOTPSetupState+Debug.swift @@ -7,20 +7,22 @@ import Foundation -extension SignInTOTPSetupState { +extension SignInTOTPSetupState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .waitingForAnswer(let signInTOTPSetupData): additionalMetadataDictionary = signInTOTPSetupData.debugDictionary case .verifying(let signInSetupData, let confirmSignInEventData): - additionalMetadataDictionary = confirmSignInEventData.debugDictionary - additionalMetadataDictionary = additionalMetadataDictionary.merging( + additionalMetadataDictionary = confirmSignInEventData.debugDictionary.merging( signInSetupData.debugDictionary, uniquingKeysWith: {$1}) - case .error(let error): - additionalMetadataDictionary["error"] = error + case .error(let data, let error): + additionalMetadataDictionary = [ + "totpSetupData": data ?? "Nil", + "error": error + ] default: additionalMetadataDictionary = [:] } return [type: additionalMetadataDictionary] diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignOutState+Debug.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignOutState+Debug.swift index e97301f625..f8805e88c5 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignOutState+Debug.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/States/DebugInfo/SignOutState+Debug.swift @@ -7,11 +7,11 @@ import Foundation -extension SignOutState { +extension SignOutState: CustomDebugDictionaryConvertible { var debugDictionary: [String: Any] { - var additionalMetadataDictionary: [String: Any] = [:] + let additionalMetadataDictionary: [String: Any] switch self { case .error(let error): diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/KeychainStoreError+AuthConvertible.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/KeychainStoreError+AuthConvertible.swift new file mode 100644 index 0000000000..1a37569191 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/ErrorMapping/KeychainStoreError+AuthConvertible.swift @@ -0,0 +1,30 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import AWSPluginsCore +import Amplify + +extension KeychainStoreError: AuthErrorConvertible { + + var authError: AuthError { + switch self { + case .configuration(let message): + return .configuration(message, self.recoverySuggestion) + case .unknown(let errorDescription, let error): + return .unknown(errorDescription, error) + case .conversionError(let errorDescription, let error): + return .configuration(errorDescription, self.recoverySuggestion, error) + case .codingError(let errorDescription, let error): + return .configuration(errorDescription, self.recoverySuggestion, error) + case .itemNotFound: + return .service(self.errorDescription, self.recoverySuggestion) + case .securityError: + return .service(self.errorDescription, self.recoverySuggestion) + } + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStatus.swift b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStatus.swift new file mode 100644 index 0000000000..ee6c00b1e0 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStatus.swift @@ -0,0 +1,54 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +enum KeychainStatus { + case success + case userCanceled + case duplicateItem + case itemNotFound + case missingEntitlement + case unexpectedError(OSStatus) +} + +extension KeychainStatus: CustomStringConvertible { + + init(status: OSStatus) { + switch status { + case 0: + self = .success + case -128: + self = .userCanceled + case -25299: + self = .duplicateItem + case -25300: + self = .itemNotFound + case -34018: + self = .missingEntitlement + default: + self = .unexpectedError(status) + } + } + + var description: String { + switch self { + case .success: + return "No error." + case .userCanceled: + return "User canceled the operation." + case .duplicateItem: + return "The specified item already exists in the keychain." + case .itemNotFound: + return "The specified item could not be found in the keychain." + case .missingEntitlement: + return "Internal error when a required entitlement isn't present, client has neither application-identifier nor keychain-access-groups entitlements." + case .unexpectedError(let status): + return "Unexpected error has occurred with status: \(status)." + } + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStoreError.swift b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStoreError.swift index 12ab43a1d7..c288ec0b38 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStoreError.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStoreError.swift @@ -52,7 +52,8 @@ extension KeychainStoreError: AmplifyError { case .conversionError(let errorDescription, _), .codingError(let errorDescription, _): return errorDescription case .securityError(let status): - return "Keychain error occurred with status: \(status)" + let keychainStatus = KeychainStatus(status: status) + return keychainStatus.description case .unknown(let errorDescription, _): return "Unexpected error occurred with message: \(errorDescription)" case .itemNotFound: @@ -65,7 +66,25 @@ extension KeychainStoreError: AmplifyError { /// Recovery Suggestion public var recoverySuggestion: RecoverySuggestion { switch self { - case .unknown, .conversionError, .securityError, .itemNotFound, .codingError, .configuration: + case .itemNotFound: + // If a keychain item is not found, there is no recovery suggestion to suggest + return "" + case .securityError(let status): + let keychainStatus = KeychainStatus(status: status) +#if os(macOS) + // If its Missing entitlement error on macOS + guard case .missingEntitlement = keychainStatus else { + return AmplifyErrorMessages.shouldNotHappenReportBugToAWS() + } + return """ + To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. + This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. See TN3137: macOS keychain APIs and implementations for more information on how Keychain works on macOS and the Keychain Sharing entitlement. + For more information on adding capabilities to your application, see Xcode Capabilities. + """ +#else + return AmplifyErrorMessages.shouldNotHappenReportBugToAWS() +#endif + case .unknown, .conversionError, .codingError, .configuration: return AmplifyErrorMessages.shouldNotHappenReportBugToAWS() } } From 7b507f3eab9fc6e6a489a37e5fe4f9d2f5df2e4a Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:00:55 -0500 Subject: [PATCH 02/10] fix(Auth): Add underlying error details to session error (#3364) * fix(Auth): Add underlying error details to session error * updated unit test target * updating error message --- .../InformSessionError.swift | 3 ++- .../Helpers/FetchAuthSessionOperationHelper.swift | 10 ++++++---- .../CodeGen/Errors/AuthorizationError.swift | 9 ++++++--- .../Helpers/AuthCognitoSignedInSessionHelper.swift | 11 +++++++---- .../AWSAuthFederationToIdentityPoolTests.swift | 3 ++- .../TestHarness/CodableStates/CodableStates.swift | 2 +- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift index c556bf89d4..d21f30363a 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/FetchAuthorizationSession/InformSessionError.swift @@ -23,7 +23,8 @@ struct InformSessionError: Action { switch error { case .service(let serviceError): if isNotAuthorizedError(serviceError) { - event = .init(eventType: .throwError(.sessionExpired)) + event = .init(eventType: .throwError( + .sessionExpired(error: serviceError))) } else { event = .init(eventType: .receivedSessionError(error)) } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift index af3a5c87b5..b9fdbc1d88 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift @@ -41,9 +41,10 @@ class FetchAuthSessionOperationHelper: DefaultLogger { forceRefresh: forceRefresh) case .error(let error): - if case .sessionExpired = error { + if case .sessionExpired(let error) = error { log.verbose("Session is expired") - let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession() + let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession( + underlyingError: error) return session } else if case .sessionError(_, let credentials) = error { return try await refreshIfRequired( @@ -125,8 +126,9 @@ class FetchAuthSessionOperationHelper: DefaultLogger { return try sessionResultWithFetchError(fetchError, authenticationState: authenticationState, existingCredentials: credentials) - case .sessionExpired: - let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession() + case .sessionExpired(let error): + let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession( + underlyingError: error) return session default: let message = "Unknown error occurred" diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Errors/AuthorizationError.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Errors/AuthorizationError.swift index 771c1aed5a..8c1ed9c1e0 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Errors/AuthorizationError.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Errors/AuthorizationError.swift @@ -15,14 +15,17 @@ enum AuthorizationError: Error { case service(error: Swift.Error) case invalidState(message: String) case sessionError(FetchSessionError, AmplifyCredentials) - case sessionExpired + case sessionExpired(error: Error) } extension AuthorizationError: AuthErrorConvertible { var authError: AuthError { switch self { - case .sessionExpired: - return .sessionExpired("", "", nil) + case .sessionExpired(let error): + return .sessionExpired( + "Session expired", + "Invoke Auth.signIn to re-authenticate the user", + error) case .configuration(let message): return .configuration(message, "") case .service(let error): diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedInSessionHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedInSessionHelper.swift index fbc1cf8d0d..2db23e0ce7 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedInSessionHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedInSessionHelper.swift @@ -32,18 +32,21 @@ struct AuthCognitoSignedInSessionHelper { return authSession } - static func makeExpiredSignedInSession() -> AWSAuthCognitoSession { + static func makeExpiredSignedInSession(underlyingError: Error) -> AWSAuthCognitoSession { let identityIdError = AuthError.sessionExpired( AuthPluginErrorConstants.identityIdSessionExpiredError.errorDescription, - AuthPluginErrorConstants.identityIdSessionExpiredError.recoverySuggestion) + AuthPluginErrorConstants.identityIdSessionExpiredError.recoverySuggestion, + underlyingError) let awsCredentialsError = AuthError.sessionExpired( AuthPluginErrorConstants.awsCredentialsSessionExpiredError.errorDescription, - AuthPluginErrorConstants.awsCredentialsSessionExpiredError.recoverySuggestion) + AuthPluginErrorConstants.awsCredentialsSessionExpiredError.recoverySuggestion, + underlyingError) let tokensError = AuthError.sessionExpired( AuthPluginErrorConstants.cognitoTokensSessionExpiredError.errorDescription, - AuthPluginErrorConstants.cognitoTokensSessionExpiredError.recoverySuggestion) + AuthPluginErrorConstants.cognitoTokensSessionExpiredError.recoverySuggestion, + underlyingError) let authSession = AWSAuthCognitoSession(isSignedIn: true, identityIdResult: .failure(identityIdError), diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift index e3612eaf69..604c0b24d8 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFederationToIdentityPoolTests.swift @@ -85,7 +85,8 @@ class AWSAuthFederationToIdentityPoolTests: BaseAuthorizationTests { AuthorizationState.configured), AuthState.configured( AuthenticationState.signedOut(.testData), - AuthorizationState.error(.sessionExpired)) + AuthorizationState.error(.sessionExpired( + error: NotAuthorizedException(message: "message")))) ] for initialState in statesToTest { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CodableStates/CodableStates.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CodableStates/CodableStates.swift index aa967a353e..0dc049110d 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CodableStates/CodableStates.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestHarness/CodableStates/CodableStates.swift @@ -107,7 +107,7 @@ extension FetchAuthSessionState: Codable { extension AuthorizationError: Codable { public init(from decoder: Decoder) throws { - self = .sessionExpired + self = .sessionExpired(error: NotAuthorizedException(message: "message")) } public func encode(to encoder: Encoder) throws { From c72184ed5fe1bbac98ef396d179e3bde4e8bec06 Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:44:38 -0500 Subject: [PATCH 03/10] fix(DataStore): Reconcile mutation responses from conflict handler path (#3370) --- .../OutgoingMutationQueue.swift | 3 +- ...ocessMutationErrorFromCloudOperation.swift | 30 +++++++++++++++---- ...MutationErrorFromCloudOperationTests.swift | 7 ++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 2fa24be34a..2acb52f892 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -282,7 +282,8 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { api: api, storageAdapter: storageAdapter, graphQLResponseError: graphQLResponseError, - apiError: apiError + apiError: apiError, + reconciliationQueue: reconciliationQueue ) { [weak self] result in guard let self = self else { return diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift index 6b7894226d..2e4528a2e6 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift @@ -27,13 +27,15 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { private let completion: (Result) -> Void private var mutationOperation: AtomicValue>?> private weak var api: APICategoryGraphQLBehaviorExtended? - + private weak var reconciliationQueue: IncomingEventReconciliationQueue? + init(dataStoreConfiguration: DataStoreConfiguration, mutationEvent: MutationEvent, api: APICategoryGraphQLBehaviorExtended, storageAdapter: StorageEngineAdapter, graphQLResponseError: GraphQLResponseError>? = nil, apiError: APIError? = nil, + reconciliationQueue: IncomingEventReconciliationQueue? = nil, completion: @escaping (Result) -> Void) { self.dataStoreConfiguration = dataStoreConfiguration self.mutationEvent = mutationEvent @@ -41,6 +43,7 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { self.storageAdapter = storageAdapter self.graphQLResponseError = graphQLResponseError self.apiError = apiError + self.reconciliationQueue = reconciliationQueue self.completion = completion self.mutationOperation = AtomicValue(initialValue: nil) @@ -311,12 +314,27 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation { if case .failure(let error) = cloudResult { dataStoreConfiguration.errorHandler(error) } - - if case .success(let response) = cloudResult, - case .failure(let error) = response { - dataStoreConfiguration.errorHandler(error) + + if case let .success(graphQLResponse) = cloudResult { + if case .failure(let error) = graphQLResponse { + dataStoreConfiguration.errorHandler(error) + } else if case let .success(graphQLResult) = graphQLResponse { + guard let reconciliationQueue = reconciliationQueue else { + let dataStoreError = DataStoreError.configuration( + "reconciliationQueue is unexpectedly nil", + """ + The reference to reconciliationQueue has been released while an ongoing mutation was being processed. + \(AmplifyErrorMessages.reportBugToAWS()) + """ + ) + finish(result: .failure(dataStoreError)) + return + } + + reconciliationQueue.offer([graphQLResult], modelName: mutationEvent.modelName) + } } - + finish(result: .success(nil)) } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift index 12afd41348..2fe9e2fe98 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift @@ -24,6 +24,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase { var storageAdapter: StorageEngineAdapter! var localPost = Post(title: "localTitle", content: "localContent", createdAt: .now()) let queue = OperationQueue() + let reconciliationQueue = MockReconciliationQueue() override func setUp() async throws { await tryOrFail { @@ -573,12 +574,13 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase { expectConflicthandlerCalled.fulfill() resolve(.retryLocal) }) - + let operation = ProcessMutationErrorFromCloudOperation(dataStoreConfiguration: configuration, mutationEvent: mutationEvent, api: mockAPIPlugin, storageAdapter: storageAdapter, graphQLResponseError: graphQLResponseError, + reconciliationQueue: reconciliationQueue, completion: completion) queue.addOperation(operation) @@ -656,6 +658,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase { api: mockAPIPlugin, storageAdapter: storageAdapter, graphQLResponseError: graphQLResponseError, + reconciliationQueue: reconciliationQueue, completion: completion) queue.addOperation(operation) @@ -950,6 +953,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase { api: mockAPIPlugin, storageAdapter: storageAdapter, graphQLResponseError: graphQLResponseError, + reconciliationQueue: reconciliationQueue, completion: completion) queue.addOperation(operation) @@ -1029,6 +1033,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase { api: mockAPIPlugin, storageAdapter: storageAdapter, graphQLResponseError: graphQLResponseError, + reconciliationQueue: reconciliationQueue, completion: completion) queue.addOperation(operation) From 8b912145c192b124b4f0d187eb5c007abe8e28a9 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:31:57 -0500 Subject: [PATCH 04/10] fix(Auth): Moving HostedUI continuations to one place (#3363) * fix(Auth): Moving HostedUI continuations to one place * updating unit tests * fix tvos test --- .../SignIn/HostedUI/ShowHostedUISignIn.swift | 20 +-- .../Actions/SignOut/ShowHostedUISignOut.swift | 19 +-- .../HostedUIASWebAuthenticationSession.swift | 75 ++++++---- .../HostedUI/HostedUISessionBehavior.swift | 11 +- .../Mocks/MockHostedUISession.swift | 13 +- ...tedUIASWebAuthenticationSessionTests.swift | 141 ++++++------------ 6 files changed, 116 insertions(+), 163 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/HostedUI/ShowHostedUISignIn.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/HostedUI/ShowHostedUISignIn.swift index 39894ff0ec..02f3c992b8 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/HostedUI/ShowHostedUISignIn.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/HostedUI/ShowHostedUISignIn.swift @@ -15,8 +15,6 @@ class ShowHostedUISignIn: NSObject, Action { let signingInData: HostedUISigningInState - var sessionAdapter: HostedUISessionBehavior? - init(signInData: HostedUISigningInState) { self.signingInData = signInData } @@ -48,17 +46,13 @@ class ShowHostedUISignIn: NSObject, Action { self.logVerbose("\(#fileID) Showing url \(url.absoluteString)", environment: environment) do { - let queryItems = try await withCheckedThrowingContinuation { - (continuation: CheckedContinuation<[URLQueryItem], Error>) in - sessionAdapter = hostedUIEnvironment.hostedUISessionFactory() - sessionAdapter?.showHostedUI( - url: url, - callbackScheme: callbackURLScheme, - inPrivate: signingInData.options.preferPrivateSession, - presentationAnchor: signingInData.presentationAnchor) { result in - continuation.resume(with: result) - } - } + let sessionAdapter = hostedUIEnvironment.hostedUISessionFactory() + let queryItems = try await sessionAdapter.showHostedUI( + url: url, + callbackScheme: callbackURLScheme, + inPrivate: signingInData.options.preferPrivateSession, + presentationAnchor: signingInData.presentationAnchor) + guard let code = queryItems.first(where: { $0.name == "code" })?.value, let state = queryItems.first(where: { $0.name == "state" })?.value, self.signingInData.state == state else { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift index 3692b4d5e8..6d64dbf93a 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift @@ -16,8 +16,6 @@ class ShowHostedUISignOut: NSObject, Action { let signOutEvent: SignOutEventData let signInData: SignedInData - var sessionAdapter: HostedUISessionBehavior? - init(signOutEvent: SignOutEventData, signInData: SignedInData) { self.signInData = signInData self.signOutEvent = signOutEvent @@ -44,17 +42,12 @@ class ShowHostedUISignOut: NSObject, Action { do { let logoutURL = try HostedUIRequestHelper.createSignOutURL(configuration: hostedUIConfig) - _ = try await withCheckedThrowingContinuation { - (continuation: CheckedContinuation<[URLQueryItem], Error>) in - sessionAdapter = hostedUIEnvironment.hostedUISessionFactory() - sessionAdapter?.showHostedUI(url: logoutURL, - callbackScheme: callbackURLScheme, - inPrivate: false, - presentationAnchor: signOutEvent.presentationAnchor) { - result in - continuation.resume(with: result) - } - } + let sessionAdapter = hostedUIEnvironment.hostedUISessionFactory() + _ = try await sessionAdapter.showHostedUI( + url: logoutURL, + callbackScheme: callbackURLScheme, + inPrivate: false, + presentationAnchor: signOutEvent.presentationAnchor) await sendEvent(with: nil, dispatcher: dispatcher, environment: environment) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift index 9c225ec931..c5e5b21835 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift @@ -15,45 +15,56 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior { weak var webPresentation: AuthUIPresentationAnchor? - func showHostedUI(url: URL, - callbackScheme: String, - inPrivate: Bool, - presentationAnchor: AuthUIPresentationAnchor?, - callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) { + func showHostedUI( + url: URL, + callbackScheme: String, + inPrivate: Bool, + presentationAnchor: AuthUIPresentationAnchor?) async throws -> [URLQueryItem] { + #if os(iOS) || os(macOS) self.webPresentation = presentationAnchor - let aswebAuthenticationSession = createAuthenticationSession( - url: url, - callbackURLScheme: callbackScheme, - completionHandler: { url, error in - if let url = url { - let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) - let queryItems = urlComponents?.queryItems ?? [] - if let error = queryItems.first(where: { $0.name == "error" })?.value { - let errorDescription = queryItems.first( - where: { $0.name == "error_description" } - )?.value?.trim() ?? "" - let message = "\(error) \(errorDescription)" - callback(.failure(.serviceMessage(message))) - return - } - callback(.success(queryItems)) - } else if let error = error { - callback(.failure(self.convertHostedUIError(error))) + return try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation<[URLQueryItem], Error>) in + + let aswebAuthenticationSession = createAuthenticationSession( + url: url, + callbackURLScheme: callbackScheme, + completionHandler: { url, error in - } else { - callback(.failure(.unknown)) - } - }) - aswebAuthenticationSession.presentationContextProvider = self - aswebAuthenticationSession.prefersEphemeralWebBrowserSession = inPrivate + if let url = url { + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + let queryItems = urlComponents?.queryItems ?? [] - DispatchQueue.main.async { - aswebAuthenticationSession.start() + // Validate if query items contains an error + if let error = queryItems.first(where: { $0.name == "error" })?.value { + let errorDescription = queryItems.first( + where: { $0.name == "error_description" } + )?.value?.trim() ?? "" + let message = "\(error) \(errorDescription)" + return continuation.resume( + throwing: HostedUIError.serviceMessage(message)) + } + return continuation.resume( + returning: queryItems) + } else if let error = error { + return continuation.resume( + throwing: self.convertHostedUIError(error)) + } else { + return continuation.resume( + throwing: HostedUIError.unknown) + } + }) + aswebAuthenticationSession.presentationContextProvider = self + aswebAuthenticationSession.prefersEphemeralWebBrowserSession = inPrivate + + DispatchQueue.main.async { + aswebAuthenticationSession.start() + } } + #else - callback(.failure(.serviceMessage("HostedUI is only available in iOS and macOS"))) + throw HostedUIError.serviceMessage("HostedUI is only available in iOS and macOS") #endif } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUISessionBehavior.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUISessionBehavior.swift index 5dee83d166..016dd21038 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUISessionBehavior.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUISessionBehavior.swift @@ -11,9 +11,10 @@ import Amplify protocol HostedUISessionBehavior { - func showHostedUI(url: URL, - callbackScheme: String, - inPrivate: Bool, - presentationAnchor: AuthUIPresentationAnchor?, - callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) + func showHostedUI( + url: URL, + callbackScheme: String, + inPrivate: Bool, + presentationAnchor: AuthUIPresentationAnchor? + ) async throws -> [URLQueryItem] } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Mocks/MockHostedUISession.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Mocks/MockHostedUISession.swift index ab3b2f23bd..f610a999f7 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Mocks/MockHostedUISession.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Mocks/MockHostedUISession.swift @@ -17,12 +17,13 @@ class MockHostedUISession: HostedUISessionBehavior { self.result = result } - func showHostedUI(url: URL, - callbackScheme: String, - inPrivate: Bool, - presentationAnchor: AuthUIPresentationAnchor?, - callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) { - callback(result) + func showHostedUI( + url: URL, + callbackScheme: String, + inPrivate: Bool, + presentationAnchor: AuthUIPresentationAnchor? + ) async throws -> [URLQueryItem] { + return try result.get() } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift index 3909827f36..eae166c4a6 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift @@ -29,77 +29,50 @@ class HostedUIASWebAuthenticationSessionTests: XCTestCase { /// Given: A HostedUIASWebAuthenticationSession /// When: showHostedUI is invoked and the session factory returns a URL with query items /// Then: An array of query items should be returned - func testShowHostedUI_withUrlInCallback_withQueryItems_shouldReturnQueryItems() { - let expectation = expectation(description: "showHostedUI") + func testShowHostedUI_withUrlInCallback_withQueryItems_shouldReturnQueryItems() async throws { factory.mockedURL = createURL(queryItems: [.init(name: "name", value: "value")]) - - session.showHostedUI() { result in - do { - let queryItems = try result.get() - XCTAssertEqual(queryItems.count, 1) - XCTAssertEqual(queryItems.first?.name, "name") - XCTAssertEqual(queryItems.first?.value, "value") - } catch { - XCTFail("Expected .success(queryItems), got \(result)") - } - expectation.fulfill() - } - waitForExpectations(timeout: 1) + let queryItems = try await session.showHostedUI() + XCTAssertEqual(queryItems.count, 1) + XCTAssertEqual(queryItems.first?.name, "name") + XCTAssertEqual(queryItems.first?.value, "value") } /// Given: A HostedUIASWebAuthenticationSession /// When: showHostedUI is invoked and the session factory returns a URL without query items /// Then: An empty array should be returned - func testShowHostedUI_withUrlInCallback_withoutQueryItems_shouldReturnEmptyQueryItems() { - let expectation = expectation(description: "showHostedUI") + func testShowHostedUI_withUrlInCallback_withoutQueryItems_shouldReturnEmptyQueryItems() async throws { factory.mockedURL = createURL() - - session.showHostedUI() { result in - do { - let queryItems = try result.get() - XCTAssertTrue(queryItems.isEmpty) - } catch { - XCTFail("Expected .success(queryItems), got \(result)") - } - expectation.fulfill() - } - waitForExpectations(timeout: 1) + let queryItems = try await session.showHostedUI() + XCTAssertTrue(queryItems.isEmpty) } /// Given: A HostedUIASWebAuthenticationSession /// When: showHostedUI is invoked and the session factory returns a URL with query items representing errors /// Then: A HostedUIError.serviceMessage should be returned - func testShowHostedUI_withUrlInCallback_withErrorInQueryItems_shouldReturnServiceMessageError() { - let expectation = expectation(description: "showHostedUI") + func testShowHostedUI_withUrlInCallback_withErrorInQueryItems_shouldReturnServiceMessageError() async { factory.mockedURL = createURL( queryItems: [ .init(name: "error", value: "Error."), .init(name: "error_description", value: "Something went wrong") ] ) - - session.showHostedUI() { result in - do { - _ = try result.get() - XCTFail("Expected failure(.serviceMessage), got \(result)") - } catch let error as HostedUIError { - if case .serviceMessage(let message) = error { - XCTAssertEqual(message, "Error. Something went wrong") - } else { - XCTFail("Expected HostedUIError.serviceMessage, got \(error)") - } - } catch { + do { + _ = try await session.showHostedUI() + } catch let error as HostedUIError { + if case .serviceMessage(let message) = error { + XCTAssertEqual(message, "Error. Something went wrong") + } else { XCTFail("Expected HostedUIError.serviceMessage, got \(error)") } - expectation.fulfill() + } catch { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") } - waitForExpectations(timeout: 1) } /// Given: A HostedUIASWebAuthenticationSession /// When: showHostedUI is invoked and the session factory returns ASWebAuthenticationSessionErrors /// Then: A HostedUIError corresponding to the error code should be returned - func testShowHostedUI_withASWebAuthenticationSessionErrors_shouldReturnRightError() { + func testShowHostedUI_withASWebAuthenticationSessionErrors_shouldReturnRightError() async { let errorMap: [ASWebAuthenticationSessionError.Code: HostedUIError] = [ .canceledLogin: .cancelled, .presentationContextNotProvided: .invalidContext, @@ -116,40 +89,28 @@ class HostedUIASWebAuthenticationSessionTests: XCTestCase { for code in errorCodes { factory.mockedError = ASWebAuthenticationSessionError(code) let expectedError = errorMap[code] ?? .unknown - let expectation = expectation(description: "showHostedUI for error \(code)") - session.showHostedUI() { result in - do { - _ = try result.get() - XCTFail("Expected failure(.\(expectedError)), got \(result)") - } catch let error as HostedUIError { - XCTAssertEqual(error, expectedError) - } catch { - XCTFail("Expected HostedUIError.\(expectedError), got \(error)") - } - expectation.fulfill() + do { + _ = try await session.showHostedUI() + } catch let error as HostedUIError { + XCTAssertEqual(error, expectedError) + } catch { + XCTFail("Expected HostedUIError.\(expectedError), got \(error)") } - waitForExpectations(timeout: 1) } } /// Given: A HostedUIASWebAuthenticationSession /// When: showHostedUI is invoked and the session factory returns an error /// Then: A HostedUIError.unknown should be returned - func testShowHostedUI_withOtherError_shouldReturnUnknownError() { + func testShowHostedUI_withOtherError_shouldReturnUnknownError() async { factory.mockedError = CancellationError() - let expectation = expectation(description: "showHostedUI") - session.showHostedUI() { result in - do { - _ = try result.get() - XCTFail("Expected failure(.unknown), got \(result)") - } catch let error as HostedUIError { - XCTAssertEqual(error, .unknown) - } catch { - XCTFail("Expected HostedUIError.unknown, got \(error)") - } - expectation.fulfill() + do { + _ = try await session.showHostedUI() + } catch let error as HostedUIError { + XCTAssertEqual(error, .unknown) + } catch { + XCTFail("Expected HostedUIError.unknown, got \(error)") } - waitForExpectations(timeout: 1) } private func createURL(queryItems: [URLQueryItem] = []) -> URL { @@ -203,13 +164,12 @@ class MockASWebAuthenticationSession: ASWebAuthenticationSession { } extension HostedUIASWebAuthenticationSession { - func showHostedUI(callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) { - showHostedUI( + func showHostedUI() async throws -> [URLQueryItem] { + return try await showHostedUI( url: URL(string: "https://test.com")!, callbackScheme: "https", inPrivate: false, - presentationAnchor: nil, - callback: callback) + presentationAnchor: nil) } } #else @@ -218,30 +178,23 @@ extension HostedUIASWebAuthenticationSession { import XCTest class HostedUIASWebAuthenticationSessionTests: XCTestCase { - func testShowHostedUI_shouldThrowServiceError() { - let expectation = expectation(description: "showHostedUI") + func testShowHostedUI_shouldThrowServiceError() async { let session = HostedUIASWebAuthenticationSession() - session.showHostedUI( - url: URL(string: "https://test.com")!, - callbackScheme: "https", - inPrivate: false, - presentationAnchor: nil - ) { result in - do { - _ = try result.get() - XCTFail("Expected failure(.serviceMessage), got \(result)") - } catch let error as HostedUIError { - if case .serviceMessage(let message) = error { - XCTAssertEqual(message, "HostedUI is only available in iOS and macOS") - } else { - XCTFail("Expected HostedUIError.serviceMessage, got \(error)") - } - } catch { + do { + _ = try await session.showHostedUI( + url: URL(string: "https://test.com")!, + callbackScheme: "https", + inPrivate: false, + presentationAnchor: nil) + } catch let error as HostedUIError { + if case .serviceMessage(let message) = error { + XCTAssertEqual(message, "HostedUI is only available in iOS and macOS") + } else { XCTFail("Expected HostedUIError.serviceMessage, got \(error)") } - expectation.fulfill() + } catch { + XCTFail("Expected HostedUIError.serviceMessage, got \(error)") } - waitForExpectations(timeout: 1) } } From 2e0c4a0cb3239c57f5af75386792115587bd4a6d Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:42:46 -0500 Subject: [PATCH 05/10] chore: add closed issue visibility auto comment action workflow (#3374) --- .github/workflows/closed_issue_message.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/closed_issue_message.yml diff --git a/.github/workflows/closed_issue_message.yml b/.github/workflows/closed_issue_message.yml new file mode 100644 index 0000000000..d72b7daf8f --- /dev/null +++ b/.github/workflows/closed_issue_message.yml @@ -0,0 +1,17 @@ + +name: Closed Issue Message +on: + issues: + types: [closed] +jobs: + auto_comment: + runs-on: ubuntu-latest + steps: + - uses: aws-actions/closed-issue-message@v1 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + message: | + ### ⚠️COMMENT VISIBILITY WARNING⚠️ + Comments on closed issues are hard for our team to see. + If you need more assistance, please either tag a team member or open a new issue that references this one. + If you wish to keep having a conversation with other community members under this issue feel free to do so. From b9b59187c85ba88755a91fdd2fd59e163c675a42 Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Wed, 22 Nov 2023 10:59:47 -0500 Subject: [PATCH 06/10] chore: Adding checkbox validations to the release workflow trigger (#3376) --- .github/workflows/release_kickoff.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/release_kickoff.yml b/.github/workflows/release_kickoff.yml index d26d5a0cc0..2c8a45e807 100644 --- a/.github/workflows/release_kickoff.yml +++ b/.github/workflows/release_kickoff.yml @@ -3,13 +3,34 @@ name: Release Amplify iOS on: workflow_dispatch: + inputs: + clearDays: + description: 'I confirm that today is either a CLEAR day or we have approval for release' + required: true + default: false + type: boolean + revertMain: + description: '⛔️ All previous commits to main have been reverted' + required: true + default: false + type: boolean permissions: pull-requests: write jobs: + validation: + name: Validation + runs-on: ubuntu-latest + if: inputs.clearDays == false || inputs.revertMain == false + steps: + - run: | + echo '❌ You need to acknowledge the input validations before releasing ❌' + exit 1 + release: name: Release + needs: validation runs-on: macos-12 steps: From 66c46a72d703f23834299554257bb639aee9b16b Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:10:59 -0500 Subject: [PATCH 07/10] fix(DataStore): Store larger than 32-bit values in Int64 over Int (#3367) * fix(DataStore): Store larger than 32-bit values in Int64 over Int * fix: Removing ModelFieldType.int64 --- .../ConflictResolutionDecorator.swift | 4 ++-- .../GraphQLRequest+AnyModelWithSync.swift | 6 +++--- .../Support/GraphQLDocumentInputValue.swift | 10 ++++++++++ .../Sync/ModelSync/ModelSyncMetadata.swift | 4 ++-- .../Sync/MutationSync/MutationSync.swift | 2 +- .../MutationSync/MutationSyncMetadata.swift | 6 +++--- .../AWSPluginsCore/Sync/PaginatedList.swift | 2 +- .../GraphQLSyncQueryTests.swift | 2 +- .../GraphQLRequestAnyModelWithSyncTests.swift | 8 ++++---- .../GraphQLRequestAuthRuleTests.swift | 4 ++-- ...tomPrimaryKeyWithMultipleFieldsTests.swift | 4 ++-- .../Migration/MutationSyncMetadataCopy.swift | 2 +- .../SQLite/ModelValueConverter+SQLite.swift | 8 +++++++- .../Subscribe/Support/Model+Sort.swift | 18 +++++++++++------ .../Sync/Events/OutboxMutationEvent.swift | 2 +- .../InitialSync/InitialSyncOperation.swift | 8 ++++---- .../Sync/Support/Model+Compare.swift | 13 ++++++++---- .../SQLiteStorageEngineAdapterTests.swift | 6 +++--- .../StorageAdapterMutationSyncTests.swift | 2 +- .../InitialSyncOperationTests.swift | 20 +++++++++---------- .../InitialSyncOrchestratorTests.swift | 10 +++++----- .../ModelSyncedEventEmitterTests.swift | 6 +++--- .../InitialSync/SyncEventEmitterTests.swift | 6 +++--- .../ReconcileAndLocalSaveOperationTests.swift | 16 +++++++-------- .../ReconcileAndSaveQueueTests.swift | 2 +- .../MutationEventExtensionsTests.swift | 2 +- .../Foundation+TestExtensions.swift | 4 ++-- 27 files changed, 102 insertions(+), 75 deletions(-) diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift index f775d80cd0..72ed86a415 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/ConflictResolutionDecorator.swift @@ -15,12 +15,12 @@ import Foundation public struct ConflictResolutionDecorator: ModelBasedGraphQLDocumentDecorator { private let version: Int? - private let lastSync: Int? + private let lastSync: Int64? private let graphQLType: GraphQLOperationType private var primaryKeysOnly: Bool public init(version: Int? = nil, - lastSync: Int? = nil, + lastSync: Int64? = nil, graphQLType: GraphQLOperationType, primaryKeysOnly: Bool = true) { self.version = version diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift index 0e63d0b63a..b779199e53 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift @@ -49,7 +49,7 @@ protocol ModelSyncGraphQLRequestFactory { where predicate: QueryPredicate?, limit: Int?, nextToken: String?, - lastSync: Int?, + lastSync: Int64?, authType: AWSAuthorizationType?) -> GraphQLRequest } @@ -110,7 +110,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory { where predicate: QueryPredicate? = nil, limit: Int? = nil, nextToken: String? = nil, - lastSync: Int? = nil, + lastSync: Int64? = nil, authType: AWSAuthorizationType? = nil) -> GraphQLRequest { syncQuery(modelSchema: modelType.schema, where: predicate, @@ -215,7 +215,7 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory { where predicate: QueryPredicate? = nil, limit: Int? = nil, nextToken: String? = nil, - lastSync: Int? = nil, + lastSync: Int64? = nil, authType: AWSAuthorizationType? = nil) -> GraphQLRequest { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelSchema, operationType: .query, diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInputValue.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInputValue.swift index b5bfa0f8e1..ea33ad9334 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInputValue.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/GraphQLDocumentInputValue.swift @@ -37,6 +37,16 @@ extension Int: GraphQLDocumentValueRepresentable { } } +extension Int64: GraphQLDocumentValueRepresentable { + public var graphQLDocumentValue: String { + return "\(self)" + } + + public var graphQLInlineValue: String { + return "\(self)" + } +} + extension String: GraphQLDocumentValueRepresentable { public var graphQLDocumentValue: String { return self diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/ModelSync/ModelSyncMetadata.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/ModelSync/ModelSyncMetadata.swift index 07eea52406..074a86f288 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/ModelSync/ModelSyncMetadata.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/ModelSync/ModelSyncMetadata.swift @@ -12,10 +12,10 @@ public struct ModelSyncMetadata: Model { public let id: String /// The timestamp (in Unix seconds) at which the last sync was started, as reported by the service - public var lastSync: Int? + public var lastSync: Int64? public init(id: String, - lastSync: Int?) { + lastSync: Int64?) { self.id = id self.lastSync = lastSync } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSync.swift index 65ec677e29..8348ac5132 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSync.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSync.swift @@ -87,7 +87,7 @@ public struct MutationSync: Decodable { self.syncMetadata = MutationSyncMetadata(modelId: modelIdentifier, modelName: resolvedModelName, deleted: deleted, - lastChangedAt: Int(lastChangedAt), + lastChangedAt: Int64(lastChangedAt), version: Int(version)) } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift index 8b4290e59f..1e905b5439 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/MutationSync/MutationSyncMetadata.swift @@ -14,7 +14,7 @@ public struct MutationSyncMetadata: Model { public let id: MutationSyncIdentifier public var deleted: Bool - public var lastChangedAt: Int + public var lastChangedAt: Int64 public var version: Int static let deliminator = "|" @@ -30,14 +30,14 @@ public struct MutationSyncMetadata: Model { The format of the `id` has changed to support unique ids across mutiple model types. Use init(modelId:modelName:deleted:lastChangedAt) to pass in the `modelName`. """) - public init(id: MutationSyncIdentifier, deleted: Bool, lastChangedAt: Int, version: Int) { + public init(id: MutationSyncIdentifier, deleted: Bool, lastChangedAt: Int64, version: Int) { self.id = id self.deleted = deleted self.lastChangedAt = lastChangedAt self.version = version } - public init(modelId: ModelId, modelName: String, deleted: Bool, lastChangedAt: Int, version: Int) { + public init(modelId: ModelId, modelName: String, deleted: Bool, lastChangedAt: Int64, version: Int) { self.id = Self.identifier(modelName: modelName, modelId: modelId) self.deleted = deleted self.lastChangedAt = lastChangedAt diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Sync/PaginatedList.swift b/AmplifyPlugins/Core/AWSPluginsCore/Sync/PaginatedList.swift index d1b126e9cf..b77a11cbb9 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Sync/PaginatedList.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Sync/PaginatedList.swift @@ -11,5 +11,5 @@ import Foundation public struct PaginatedList: Decodable { public let items: [MutationSync] public let nextToken: String? - public let startedAt: Int? + public let startedAt: Int64? } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift index 0426140a0f..f8d225a8c4 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLDocument/GraphQLSyncQueryTests.swift @@ -77,7 +77,7 @@ class GraphQLSyncQueryTests: XCTestCase { XCTAssertEqual(variables["nextToken"] as? String, "token") XCTAssertNotNil(variables["filter"]) XCTAssertNotNil(variables["lastSync"]) - XCTAssertEqual(variables["lastSync"] as? Int, 123) + XCTAssertEqual(variables["lastSync"] as? Int64, 123) } func testSyncGraphQLQueryForComment() { diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift index 0779edc428..7af997bad4 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAnyModelWithSyncTests.swift @@ -228,7 +228,7 @@ class GraphQLRequestAnyModelWithSyncTests: XCTestCase { let modelType = Post.self as Model.Type let nextToken = "nextToken" let limit = 100 - let lastSync = 123 + let lastSync: Int64 = 123 var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken)) @@ -274,14 +274,14 @@ class GraphQLRequestAnyModelWithSyncTests: XCTestCase { XCTAssertNotNil(variables["nextToken"]) XCTAssertEqual(variables["nextToken"] as? String, nextToken) XCTAssertNotNil(variables["lastSync"]) - XCTAssertEqual(variables["lastSync"] as? Int, lastSync) + XCTAssertEqual(variables["lastSync"] as? Int64, lastSync) } func testOptimizedSyncQueryGraphQLRequestWithFilter() { let modelType = Post.self as Model.Type let nextToken = "nextToken" let limit = 100 - let lastSync = 123 + let lastSync: Int64 = 123 let postId = "123" let predicate = Post.CodingKeys.id.eq(postId) let request = GraphQLRequest.syncQuery( @@ -310,7 +310,7 @@ class GraphQLRequestAnyModelWithSyncTests: XCTestCase { let modelType = Post.self as Model.Type let nextToken = "nextToken" let limit = 100 - let lastSync = 123 + let lastSync: Int64 = 123 let postId = "123" let altPostId = "456" let predicate = Post.CodingKeys.id.eq(postId) || Post.CodingKeys.id.eq(altPostId) diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAuthRuleTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAuthRuleTests.swift index 1e71d4db58..7c24a3182a 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAuthRuleTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestAuthRuleTests.swift @@ -303,7 +303,7 @@ class GraphQLRequestAuthRuleTests: XCTestCase { let modelType = Article.self as Model.Type let nextToken = "nextToken" let limit = 100 - let lastSync = 123 + let lastSync: Int64 = 123 var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken)) @@ -347,6 +347,6 @@ class GraphQLRequestAuthRuleTests: XCTestCase { XCTAssertNotNil(variables["nextToken"]) XCTAssertEqual(variables["nextToken"] as? String, nextToken) XCTAssertNotNil(variables["lastSync"]) - XCTAssertEqual(variables["lastSync"] as? Int, lastSync) + XCTAssertEqual(variables["lastSync"] as? Int64, lastSync) } } diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift index 29051f17f2..694dcc8600 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Model/GraphQLRequest/GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests.swift @@ -137,7 +137,7 @@ class GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests: XCTestCase { func testSyncQueryGraphQLRequestWithDateInPK() throws { let nextToken = "nextToken" let limit = 100 - let lastSync = 123 + let lastSync: Int64 = 123 var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelName: CustomerWithMultipleFieldsinPK.modelName, operationType: .query) documentBuilder.add(decorator: DirectiveNameDecorator(type: .sync)) @@ -188,7 +188,7 @@ class GraphQLRequestSyncCustomPrimaryKeyWithMultipleFieldsTests: XCTestCase { XCTAssertNotNil(variables["nextToken"]) XCTAssertEqual(variables["nextToken"] as? String, nextToken) XCTAssertNotNil(variables["lastSync"]) - XCTAssertEqual(variables["lastSync"] as? Int, lastSync) + XCTAssertEqual(variables["lastSync"] as? Int64, lastSync) } } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Migration/MutationSyncMetadataCopy.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Migration/MutationSyncMetadataCopy.swift index 592f0a62fd..855b3cf079 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Migration/MutationSyncMetadataCopy.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Migration/MutationSyncMetadataCopy.swift @@ -12,7 +12,7 @@ extension MutationSyncMetadataMigration { public struct MutationSyncMetadataCopy: Model { public let id: String public var deleted: Bool - public var lastChangedAt: Int + public var lastChangedAt: Int64 public var version: Int // MARK: - CodingKeys diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/ModelValueConverter+SQLite.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/ModelValueConverter+SQLite.swift index 369fb58981..ab1a85aed9 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/ModelValueConverter+SQLite.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/ModelValueConverter+SQLite.swift @@ -31,7 +31,13 @@ public struct SQLiteModelValueConverter: ModelValueConverter { case .string: return value as? String case .int: - return value as? Int + if let intValue = value as? Int { + return intValue + } + if let int64Value = value as? Int64 { + return int64Value + } + return nil case .double: return value as? Double case .date, .dateTime, .time: diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Subscribe/Support/Model+Sort.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Subscribe/Support/Model+Sort.swift index b9bdbfaf20..4c3b72f55c 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Subscribe/Support/Model+Sort.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Subscribe/Support/Model+Sort.swift @@ -103,13 +103,19 @@ extension ModelSchema { value2Optional: value2Optional) .sortComparator(sortOrder: sortOrder) case .int, .timestamp: - guard let value1Optional = value1 as? Int?, let value2Optional = value2 as? Int? else { - return false + if let value1Optional = value1 as? Int?, let value2Optional = value2 as? Int? { + return ModelValueCompare(value1Optional: value1Optional, + value2Optional: value2Optional) + .sortComparator(sortOrder: sortOrder) } - return ModelValueCompare(value1Optional: value1Optional, - value2Optional: value2Optional) - .sortComparator(sortOrder: sortOrder) - + + if let value1Optional = value1 as? Int64?, let value2Optional = value2 as? Int64? { + return ModelValueCompare(value1Optional: value1Optional, + value2Optional: value2Optional) + .sortComparator(sortOrder: sortOrder) + } + + return false case .double: guard let value1Optional = value1 as? Double?, let value2Optional = value2 as? Double? else { return false diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Events/OutboxMutationEvent.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Events/OutboxMutationEvent.swift index 0401663f6f..3fdf36b4d7 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Events/OutboxMutationEvent.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Events/OutboxMutationEvent.swift @@ -36,7 +36,7 @@ public struct OutboxMutationEvent { public struct OutboxMutationEventElement { public let model: Model public var version: Int? - public var lastChangedAt: Int? + public var lastChangedAt: Int64? public var deleted: Bool? } } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift index d90392260a..a81f840cd0 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift @@ -67,7 +67,7 @@ final class InitialSyncOperation: AsynchronousOperation { } } - private func getLastSyncTime() -> Int? { + private func getLastSyncTime() -> Int64? { guard !isCancelled else { finish(result: .successfulVoid) return nil @@ -109,7 +109,7 @@ final class InitialSyncOperation: AsynchronousOperation { } } - private func query(lastSyncTime: Int?, nextToken: String? = nil) async { + private func query(lastSyncTime: Int64?, nextToken: String? = nil) async { guard !isCancelled else { finish(result: .successfulVoid) return @@ -160,7 +160,7 @@ final class InitialSyncOperation: AsynchronousOperation { /// Disposes of the query results: Stops if error, reconciles results if success, and kick off a new query if there /// is a next token - private func handleQueryResults(lastSyncTime: Int?, + private func handleQueryResults(lastSyncTime: Int64?, graphQLResult: Result>) { guard !isCancelled else { finish(result: .successfulVoid) @@ -198,7 +198,7 @@ final class InitialSyncOperation: AsynchronousOperation { } } - private func updateModelSyncMetadata(lastSyncTime: Int?) { + private func updateModelSyncMetadata(lastSyncTime: Int64?) { guard !isCancelled else { finish(result: .successfulVoid) return diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/Model+Compare.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/Model+Compare.swift index 9860ae68bb..ca9595dbf1 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/Model+Compare.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/Model+Compare.swift @@ -56,12 +56,17 @@ extension ModelSchema { return false } case .int: - guard let value1Optional = value1 as? Int?, let value2Optional = value2 as? Int? else { - return false + if let value1Optional = value1 as? Int?, let value2Optional = value2 as? Int? { + if !compare(value1Optional, value2Optional) { + return false + } } - if !compare(value1Optional, value2Optional) { - return false + if let value1Optional = value1 as? Int64?, let value2Optional = value2 as? Int64? { + if !compare(value1Optional, value2Optional) { + return false + } } + return false case .double: guard let value1Optional = value1 as? Double?, let value2Optional = value2 as? Double? else { return false diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterTests.swift index 16870f2ed4..c14e909701 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterTests.swift @@ -676,7 +676,7 @@ class SQLiteStorageEngineAdapterTests: BaseDataStoreTests { let metadata = MutationSyncMetadata(modelId: modelId, modelName: modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) storageAdapter.save(metadata) { result in @@ -700,12 +700,12 @@ class SQLiteStorageEngineAdapterTests: BaseDataStoreTests { let metadata1 = MutationSyncMetadata(modelId: UUID().uuidString, modelName: modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let metadata2 = MutationSyncMetadata(modelId: UUID().uuidString, modelName: modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let saveMetadata1 = expectation(description: "save metadata1 success") diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StorageAdapterMutationSyncTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StorageAdapterMutationSyncTests.swift index f33024288b..07c9e922b2 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StorageAdapterMutationSyncTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/StorageAdapterMutationSyncTests.swift @@ -32,7 +32,7 @@ class StorageAdapterMutationSyncTests: BaseDataStoreTests { MutationSyncMetadata(modelId: $0.id, modelName: Post.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) } populateData(syncMetadataList) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift index 405bb0f18b..c320409284 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift @@ -29,7 +29,7 @@ class InitialSyncOperationTests: XCTestCase { /// - It reads sync metadata from storage func testReadsMetadata() { let responder = QueryRequestListenerResponder> { _, listener in - let startDateMilliseconds = Int(Date().timeIntervalSince1970) * 1_000 + let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000 let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -90,7 +90,7 @@ class InitialSyncOperationTests: XCTestCase { func testQueriesAPI() { let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel") let responder = QueryRequestListenerResponder> { _, listener in - let startDateMilliseconds = Int(Date().timeIntervalSince1970) * 1_000 + let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000 let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -148,7 +148,7 @@ class InitialSyncOperationTests: XCTestCase { /// - The method invokes a completion callback when complete func testInvokesPublisherCompletion() { let responder = QueryRequestListenerResponder> { _, listener in - let startDateMilliseconds = Int(Date().timeIntervalSince1970) * 1_000 + let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000 let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -203,7 +203,7 @@ class InitialSyncOperationTests: XCTestCase { var nextTokens = ["token1", "token2"] let responder = QueryRequestListenerResponder> { _, listener in - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let nextToken = nextTokens.isEmpty ? nil : nextTokens.removeFirst() let list = PaginatedList(items: [], nextToken: nextToken, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) @@ -254,13 +254,13 @@ class InitialSyncOperationTests: XCTestCase { /// - Then: /// - The method submits the returned data to the reconciliation queue func testSubmitsToReconciliationQueue() { - let startedAtMilliseconds = Int(Date().timeIntervalSince1970) * 1_000 + let startedAtMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000 let model = MockSynced(id: "1") let anyModel = AnyModel(model) let metadata = MutationSyncMetadata(modelId: "1", modelName: MockSynced.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let mutationSync = MutationSync(model: anyModel, syncMetadata: metadata) let responder = QueryRequestListenerResponder> { _, listener in @@ -331,7 +331,7 @@ class InitialSyncOperationTests: XCTestCase { /// - Then: /// - The method submits the returned data to the reconciliation queue func testUpdatesSyncMetadata() throws { - let startDateMilliseconds = Int(Date().timeIntervalSince1970) * 1_000 + let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000 let responder = QueryRequestListenerResponder> { _, listener in let startedAt = startDateMilliseconds let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt) @@ -477,7 +477,7 @@ class InitialSyncOperationTests: XCTestCase { /// - It performs a sync query against the API category with a "lastSync" time from the last start time of /// the stored metadata func testQueriesFromLastSync() throws { - let startDateMilliseconds = (Int(Date().timeIntervalSince1970) - 100) * 1_000 + let startDateMilliseconds = (Int64(Date().timeIntervalSince1970) - 100) * 1_000 let storageAdapter = try SQLiteStorageEngineAdapter(connection: Connection(.inMemory)) try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas + [MockSynced.schema]) @@ -496,7 +496,7 @@ class InitialSyncOperationTests: XCTestCase { let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel") let responder = QueryRequestListenerResponder> { request, listener in - let lastSync = request.variables?["lastSync"] as? Int + let lastSync = request.variables?["lastSync"] as? Int64 XCTAssertEqual(lastSync, startDateMilliseconds) let list = PaginatedList(items: [], nextToken: nil, startedAt: nil) @@ -548,7 +548,7 @@ class InitialSyncOperationTests: XCTestCase { func testBaseQueryWhenExpiredLastSync() throws { // Set start date to 100 seconds in the past - let startDateMilliSeconds = (Int(Date().timeIntervalSince1970) - 100) * 1_000 + let startDateMilliSeconds = (Int64(Date().timeIntervalSince1970) - 100) * 1_000 let storageAdapter = try SQLiteStorageEngineAdapter(connection: Connection(.inMemory)) try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas + [MockSynced.schema]) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift index 6d85a46cf6..5dc0082601 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift @@ -28,7 +28,7 @@ class InitialSyncOrchestratorTests: XCTestCase { ModelRegistry.reset() PostCommentModelRegistration().registerModels(registry: ModelRegistry.self) let responder = QueryRequestListenerResponder> { _, listener in - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -126,7 +126,7 @@ class InitialSyncOrchestratorTests: XCTestCase { .failure(APIError.operationError("", "", nil)) listener?(event) } else if request.document.contains("SyncComments") { - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -239,7 +239,7 @@ class InitialSyncOrchestratorTests: XCTestCase { TestModelsWithNoAssociations().registerModels(registry: ModelRegistry.self) let responder = QueryRequestListenerResponder> { _, listener in - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -306,7 +306,7 @@ class InitialSyncOrchestratorTests: XCTestCase { commentWasQueried.fulfill() } - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) listener?(event) @@ -380,7 +380,7 @@ class InitialSyncOrchestratorTests: XCTestCase { commentWasQueried.fulfill() } - let startedAt = Int(Date().timeIntervalSince1970) + let startedAt = Int64(Date().timeIntervalSince1970) let nextToken = nextTokens.isEmpty ? nil : nextTokens.removeFirst() let list = PaginatedList(items: [], nextToken: nextToken, startedAt: startedAt) let event: GraphQLOperation>.OperationResult = .success(.success(list)) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/ModelSyncedEventEmitterTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/ModelSyncedEventEmitterTests.swift index 2dabd2bac3..38780e8ee2 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/ModelSyncedEventEmitterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/ModelSyncedEventEmitterTests.swift @@ -49,7 +49,7 @@ class ModelSyncedEventEmitterTests: XCTestCase { let anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: Post.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let testPost = Post(id: "1", title: "post1", content: "content", createdAt: .now()) let anyPost = AnyModel(testPost) @@ -129,7 +129,7 @@ class ModelSyncedEventEmitterTests: XCTestCase { let anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: "", deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let testPost = Post(id: "1", title: "post1", content: "content", createdAt: .now()) let anyPost = AnyModel(testPost) @@ -202,7 +202,7 @@ class ModelSyncedEventEmitterTests: XCTestCase { let anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: Post.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let testPost = Post(id: "1", title: "post1", content: "content", createdAt: .now()) let anyPost = AnyModel(testPost) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/SyncEventEmitterTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/SyncEventEmitterTests.swift index d5f497274c..9dfbbadfa5 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/SyncEventEmitterTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/SyncEventEmitterTests.swift @@ -45,7 +45,7 @@ class SyncEventEmitterTests: XCTestCase { let anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: Post.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let anyPostMutationSync = MutationSync(model: anyPost, syncMetadata: anyPostMetadata) let postMutationEvent = try MutationEvent(untypedModel: testPost, mutationType: .create) @@ -196,7 +196,7 @@ class SyncEventEmitterTests: XCTestCase { let anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: Post.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let anyPostMutationSync = MutationSync(model: anyPost, syncMetadata: anyPostMetadata) @@ -207,7 +207,7 @@ class SyncEventEmitterTests: XCTestCase { let anyCommentMetadata = MutationSyncMetadata(modelId: "1", modelName: Comment.modelName, deleted: true, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 2) let anyCommentMutationSync = MutationSync(model: anyComment, syncMetadata: anyCommentMetadata) let commentMutationEvent = try MutationEvent(untypedModel: testComment, mutationType: .delete) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift index 2b7d4220e6..d0d19eeb4e 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift @@ -39,7 +39,7 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase { anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: testPost.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) anyPostMutationSync = MutationSync(model: anyPost, syncMetadata: anyPostMetadata) @@ -48,7 +48,7 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase { let anyPostDeleteMetadata = MutationSyncMetadata(modelId: "2", modelName: testPost.modelName, deleted: true, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 2) anyPostDeletedMutationSync = MutationSync(model: anyPostDelete, syncMetadata: anyPostDeleteMetadata) anyPostMutationEvent = MutationEvent(id: "1", @@ -338,12 +338,12 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase { let metadata1 = MutationSyncMetadata(modelId: model1.id, modelName: model1.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let metadata2 = MutationSyncMetadata(modelId: model2.id, modelName: model2.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let remoteModel1 = MutationSync(model: model1, syncMetadata: metadata1) let remoteModel2 = MutationSync(model: model2, syncMetadata: metadata2) @@ -491,12 +491,12 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase { let metadata1 = MutationSyncMetadata(modelId: model1.id, modelName: model1.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let metadata2 = MutationSyncMetadata(modelId: model2.id, modelName: model2.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) let remoteModel1 = MutationSync(model: model1, syncMetadata: metadata1) let remoteModel2 = MutationSync(model: model2, syncMetadata: metadata2) @@ -504,12 +504,12 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase { let localMetadata1 = MutationSyncMetadata(modelId: model1.id, modelName: model1.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 3) let localMetadata2 = MutationSyncMetadata(modelId: model2.id, modelName: model2.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 4) operation.publisher .sink { completion in diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndSaveQueueTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndSaveQueueTests.swift index aeb93311cb..da06a88397 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndSaveQueueTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndSaveQueueTests.swift @@ -23,7 +23,7 @@ class ReconcileAndSaveQueueTests: XCTestCase { anyPostMetadata = MutationSyncMetadata(modelId: "1", modelName: testPost.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: 1) anyPostMutationSync = MutationSync(model: anyPost, syncMetadata: anyPostMetadata) anyPostMutationSync = MutationSync(model: anyPost, syncMetadata: anyPostMetadata) diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift index e709ebe364..7938a953ff 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift @@ -361,7 +361,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests { let metadata = MutationSyncMetadata(modelId: model.identifier(schema: MutationEvent.schema).stringValue, modelName: model.modelName, deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), + lastChangedAt: Int64(Date().timeIntervalSince1970), version: version) return MutationSync(model: AnyModel(model), syncMetadata: metadata) } diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Foundation+TestExtensions.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Foundation+TestExtensions.swift index a86c286cdf..53c8c27732 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Foundation+TestExtensions.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Foundation+TestExtensions.swift @@ -8,7 +8,7 @@ import Foundation extension Date { - var unixSeconds: Int { - Int(timeIntervalSince1970) + var unixSeconds: Int64 { + Int64(timeIntervalSince1970) } } From 71c600d18344dd3ba3555bd92329e0cbe0d17bb0 Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:09:45 -0500 Subject: [PATCH 08/10] fix(storage): Fixing watchOS crash when dealing with big files (#3389) --- .../Support/Internal/Bytes.swift | 14 +++--- .../Support/Internal/FileHandle+UInt64.swift | 43 +++++++++++++++++ .../Support/Internal/FileSystem.swift | 6 +-- .../StoragePersistableTransferTask.swift | 4 +- .../StorageServiceSessionDelegate.swift | 2 +- .../Support/Internal/StorageUploadPart.swift | 32 ++++++------- .../Internal/StorageUploadPartEvent.swift | 2 +- .../Support/Internal/FileHandleTests.swift | 47 +++++++++++++++++++ .../Support/Internal/FileSystemTests.swift | 8 ++-- .../Internal/MockMultipartUploadClient.swift | 2 +- .../StorageMultipartUploadSessionTests.swift | 2 +- .../StorageTransferDatabaseTests.swift | 4 +- .../Internal/StorageUploadPartSizeTests.swift | 4 +- .../Internal/StorageUploadPartTests.swift | 8 ++-- 14 files changed, 134 insertions(+), 44 deletions(-) create mode 100644 AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileHandle+UInt64.swift create mode 100644 AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileHandleTests.swift diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/Bytes.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/Bytes.swift index 7195cfa050..af562229f4 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/Bytes.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/Bytes.swift @@ -16,22 +16,22 @@ enum Bytes { case kilobytes(Int) case bytes(Int) - var bytes: Int { + var bytes: UInt64 { switch self { case .terabytes(let tb): - return Int(pow(1_024.0, 4.0)) * tb + return UInt64(pow(1_024.0, 4.0)) * UInt64(tb) case .gigabytes(let gb): - return Int(pow(1_024.0, 3.0)) * gb + return UInt64(pow(1_024.0, 3.0)) * UInt64(gb) case .megabytes(let mb): - return Int(pow(1_024.0, 2.0)) * mb + return UInt64(pow(1_024.0, 2.0)) * UInt64(mb) case .kilobytes(let kb): - return 1_024 * kb + return 1_024 * UInt64(kb) case .bytes(let b): - return b + return UInt64(b) } } - var bits: Int { + var bits: UInt64 { return bytes * 8 } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileHandle+UInt64.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileHandle+UInt64.swift new file mode 100644 index 0000000000..be6eb4bb49 --- /dev/null +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileHandle+UInt64.swift @@ -0,0 +1,43 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +extension FileHandle { + /// Reads data synchronously up to the specified number of bytes. + /// - Parameter bytes: The number of bytes to read from the file handle. + /// - Parameter bytesReadLimit: The maximum number of bytes that can be read at a time. Defaults to `Int.max`. + /// - Returns: The data available through the receiver up to a maximum of length bytes, or the maximum size that can be represented by a Data object. + /// - Throws: An error if attempts to determine the file-handle type fail or if attempts to read from the file or channel fail. + func read(bytes: UInt64, bytesReadLimit: Int = Int.max) throws -> Data { + // Read as much as it's possible considering the `bytesReadLimit` maximum + let bytesRead = bytes <= bytesReadLimit ? Int(bytes) : bytesReadLimit + guard var data = try readData(upToCount: bytesRead) else { + // There is no more data to read from the file + return Data() + } + + // If there's remaining bytes to read, do it and append to the current data + let remainingBytes = bytes - UInt64(bytesRead) + if remainingBytes > 0 { + try data.append(read( + bytes: remainingBytes, + bytesReadLimit: bytesReadLimit + )) + } + + return data + } + + private func readData(upToCount length: Int) throws -> Data? { + if #available(iOS 13.4, macOS 10.15.4, tvOS 13.4, *) { + return try read(upToCount: length) + } else { + return readData(ofLength: length) + } + } +} diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileSystem.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileSystem.swift index 3f2bd33ae8..fd7c103414 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileSystem.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/FileSystem.swift @@ -185,7 +185,7 @@ class FileSystem { /// - offset: position to start reading /// - length: length of the part /// - completionHandler: completion handler - func createPartialFile(fileURL: URL, offset: Int, length: Int, completionHandler: @escaping (Result) -> Void) { + func createPartialFile(fileURL: URL, offset: UInt64, length: UInt64, completionHandler: @escaping (Result) -> Void) { // 4.5 MB (1 MB per part) // 1024 1024 1024 1024 512 @@ -198,8 +198,8 @@ class FileSystem { defer { try? fileHandle.close() } - try fileHandle.seek(toOffset: UInt64(offset)) - let data = fileHandle.readData(ofLength: length) + try fileHandle.seek(toOffset: offset) + let data = try fileHandle.read(bytes: length) let fileURL = try self.createTemporaryFile(data: data) completionHandler(.success(fileURL)) } catch { diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StoragePersistableTransferTask.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StoragePersistableTransferTask.swift index 5a1233f208..708dc4d93a 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StoragePersistableTransferTask.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StoragePersistableTransferTask.swift @@ -108,8 +108,8 @@ struct StoragePersistableMultipartUpload: Codable { struct StoragePersistableSubTask: Codable { let uploadId: UploadID let partNumber: PartNumber - let bytes: Int - let bytesTransferred: Int + let bytes: UInt64 + let bytesTransferred: UInt64 let taskIdentifier: TaskIdentifier? // once an UploadPart starts uploading it will have a taskIdentifier let eTag: String? diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift index 03da3cc4fe..760940dfc7 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift @@ -165,7 +165,7 @@ extension StorageServiceSessionDelegate: URLSessionTaskDelegate { return } - multipartUploadSession.handle(uploadPartEvent: .progressUpdated(partNumber: partNumber, bytesTransferred: Int(bytesSent), taskIdentifier: task.taskIdentifier)) + multipartUploadSession.handle(uploadPartEvent: .progressUpdated(partNumber: partNumber, bytesTransferred: UInt64(bytesSent), taskIdentifier: task.taskIdentifier)) case .upload(let onEvent): let progress = Progress(totalUnitCount: totalBytesExpectedToSend) progress.completedUnitCount = totalBytesSent diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPart.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPart.swift index 8481ca61a1..4e5627e20d 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPart.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPart.swift @@ -28,11 +28,11 @@ let minimumPartCount = 1 let maximumPartCount = 10_000 enum StorageUploadPart { - case pending(bytes: Int) - case queued(bytes: Int) - case inProgress(bytes: Int, bytesTransferred: Int, taskIdentifier: TaskIdentifier) - case failed(bytes: Int, bytesTransferred: Int, error: Error) - case completed(bytes: Int, eTag: String) + case pending(bytes: UInt64) + case queued(bytes: UInt64) + case inProgress(bytes: UInt64, bytesTransferred: UInt64, taskIdentifier: TaskIdentifier) + case failed(bytes: UInt64, bytesTransferred: UInt64, error: Error) + case completed(bytes: UInt64, eTag: String) var isPending: Bool { if case .pending = self { @@ -90,8 +90,8 @@ enum StorageUploadPart { return result } - var bytes: Int { - let result: Int + var bytes: UInt64 { + let result: UInt64 switch self { case .pending(let bytes), .queued(let bytes): result = bytes @@ -106,8 +106,8 @@ enum StorageUploadPart { return result } - var bytesTransferred: Int { - let result: Int + var bytesTransferred: UInt64 { + let result: UInt64 switch self { case .pending, .queued, .failed: result = 0 @@ -157,7 +157,7 @@ struct StorageUploadPartSize { case exceedsSupportedFileSize case exceedsMaximumObjectSize } - let size: Int + let size: UInt64 static let `default`: StorageUploadPartSize = StorageUploadPartSize() @@ -167,7 +167,7 @@ struct StorageUploadPartSize { /// Creates custom part size in bytes. Throws if file part is invalid. /// - Parameter size: part size - init(size: Int) throws { + init(size: UInt64) throws { if size < minimumPartSize { throw Failure.belowMinimumPartSize } else if size > maximumPartSize { @@ -215,8 +215,8 @@ struct StorageUploadPartSize { } } - func offset(for partNumber: PartNumber) -> Int { - let result = (partNumber - 1) * size + func offset(for partNumber: PartNumber) -> UInt64 { + let result = UInt64(partNumber - 1) * size return result } @@ -242,7 +242,7 @@ extension Array where Element == StorageUploadPart { throw Failure.partCountOverUpperLimit } - let remainingBytes = Int(fileSize % UInt64(size)) + let remainingBytes = fileSize % size logger.debug("count = \(count), remainingBytes = \(remainingBytes), size = \(size), totalBytes = \(fileSize)") self.init(repeating: .pending(bytes: size), count: count) @@ -298,13 +298,13 @@ extension Sequence where Element == StorageUploadPart { filter { $0.completed } } - var totalBytes: Int { + var totalBytes: UInt64 { reduce(into: 0) { result, part in result += part.bytes } } - var bytesTransferred: Int { + var bytesTransferred: UInt64 { reduce(into: 0) { result, part in result += part.bytesTransferred } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPartEvent.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPartEvent.swift index 3e926a0359..7e0981bf59 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPartEvent.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageUploadPartEvent.swift @@ -10,7 +10,7 @@ import Foundation enum StorageUploadPartEvent { case queued(partNumber: PartNumber) case started(partNumber: PartNumber, taskIdentifier: TaskIdentifier) - case progressUpdated(partNumber: PartNumber, bytesTransferred: Int, taskIdentifier: TaskIdentifier) + case progressUpdated(partNumber: PartNumber, bytesTransferred: UInt64, taskIdentifier: TaskIdentifier) case completed(partNumber: PartNumber, eTag: String, taskIdentifier: TaskIdentifier) case failed(partNumber: PartNumber, error: Error) diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileHandleTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileHandleTests.swift new file mode 100644 index 0000000000..8d12cca7af --- /dev/null +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileHandleTests.swift @@ -0,0 +1,47 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import AWSS3StoragePlugin + +class FileHandleTests: XCTestCase { + + /// Given: A FileHandle and a file + /// When: `read(bytes:bytesReadLimit)` is invoked with `bytesReadLimit` being lower than `bytes` + /// Then: Only `bytesReadLimit` bytes will be read at a time, but all `bytes` will be read and returned + func testRead_withBytesHigherThanLimit_shouldSucceedByReadingMultipleTimes() throws { + let sourceString = "012345678910" // 11 bytes + let sourceData = sourceString.data(using: .utf8)! + let sourceFile = try createFile(from: sourceData) + XCTAssertEqual(try StorageRequestUtils.getSize(sourceFile), UInt64(sourceString.count)) + + let fileSystem = FileSystem() + let bytesReadLimit = 2 + + let fileHandle = try FileHandle(forReadingFrom: sourceFile) + let firstPartData = try fileHandle.read(bytes: 5, bytesReadLimit: bytesReadLimit) + let firstPartString = String(decoding: firstPartData, as: UTF8.self) + XCTAssertEqual(firstPartString, "01234") // i.e. the first 5 bytes + + let secondPartData = try fileHandle.read(bytes: 5, bytesReadLimit: bytesReadLimit) + let secondPartString = String(decoding: secondPartData, as: UTF8.self) + XCTAssertEqual(secondPartString, "56789") // i.e. the second 5 bytes + + let thirdPartData = try fileHandle.read(bytes: 5, bytesReadLimit: bytesReadLimit) + let thirdPartString = String(decoding: thirdPartData, as: UTF8.self) + XCTAssertEqual(thirdPartString, "10") // i.e. the remaining bytes + + try FileManager.default.removeItem(at: sourceFile) + } + + private func createFile(from data: Data) throws -> URL { + let fileUrl = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + .appendingPathComponent("\(UUID().uuidString).tmp") + try data.write(to: fileUrl, options: .atomic) + return fileUrl + } +} diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileSystemTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileSystemTests.swift index 2ccce7c3ab..7c573bd843 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileSystemTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/FileSystemTests.swift @@ -18,7 +18,7 @@ extension FileSystem { /// - Parameter bytes: bytes /// - Returns: random data func randomData(bytes: Bytes) -> Data { - let count = bytes.bytes + let count = Int(bytes.bytes) var bytes = [Int8](repeating: 0, count: count) // Fill bytes with secure random data let status = SecRandomCopyBytes( @@ -174,7 +174,7 @@ class FileSystemTests: XCTestCase { defer { fs.removeFileIfExists(fileURL: fileURL) } - var offset = 0 + var offset: UInt64 = 0 var step: ((Int) -> Void)? let queue = DispatchQueue(label: "done-count-queue") @@ -190,7 +190,7 @@ class FileSystemTests: XCTestCase { let part = parts[index] print("Creating partial file [\(index)]") - fs.createPartialFile(fileURL: fileURL, offset: offset, length: part.count) { result in + fs.createPartialFile(fileURL: fileURL, offset: offset, length: UInt64(part.count)) { result in do { let partFileURL = try result.get() let fileContents = try String(contentsOf: partFileURL) @@ -210,7 +210,7 @@ class FileSystemTests: XCTestCase { XCTFail("Failed to create partial file: \(error)") } } - offset += part.count + offset += UInt64(part.count) step?(index + 1) } diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/MockMultipartUploadClient.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/MockMultipartUploadClient.swift index 2800a2bb73..49a4bb3265 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/MockMultipartUploadClient.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/MockMultipartUploadClient.swift @@ -21,7 +21,7 @@ class MockMultipartUploadClient: StorageMultipartUploadClient { var didCreate: ((StorageMultipartUploadSession) -> Void)? var didStartPartUpload: ((StorageMultipartUploadSession, PartNumber) -> Void)? - var didTransferBytesForPartUpload: ((StorageMultipartUploadSession, PartNumber, Int) -> Void)? + var didTransferBytesForPartUpload: ((StorageMultipartUploadSession, PartNumber, UInt64) -> Void)? var shouldFailPartUpload: ((StorageMultipartUploadSession, PartNumber) -> Bool)? var didCompletePartUpload: ((StorageMultipartUploadSession, PartNumber, String, TaskIdentifier) -> Void)? var didFailPartUpload: ((StorageMultipartUploadSession, PartNumber, Error) -> Void)? diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift index dad5a0a531..1ae2882915 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageMultipartUploadSessionTests.swift @@ -321,7 +321,7 @@ class StorageMultipartUploadSessionTests: XCTestCase { // MARK: - Private - private func createFile() throws -> URL { - let size = minimumPartSize + let size = Int(minimumPartSize) let parts: [String] = [ Array(repeating: "a", count: size).joined(), Array(repeating: "b", count: size).joined(), diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift index 15719cde1c..884d4741c1 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageTransferDatabaseTests.swift @@ -196,11 +196,11 @@ class StorageTransferDatabaseTests: XCTestCase { subTask.uploadPart = .pending(bytes: Bytes.megabytes(5).bytes) if index == 0 { parts[index] = .inProgress(bytes: part.bytes, - bytesTransferred: Int(Double(part.bytes) * 0.75), + bytesTransferred: UInt64(Double(part.bytes) * 0.75), taskIdentifier: sessionTask.taskIdentifier) } else if index == 1 { parts[index] = .inProgress(bytes: part.bytes, - bytesTransferred: Int(Double(part.bytes) * 0.25), + bytesTransferred: UInt64(Double(part.bytes) * 0.25), taskIdentifier: sessionTask.taskIdentifier) } } else { diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartSizeTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartSizeTests.swift index c73c771480..2fb9d9429a 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartSizeTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartSizeTests.swift @@ -48,7 +48,7 @@ class StorageUploadPartSizeTests: XCTestCase { func testUploadPartSizeForLargeValidFile() throws { // use a file size which requires increasing from minimum part size - let fileSize = UInt64(minimumPartSize * maximumPartCount * 10) + let fileSize = minimumPartSize * UInt64(maximumPartCount) * 10 let partSize = assertNoThrow(try StorageUploadPartSize(fileSize: fileSize)) XCTAssertNotNil(partSize) if let partSize = partSize, @@ -62,7 +62,7 @@ class StorageUploadPartSizeTests: XCTestCase { func testUploadPartSizeForSuperCrazyBigFile() throws { // use the maximum object size / max part count - let fileSize = UInt64(maximumObjectSize / maximumPartCount) + let fileSize = maximumObjectSize / UInt64(maximumPartCount) let partSize = assertNoThrow(try StorageUploadPartSize(fileSize: fileSize)) XCTAssertNotNil(partSize) if let partSize = partSize, diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartTests.swift index 8d7433563a..73bad639ca 100644 --- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartTests.swift +++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/StorageUploadPartTests.swift @@ -15,9 +15,9 @@ class StorageUploadPartTests: XCTestCase { func testUploadPartCreation() throws { // Creates an array of upload parts with 21 parts // where the last part is 512 bytes. - let lastPartSize = 512 + let lastPartSize: UInt64 = 512 let partSize: StorageUploadPartSize = .default - let fileSize: UInt64 = UInt64(partSize.size * 20 + lastPartSize) + let fileSize = partSize.size * 20 + lastPartSize let parts = try StorageUploadParts(fileSize: fileSize, partSize: partSize) XCTAssertEqual(parts.count, 21) XCTAssertEqual(parts.pending.count, parts.count) @@ -43,8 +43,8 @@ class StorageUploadPartTests: XCTestCase { XCTAssertEqual(parts.inProgress.count, parts.count) XCTAssertEqual(parts.failed.count, 0) XCTAssertEqual(parts.completed.count, 0) - XCTAssertEqual(parts.totalBytes, 100 * parts.count) - XCTAssertEqual(parts.bytesTransferred, 50 * parts.count) + XCTAssertEqual(parts.totalBytes, UInt64(100 * parts.count)) + XCTAssertEqual(parts.bytesTransferred, UInt64(50 * parts.count)) XCTAssertEqual(parts.percentTransferred, 0.5) } From 837949179ef78e7caa82efc17a81234a85184944 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Tue, 5 Dec 2023 08:57:15 -0800 Subject: [PATCH 09/10] fix: appsync-realtime-client 3.1.2 watchOS support (#3395) --- Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index 455d919c65..bec1dc9ab3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git", "state" : { - "revision" : "c7ec93dcbbcd8abc90c74203937f207a7fcaa611", - "version" : "3.1.1" + "revision" : "a08684c5004e2049c29f57a5938beae9695a1ef7", + "version" : "3.1.2" } }, { From 274ca6a6f78eff326e68f3cca0c425d80ed7f3cb Mon Sep 17 00:00:00 2001 From: Di Wu Date: Tue, 5 Dec 2023 09:36:59 -0800 Subject: [PATCH 10/10] Revert "chore: Adding checkbox validations to the release workflow trigger (#3376)" (#3397) This reverts commit b9b59187c85ba88755a91fdd2fd59e163c675a42. --- .github/workflows/release_kickoff.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/release_kickoff.yml b/.github/workflows/release_kickoff.yml index 2c8a45e807..d26d5a0cc0 100644 --- a/.github/workflows/release_kickoff.yml +++ b/.github/workflows/release_kickoff.yml @@ -3,34 +3,13 @@ name: Release Amplify iOS on: workflow_dispatch: - inputs: - clearDays: - description: 'I confirm that today is either a CLEAR day or we have approval for release' - required: true - default: false - type: boolean - revertMain: - description: '⛔️ All previous commits to main have been reverted' - required: true - default: false - type: boolean permissions: pull-requests: write jobs: - validation: - name: Validation - runs-on: ubuntu-latest - if: inputs.clearDays == false || inputs.revertMain == false - steps: - - run: | - echo '❌ You need to acknowledge the input validations before releasing ❌' - exit 1 - release: name: Release - needs: validation runs-on: macos-12 steps: