From 5e0b768aa9ecd75ca1f8e534738c1296284f08f2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Sep 2026 13:46:19 -0400 Subject: [PATCH 1/5] fix(recaptcha): address review items for requestHooks bridging - Add AppCheckRecaptchaProvider as dependency to AppCheckCoreUnitObjC and add an Objective-C bridging test constructing GACRecaptchaProvider with request hooks. - Define invalidRequestHook (3002) message code in AppCheckCoreErrors and use it when logging rejected hooks. - Add CHANGELOG entry for GACRecaptchaProvider requestHooks bridging fix. - Format doc-comment parameters across AppCheckRecaptchaProvider convenience initializers. --- .../APIService/AppCheckCoreAPIService.swift | 2 +- .../Core/Errors/AppCheckCoreErrors.swift | 1 + .../Unit/ObjC/AppCheckCoreObjCAPITests.m | 18 +++++++++++++++ .../Tests/Unit/Swift/AppCheckAPITests.swift | 1 + .../Public/AppCheckRecaptchaProvider.swift | 22 ++++++++++++------- CHANGELOG.md | 1 + Package.swift | 5 ++++- 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/AppCheckCore/Sources/Core/APIService/AppCheckCoreAPIService.swift b/AppCheckCore/Sources/Core/APIService/AppCheckCoreAPIService.swift index 2eae6ba..ae948fc 100644 --- a/AppCheckCore/Sources/Core/APIService/AppCheckCoreAPIService.swift +++ b/AppCheckCore/Sources/Core/APIService/AppCheckCoreAPIService.swift @@ -95,7 +95,7 @@ public class AppCheckCoreAPIService: NSObject, // Dropping a hook is otherwise invisible: the request still succeeds, // just without whatever the hook would have contributed. AppCheckCoreLogger.log( - code: .unknown, + code: .invalidRequestHook, logLevel: .error, message: "Ignoring a request hook that is not a block: \(type(of: obj))." ) diff --git a/AppCheckCore/Sources/Core/Errors/AppCheckCoreErrors.swift b/AppCheckCore/Sources/Core/Errors/AppCheckCoreErrors.swift index d38eb8a..34b2617 100644 --- a/AppCheckCore/Sources/Core/Errors/AppCheckCoreErrors.swift +++ b/AppCheckCore/Sources/Core/Errors/AppCheckCoreErrors.swift @@ -80,6 +80,7 @@ public enum AppCheckCoreMessageCode: Int { case providerIsMissing = 2002 case stagingModeEnabled = 2003 case unexpectedHTTPCode = 3001 + case invalidRequestHook = 3002 // Debug Provider case localDebugToken = 4001 diff --git a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m index 5bee82a..2aeae08 100644 --- a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m +++ b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m @@ -15,6 +15,9 @@ #import @import AppCheckCore; +#if TARGET_OS_IOS +@import AppCheckRecaptchaProvider; +#endif #pragma mark - Protocol Conformance Dummies @@ -219,6 +222,7 @@ - (void)testPublicAPICompileAndLink { GACAppCheckMessageCode msgProviderMissing = GACAppCheckMessageCodeProviderIsMissing; GACAppCheckMessageCode msgStaging = GACAppCheckMessageCodeStagingModeEnabled; GACAppCheckMessageCode msgHTTP = GACAppCheckMessageCodeUnexpectedHTTPCode; + GACAppCheckMessageCode msgInvalidRequestHook = GACAppCheckMessageCodeInvalidRequestHook; GACAppCheckMessageCode msgLocalToken = GACAppCheckMessageCodeLocalDebugToken; GACAppCheckMessageCode msgEnvToken = GACAppCheckMessageCodeEnvironmentVariableDebugToken; GACAppCheckMessageCode msgFirebaseEnv = @@ -231,6 +235,7 @@ - (void)testPublicAPICompileAndLink { XCTAssertEqual(msgProviderMissing, 2002); XCTAssertEqual(msgStaging, 2003); XCTAssertEqual(msgHTTP, 3001); + XCTAssertEqual(msgInvalidRequestHook, 3002); XCTAssertEqual(msgLocalToken, 4001); XCTAssertEqual(msgEnvToken, 4002); XCTAssertEqual(msgFirebaseEnv, 4003); @@ -342,4 +347,17 @@ - (void)testNonBlockRequestHooksAreIgnored { [self waitForExpectations:@[ hookExpectation, completionExpectation ] timeout:2.0]; } +#if TARGET_OS_IOS +- (void)testRecaptchaProviderRequestHooksBridging { + if (@available(iOS 15.0, *)) { + void (^hook)(NSMutableURLRequest *) = ^(NSMutableURLRequest *r) { + }; + (void)[[GACRecaptchaProvider alloc] initWithSiteKey:@"key" + resourceName:@"projects/p/apps/a" + APIKey:@"key" + requestHooks:@[ hook ]]; + } +} +#endif + @end diff --git a/AppCheckCore/Tests/Unit/Swift/AppCheckAPITests.swift b/AppCheckCore/Tests/Unit/Swift/AppCheckAPITests.swift index db9f42c..6481cf0 100644 --- a/AppCheckCore/Tests/Unit/Swift/AppCheckAPITests.swift +++ b/AppCheckCore/Tests/Unit/Swift/AppCheckAPITests.swift @@ -253,6 +253,7 @@ final class AppCheckAPITests { case .providerIsMissing: break case .stagingModeEnabled: break case .unexpectedHTTPCode: break + case .invalidRequestHook: break case .localDebugToken: break case .environmentVariableDebugToken: break case .debugProviderFirebaseEnvironmentVariable: break diff --git a/AppCheckRecaptchaProvider/Sources/Public/AppCheckRecaptchaProvider.swift b/AppCheckRecaptchaProvider/Sources/Public/AppCheckRecaptchaProvider.swift index 61cf40d..1e90f4c 100644 --- a/AppCheckRecaptchaProvider/Sources/Public/AppCheckRecaptchaProvider.swift +++ b/AppCheckRecaptchaProvider/Sources/Public/AppCheckRecaptchaProvider.swift @@ -66,15 +66,21 @@ public final class AppCheckRecaptchaProvider: NSObject, AppCheckCoreProvider { ) } - /// - Parameter requestHooks: Hooks invoked on each outgoing request. From Swift, pass - /// `[AppCheckCoreAPIRequestHook]`. From Objective-C, pass an `NSArray` of blocks with the - /// signature `void (^)(NSMutableURLRequest *)`; the signature is not checked at compile - /// time and a mismatch will crash when the hook is invoked. + /// - Parameters: + /// - siteKey: The reCAPTCHA site key. + /// - resourceName: The name of the resource protected by App Check; for a Firebase App this is + /// "projects/{project_id}/apps/{app_id}". + /// - APIKey: The Google Cloud Platform API key. + /// - requestHooks: Hooks invoked on each outgoing request. From Swift, pass + /// `[AppCheckCoreAPIRequestHook]`. From Objective-C, pass an `NSArray` of blocks with the + /// signature `void (^)(NSMutableURLRequest *)`; the signature is not checked at compile + /// time and a mismatch will crash when the hook is invoked. /// - /// Typed `[Any]?` rather than `[AppCheckCoreAPIRequestHook]?` deliberately: Swift cannot - /// bridge an `NSArray` into a Swift `Array` whose element is a function type, so the typed - /// signature traps at runtime for any non-nil array passed from Objective-C. Do not - /// "simplify" this type — see PR #111. + /// Typed `[Any]?` rather than `[AppCheckCoreAPIRequestHook]?` deliberately: Swift cannot + /// bridge an `NSArray` into a Swift `Array` whose element is a function type, so the typed + /// signature traps at runtime for any non-nil array passed from Objective-C. Do not + /// "simplify" this type — see PR #111. + /// - actionName: The reCAPTCHA custom action name. @objc public convenience init?(siteKey: String, resourceName: String, APIKey: String, requestHooks: [Any]? = nil, actionName: String) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c4c8c..56ae7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [changed] **Breaking change**: `GACAppCheckTokenResult` is now `final`. - [changed] **Breaking change**: `requestHooks:` is now typed `NSArray *` instead of `NSArray *`; blocks must match `void (^)(NSMutableURLRequest *)` exactly or they will crash when invoked. - [changed] Forced token refreshes are no longer coalesced with an in-flight unforced refresh. This resolves TODO(#42) from v11's `GACAppCheck.m`. +- [fixed] `GACRecaptchaProvider`'s `requestHooks:` parameter now accepts `NSArray *` / `[Any]?` fixing an `@objc` argument bridging trap when passing request hooks, and establishing `requestHooks:` bridging parity with other providers. # 11.3.2 - [fixed] Fixed an issue where the time-to-live (TTL) for a cached token was diff --git a/Package.swift b/Package.swift index a815ba5..6e80cfc 100644 --- a/Package.swift +++ b/Package.swift @@ -94,7 +94,10 @@ let package = Package( ), .testTarget( name: "AppCheckCoreUnitObjC", - dependencies: ["AppCheckCore"], + dependencies: [ + "AppCheckCore", + "AppCheckRecaptchaProvider", + ], path: "AppCheckCore/Tests/Unit/ObjC", cSettings: [ .headerSearchPath("../.."), From 1731eb32006ec21b547422aedf51664aa23f317d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Sep 2026 16:11:13 -0400 Subject: [PATCH 2/5] fix(recaptcha): guard AppCheckRecaptchaProvider import and test for Catalyst and CocoaPods --- AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m index 2aeae08..7fbfc7c 100644 --- a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m +++ b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m @@ -15,7 +15,7 @@ #import @import AppCheckCore; -#if TARGET_OS_IOS +#if SWIFT_PACKAGE && (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST @import AppCheckRecaptchaProvider; #endif @@ -347,9 +347,9 @@ - (void)testNonBlockRequestHooksAreIgnored { [self waitForExpectations:@[ hookExpectation, completionExpectation ] timeout:2.0]; } -#if TARGET_OS_IOS +#if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST - (void)testRecaptchaProviderRequestHooksBridging { - if (@available(iOS 15.0, *)) { + if (@available(iOS 15.0, visionOS 1.0, *)) { void (^hook)(NSMutableURLRequest *) = ^(NSMutableURLRequest *r) { }; (void)[[GACRecaptchaProvider alloc] initWithSiteKey:@"key" From 6de25bb3bb66d063f5a2dd6e52488a0e0c31ef9d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:50:09 -0400 Subject: [PATCH 3/5] Apply suggestion from @ncooke3 Signed-off-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m index 7fbfc7c..82a8e0d 100644 --- a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m +++ b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m @@ -15,7 +15,7 @@ #import @import AppCheckCore; -#if SWIFT_PACKAGE && (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST +if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST @import AppCheckRecaptchaProvider; #endif From eeeea53892359103fa8a109166db7905a47b7ccf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:51:30 -0400 Subject: [PATCH 4/5] Update AppCheckCoreObjCAPITests.m Signed-off-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- .../Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m index 82a8e0d..c1bc66f 100644 --- a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m +++ b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m @@ -349,14 +349,12 @@ - (void)testNonBlockRequestHooksAreIgnored { #if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST - (void)testRecaptchaProviderRequestHooksBridging { - if (@available(iOS 15.0, visionOS 1.0, *)) { - void (^hook)(NSMutableURLRequest *) = ^(NSMutableURLRequest *r) { - }; - (void)[[GACRecaptchaProvider alloc] initWithSiteKey:@"key" - resourceName:@"projects/p/apps/a" - APIKey:@"key" - requestHooks:@[ hook ]]; - } + void (^hook)(NSMutableURLRequest *) = ^(NSMutableURLRequest *r) { + }; + (void)[[GACRecaptchaProvider alloc] initWithSiteKey:@"key" + resourceName:@"projects/p/apps/a" + APIKey:@"key" + requestHooks:@[ hook ]]; } #endif From 3f95be9114015d57f826291463e77288cff58d54 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Sep 2026 17:04:37 -0400 Subject: [PATCH 5/5] fix(recaptcha): add missing pound symbols to preprocessor directives --- AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m index c1bc66f..7cdfe98 100644 --- a/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m +++ b/AppCheckCore/Tests/Unit/ObjC/AppCheckCoreObjCAPITests.m @@ -15,7 +15,7 @@ #import @import AppCheckCore; -if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST +#if SWIFT_PACKAGE && (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST @import AppCheckRecaptchaProvider; #endif @@ -347,7 +347,7 @@ - (void)testNonBlockRequestHooksAreIgnored { [self waitForExpectations:@[ hookExpectation, completionExpectation ] timeout:2.0]; } -#if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST +#if SWIFT_PACKAGE && (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST - (void)testRecaptchaProviderRequestHooksBridging { void (^hook)(NSMutableURLRequest *) = ^(NSMutableURLRequest *r) { };