Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 54 additions & 26 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,68 @@ 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
# 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

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: |
# 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
xcodebuild -version
swift --version

- name: Install xcbeautify
run: |
if ! command -v xcbeautify >/dev/null 2>&1; then
brew install xcbeautify
fi

echo "📱 Using device: $device_name"
- name: Pick iPhone simulator
id: sim
run: |
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"

# Build and run the tests
- name: Build & test
run: |
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 }}" \
| xcbeautify --renderer github-actions
Comment thread
ssathy2 marked this conversation as resolved.
17 changes: 10 additions & 7 deletions IFTTT SDK/AuthenticationSessionPresentationContextProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
}
Expand Down
3 changes: 2 additions & 1 deletion IFTTT SDK/ConnectButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 26 additions & 14 deletions IFTTT SDK/SignInWithAppleAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,33 @@ final class AppleSignInWebService: ServiceAuthentication {

@available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
let authorizationError = ASAuthorizationError(_nsError: error as NSError)
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
Comment thread
ssathy2 marked this conversation as resolved.
}
completion(.failure(mapped))
}
}

Expand Down
15 changes: 14 additions & 1 deletion IFTTT SDK/WebServiceAuthentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@ 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.
/// - unknown: Some unknown error ocurred when authenticating with the web service.
/// - 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 occurred when authenticating with the web service.
enum AuthenticationError: Error {
case userCanceled
case failed
case invalidResponse
case notHandled
case presentationContextInvalid
case notInteractive
case matchedExcludedCredential
case credentialImport
case credentialExport
case unknown
}

Expand Down
Loading