Skip to content

Commit 3efe0a5

Browse files
authored
Add support for FirebaseAppCheck APIs (#61)
1 parent c0b3a0d commit 3efe0a5

File tree

3 files changed

+158
-6
lines changed

3 files changed

+158
-6
lines changed

Sources/SkipFirebaseAppCheck/Skip/skip.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ build:
1010
- block: 'dependencies'
1111
contents:
1212
- 'implementation("com.google.firebase:firebase-appcheck")'
13+
- 'implementation("com.google.firebase:firebase-appcheck-debug")'
14+
- 'implementation("com.google.firebase:firebase-appcheck-playintegrity")'
Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,158 @@
11
// SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
22
#if !SKIP_BRIDGE
33
#if SKIP
4+
import Foundation
45
import SkipFirebaseCore
56
import kotlinx.coroutines.tasks.await
67

78
// https://firebase.google.com/docs/reference/swift/firebaseappcheck/api/reference/Classes/AppCheck
89
// https://firebase.google.com/docs/reference/android/com/google/firebase/appcheck/FirebaseAppCheck
910

1011
public final class AppCheck {
11-
public let appcheck: com.google.firebase.appcheck.FirebaseAppCheck
12+
public let platformValue: com.google.firebase.appcheck.FirebaseAppCheck
1213

13-
public init(appcheck: com.google.firebase.appcheck.FirebaseAppCheck) {
14-
self.appcheck = appcheck
14+
public init(platformValue: com.google.firebase.appcheck.FirebaseAppCheck) {
15+
self.platformValue = platformValue
1516
}
1617

1718
public static func appCheck() -> AppCheck {
18-
AppCheck(appcheck: com.google.firebase.appcheck.FirebaseAppCheck.getInstance())
19+
AppCheck(platformValue: com.google.firebase.appcheck.FirebaseAppCheck.getInstance())
1920
}
2021

2122
public static func appCheck(app: FirebaseApp) -> AppCheck {
22-
AppCheck(appcheck: com.google.firebase.appcheck.FirebaseAppCheck.getInstance(app.app))
23+
AppCheck(platformValue: com.google.firebase.appcheck.FirebaseAppCheck.getInstance(app.app))
24+
}
25+
26+
/// Sets the provider factory for App Check.
27+
/// On iOS this is a static method; on Android it calls `installAppCheckProviderFactory` on the instance.
28+
public static func setAppCheckProviderFactory(_ factory: AppCheckProviderFactory?) {
29+
guard let factory = factory else { return }
30+
com.google.firebase.appcheck.FirebaseAppCheck.getInstance().installAppCheckProviderFactory(factory.platformValue)
31+
}
32+
33+
/// Requests a Firebase App Check token.
34+
/// - Parameter forcingRefresh: If `true`, forces a token refresh even if the cached token is still valid.
35+
/// - Returns: An `AppCheckToken` containing the token string and expiration date.
36+
public func token(forcingRefresh: Bool) async throws -> AppCheckToken {
37+
let result = platformValue.getAppCheckToken(forcingRefresh).await()
38+
return AppCheckToken(platformValue: result)
39+
}
40+
41+
/// Requests a limited-use Firebase App Check token for server-side replay protection.
42+
/// - Returns: An `AppCheckToken` containing the token string and expiration date.
43+
public func limitedUseToken() async throws -> AppCheckToken {
44+
let result = platformValue.getLimitedUseAppCheckToken().await()
45+
return AppCheckToken(platformValue: result)
46+
}
47+
48+
/// Controls whether the App Check SDK automatically refreshes the token when needed.
49+
public var isTokenAutoRefreshEnabled: Bool {
50+
// Android only exposes a setter; store and return the last set value
51+
get { _isTokenAutoRefreshEnabled }
52+
set {
53+
_isTokenAutoRefreshEnabled = newValue
54+
platformValue.setTokenAutoRefreshEnabled(newValue)
55+
}
56+
}
57+
private var _isTokenAutoRefreshEnabled: Bool = false
58+
59+
/// The notification name posted when the App Check token changes.
60+
/// On Android this is backed by `AppCheckListener`.
61+
// SKIP @nobridge // no support for bridging notifications yet
62+
public static let appCheckTokenDidChangeNotification = Notification.Name("AppCheckAppCheckTokenDidChangeNotification")
63+
64+
/// Adds a listener that is called when the App Check token changes.
65+
/// - Parameter listener: A closure called with the updated `AppCheckToken`.
66+
/// - Returns: An opaque listener handle that can be passed to `removeTokenChangeListener`.
67+
public func addTokenChangeListener(_ listener: @escaping (AppCheckToken) -> Void) -> AppCheckListenerHandle {
68+
let androidListener = com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener { token in
69+
let wrapped = AppCheckToken(platformValue: token)
70+
listener(wrapped)
71+
NotificationCenter.default.post(
72+
name: AppCheck.appCheckTokenDidChangeNotification,
73+
object: self,
74+
userInfo: [AppCheckTokenNotificationKey: wrapped]
75+
)
76+
}
77+
platformValue.addAppCheckListener(androidListener)
78+
return AppCheckListenerHandle(platformValue: androidListener)
79+
}
80+
81+
/// Removes a previously added token change listener.
82+
public func removeTokenChangeListener(_ handle: AppCheckListenerHandle) {
83+
platformValue.removeAppCheckListener(handle.platformValue)
84+
}
85+
}
86+
87+
// MARK: - AppCheckToken
88+
89+
/// A token returned by the App Check service, containing the token string and its expiration.
90+
public final class AppCheckToken {
91+
public let platformValue: com.google.firebase.appcheck.AppCheckToken
92+
93+
public init(platformValue: com.google.firebase.appcheck.AppCheckToken) {
94+
self.platformValue = platformValue
95+
}
96+
97+
/// The token string to include in requests to your backend.
98+
public var token: String {
99+
platformValue.getToken()
100+
}
101+
102+
/// The expiration date of this token.
103+
public var expirationDate: Date {
104+
Date(timeIntervalSince1970: Double(platformValue.getExpireTimeMillis()) / 1000.0)
23105
}
24106
}
107+
108+
/// Key for retrieving the `AppCheckToken` from the `userInfo` dictionary of an `appCheckTokenDidChangeNotification`.
109+
public let AppCheckTokenNotificationKey = "AppCheckTokenNotificationKey"
110+
111+
// MARK: - AppCheckListenerHandle
112+
113+
/// An opaque handle for a registered token change listener.
114+
public class AppCheckListenerHandle {
115+
public let platformValue: com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener
116+
117+
public init(platformValue: com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener) {
118+
self.platformValue = platformValue
119+
}
120+
}
121+
122+
// MARK: - AppCheckProviderFactory
123+
124+
/// A wrapper around the Android `AppCheckProviderFactory` that matches the Swift `AppCheckProviderFactory` protocol.
125+
public class AppCheckProviderFactory {
126+
public let platformValue: com.google.firebase.appcheck.AppCheckProviderFactory
127+
128+
public init(platformValue: com.google.firebase.appcheck.AppCheckProviderFactory) {
129+
self.platformValue = platformValue
130+
}
131+
}
132+
133+
// MARK: - AppCheckDebugProviderFactory
134+
135+
/// A factory that creates debug App Check providers, useful for testing.
136+
/// On iOS this is `AppCheckDebugProviderFactory`; on Android it is `DebugAppCheckProviderFactory`.
137+
public class AppCheckDebugProviderFactory: AppCheckProviderFactory {
138+
public init() {
139+
super.init(platformValue: com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory.getInstance())
140+
}
141+
}
142+
143+
// MARK: - AppCheckError
144+
145+
/// Error codes for App Check operations, matching the Swift `AppCheckErrorCode` enum.
146+
public enum AppCheckErrorCode: Int {
147+
case unknown = 0
148+
case serverUnreachable = 1
149+
case invalidConfiguration = 2
150+
case keychain = 3
151+
case unsupported = 4
152+
}
153+
154+
/// The error domain for App Check errors.
155+
public let AppCheckErrorDomain = "FIRAppCheckErrorDomain"
156+
25157
#endif
26158
#endif

Tests/SkipFirebaseAppCheckTests/SkipFirebaseAppCheckTests.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,25 @@ let logger: Logger = Logger(subsystem: "SkipFirebaseAppCheckTests", category: "T
1515
@MainActor final class SkipFirebaseAppCheckTests: XCTestCase {
1616
func testSkipFirebaseAppCheckTests() async throws {
1717
if false {
18-
let _: AppCheck = AppCheck.appCheck()
18+
let appCheck: AppCheck = AppCheck.appCheck()
19+
//let _: AppCheck = AppCheck.appCheck(app: FirebaseApp.app()!)
20+
21+
// Provider factory
22+
let debugFactory = AppCheckDebugProviderFactory()
23+
AppCheck.setAppCheckProviderFactory(debugFactory)
24+
25+
// Token retrieval
26+
let token: AppCheckToken = try await appCheck.token(forcingRefresh: false)
27+
let _: String = token.token
28+
let _: Date = token.expirationDate
29+
30+
// Limited-use token
31+
let limitedToken: AppCheckToken = try await appCheck.limitedUseToken()
32+
let _: String = limitedToken.token
33+
34+
// Auto-refresh
35+
appCheck.isTokenAutoRefreshEnabled = true
36+
let _: Bool = appCheck.isTokenAutoRefreshEnabled
1937
}
2038
}
2139
}

0 commit comments

Comments
 (0)