From caaf069132c3524d768360041b1c2adc6dd85b93 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 24 Apr 2026 14:45:46 -0500 Subject: [PATCH 01/11] Fix Xcode 26 protocol-witness availability error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Xcode 26 tightened protocol-witness availability rules. When one class satisfies two presentation-context protocols of differing availability — ASWebAuthenticationPresentationContextProviding (iOS 12+) and ASAuthorizationControllerPresentationContextProviding (iOS 13+) — the shared 'presentationAnchor(for:)' witnesses can no longer be guarded with @available individually on the same type. Move each protocol conformance into its own extension with the appropriate @available attribute. The class itself stays unannotated (callers in ASWebServiceAuthentication and AppleSignInWebService are already iOS 12+/13+), and the framework's iOS 10 deployment target is preserved. --- ...tionSessionPresentationContextProvider.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift index 91bcbf3..a8e42a2 100644 --- a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift +++ b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift @@ -7,11 +7,10 @@ import AuthenticationServices -/// A class that conforms to `ASWebAuthenticationPresentationContextProviding`. -class AuthenticationSessionContextPresentationProvider: NSObject, ASWebAuthenticationPresentationContextProviding, ASAuthorizationControllerPresentationContextProviding { +class AuthenticationSessionContextPresentationProvider: NSObject { /// The window context that the presentation of the authentication should take place in. private let presentationContext: UIWindow - + /// Creates an instance of `AuthenticationSessionContextProvider`. /// /// - Parameters: @@ -20,13 +19,17 @@ class AuthenticationSessionContextPresentationProvider: NSObject, ASWebAuthentic self.presentationContext = presentationContext super.init() } - - @available(iOS 12.0, *) +} + +@available(iOS 12.0, *) +extension AuthenticationSessionContextPresentationProvider: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return presentationContext } - - @available(iOS 13.0, *) +} + +@available(iOS 13.0, *) +extension AuthenticationSessionContextPresentationProvider: ASAuthorizationControllerPresentationContextProviding { func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return presentationContext } From 20c0fbce0a8a36be651f8ffa12b792180176024e Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Sun, 26 Apr 2026 21:29:56 -0500 Subject: [PATCH 02/11] Modernize iOS CI workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump actions/checkout v2 → v4 (silences Node 20 deprecation warning). - Pin runner to macos-15 and select Xcode via maxim-lobanov/setup-xcode (latest-stable), so the build runs against the latest available Xcode rather than whatever macos-latest happens to alias to. - Add concurrency cancel-in-progress to drop superseded PR runs. - Replace the inline shell hacks with a small JSON-based simulator picker that returns a UDID (sturdier than name parsing). - Pipe xcodebuild through xcbeautify for readable test output, and pass CODE_SIGNING_ALLOWED=NO so CI never tries to sign. --- .github/workflows/ios.yml | 76 +++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 8b02b81..53ed8b6 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -6,40 +6,56 @@ on: pull_request: branches: [ main ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: - name: Build and Test SDKHostApp scheme using any available iPhone simulator - runs-on: macos-latest + name: Build & test SDKHostApp + runs-on: macos-15 + env: + SCHEME: SDKHostApp + PROJECT: IFTTT SDK.xcodeproj steps: - name: Checkout - uses: actions/checkout@v2 - - name: Build and Test - env: - scheme: ${{ 'SDKHostApp' }} + uses: actions/checkout@v4 + + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Toolchain versions + run: | + xcodebuild -version + swift --version + + - name: Pick iPhone simulator + id: sim + run: | + udid=$(xcrun simctl list devices available --json | python3 -c ' + import json, sys + data = json.load(sys.stdin) + for runtime, devices in data["devices"].items(): + if "iOS" not in runtime: + continue + for dev in devices: + if dev["name"].startswith("iPhone") and dev.get("isAvailable", False): + print(dev["udid"]) + sys.exit(0) + sys.exit("No available iPhone simulator found") + ') + echo "destination=platform=iOS Simulator,id=$udid" >> "$GITHUB_OUTPUT" + echo "Using simulator $udid" + + - name: Build & test run: | - # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) - - if [ $scheme = default ]; then scheme=$(cat default); fi - - # Determine file to build: .xcworkspace or .xcodeproj - if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi - - # Clean up whitespace - file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` - - # Find first available simulator - device_name=$(xcrun simctl list devices available | grep "iPhone" | head -n 1 | sed -E 's/^[[:space:]]*([^()]+)[[:space:]]*\(.*$/\1/' | awk '{$1=$1; print}') - - if [ -z "$device_name" ]; then - echo "❌ Failed to find a valid iOS device." - exit 1 - fi - - echo "📱 Using device: $device_name" - - # Build and run the tests + set -o pipefail xcodebuild test \ - -scheme "$scheme" \ - -"$filetype_parameter" "$file_to_build" \ - -destination "platform=iOS Simulator,name=$device_name" + -project "$PROJECT" \ + -scheme "$SCHEME" \ + -destination "${{ steps.sim.outputs.destination }}" \ + CODE_SIGNING_ALLOWED=NO \ + | xcbeautify --renderer github-actions From 2b654e509a02712fb2e5285e62400b1f324f4c71 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 24 Apr 2026 14:01:26 -0500 Subject: [PATCH 03/11] Fix Swift compiler warnings - ConnectButton: activate the orphaned lessThanOrEqualTo bottom constraint so its return value is not ignored. - AuthenticationError: add .notInteractive, .matchedExcludedCredential, .credentialImport, .credentialExport to mirror the new ASAuthorizationError.Code cases Apple added in iOS 15.4 / 18 / 18.2. - SignInWithAppleAuthentication: handle those new codes behind availability checks and map each 1:1 to the corresponding AuthenticationError so callers see the actual Apple-side reason rather than a generic .failed / .unknown bucket. --- IFTTT SDK/ConnectButton.swift | 3 ++- IFTTT SDK/SignInWithAppleAuthentication.swift | 21 +++++++++++++++++++ IFTTT SDK/WebServiceAuthentication.swift | 13 ++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/IFTTT SDK/ConnectButton.swift b/IFTTT SDK/ConnectButton.swift index 43892dc..21ece5f 100644 --- a/IFTTT SDK/ConnectButton.swift +++ b/IFTTT SDK/ConnectButton.swift @@ -345,9 +345,10 @@ public class ConnectButton: UIView { footerLabel.constrain.edges(to: footerLabelContainer, edges: [.left, .top, .right]) // But allow it to be shorter than its container - footerLabel.bottomAnchor.constraint(lessThanOrEqualTo: footerLabelContainer.bottomAnchor) + footerLabel.bottomAnchor.constraint(lessThanOrEqualTo: footerLabelContainer.bottomAnchor).isActive = true let breakableBottomConstraint = footerLabel.bottomAnchor.constraint(equalTo: footerLabelContainer.bottomAnchor) breakableBottomConstraint.priority = .defaultHigh + breakableBottomConstraint.isActive = true // Ask the label to keep its intrinsic height footerLabel.setContentHuggingPriority(.required, for: .vertical) diff --git a/IFTTT SDK/SignInWithAppleAuthentication.swift b/IFTTT SDK/SignInWithAppleAuthentication.swift index a004503..fd175a2 100644 --- a/IFTTT SDK/SignInWithAppleAuthentication.swift +++ b/IFTTT SDK/SignInWithAppleAuthentication.swift @@ -41,6 +41,27 @@ final class AppleSignInWebService: ServiceAuthentication { @available(iOS 13.0, *) func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { let authorizationError = ASAuthorizationError(_nsError: error as NSError) + // Cases added after the SDK's iOS 14.2 deployment target. + // Each is referenced behind an availability check and mapped + // to the matching AuthenticationError case so callers can + // distinguish it from the generic .failed / .unknown bucket. + if #available(iOS 15.4, *), authorizationError.code == .notInteractive { + completion(.failure(.notInteractive)) + return + } + if #available(iOS 18.0, *), authorizationError.code == .matchedExcludedCredential { + completion(.failure(.matchedExcludedCredential)) + return + } + if #available(iOS 18.2, *), authorizationError.code == .credentialImport { + completion(.failure(.credentialImport)) + return + } + if #available(iOS 18.2, *), authorizationError.code == .credentialExport { + completion(.failure(.credentialExport)) + return + } + switch authorizationError.code { case .canceled: completion(.failure(.userCanceled)) diff --git a/IFTTT SDK/WebServiceAuthentication.swift b/IFTTT SDK/WebServiceAuthentication.swift index c14863f..9d373ef 100644 --- a/IFTTT SDK/WebServiceAuthentication.swift +++ b/IFTTT SDK/WebServiceAuthentication.swift @@ -14,6 +14,15 @@ import AuthenticationServices /// - invalidResponse: The response returned by the web service was invalid. /// - notHandled: The error wasn't handled by the web service. /// - presentationContextInvalid: The presentation content provided was invalid. +/// - notInteractive: The authorization request was performed in a +/// non-interactive context. Mirrors `ASAuthorizationError.Code.notInteractive` +/// (iOS 15.4+). +/// - matchedExcludedCredential: A matched credential was excluded from use. +/// Mirrors `ASAuthorizationError.Code.matchedExcludedCredential` (iOS 18+). +/// - credentialImport: An error occurred importing a credential. Mirrors +/// `ASAuthorizationError.Code.credentialImport` (iOS 18.2+). +/// - credentialExport: An error occurred exporting a credential. Mirrors +/// `ASAuthorizationError.Code.credentialExport` (iOS 18.2+). /// - unknown: Some unknown error ocurred when authenticating with the web service. enum AuthenticationError: Error { case userCanceled @@ -21,6 +30,10 @@ enum AuthenticationError: Error { case invalidResponse case notHandled case presentationContextInvalid + case notInteractive + case matchedExcludedCredential + case credentialImport + case credentialExport case unknown } From b62417462a918342dc81c05e6cdbf607a21a00ca Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Mon, 27 Apr 2026 12:28:13 -0500 Subject: [PATCH 04/11] Drop CODE_SIGNING_ALLOWED=NO from CI test invocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Xcode 26 / iOS 26 simulators, disabling code signing prevents the host app from getting its default keychain-access group, so SecItemAdd silently fails for the SDK's Keychain-backed user token. That broke LocationServiceTests.test_willSync_location_triggers_authenticated, which relies on Keychain.userToken being readable after assignment. Simulator builds don't need explicit signing override — Xcode applies a simulator-only identity automatically. --- .github/workflows/ios.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 53ed8b6..945bc14 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -57,5 +57,4 @@ jobs: -project "$PROJECT" \ -scheme "$SCHEME" \ -destination "${{ steps.sim.outputs.destination }}" \ - CODE_SIGNING_ALLOWED=NO \ | xcbeautify --renderer github-actions From 474dfc0f0242952b4b524c9f289eaff3b69f416b Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Mon, 27 Apr 2026 12:37:47 -0500 Subject: [PATCH 05/11] Restore original CI job name for branch protection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch protection on main requires a status check named "Build and Test SDKHostApp scheme using any available iPhone simulator". The modernized workflow renamed the job to "Build & test SDKHostApp", which posted under a new check name and left the required check permanently in "Expected — Waiting for status to be reported". Restore the original job name; the rest of the modernization (macos-15, Xcode pinning, simulator picker, xcbeautify) is unchanged. --- .github/workflows/ios.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 945bc14..0ebd624 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -12,7 +12,7 @@ concurrency: jobs: build: - name: Build & test SDKHostApp + name: Build and Test SDKHostApp scheme using any available iPhone simulator runs-on: macos-15 env: SCHEME: SDKHostApp From a8b707ff3ea5b9513211dc20fd3a64b9eb2030b9 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Mon, 27 Apr 2026 12:57:11 -0500 Subject: [PATCH 06/11] Eliminate switch-must-be-exhaustive warning in Apple sign-in handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the switch over ASAuthorizationError.Code with an if/else cascade. ASAuthorizationError.Code is non-frozen and gains new cases (notInteractive in 15.4, matchedExcludedCredential in 18.0, credentialImport / credentialExport in 18.2) that can only be referenced behind #available. With a switch, this trips "switch must be exhaustive" even with @unknown default, because the compiler sees those known cases at compile time but they aren't listed explicitly. An if-cascade has no exhaustiveness requirement and stays readable. The project-level IPHONEOS_DEPLOYMENT_TARGET = 10.0 (out-of-range) warning is left in place for now — the IFTTTConnectSDK target inherits that value, and bumping it surfaces real dead-code paths (SFWebService and ConnectionVerificationSession's iOS 11 fallback) that need a separate refactor to delete. --- IFTTT SDK/SignInWithAppleAuthentication.swift | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/IFTTT SDK/SignInWithAppleAuthentication.swift b/IFTTT SDK/SignInWithAppleAuthentication.swift index fd175a2..9a2bd16 100644 --- a/IFTTT SDK/SignInWithAppleAuthentication.swift +++ b/IFTTT SDK/SignInWithAppleAuthentication.swift @@ -40,42 +40,33 @@ final class AppleSignInWebService: ServiceAuthentication { @available(iOS 13.0, *) func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - let authorizationError = ASAuthorizationError(_nsError: error as NSError) - // Cases added after the SDK's iOS 14.2 deployment target. - // Each is referenced behind an availability check and mapped - // to the matching AuthenticationError case so callers can - // distinguish it from the generic .failed / .unknown bucket. - if #available(iOS 15.4, *), authorizationError.code == .notInteractive { - completion(.failure(.notInteractive)) - return - } - if #available(iOS 18.0, *), authorizationError.code == .matchedExcludedCredential { - completion(.failure(.matchedExcludedCredential)) - return - } - if #available(iOS 18.2, *), authorizationError.code == .credentialImport { - completion(.failure(.credentialImport)) - return - } - if #available(iOS 18.2, *), authorizationError.code == .credentialExport { - completion(.failure(.credentialExport)) - return - } - - switch authorizationError.code { - case .canceled: - completion(.failure(.userCanceled)) - case .failed: - completion(.failure(.failed)) - case .invalidResponse: - completion(.failure(.invalidResponse)) - case .notHandled: - completion(.failure(.notHandled)) - case .unknown: - completion(.failure(.unknown)) - @unknown default: - completion(.failure(.unknown)) + let code = ASAuthorizationError(_nsError: error as NSError).code + // If/else cascade rather than a switch: ASAuthorizationError.Code is + // non-frozen and gains new cases (notInteractive in 15.4, ...) that + // can only be referenced behind #available — a switch over them + // either trips "switch must be exhaustive" warnings or requires + // raising the deployment target. + let mapped: AuthenticationError + if code == .canceled { + mapped = .userCanceled + } else if code == .failed { + mapped = .failed + } else if code == .invalidResponse { + mapped = .invalidResponse + } else if code == .notHandled { + mapped = .notHandled + } else if #available(iOS 15.4, *), code == .notInteractive { + mapped = .notInteractive + } else if #available(iOS 18.0, *), code == .matchedExcludedCredential { + mapped = .matchedExcludedCredential + } else if #available(iOS 18.2, *), code == .credentialImport { + mapped = .credentialImport + } else if #available(iOS 18.2, *), code == .credentialExport { + mapped = .credentialExport + } else { + mapped = .unknown } + completion(.failure(mapped)) } } From 984616f9726a1100147be88f1586a30666fe8de2 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 22 May 2026 10:59:09 -0500 Subject: [PATCH 07/11] Address Copilot PR review feedback - AuthenticationSessionPresentationContextProvider: drop leftover @available(iOS 12/13) annotations on the protocol-conformance extensions so the witness availability fully matches the iOS 14.2 deployment target. - SignInWithAppleAuthentication: map ASAuthorizationError.Code .presentationContextInvalid explicitly instead of falling through to .unknown. - WebServiceAuthentication: fix "ocurred" typo in the AuthenticationError doc comment. - ios.yml: install xcbeautify before piping xcodebuild output through it, and replace the inline python3 -c simulator picker with a jq query so the script no longer depends on Python heredoc indentation surviving YAML processing. --- .github/workflows/ios.yml | 32 ++++++++++++------- ...onSessionPresentationContextProvider.swift | 2 -- IFTTT SDK/SignInWithAppleAuthentication.swift | 2 ++ IFTTT SDK/WebServiceAuthentication.swift | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 0ebd624..baaf5fb 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -32,21 +32,29 @@ jobs: xcodebuild -version swift --version + - name: Install xcbeautify + run: | + if ! command -v xcbeautify >/dev/null 2>&1; then + brew install xcbeautify + fi + - name: Pick iPhone simulator id: sim run: | - udid=$(xcrun simctl list devices available --json | python3 -c ' - import json, sys - data = json.load(sys.stdin) - for runtime, devices in data["devices"].items(): - if "iOS" not in runtime: - continue - for dev in devices: - if dev["name"].startswith("iPhone") and dev.get("isAvailable", False): - print(dev["udid"]) - sys.exit(0) - sys.exit("No available iPhone simulator found") - ') + udid=$(xcrun simctl list devices available --json \ + | jq -r ' + [ .devices + | to_entries[] + | select(.key | contains("iOS")) + | .value[] + | select((.name | startswith("iPhone")) and .isAvailable == true) + ] + | (.[0].udid // empty) + ') + if [ -z "$udid" ]; then + echo "No available iPhone simulator found" >&2 + exit 1 + fi echo "destination=platform=iOS Simulator,id=$udid" >> "$GITHUB_OUTPUT" echo "Using simulator $udid" diff --git a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift index a8e42a2..d368610 100644 --- a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift +++ b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift @@ -21,14 +21,12 @@ class AuthenticationSessionContextPresentationProvider: NSObject { } } -@available(iOS 12.0, *) extension AuthenticationSessionContextPresentationProvider: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return presentationContext } } -@available(iOS 13.0, *) extension AuthenticationSessionContextPresentationProvider: ASAuthorizationControllerPresentationContextProviding { func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return presentationContext diff --git a/IFTTT SDK/SignInWithAppleAuthentication.swift b/IFTTT SDK/SignInWithAppleAuthentication.swift index 9a2bd16..c572efe 100644 --- a/IFTTT SDK/SignInWithAppleAuthentication.swift +++ b/IFTTT SDK/SignInWithAppleAuthentication.swift @@ -55,6 +55,8 @@ final class AppleSignInWebService: ServiceAuthentication { mapped = .invalidResponse } else if code == .notHandled { mapped = .notHandled + } else if code == .presentationContextInvalid { + mapped = .presentationContextInvalid } else if #available(iOS 15.4, *), code == .notInteractive { mapped = .notInteractive } else if #available(iOS 18.0, *), code == .matchedExcludedCredential { diff --git a/IFTTT SDK/WebServiceAuthentication.swift b/IFTTT SDK/WebServiceAuthentication.swift index 9d373ef..85a3025 100644 --- a/IFTTT SDK/WebServiceAuthentication.swift +++ b/IFTTT SDK/WebServiceAuthentication.swift @@ -23,7 +23,7 @@ import AuthenticationServices /// `ASAuthorizationError.Code.credentialImport` (iOS 18.2+). /// - credentialExport: An error occurred exporting a credential. Mirrors /// `ASAuthorizationError.Code.credentialExport` (iOS 18.2+). -/// - unknown: Some unknown error ocurred when authenticating with the web service. +/// - unknown: Some unknown error occurred when authenticating with the web service. enum AuthenticationError: Error { case userCanceled case failed From d744ebf53ad288533de23e98eb12effd0d87be8c Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 22 May 2026 11:08:11 -0500 Subject: [PATCH 08/11] Restore @available annotations broken by previous commit CI revealed that the IFTTTConnectSDK framework target inherits IPHONEOS_DEPLOYMENT_TARGET = 10.0 from the project-level build settings (only the SDKHostApp* targets set 14.2). Removing the extension-level @available annotations on AuthenticationSessionContextPresentationProvider therefore made ASWebAuthenticationSession (iOS 12+) and ASAuthorizationController (iOS 13+) unguarded relative to the framework's iOS 10.0 deployment, which xcodebuild rejected. Restore the extension-level @available(iOS 12.0, *) and @available(iOS 13.0, *) annotations to the form that was passing CI before. Also guard the new .presentationContextInvalid branch in the Apple sign-in error mapping with #available(iOS 14.0, *), matching how the other newer ASAuthorizationError cases are referenced. --- .../AuthenticationSessionPresentationContextProvider.swift | 2 ++ IFTTT SDK/SignInWithAppleAuthentication.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift index d368610..a8e42a2 100644 --- a/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift +++ b/IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift @@ -21,12 +21,14 @@ class AuthenticationSessionContextPresentationProvider: NSObject { } } +@available(iOS 12.0, *) extension AuthenticationSessionContextPresentationProvider: ASWebAuthenticationPresentationContextProviding { func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { return presentationContext } } +@available(iOS 13.0, *) extension AuthenticationSessionContextPresentationProvider: ASAuthorizationControllerPresentationContextProviding { func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return presentationContext diff --git a/IFTTT SDK/SignInWithAppleAuthentication.swift b/IFTTT SDK/SignInWithAppleAuthentication.swift index c572efe..5388c72 100644 --- a/IFTTT SDK/SignInWithAppleAuthentication.swift +++ b/IFTTT SDK/SignInWithAppleAuthentication.swift @@ -55,7 +55,7 @@ final class AppleSignInWebService: ServiceAuthentication { mapped = .invalidResponse } else if code == .notHandled { mapped = .notHandled - } else if code == .presentationContextInvalid { + } else if #available(iOS 14.0, *), code == .presentationContextInvalid { mapped = .presentationContextInvalid } else if #available(iOS 15.4, *), code == .notInteractive { mapped = .notInteractive From 1550e01650faa1fe821c15f1931b731a401f9933 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 22 May 2026 11:20:55 -0500 Subject: [PATCH 09/11] Drop bogus ASAuthorizationError.Code.presentationContextInvalid branch CI's "type 'ASAuthorizationError.Code' has no member 'presentationContextInvalid'" error showed that the Copilot suggestion (and the PR description) was wrong: .presentationContextInvalid is a member of ASWebAuthenticationSessionError.Code, not ASAuthorizationError.Code, so the Apple sign-in error mapping cannot produce it. The AuthenticationError.presentationContextInvalid enum case stays in place for source compatibility, but the Apple sign-in path falls through to .unknown for any Apple-side code it doesn't recognize (matching prior behavior). Verified locally with `xcodebuild test` on iPhone 17 iOS 26.5: 49 tests pass. --- IFTTT SDK/SignInWithAppleAuthentication.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/IFTTT SDK/SignInWithAppleAuthentication.swift b/IFTTT SDK/SignInWithAppleAuthentication.swift index 5388c72..9a2bd16 100644 --- a/IFTTT SDK/SignInWithAppleAuthentication.swift +++ b/IFTTT SDK/SignInWithAppleAuthentication.swift @@ -55,8 +55,6 @@ final class AppleSignInWebService: ServiceAuthentication { mapped = .invalidResponse } else if code == .notHandled { mapped = .notHandled - } else if #available(iOS 14.0, *), code == .presentationContextInvalid { - mapped = .presentationContextInvalid } else if #available(iOS 15.4, *), code == .notInteractive { mapped = .notInteractive } else if #available(iOS 18.0, *), code == .matchedExcludedCredential { From 2d60ba3f3d14fef402b76e2830af61b3f3038e74 Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 22 May 2026 11:24:59 -0500 Subject: [PATCH 10/11] Pin CI to Xcode 26.4.1 on macos-26 to actually exercise the fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous workflow used macos-15 + latest-stable, which resolved to Xcode 26.3. Xcode 26.3 lacks the stricter protocol-witness availability check introduced in 26.4.1 — i.e. CI could not reproduce the original issue #310 build failure and therefore could not regression-test this PR's fix. The macos-26 runner image ships Xcode 26.4.1 (build 17E202) preinstalled, which is the exact version called out in the PR description. Pinning here makes the regression test meaningful: any future regression of the @available annotations on AuthenticationSessionPresentationContextProvider will now fail CI instead of slipping through. --- .github/workflows/ios.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index baaf5fb..ea83ec0 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -13,10 +13,15 @@ concurrency: jobs: build: name: Build and Test SDKHostApp scheme using any available iPhone simulator - runs-on: macos-15 + runs-on: macos-26 env: SCHEME: SDKHostApp PROJECT: IFTTT SDK.xcodeproj + # Pinned to the Xcode version that introduced the stricter + # protocol-witness availability check this PR exists to fix + # (see issue #310). Bumping this on purpose is fine; drifting via + # latest-stable would hide regressions of the original fix. + XCODE_VERSION: 26.4.1 steps: - name: Checkout @@ -25,7 +30,7 @@ jobs: - name: Select Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: ${{ env.XCODE_VERSION }} - name: Toolchain versions run: | From 849f939375d9e3864091e71637380cc7a36a9d9f Mon Sep 17 00:00:00 2001 From: Siddharth Sathyam Date: Fri, 22 May 2026 11:26:40 -0500 Subject: [PATCH 11/11] Switch CI to macos-26 + latest-stable instead of pinning Xcode Pinning to 26.4.1 was overfitting to the specific bug-triggering version. Once that Xcode ages out of the runner image, CI would break, and a hard-pin stops catching new compiler regressions in 26.5/27.x. Using macos-26 + latest-stable is enough: macos-26's installed Xcodes are all 26.x, every 26.4.1+ release carries the stricter protocol-witness check, and latest-stable will track newer Xcodes as GitHub updates the image. The old macos-15 + latest-stable combination was the actual hole - it resolved to Xcode 26.3, which lacks the check and silently let the original issue #310 build failure ship. --- .github/workflows/ios.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index ea83ec0..22c87d0 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -13,15 +13,15 @@ concurrency: jobs: build: name: Build and Test SDKHostApp scheme using any available iPhone simulator + # macos-26 is required: only this image's installed Xcodes include + # 26.4.1+, where the stricter protocol-witness availability check + # this PR exists to fix (issue #310) lives. macos-15 tops out at + # Xcode 26.3, which does not exercise the check and would let + # regressions of the @available annotations slip through. runs-on: macos-26 env: SCHEME: SDKHostApp PROJECT: IFTTT SDK.xcodeproj - # Pinned to the Xcode version that introduced the stricter - # protocol-witness availability check this PR exists to fix - # (see issue #310). Bumping this on purpose is fine; drifting via - # latest-stable would hide regressions of the original fix. - XCODE_VERSION: 26.4.1 steps: - name: Checkout @@ -30,7 +30,7 @@ jobs: - name: Select Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: ${{ env.XCODE_VERSION }} + xcode-version: latest-stable - name: Toolchain versions run: |