From ce0baa297c2a31ac0b773875cdc82b9ff873c0e2 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 3 Sep 2024 06:43:26 -0700 Subject: [PATCH 001/258] Revert zip builds to macos-13 (#13562) --- .github/workflows/zip.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index e3f9e3d6e10..4a40949a27e 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -30,7 +30,7 @@ jobs: package-release: # Don't run on private repo. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - runs-on: macos-14 + runs-on: macos-13 steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 @@ -58,7 +58,7 @@ jobs: build: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - runs-on: macos-14 + runs-on: macos-13 steps: - uses: actions/checkout@v4 - name: Xcode 15.2 @@ -75,7 +75,7 @@ jobs: strategy: matrix: linking_type: [static, dynamic] - runs-on: macos-14 + runs-on: macos-13 steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 From 7700b588363b8ca005d9f774fbee2a3218812920 Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Tue, 3 Sep 2024 15:46:00 +0200 Subject: [PATCH 002/258] Updated Functions Errors (#13535) --- FirebaseFunctions/Sources/Functions.swift | 4 +- .../Sources/FunctionsError.swift | 137 ++++++++---------- 2 files changed, 65 insertions(+), 76 deletions(-) diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index 19b3da0d675..aa9daaa543b 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -509,7 +509,7 @@ enum FunctionsConstants { if let error = error as NSError? { let localError: (any Error)? if error.domain == kGTMSessionFetcherStatusDomain { - localError = FunctionsErrorForResponse( + localError = FunctionsErrorCode.errorForResponse( status: error.code, body: data, serializer: serializer @@ -529,7 +529,7 @@ enum FunctionsConstants { } // Case 3: `data` is not `nil` but might specify a custom error -> throws conditionally - if let bodyError = FunctionsErrorForResponse( + if let bodyError = FunctionsErrorCode.errorForResponse( status: 200, body: data, serializer: serializer diff --git a/FirebaseFunctions/Sources/FunctionsError.swift b/FirebaseFunctions/Sources/FunctionsError.swift index 08355031fa1..8755b362bd1 100644 --- a/FirebaseFunctions/Sources/FunctionsError.swift +++ b/FirebaseFunctions/Sources/FunctionsError.swift @@ -101,45 +101,32 @@ public let FunctionsErrorDetailsKey: String = "details" case unauthenticated = 16 } -/** - * Takes an HTTP status code and returns the corresponding `FIRFunctionsErrorCode` error code. - * This is the standard HTTP status code -> error mapping defined in: - * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto - * - Parameter status An HTTP status code. - * - Returns: The corresponding error code, or `FIRFunctionsErrorCodeUnknown` if none. - */ -func FunctionsCodeForHTTPStatus(_ status: NSInteger) -> FunctionsErrorCode { - switch status { - case 200: - return .OK - case 400: - return .invalidArgument - case 401: - return .unauthenticated - case 403: - return .permissionDenied - case 404: - return .notFound - case 409: - return .alreadyExists - case 429: - return .resourceExhausted - case 499: - return .cancelled - case 500: - return .internal - case 501: - return .unimplemented - case 503: - return .unavailable - case 504: - return .deadlineExceeded - default: - return .internal +extension FunctionsErrorCode { + /// Takes an HTTP status code and returns the corresponding `FIRFunctionsErrorCode` error code. + /// + /// + This is the standard HTTP status code -> error mapping defined in: + /// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto + /// + /// - Parameter status: An HTTP status code. + /// - Returns: A `FunctionsErrorCode`. Falls back to `internal` for unknown status codes. + static func errorCode(forHTTPStatus status: Int) -> Self { + switch status { + case 200: .OK + case 400: .invalidArgument + case 401: .unauthenticated + case 403: .permissionDenied + case 404: .notFound + case 409: .alreadyExists + case 429: .resourceExhausted + case 499: .cancelled + case 500: .internal + case 501: .unimplemented + case 503: .unavailable + case 504: .deadlineExceeded + default: .internal + } } -} -extension FunctionsErrorCode { static func errorCode(forName name: String) -> FunctionsErrorCode { switch name { case "OK": return .OK @@ -207,53 +194,55 @@ extension FunctionsErrorCode { code: rawValue, userInfo: userInfo ?? [NSLocalizedDescriptionKey: descriptionForErrorCode]) } -} -func FunctionsErrorForResponse(status: NSInteger, + static func errorForResponse(status: Int, body: Data?, serializer: FunctionsSerializer) -> NSError? { - // Start with reasonable defaults from the status code. - var code = FunctionsCodeForHTTPStatus(status) - var description = code.descriptionForErrorCode - - var details: AnyObject? + // Start with reasonable defaults from the status code. + var code = FunctionsErrorCode.errorCode(forHTTPStatus: status) + var description = code.descriptionForErrorCode + var details: AnyObject? + + // Then look through the body for explicit details. + if let body, + let json = try? JSONSerialization.jsonObject(with: body) as? NSDictionary, + let errorDetails = json["error"] as? NSDictionary { + if let status = errorDetails["status"] as? String { + code = .errorCode(forName: status) + + // If the code in the body is invalid, treat the whole response as malformed. + guard code != .internal else { + return code.generatedError(userInfo: nil) + } + } - // Then look through the body for explicit details. - if let body, - let json = try? JSONSerialization.jsonObject(with: body) as? NSDictionary, - let errorDetails = json["error"] as? NSDictionary { - if let status = errorDetails["status"] as? String { - code = FunctionsErrorCode.errorCode(forName: status) + if let message = errorDetails["message"] as? String { + description = message + } else { + description = code.descriptionForErrorCode + } - // If the code in the body is invalid, treat the whole response as malformed. - guard code != .internal else { - return code.generatedError(userInfo: nil) + details = errorDetails["details"] as AnyObject? + // Update `details` only if decoding succeeds; + // otherwise, keep the original object. + if let innerDetails = details, + let decodedDetails = try? serializer.decode(innerDetails) { + details = decodedDetails } } - if let message = errorDetails["message"] as? String { - description = message - } else { - description = code.descriptionForErrorCode + if code == .OK { + // Technically, there's an edge case where a developer could explicitly return an error code + // of + // OK, and we will treat it as success, but that seems reasonable. + return nil } - details = errorDetails["details"] as AnyObject? - if let innerDetails = details { - // Just ignore the details if there an error decoding them. - details = try? serializer.decode(innerDetails) + var userInfo = [String: Any]() + userInfo[NSLocalizedDescriptionKey] = description + if let details { + userInfo[FunctionsErrorDetailsKey] = details } + return code.generatedError(userInfo: userInfo) } - - if code == .OK { - // Technically, there's an edge case where a developer could explicitly return an error code of - // OK, and we will treat it as success, but that seems reasonable. - return nil - } - - var userInfo = [String: Any]() - userInfo[NSLocalizedDescriptionKey] = description - if let details { - userInfo[FunctionsErrorDetailsKey] = details - } - return code.generatedError(userInfo: userInfo) } From d1b950f91b279e745cba68382174b0630df826c8 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 3 Sep 2024 06:46:18 -0700 Subject: [PATCH 003/258] Fix user session persistence in multi tenant projects (#13567) --- FirebaseAuth/CHANGELOG.md | 4 ++-- FirebaseAuth/Sources/Swift/Auth/Auth.swift | 7 ++++--- FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 33675257635..ca2be73ed87 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -2,11 +2,11 @@ - [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 betas. (#13480) - [Fixed] Fixed Phone Auth via Sandbox APNS tokens that broke in 11.0.0. (#13479) -- [Fixed] Fix crash when fetching sign in methods due to unexpected nil. +- [Fixed] Fixed crash when fetching sign in methods due to unexpected nil. Previously, fetching sign in methods could return both a `nil` array of sign in methods and a `nil` error. In such cases, an empty array is instead returned with the `nil` error. (#13550) - +- [Fixed] Fixed user session persistence in multi tenant projects. Introduced in 11.0.0. (#13565) # 11.1.0 - [fixed] Fixed `Swift.error` conformance for `AuthErrorCode`. (#13430) diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index 6c6932973de..8effd15c101 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -1667,9 +1667,11 @@ extension Auth: AuthInterop { try self.internalUseUserAccessGroup(storedUserAccessGroup) } else { let user = try self.getUser() - try self.updateCurrentUser(user, byForce: false, savingToDisk: false) if let user { self.tenantID = user.tenantID + } + try self.updateCurrentUser(user, byForce: false, savingToDisk: false) + if let user { self.lastNotifiedUserToken = user.rawAccessToken() } } @@ -1941,8 +1943,7 @@ extension Auth: AuthInterop { } if let user { if user.tenantID != nil || tenantID != nil, tenantID != user.tenantID { - let error = AuthErrorUtils.tenantIDMismatchError() - throw error + throw AuthErrorUtils.tenantIDMismatchError() } } var throwError: Error? diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 5a5e6d0664a..b0e9a93fca5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -484,8 +484,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation code: AuthErrorCode.maximumSecondFactorCountExceeded, message: serverDetailErrorMessage ) - case "TENANT_ID_MISMATCH": return AuthErrorUtils - .tenantIDMismatchError() + case "TENANT_ID_MISMATCH": return AuthErrorUtils.tenantIDMismatchError() case "TOKEN_EXPIRED": return AuthErrorUtils .userTokenExpiredError(message: serverDetailErrorMessage) case "UNSUPPORTED_FIRST_FACTOR": return AuthErrorUtils From 936ddf44f84238713beef4b73ec138e92ba93b89 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 3 Sep 2024 06:49:14 -0700 Subject: [PATCH 004/258] Fix temporary disconnect when app goes inactive (#13564) --- FirebaseDatabase/CHANGELOG.md | 4 +++ .../Sources/Core/FPersistentConnection.m | 30 ++----------------- .../Sources/Realtime/FWebSocketConnection.m | 21 ------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/FirebaseDatabase/CHANGELOG.md b/FirebaseDatabase/CHANGELOG.md index 7ee5692d4f0..7df20ba497e 100644 --- a/FirebaseDatabase/CHANGELOG.md +++ b/FirebaseDatabase/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased +- [fixed] Fix temporary disconnects when the app goes inactive. The issue was + introduced in 10.27.0. (#13529) + # 11.0.0 - [removed] **Breaking change**: The deprecated `FirebaseDatabaseSwift` module has been removed. See diff --git a/FirebaseDatabase/Sources/Core/FPersistentConnection.m b/FirebaseDatabase/Sources/Core/FPersistentConnection.m index 8c22814df00..f07ee977246 100644 --- a/FirebaseDatabase/Sources/Core/FPersistentConnection.m +++ b/FirebaseDatabase/Sources/Core/FPersistentConnection.m @@ -34,11 +34,9 @@ #import "FirebaseDatabase/Sources/Utilities/FUtilities.h" #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleCallbackStatus.h" #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleOnDisconnect.h" -#if TARGET_OS_WATCH -#import -#else +#if !TARGET_OS_WATCH #import -#endif // TARGET_OS_WATCH +#endif // !TARGET_OS_WATCH #import #import @@ -168,7 +166,6 @@ - (id)initWithRepoInfo:(FRepoInfo *)repoInfo retryExponent:kPersistentConnReconnectMultiplier jitterFactor:0.7]; - [self setupNotifications]; // Make sure we don't actually connect until open is called [self interruptForReason:kFInterruptReasonWaitingForOpen]; } @@ -553,29 +550,6 @@ - (void)openNetworkConnectionWithContext: [self.realtime open]; } -- (void)enteringForeground { - dispatch_async(self.dispatchQueue, ^{ - // Reset reconnect delay - [self.retryHelper signalSuccess]; - if (self->connectionState == ConnectionStateDisconnected) { - [self tryScheduleReconnect]; - } - }); -} - -- (void)setupNotifications { -#if TARGET_OS_WATCH - __weak FPersistentConnection *weakSelf = self; - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserverForName:WKApplicationWillEnterForegroundNotification - object:nil - queue:nil - usingBlock:^(NSNotification *_Nonnull note) { - [weakSelf enteringForeground]; - }]; -#endif // TARGET_OS_WATCH -} - - (void)sendAuthAndRestoreStateAfterComplete:(BOOL)restoreStateAfterComplete { NSAssert([self connected], @"Must be connected to send auth"); NSAssert(self.authToken != nil, diff --git a/FirebaseDatabase/Sources/Realtime/FWebSocketConnection.m b/FirebaseDatabase/Sources/Realtime/FWebSocketConnection.m index 6aa9d7365e5..078fbb06ef6 100644 --- a/FirebaseDatabase/Sources/Realtime/FWebSocketConnection.m +++ b/FirebaseDatabase/Sources/Realtime/FWebSocketConnection.m @@ -110,27 +110,6 @@ - (instancetype)initWith:(FRepoInfo *)repoInfo NSURLSessionWebSocketTask *task = [session webSocketTaskWithRequest:req]; self.webSocketTask = task; - -#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION || TARGET_OS_MACCATALYST - NSString *resignName = UIApplicationWillResignActiveNotification; -#elif TARGET_OS_OSX - NSString *resignName = NSApplicationWillResignActiveNotification; -#elif TARGET_OS_WATCH - NSString *resignName = WKApplicationWillResignActiveNotification; -#elif -#error("missing platform") -#endif - [[NSNotificationCenter defaultCenter] - addObserverForName:resignName - object:nil - queue:opQueue - usingBlock:^(NSNotification *_Nonnull note) { - FFLog(@"I-RDB083015", - @"Received notification that application " - @"will resign, " - @"closing web socket."); - [self onClosed]; - }]; } } return self; From 70e444df40cbef39f816b2e2429df2c42679f79b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 11:14:42 -0400 Subject: [PATCH 005/258] [Release] Update unreleased changelog entries for 11.2.0 (#13569) --- FirebaseAuth/CHANGELOG.md | 2 +- FirebaseDatabase/CHANGELOG.md | 2 +- FirebaseVertexAI/CHANGELOG.md | 2 +- Firestore/CHANGELOG.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index ca2be73ed87..96771bafd05 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.2.0 - [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 betas. (#13480) - [Fixed] Fixed Phone Auth via Sandbox APNS tokens that broke in 11.0.0. (#13479) diff --git a/FirebaseDatabase/CHANGELOG.md b/FirebaseDatabase/CHANGELOG.md index 7df20ba497e..f71cbe0fe00 100644 --- a/FirebaseDatabase/CHANGELOG.md +++ b/FirebaseDatabase/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.2.0 - [fixed] Fix temporary disconnects when the app goes inactive. The issue was introduced in 10.27.0. (#13529) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 6c90c0f6825..901378a12ba 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.2.0 - [fixed] Resolved a decoding error for citations without a `uri` and added support for decoding `title` fields, which were previously ignored. (#13518) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 5ee300f8015..3e767205d77 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.2.0 - [fixed] Marked all public classes with only readonly properties as `Sendable` to address Swift Concurrency Check warning. (#12666) From 047856b5f5b65c5f29f66746aec9a815d14a089a Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 12:11:39 -0400 Subject: [PATCH 006/258] [Vertex AI] Make `Logger` properties constants (#13570) --- FirebaseVertexAI/Sources/Logging.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseVertexAI/Sources/Logging.swift b/FirebaseVertexAI/Sources/Logging.swift index 037c7450dd9..5806ac2368a 100644 --- a/FirebaseVertexAI/Sources/Logging.swift +++ b/FirebaseVertexAI/Sources/Logging.swift @@ -38,10 +38,10 @@ struct Logging { /// The default logger that is visible for all users. Note: we shouldn't be using anything lower /// than `.notice`. - static var `default` = Logger(subsystem: subsystem, category: defaultCategory) + static let `default` = Logger(subsystem: subsystem, category: defaultCategory) /// A non default - static var network: Logger = { + static let network: Logger = { if additionalLoggingEnabled() { return Logger(subsystem: subsystem, category: "NetworkResponse") } else { @@ -51,7 +51,7 @@ struct Logging { }() /// - static var verbose: Logger = { + static let verbose: Logger = { if additionalLoggingEnabled() { return Logger(subsystem: subsystem, category: defaultCategory) } else { From 20ec9a3877c28d6a48687c642ee0bdb9a49dc31a Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 17:58:10 -0400 Subject: [PATCH 007/258] [Vertex AI] Make `GenerativeModel` and `Chat` into Swift actors (#13545) --- FirebaseVertexAI/CHANGELOG.md | 3 ++ .../Screens/ConversationScreen.swift | 4 +- .../ViewModels/ConversationViewModel.swift | 42 +++++++++++------- .../Screens/FunctionCallingScreen.swift | 4 +- .../ViewModels/FunctionCallingViewModel.swift | 33 +++++++++----- .../ViewModels/PhotoReasoningViewModel.swift | 2 +- .../ViewModels/SummarizeViewModel.swift | 2 +- FirebaseVertexAI/Sources/Chat.swift | 4 +- .../Sources/GenerativeModel.swift | 44 +++++++++---------- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 11 ++--- .../Tests/Unit/GenerativeModelTests.swift | 36 +++++++-------- .../Tests/Unit/VertexAIAPITests.swift | 4 +- .../Tests/Unit/VertexComponentTests.swift | 15 ++++--- 13 files changed, 119 insertions(+), 85 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 901378a12ba..d24cbc20dbd 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,6 +1,9 @@ # 11.2.0 - [fixed] Resolved a decoding error for citations without a `uri` and added support for decoding `title` fields, which were previously ignored. (#13518) +- [changed] **Breaking Change**: The methods for starting streaming requests + (`generateContentStream` and `sendMessageStream`) and creating a chat instance + (`startChat`) are now asynchronous and must be called with `await`. (#13545) # 10.29.0 - [feature] Added community support for watchOS. (#13215) diff --git a/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift index 78c903e3412..43da223aa78 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift @@ -104,7 +104,9 @@ struct ConversationScreen: View { } private func newChat() { - viewModel.startNewChat() + Task { + await viewModel.startNewChat() + } } } diff --git a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift index 04d8eeea33c..f3f15f35b86 100644 --- a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift +++ b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift @@ -21,8 +21,8 @@ class ConversationViewModel: ObservableObject { /// This array holds both the user's and the system's chat messages @Published var messages = [ChatMessage]() - /// Indicates we're waiting for the model to finish - @Published var busy = false + /// Indicates we're waiting for the model to finish or the UI is loading + @Published var busy = true @Published var error: Error? var hasError: Bool { @@ -30,18 +30,20 @@ class ConversationViewModel: ObservableObject { } private var model: GenerativeModel - private var chat: Chat + private var chat: Chat? = nil private var stopGenerating = false private var chatTask: Task? init() { model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") - chat = model.startChat() + Task { + await startNewChat() + } } func sendMessage(_ text: String, streaming: Bool = true) async { - error = nil + stop() if streaming { await internalSendMessageStreaming(text) } else { @@ -49,11 +51,14 @@ class ConversationViewModel: ObservableObject { } } - func startNewChat() { + func startNewChat() async { + busy = true + defer { + busy = false + } stop() - error = nil - chat = model.startChat() messages.removeAll() + chat = await model.startChat() } func stop() { @@ -62,8 +67,6 @@ class ConversationViewModel: ObservableObject { } private func internalSendMessageStreaming(_ text: String) async { - chatTask?.cancel() - chatTask = Task { busy = true defer { @@ -79,7 +82,10 @@ class ConversationViewModel: ObservableObject { messages.append(systemMessage) do { - let responseStream = chat.sendMessageStream(text) + guard let chat else { + throw ChatError.notInitialized + } + let responseStream = await chat.sendMessageStream(text) for try await chunk in responseStream { messages[messages.count - 1].pending = false if let text = chunk.text { @@ -95,8 +101,6 @@ class ConversationViewModel: ObservableObject { } private func internalSendMessage(_ text: String) async { - chatTask?.cancel() - chatTask = Task { busy = true defer { @@ -112,10 +116,12 @@ class ConversationViewModel: ObservableObject { messages.append(systemMessage) do { - var response: GenerateContentResponse? - response = try await chat.sendMessage(text) + guard let chat = chat else { + throw ChatError.notInitialized + } + let response = try await chat.sendMessage(text) - if let responseText = response?.text { + if let responseText = response.text { // replace pending message with backend response messages[messages.count - 1].message = responseText messages[messages.count - 1].pending = false @@ -127,4 +133,8 @@ class ConversationViewModel: ObservableObject { } } } + + enum ChatError: Error { + case notInitialized + } } diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift index f16da39e22f..dbfd04eb52c 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift @@ -106,7 +106,9 @@ struct FunctionCallingScreen: View { } private func newChat() { - viewModel.startNewChat() + Task { + await viewModel.startNewChat() + } } } diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 13ad5afe23c..56c4c2453a3 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -33,7 +33,7 @@ class FunctionCallingViewModel: ObservableObject { private var functionCalls = [FunctionCall]() private var model: GenerativeModel - private var chat: Chat + private var chat: Chat? = nil private var chatTask: Task? @@ -62,13 +62,13 @@ class FunctionCallingViewModel: ObservableObject { ), ])] ) - chat = model.startChat() + Task { + await startNewChat() + } } func sendMessage(_ text: String, streaming: Bool = true) async { - error = nil - chatTask?.cancel() - + stop() chatTask = Task { busy = true defer { @@ -100,11 +100,14 @@ class FunctionCallingViewModel: ObservableObject { } } - func startNewChat() { + func startNewChat() async { + busy = true + defer { + busy = false + } stop() - error = nil - chat = model.startChat() messages.removeAll() + chat = await model.startChat() } func stop() { @@ -114,14 +117,17 @@ class FunctionCallingViewModel: ObservableObject { private func internalSendMessageStreaming(_ text: String) async throws { let functionResponses = try await processFunctionCalls() + guard let chat else { + throw ChatError.notInitialized + } let responseStream: AsyncThrowingStream if functionResponses.isEmpty { - responseStream = chat.sendMessageStream(text) + responseStream = await chat.sendMessageStream(text) } else { for functionResponse in functionResponses { messages.insert(functionResponse.chatMessage(), at: messages.count - 1) } - responseStream = chat.sendMessageStream(functionResponses.modelContent()) + responseStream = await chat.sendMessageStream(functionResponses.modelContent()) } for try await chunk in responseStream { processResponseContent(content: chunk) @@ -130,6 +136,9 @@ class FunctionCallingViewModel: ObservableObject { private func internalSendMessage(_ text: String) async throws { let functionResponses = try await processFunctionCalls() + guard let chat else { + throw ChatError.notInitialized + } let response: GenerateContentResponse if functionResponses.isEmpty { response = try await chat.sendMessage(text) @@ -181,6 +190,10 @@ class FunctionCallingViewModel: ObservableObject { return functionResponses } + enum ChatError: Error { + case notInitialized + } + // MARK: - Callable Functions func getExchangeRate(args: JSONObject) -> JSONObject { diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift index d937b92f716..a8b3972561b 100644 --- a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -84,7 +84,7 @@ class PhotoReasoningViewModel: ObservableObject { } } - let outputContentStream = model.generateContentStream(prompt, images) + let outputContentStream = await model.generateContentStream(prompt, images) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift index 8b08ec71682..025e30abc39 100644 --- a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -50,7 +50,7 @@ class SummarizeViewModel: ObservableObject { let prompt = "Summarize the following text for me: \(inputText)" - let outputContentStream = model.generateContentStream(prompt) + let outputContentStream = await model.generateContentStream(prompt) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index 81d6e50b3fe..9e200a47890 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -17,7 +17,7 @@ import Foundation /// An object that represents a back-and-forth chat with a model, capturing the history and saving /// the context in memory between each message sent. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public class Chat { +public actor Chat { private let model: GenerativeModel /// Initializes a new chat representing a 1:1 conversation between model and user. @@ -121,7 +121,7 @@ public class Chat { // Send the history alongside the new message as context. let request = history + newContent - let stream = model.generateContentStream(request) + let stream = await model.generateContentStream(request) do { for try await chunk in stream { // Capture any content that's streaming. This should be populated if there's no error. diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index fb65209cd9f..c8642e83fc1 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -19,7 +19,7 @@ import Foundation /// A type that represents a remote multimodal model (like Gemini), with the ability to generate /// content based on various input types. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public final class GenerativeModel { +public final actor GenerativeModel { /// The resource name of the model in the backend; has the format "models/model-name". let modelResourceName: String @@ -217,33 +217,31 @@ public final class GenerativeModel { isStreaming: true, options: requestOptions) - var responseIterator = generativeAIService.loadRequestStream(request: generateContentRequest) - .makeAsyncIterator() + let responseStream = generativeAIService.loadRequestStream(request: generateContentRequest) + return AsyncThrowingStream { - let response: GenerateContentResponse? do { - response = try await responseIterator.next() - } catch { - throw GenerativeModel.generateContentError(from: error) - } + for try await response in responseStream { + // Check the prompt feedback to see if the prompt was blocked. + if response.promptFeedback?.blockReason != nil { + throw GenerateContentError.promptBlocked(response: response) + } - // The responseIterator will return `nil` when it's done. - guard let response = response else { + // If the stream ended early unexpectedly, throw an error. + if let finishReason = response.candidates.first?.finishReason, finishReason != .stop { + throw GenerateContentError.responseStoppedEarly( + reason: finishReason, + response: response + ) + } else { + // Response was valid content, pass it along and continue. + return response + } + } // This is the end of the stream! Signal it by sending `nil`. return nil - } - - // Check the prompt feedback to see if the prompt was blocked. - if response.promptFeedback?.blockReason != nil { - throw GenerateContentError.promptBlocked(response: response) - } - - // If the stream ended early unexpectedly, throw an error. - if let finishReason = response.candidates.first?.finishReason, finishReason != .stop { - throw GenerateContentError.responseStoppedEarly(reason: finishReason, response: response) - } else { - // Response was valid content, pass it along and continue. - return response + } catch { + throw GenerativeModel.generateContentError(from: error) } } } diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 389fcec1c5f..6191eb234ee 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -64,19 +64,20 @@ final class ChatTests: XCTestCase { ) let chat = Chat(model: model, history: []) let input = "Test input" - let stream = chat.sendMessageStream(input) + let stream = await chat.sendMessageStream(input) // Ensure the values are parsed correctly for try await value in stream { XCTAssertNotNil(value.text) } - XCTAssertEqual(chat.history.count, 2) - XCTAssertEqual(chat.history[0].parts[0].text, input) + let history = await chat.history + XCTAssertEqual(history.count, 2) + XCTAssertEqual(history[0].parts[0].text, input) let finalText = "1 2 3 4 5 6 7 8" let assembledExpectation = ModelContent(role: "model", parts: finalText) - XCTAssertEqual(chat.history[0].parts[0].text, input) - XCTAssertEqual(chat.history[1], assembledExpectation) + XCTAssertEqual(history[0].parts[0].text, input) + XCTAssertEqual(history[1], assembledExpectation) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index b62d224122f..19c7a4bf80b 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -760,7 +760,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -784,7 +784,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -807,7 +807,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -827,7 +827,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -847,7 +847,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -866,7 +866,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") do { for try await content in stream { XCTAssertNotNil(content.text) @@ -887,7 +887,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -904,7 +904,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -921,7 +921,7 @@ final class GenerativeModelTests: XCTestCase { ) var hadUnknown = false - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) if let ratings = content.candidates.first?.safetyRatings, @@ -940,7 +940,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") var citations = [Citation]() var responses = [GenerateContentResponse]() for try await content in stream { @@ -996,7 +996,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: appCheckToken ) - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1018,7 +1018,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: AppCheckInteropFake.placeholderTokenValue ) - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1030,7 +1030,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = [GenerateContentResponse]() - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) for try await response in stream { responses.append(response) } @@ -1056,7 +1056,7 @@ final class GenerativeModelTests: XCTestCase { var responseCount = 0 do { - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responseCount += 1 @@ -1076,7 +1076,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_nonHTTPResponse() async throws { MockURLProtocol.requestHandler = try nonHTTPRequestHandler() - let stream = model.generateContentStream("Hi") + let stream = await model.generateContentStream("Hi") do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1096,7 +1096,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1120,7 +1120,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1159,7 +1159,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = model.generateContentStream(testPrompt) + let stream = await model.generateContentStream(testPrompt) for try await content in stream { XCTAssertNotNil(content.text) responses += 1 diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index c68b69b03ec..f2c38a03e61 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -170,8 +170,8 @@ final class VertexAIAPITests: XCTestCase { #endif // Chat - _ = genAI.startChat() - _ = genAI.startChat(history: [ModelContent(parts: "abc")]) + _ = await genAI.startChat() + _ = await genAI.startChat(history: [ModelContent(parts: "abc")]) } // Public API tests for GenerateContentResponse. diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift index 9d5b9c65251..3ee12eb1c4d 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift @@ -106,15 +106,20 @@ class VertexComponentTests: XCTestCase { let app = try XCTUnwrap(VertexComponentTests.app) let vertex = VertexAI.vertexAI(app: app, location: location) let modelName = "test-model-name" - let modelResourceName = vertex.modelResourceName(modelName: modelName) - let systemInstruction = ModelContent(role: "system", parts: "test-system-instruction-prompt") + let expectedModelResourceName = vertex.modelResourceName(modelName: modelName) + let expectedSystemInstruction = ModelContent( + role: "system", + parts: "test-system-instruction-prompt" + ) let generativeModel = vertex.generativeModel( modelName: modelName, - systemInstruction: systemInstruction + systemInstruction: expectedSystemInstruction ) - XCTAssertEqual(generativeModel.modelResourceName, modelResourceName) - XCTAssertEqual(generativeModel.systemInstruction, systemInstruction) + let modelResourceName = await generativeModel.modelResourceName + let systemInstruction = await generativeModel.systemInstruction + XCTAssertEqual(modelResourceName, expectedModelResourceName) + XCTAssertEqual(systemInstruction, expectedSystemInstruction) } } From 8f82f5d7a4b9a08cf4d23c6774ed9721b3c6abfc Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 18:47:02 -0400 Subject: [PATCH 008/258] [Vertex AI] Make generateContentStream/sendMessageStream throws (#13573) --- FirebaseVertexAI/CHANGELOG.md | 6 ++-- .../ViewModels/ConversationViewModel.swift | 2 +- .../ViewModels/FunctionCallingViewModel.swift | 4 +-- .../ViewModels/PhotoReasoningViewModel.swift | 2 +- .../ViewModels/SummarizeViewModel.swift | 2 +- FirebaseVertexAI/Sources/Chat.swift | 17 ++++----- .../Sources/GenerativeModel.swift | 15 +++----- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 36 +++++++++---------- 9 files changed, 39 insertions(+), 47 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index d24cbc20dbd..98f099be1aa 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -2,8 +2,10 @@ - [fixed] Resolved a decoding error for citations without a `uri` and added support for decoding `title` fields, which were previously ignored. (#13518) - [changed] **Breaking Change**: The methods for starting streaming requests - (`generateContentStream` and `sendMessageStream`) and creating a chat instance - (`startChat`) are now asynchronous and must be called with `await`. (#13545) + (`generateContentStream` and `sendMessageStream`) are now throwing and + asynchronous and must be called with `try await`. (#13545, #13573) +- [changed] **Breaking Change**: Creating a chat instance (`startChat`) is now + asynchronous and must be called with `await`. (#13545) # 10.29.0 - [feature] Added community support for watchOS. (#13215) diff --git a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift index f3f15f35b86..3db45eb8d19 100644 --- a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift +++ b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift @@ -85,7 +85,7 @@ class ConversationViewModel: ObservableObject { guard let chat else { throw ChatError.notInitialized } - let responseStream = await chat.sendMessageStream(text) + let responseStream = try await chat.sendMessageStream(text) for try await chunk in responseStream { messages[messages.count - 1].pending = false if let text = chunk.text { diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 56c4c2453a3..36b53f2e2da 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -122,12 +122,12 @@ class FunctionCallingViewModel: ObservableObject { } let responseStream: AsyncThrowingStream if functionResponses.isEmpty { - responseStream = await chat.sendMessageStream(text) + responseStream = try await chat.sendMessageStream(text) } else { for functionResponse in functionResponses { messages.insert(functionResponse.chatMessage(), at: messages.count - 1) } - responseStream = await chat.sendMessageStream(functionResponses.modelContent()) + responseStream = try await chat.sendMessageStream(functionResponses.modelContent()) } for try await chunk in responseStream { processResponseContent(content: chunk) diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift index a8b3972561b..4eee954a68d 100644 --- a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -84,7 +84,7 @@ class PhotoReasoningViewModel: ObservableObject { } } - let outputContentStream = await model.generateContentStream(prompt, images) + let outputContentStream = try await model.generateContentStream(prompt, images) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift index 025e30abc39..4467b85fe3d 100644 --- a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -50,7 +50,7 @@ class SummarizeViewModel: ObservableObject { let prompt = "Summarize the following text for me: \(inputText)" - let outputContentStream = await model.generateContentStream(prompt) + let outputContentStream = try await model.generateContentStream(prompt) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index 9e200a47890..0c0239ed54e 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -85,7 +85,7 @@ public actor Chat { /// - Parameter parts: The new content to send as a single chat message. /// - Returns: A stream containing the model's response or an error if an error occurred. @available(macOS 12.0, *) - public func sendMessageStream(_ parts: any ThrowingPartsRepresentable...) + public func sendMessageStream(_ parts: any ThrowingPartsRepresentable...) throws -> AsyncThrowingStream { return try sendMessageStream([ModelContent(parts: parts)]) } @@ -95,21 +95,16 @@ public actor Chat { /// - Parameter content: The new content to send as a single chat message. /// - Returns: A stream containing the model's response or an error if an error occurred. @available(macOS 12.0, *) - public func sendMessageStream(_ content: @autoclosure () throws -> [ModelContent]) + public func sendMessageStream(_ content: @autoclosure () throws -> [ModelContent]) throws -> AsyncThrowingStream { let resolvedContent: [ModelContent] do { resolvedContent = try content() } catch let underlying { - return AsyncThrowingStream { continuation in - let error: Error - if let contentError = underlying as? ImageConversionError { - error = GenerateContentError.promptImageContentError(underlying: contentError) - } else { - error = GenerateContentError.internalError(underlying: underlying) - } - continuation.finish(throwing: error) + if let contentError = underlying as? ImageConversionError { + throw GenerateContentError.promptImageContentError(underlying: contentError) } + throw GenerateContentError.internalError(underlying: underlying) } return AsyncThrowingStream { continuation in @@ -121,7 +116,7 @@ public actor Chat { // Send the history alongside the new message as context. let request = history + newContent - let stream = await model.generateContentStream(request) + let stream = try await model.generateContentStream(request) do { for try await chunk in stream { // Capture any content that's streaming. This should be populated if there's no error. diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index c8642e83fc1..64c97729177 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -179,7 +179,7 @@ public final actor GenerativeModel { /// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError`` /// error if an error occurred. @available(macOS 12.0, *) - public func generateContentStream(_ parts: any ThrowingPartsRepresentable...) + public func generateContentStream(_ parts: any ThrowingPartsRepresentable...) throws -> AsyncThrowingStream { return try generateContentStream([ModelContent(parts: parts)]) } @@ -190,21 +190,16 @@ public final actor GenerativeModel { /// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError`` /// error if an error occurred. @available(macOS 12.0, *) - public func generateContentStream(_ content: @autoclosure () throws -> [ModelContent]) + public func generateContentStream(_ content: @autoclosure () throws -> [ModelContent]) throws -> AsyncThrowingStream { let evaluatedContent: [ModelContent] do { evaluatedContent = try content() } catch let underlying { - return AsyncThrowingStream { continuation in - let error: Error - if let contentError = underlying as? ImageConversionError { - error = GenerateContentError.promptImageContentError(underlying: contentError) - } else { - error = GenerateContentError.internalError(underlying: underlying) - } - continuation.finish(throwing: error) + if let contentError = underlying as? ImageConversionError { + throw GenerateContentError.promptImageContentError(underlying: contentError) } + throw GenerateContentError.internalError(underlying: underlying) } let generateContentRequest = GenerateContentRequest(model: modelResourceName, diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 6191eb234ee..56dbf5ff5cc 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -64,7 +64,7 @@ final class ChatTests: XCTestCase { ) let chat = Chat(model: model, history: []) let input = "Test input" - let stream = await chat.sendMessageStream(input) + let stream = try await chat.sendMessageStream(input) // Ensure the values are parsed correctly for try await value in stream { diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 19c7a4bf80b..ef494760b60 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -760,7 +760,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -784,7 +784,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -807,7 +807,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -827,7 +827,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -847,7 +847,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -866,7 +866,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") do { for try await content in stream { XCTAssertNotNil(content.text) @@ -887,7 +887,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -904,7 +904,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -921,7 +921,7 @@ final class GenerativeModelTests: XCTestCase { ) var hadUnknown = false - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) if let ratings = content.candidates.first?.safetyRatings, @@ -940,7 +940,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") var citations = [Citation]() var responses = [GenerateContentResponse]() for try await content in stream { @@ -996,7 +996,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: appCheckToken ) - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1018,7 +1018,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: AppCheckInteropFake.placeholderTokenValue ) - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1030,7 +1030,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = [GenerateContentResponse]() - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) for try await response in stream { responses.append(response) } @@ -1056,7 +1056,7 @@ final class GenerativeModelTests: XCTestCase { var responseCount = 0 do { - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responseCount += 1 @@ -1076,7 +1076,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_nonHTTPResponse() async throws { MockURLProtocol.requestHandler = try nonHTTPRequestHandler() - let stream = await model.generateContentStream("Hi") + let stream = try await model.generateContentStream("Hi") do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1096,7 +1096,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1120,7 +1120,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1159,7 +1159,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = await model.generateContentStream(testPrompt) + let stream = try await model.generateContentStream(testPrompt) for try await content in stream { XCTAssertNotNil(content.text) responses += 1 From 52b9c4b1712e7ee0f20e07d87517405a0dd9befc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:50:48 -0700 Subject: [PATCH 009/258] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#13574) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../workflows/health-metrics-presubmit.yml | 2 +- .github/workflows/prerelease.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- .github/workflows/zip.yml | 22 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/health-metrics-presubmit.yml b/.github/workflows/health-metrics-presubmit.yml index e7e5aa1020c..97320bd8d4c 100644 --- a/.github/workflows/health-metrics-presubmit.yml +++ b/.github/workflows/health-metrics-presubmit.yml @@ -337,7 +337,7 @@ jobs: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/metrics_service_access.json.gpg \ metrics-access.json "${{ env.METRICS_SERVICE_SECRET }}" gcloud auth activate-service-account --key-file metrics-access.json - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4.1.7 id: download with: path: /Users/runner/test diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index c586d589e5e..a56f44ba74c 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -78,7 +78,7 @@ jobs: targeted_pod: FirebaseCore steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -120,7 +120,7 @@ jobs: targeted_pod: ${{ matrix.podspec }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08e5c2162b9..2d934918c71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: targeted_pod: FirebaseCore steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -119,7 +119,7 @@ jobs: targeted_pod: ${{ matrix.podspec }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index 4a40949a27e..bd49ad23063 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -122,7 +122,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -184,7 +184,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -238,7 +238,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -290,7 +290,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -361,7 +361,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -417,7 +417,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -477,7 +477,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -522,7 +522,7 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -562,7 +562,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -619,7 +619,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 @@ -675,7 +675,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v4.1.7 with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@v1 From a6fd721230b58652c467ba228bbf3c5e4298ad77 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 21:00:45 -0400 Subject: [PATCH 010/258] [Vertex AI] Add `SourceImage` enum to `ImageConversionError` (#13575) --- FirebaseVertexAI/CHANGELOG.md | 3 +++ .../Sources/PartsRepresentable+Image.swift | 25 ++++++++++++++----- .../Tests/Unit/PartsRepresentableTests.swift | 24 ++++++++++-------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 98f099be1aa..a848362a3a8 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -6,6 +6,9 @@ asynchronous and must be called with `try await`. (#13545, #13573) - [changed] **Breaking Change**: Creating a chat instance (`startChat`) is now asynchronous and must be called with `await`. (#13545) +- [changed] **Breaking Change**: The source image in the + `ImageConversionError.couldNotConvertToJPEG` error case is now an enum value + instead of the `Any` type. (#13575) # 10.29.0 - [feature] Added community support for watchOS. (#13215) diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift index ad04dbb2bcc..1991503a224 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift @@ -25,6 +25,19 @@ private let imageCompressionQuality: CGFloat = 0.8 /// For some image types like `CIImage`, creating valid model content requires creating a JPEG /// representation of the image that may not yet exist, which may be computationally expensive. public enum ImageConversionError: Error { + /// The image that could not be converted. + public enum SourceImage { + #if canImport(UIKit) + case uiImage(UIImage) + #elseif canImport(AppKit) + case nsImage(NSImage) + #endif // canImport(UIKit) + case cgImage(CGImage) + #if canImport(CoreImage) + case ciImage(CIImage) + #endif // canImport(CoreImage) + } + /// The image (the receiver of the call `toModelContentParts()`) was invalid. case invalidUnderlyingImage @@ -32,8 +45,8 @@ public enum ImageConversionError: Error { case couldNotAllocateDestination /// JPEG image data conversion failed, accompanied by the original image, which may be an - /// instance of `NSImageRep`, `UIImage`, `CGImage`, or `CIImage`. - case couldNotConvertToJPEG(Any) + /// instance of `NSImage`, `UIImage`, `CGImage`, or `CIImage`. + case couldNotConvertToJPEG(SourceImage) } #if canImport(UIKit) @@ -42,7 +55,7 @@ public enum ImageConversionError: Error { extension UIImage: ThrowingPartsRepresentable { public func tryPartsValue() throws -> [ModelContent.Part] { guard let data = jpegData(compressionQuality: imageCompressionQuality) else { - throw ImageConversionError.couldNotConvertToJPEG(self) + throw ImageConversionError.couldNotConvertToJPEG(.uiImage(self)) } return [ModelContent.Part.data(mimetype: "image/jpeg", data)] } @@ -59,7 +72,7 @@ public enum ImageConversionError: Error { let bmp = NSBitmapImageRep(cgImage: cgImage) guard let data = bmp.representation(using: .jpeg, properties: [.compressionFactor: 0.8]) else { - throw ImageConversionError.couldNotConvertToJPEG(bmp) + throw ImageConversionError.couldNotConvertToJPEG(.nsImage(self)) } return [ModelContent.Part.data(mimetype: "image/jpeg", data)] } @@ -84,7 +97,7 @@ public enum ImageConversionError: Error { if CGImageDestinationFinalize(imageDestination) { return [.data(mimetype: "image/jpeg", output as Data)] } - throw ImageConversionError.couldNotConvertToJPEG(self) + throw ImageConversionError.couldNotConvertToJPEG(.cgImage(self)) } } #endif // !os(watchOS) @@ -105,7 +118,7 @@ public enum ImageConversionError: Error { if let jpegData = jpegData { return [.data(mimetype: "image/jpeg", jpegData)] } - throw ImageConversionError.couldNotConvertToJPEG(self) + throw ImageConversionError.couldNotConvertToJPEG(.ciImage(self)) } } #endif // canImport(CoreImage) diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift index ac5ce6e232b..bd539d825a8 100644 --- a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift @@ -60,6 +60,7 @@ final class PartsRepresentableTests: XCTestCase { let image = CIImage.empty() do { _ = try image.tryPartsValue() + XCTFail("Expected model content from invalid image to error") } catch { guard let imageError = (error as? ImageConversionError) else { XCTFail("Got unexpected error type: \(error)") @@ -67,15 +68,16 @@ final class PartsRepresentableTests: XCTestCase { } switch imageError { case let .couldNotConvertToJPEG(source): - // String(describing:) works around a type error. - XCTAssertEqual(String(describing: source), String(describing: image)) - return - case _: + guard case let .ciImage(ciImage) = source else { + XCTFail("Unexpected image source: \(source)") + return + } + XCTAssertEqual(ciImage, image) + default: XCTFail("Expected image conversion error, got \(imageError) instead") return } } - XCTFail("Expected model content from invalid image to error") } #endif // canImport(CoreImage) @@ -84,6 +86,7 @@ final class PartsRepresentableTests: XCTestCase { let image = UIImage() do { _ = try image.tryPartsValue() + XCTFail("Expected model content from invalid image to error") } catch { guard let imageError = (error as? ImageConversionError) else { XCTFail("Got unexpected error type: \(error)") @@ -91,15 +94,16 @@ final class PartsRepresentableTests: XCTestCase { } switch imageError { case let .couldNotConvertToJPEG(source): - // String(describing:) works around a type error. - XCTAssertEqual(String(describing: source), String(describing: image)) - return - case _: + guard case let .uiImage(uiImage) = source else { + XCTFail("Unexpected image source: \(source)") + return + } + XCTAssertEqual(uiImage, image) + default: XCTFail("Expected image conversion error, got \(imageError) instead") return } } - XCTFail("Expected model content from invalid image to error") } func testModelContentFromUIImageIsNotEmpty() throws { From 71d8e92d168333f79ee29a555d32fee086e66ca5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 3 Sep 2024 21:49:51 -0400 Subject: [PATCH 011/258] [Vertex AI] Add `responseSchema` to `GenerationConfig` (#13576) --- FirebaseVertexAI/CHANGELOG.md | 4 ++ .../Sources/GenerationConfig.swift | 17 ++++++- .../Tests/Unit/GenerationConfigTests.swift | 49 ++++++++++++++++--- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index a848362a3a8..7390ef0c57c 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -9,6 +9,10 @@ - [changed] **Breaking Change**: The source image in the `ImageConversionError.couldNotConvertToJPEG` error case is now an enum value instead of the `Any` type. (#13575) +- [added] Added support for specifying a JSON `responseSchema` in + `GenerationConfig`; see + [control generated output](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output) + for more details. (#13576) # 10.29.0 - [feature] Added community support for watchOS. (#13215) diff --git a/FirebaseVertexAI/Sources/GenerationConfig.swift b/FirebaseVertexAI/Sources/GenerationConfig.swift index ec29b708c75..3f3a4b6f214 100644 --- a/FirebaseVertexAI/Sources/GenerationConfig.swift +++ b/FirebaseVertexAI/Sources/GenerationConfig.swift @@ -70,6 +70,17 @@ public struct GenerationConfig { /// - `application/json`: JSON response in the candidates. public let responseMIMEType: String? + /// Output schema of the generated candidate text. + /// If set, a compatible ``responseMIMEType`` must also be set. + /// + /// Compatible MIME types: + /// - `application/json`: Schema for JSON response. + /// + /// Refer to the [Control generated + /// output](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output) + /// guide for more details. + public let responseSchema: Schema? + /// Creates a new `GenerationConfig` value. /// /// - Parameter temperature: See ``temperature`` @@ -78,9 +89,12 @@ public struct GenerationConfig { /// - Parameter candidateCount: See ``candidateCount`` /// - Parameter maxOutputTokens: See ``maxOutputTokens`` /// - Parameter stopSequences: See ``stopSequences`` + /// - Parameter responseMIMEType: See ``responseMIMEType`` + /// - Parameter responseSchema: See ``responseSchema`` public init(temperature: Float? = nil, topP: Float? = nil, topK: Int? = nil, candidateCount: Int? = nil, maxOutputTokens: Int? = nil, - stopSequences: [String]? = nil, responseMIMEType: String? = nil) { + stopSequences: [String]? = nil, responseMIMEType: String? = nil, + responseSchema: Schema? = nil) { // Explicit init because otherwise if we re-arrange the above variables it changes the API // surface. self.temperature = temperature @@ -90,6 +104,7 @@ public struct GenerationConfig { self.maxOutputTokens = maxOutputTokens self.stopSequences = stopSequences self.responseMIMEType = responseMIMEType + self.responseSchema = responseSchema } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift index e2bfe0d4fb6..35450c03758 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift @@ -48,7 +48,7 @@ final class GenerationConfigTests: XCTestCase { let candidateCount = 2 let maxOutputTokens = 256 let stopSequences = ["END", "DONE"] - let responseMIMEType = "text/plain" + let responseMIMEType = "application/json" let generationConfig = GenerationConfig( temperature: temperature, topP: topP, @@ -56,7 +56,8 @@ final class GenerationConfigTests: XCTestCase { candidateCount: candidateCount, maxOutputTokens: maxOutputTokens, stopSequences: stopSequences, - responseMIMEType: responseMIMEType + responseMIMEType: responseMIMEType, + responseSchema: Schema(type: .array, items: Schema(type: .string)) ) let jsonData = try encoder.encode(generationConfig) @@ -67,6 +68,12 @@ final class GenerationConfigTests: XCTestCase { "candidateCount" : \(candidateCount), "maxOutputTokens" : \(maxOutputTokens), "responseMIMEType" : "\(responseMIMEType)", + "responseSchema" : { + "items" : { + "type" : "STRING" + }, + "type" : "ARRAY" + }, "stopSequences" : [ "END", "DONE" @@ -78,16 +85,46 @@ final class GenerationConfigTests: XCTestCase { """) } - func testEncodeGenerationConfig_responseMIMEType() throws { - let mimeType = "image/jpeg" - let generationConfig = GenerationConfig(responseMIMEType: mimeType) + func testEncodeGenerationConfig_jsonResponse() throws { + let mimeType = "application/json" + let generationConfig = GenerationConfig( + responseMIMEType: mimeType, + responseSchema: Schema( + type: .object, + properties: [ + "firstName": Schema(type: .string), + "lastName": Schema(type: .string), + "age": Schema(type: .integer), + ], + requiredProperties: ["firstName", "lastName", "age"] + ) + ) let jsonData = try encoder.encode(generationConfig) let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) XCTAssertEqual(json, """ { - "responseMIMEType" : "\(mimeType)" + "responseMIMEType" : "\(mimeType)", + "responseSchema" : { + "properties" : { + "age" : { + "type" : "INTEGER" + }, + "firstName" : { + "type" : "STRING" + }, + "lastName" : { + "type" : "STRING" + } + }, + "required" : [ + "firstName", + "lastName", + "age" + ], + "type" : "OBJECT" + } } """) } From b0dbeb8d37869f48b052170633510bb4c2753d33 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 4 Sep 2024 07:29:05 -0700 Subject: [PATCH 012/258] Fix Firebase/Crashlytics min iOS version (#13580) --- Firebase.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firebase.podspec b/Firebase.podspec index 78a0093eef2..75dd6212b16 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -122,7 +122,7 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.dependency 'Firebase/CoreOnly' ss.dependency 'FirebaseCrashlytics', '~> 11.2.0' # Standard platforms PLUS watchOS. - ss.ios.deployment_target = '13.0' + ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' ss.watchos.deployment_target = '7.0' From e0158c04bf37ff034cd4b6d6ed93c20bba9fd352 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 4 Sep 2024 13:37:43 -0400 Subject: [PATCH 013/258] Add `-Wno-error=redundant-move` to cmake builds (#13582) --- cmake/compiler_setup.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmake/compiler_setup.cmake b/cmake/compiler_setup.cmake index 53c13d5576f..f214c55ca66 100644 --- a/cmake/compiler_setup.cmake +++ b/cmake/compiler_setup.cmake @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +include(CheckCXXCompilerFlag) + # C++ Compiler setup # We use C++14 @@ -76,6 +78,14 @@ if(CXX_CLANG OR CXX_GNU) list(APPEND common_flags -fdiagnostics-color) endif() endif() + + # Disable treating "redundant-move" as an error since it's not really a problem, + # and is even a valid coding style to over-use std::move() in case the type is + # ever changed to become non-trivially moveable. + CHECK_CXX_COMPILER_FLAG("-Wno-error=redundant-move" FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + if(FIREBASE_CXX_COMPILER_FLAG_REDUNDANT_MOVE_SUPPORTED) + list(APPEND common_flags -Wno-error=redundant-move) + endif() endif() if(APPLE) From 3755841ed010172c41ea3d43590ca3bc5620700c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 4 Sep 2024 14:17:04 -0400 Subject: [PATCH 014/258] Fix cmake builds with Python 3.12, which deleted the long-deprecated 'distutils' module (#13583) --- cmake/external/nanopb.patch | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmake/external/nanopb.patch b/cmake/external/nanopb.patch index c3c3fe8e596..d4a51570440 100644 --- a/cmake/external/nanopb.patch +++ b/cmake/external/nanopb.patch @@ -1,7 +1,7 @@ diff -Naur nanopb/CMakeLists.txt nanopb-fix/CMakeLists.txt --- nanopb/CMakeLists.txt 2021-03-22 08:50:07.000000000 -0400 +++ nanopb-fix/CMakeLists.txt 2022-06-24 16:17:09.130783413 -0400 -@@ -41,7 +41,7 @@ +@@ -41,10 +41,10 @@ if(nanopb_BUILD_GENERATOR) set(generator_protos nanopb plugin) @@ -9,7 +9,11 @@ diff -Naur nanopb/CMakeLists.txt nanopb-fix/CMakeLists.txt + find_package(PythonInterp 3.7 REQUIRED) execute_process( COMMAND ${PYTHON_EXECUTABLE} -c - "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix=''))" +- "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix=''))" ++ "import os.path, sys, sysconfig; print(os.path.relpath(sysconfig.get_path('purelib'), start=sys.prefix))" + OUTPUT_VARIABLE PYTHON_INSTDIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) diff -Naur nanopb/generator/nanopb_generator.py nanopb-fix/generator/nanopb_generator.py --- nanopb/generator/nanopb_generator.py 2021-03-22 08:50:07.000000000 -0400 +++ nanopb-fix/generator/nanopb_generator.py 2022-11-01 15:37:38.112297044 -0400 From 7c6c5556de9c5db6b7dc64eba8ddf5f495f572cd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 4 Sep 2024 15:40:59 -0400 Subject: [PATCH 015/258] Firestore: Improve efficiency of memory persistence when processing a large number of writes (#13572) --- Firestore/CHANGELOG.md | 3 +++ Firestore/core/src/local/memory_mutation_queue.h | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 3e767205d77..0b72c4d470b 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [changed] Improve efficiency of memory persistence when processing a large number of writes. (#13572) + # 11.2.0 - [fixed] Marked all public classes with only readonly properties as `Sendable` to address Swift Concurrency Check warning. (#12666) diff --git a/Firestore/core/src/local/memory_mutation_queue.h b/Firestore/core/src/local/memory_mutation_queue.h index 479c676a480..e15a396a6cc 100644 --- a/Firestore/core/src/local/memory_mutation_queue.h +++ b/Firestore/core/src/local/memory_mutation_queue.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_LOCAL_MEMORY_MUTATION_QUEUE_H_ #define FIRESTORE_CORE_SRC_LOCAL_MEMORY_MUTATION_QUEUE_H_ +#include #include #include @@ -59,7 +60,7 @@ class MemoryMutationQueue : public MutationQueue { void RemoveMutationBatch(const model::MutationBatch& batch) override; std::vector AllMutationBatches() override { - return queue_; + return std::vector(queue_.begin(), queue_.end()); } std::vector AllMutationBatchesAffectingDocumentKeys( @@ -128,7 +129,7 @@ class MemoryMutationQueue : public MutationQueue { * Once the held write acknowledgements become visible they are removed from * the head of the queue along with any tombstones that follow. */ - std::vector queue_; + std::deque queue_; /** * The next value to use when assigning sequential IDs to each mutation From 413dfef9ef85c7af866b0c528b85372d0f5b98f5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 4 Sep 2024 18:14:26 -0400 Subject: [PATCH 016/258] [Vertex AI] Prepare podspec for a CocoaPods release (#13585) --- .github/workflows/vertexai.yml | 23 +++++++ FirebaseVertexAI-Docs.not_podspec | 47 ------------- FirebaseVertexAI.podspec | 67 +++++++++++++++++++ FirebaseVertexAI/Tests/Unit/ChatTests.swift | 7 +- .../Tests/Unit/GenerativeModelTests.swift | 7 +- .../Tests/Unit/VertexComponentTests.swift | 3 +- Package.swift | 2 +- 7 files changed, 105 insertions(+), 51 deletions(-) delete mode 100644 FirebaseVertexAI-Docs.not_podspec create mode 100644 FirebaseVertexAI.podspec diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 2b727b13831..3e9bf41e292 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -72,6 +72,29 @@ jobs: retry_wait_seconds: 120 command: scripts/build.sh FirebaseVertexAIIntegration ${{ matrix.target }} spm + pod-lib-lint: + # Don't run on private repo unless it is a PR. + if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + strategy: + matrix: + target: [ios] + os: [macos-14] + include: + - os: macos-14 + xcode: Xcode_15.2 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Clone mock responses + run: scripts/update_vertexai_responses.sh + - uses: ruby/setup-ruby@v1 + - name: Setup Bundler + run: scripts/setup_bundler.sh + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Build and test + run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseVertexAI.podspec --platforms=${{ matrix.target }} + sample: strategy: matrix: diff --git a/FirebaseVertexAI-Docs.not_podspec b/FirebaseVertexAI-Docs.not_podspec deleted file mode 100644 index 4c447335f65..00000000000 --- a/FirebaseVertexAI-Docs.not_podspec +++ /dev/null @@ -1,47 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'FirebaseVertexAI-Docs' - s.version = '10.27.0' - s.summary = 'Firebase Vertex AI' - - s.description = <<-DESC - Placeholder podspec for docsgen only. Do not use this pod. - - NOTE: Rename the file extension from `.not_podspec` to `.podspec` before - running `pod gen` for docs generation. - DESC - - s.homepage = 'https://firebase.google.com' - s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } - s.authors = 'Google, Inc.' - - s.source = { - :git => 'https://github.com/firebase/firebase-ios-sdk.git', - :tag => 'CocoaPods-' + s.version.to_s - } - - s.social_media_url = 'https://twitter.com/Firebase' - - ios_deployment_target = '15.0' - osx_deployment_target = '11.0' - - s.ios.deployment_target = ios_deployment_target - s.osx.deployment_target = osx_deployment_target - - s.cocoapods_version = '>= 1.12.0' - s.prefix_header_file = false - - s.source_files = [ - 'FirebaseVertexAI/Sources/**/*.swift', - ] - - s.swift_version = '5.9' - - s.framework = 'Foundation' - s.ios.framework = 'UIKit' - s.osx.framework = 'AppKit' - - s.dependency 'FirebaseAppCheckInterop', '~> 10.17' - s.dependency 'FirebaseAuthInterop', '~> 10.25' - s.dependency 'FirebaseCore', '~> 10.5' - s.dependency 'FirebaseCoreExtension', '~> 10.0' -end diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec new file mode 100644 index 00000000000..a5251b4dc1f --- /dev/null +++ b/FirebaseVertexAI.podspec @@ -0,0 +1,67 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseVertexAI' + s.version = '11.2.0-beta' + s.summary = 'Vertex AI in Firebase - Public Preview' + + s.description = <<-DESC +[Public Preview] Build AI-powered apps and features with the Gemini API using +the Vertex AI in Firebase SDK. + DESC + + s.homepage = 'https://firebase.google.com' + s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + s.source = { + :git => 'https://github.com/firebase/firebase-ios-sdk.git', + :tag => 'CocoaPods-' + s.version.to_s + } + + s.social_media_url = 'https://twitter.com/Firebase' + + ios_deployment_target = '15.0' + osx_deployment_target = '11.0' + tvos_deployment_target = '15.0' + watchos_deployment_target = '8.0' + + s.ios.deployment_target = ios_deployment_target + s.osx.deployment_target = osx_deployment_target + s.tvos.deployment_target = tvos_deployment_target + s.watchos.deployment_target = watchos_deployment_target + + s.cocoapods_version = '>= 1.12.0' + s.prefix_header_file = false + + s.source_files = [ + 'FirebaseVertexAI/Sources/**/*.swift', + ] + + s.swift_version = '5.9' + + s.framework = 'Foundation' + s.ios.framework = 'UIKit' + s.osx.framework = 'AppKit' + s.tvos.framework = 'UIKit' + s.watchos.framework = 'WatchKit' + + s.dependency 'FirebaseAppCheckInterop', '~> 11.2' + s.dependency 'FirebaseAuthInterop', '~> 11.2' + s.dependency 'FirebaseCore', '~> 11.2' + s.dependency 'FirebaseCoreExtension', '~> 11.2' + + s.test_spec 'unit' do |unit_tests| + unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' + unit_tests.scheme = { :code_coverage => true } + unit_tests.platforms = { + :ios => ios_deployment_target, + :osx => osx_deployment_target, + :tvos => tvos_deployment_target + } + unit_tests.source_files = [ + unit_tests_dir + '**/*.swift', + ] + unit_tests.resources = [ + unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/**/*.{txt,json}', + ] + end +end diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 56dbf5ff5cc..2e55b73020f 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -32,7 +32,12 @@ final class ChatTests: XCTestCase { } func testMergingText() async throws { - let fileURL = try XCTUnwrap(Bundle.module.url( + #if SWIFT_PACKAGE + let bundle = Bundle.module + #else // SWIFT_PACKAGE + let bundle = Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + let fileURL = try XCTUnwrap(bundle.url( forResource: "streaming-success-basic-reply-parts", withExtension: "txt" )) diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index ef494760b60..f112168151a 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1278,7 +1278,12 @@ final class GenerativeModelTests: XCTestCase { #if os(watchOS) throw XCTSkip("Custom URL protocols are unsupported in watchOS 2 and later.") #endif // os(watchOS) - let fileURL = try XCTUnwrap(Bundle.module.url(forResource: name, withExtension: ext)) + #if SWIFT_PACKAGE + let bundle = Bundle.module + #else // SWIFT_PACKAGE + let bundle = Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + let fileURL = try XCTUnwrap(bundle.url(forResource: name, withExtension: ext)) return { request in let requestURL = try XCTUnwrap(request.url) XCTAssertEqual(requestURL.path.occurrenceCount(of: "models/"), 1) diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift index 3ee12eb1c4d..a6f77467c24 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift @@ -14,9 +14,10 @@ import FirebaseCore import Foundation -import SharedTestUtilities import XCTest +@_implementationOnly import FirebaseCoreExtension + @testable import FirebaseVertexAI @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/Package.swift b/Package.swift index ca02d9e4ff0..675d1e3e7aa 100644 --- a/Package.swift +++ b/Package.swift @@ -1305,7 +1305,7 @@ let package = Package( ), .testTarget( name: "FirebaseVertexAIUnit", - dependencies: ["FirebaseVertexAI", "SharedTestUtilities"], + dependencies: ["FirebaseVertexAI"], path: "FirebaseVertexAI/Tests/Unit", resources: [ .process("vertexai-sdk-test-data/mock-responses"), From 77f7357442e1e983b35b7f7dedcff0dc42095641 Mon Sep 17 00:00:00 2001 From: "LamTrinh.Dev" Date: Thu, 5 Sep 2024 21:33:08 +0700 Subject: [PATCH 017/258] Correct AddNewPod link at README.md (#13588) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 665e16c2dc1..5da753b2321 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Alternatively, disable signing in each target: ### Adding a New Firebase Pod -Refer to [AddNewPod](AddNewPod.md) Markdown file for details. +Refer to [AddNewPod](docs/AddNewPod.md) Markdown file for details. ### Managing Headers and Imports From 60b6dd591e1e24ede5a82b8e1b2fa44c807da4c8 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 5 Sep 2024 12:28:37 -0400 Subject: [PATCH 018/258] [Release] Update Firestore SPM binary for 11.2.0 (#13594) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 675d1e3e7aa..57f58043df8 100644 --- a/Package.swift +++ b/Package.swift @@ -1507,8 +1507,8 @@ func firestoreTargets() -> [Target] { } else { return .binaryTarget( name: "FirebaseFirestoreInternal", - url: "https://dl.google.com/firebase/ios/bin/firestore/11.1.0/rc1/FirebaseFirestoreInternal.zip", - checksum: "fc5453c2dc5f77426a62992bda3bcea21051b27c0f697f3a8ec461ecfafcdc44" + url: "https://dl.google.com/firebase/ios/bin/firestore/11.2.0/rc0/FirebaseFirestoreInternal.zip", + checksum: "821acae8d3b79c6d35539d87926da8aeae4a63878bec2987b01cb885b5120df2" ) } }() From fab6834dd48c0e447cdf2d8958963e33e24f0ed1 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:23:38 -0400 Subject: [PATCH 019/258] [Auth] Add app ID prefix to sample plist (#13599) --- .../AuthSample/SwiftApplication.plist.gpg | Bin 881 -> 882 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/scripts/gha-encrypted/AuthSample/SwiftApplication.plist.gpg b/scripts/gha-encrypted/AuthSample/SwiftApplication.plist.gpg index 4aff709a2a89010d34aced645ba4b5401cc22809..3c836d05c41a6eb077e7d45cfec19de5f39b0e5b 100644 GIT binary patch literal 882 zcmV-&1C9KQ4Fm}T2oeP(U-ryS!~fFh0in4j%>GJMO(A4~i?~!Dt7{$tV(w3TgR=qb%Qac%=mj2xJ^_jm$g^ZUR0rcP+m*0 zeFDZW5vQf$477_P7ci%PzNOYXa5H6H`#ea)7|21YKLB3AfOTbxlT3B*2+H3~JvDyE zs|7c<{KLBzlfM~Cw0?RwpoA@?EXii|pa4$oMq3eJ(Zn{~eBm4I?69!f@2-c_EesEZ zAA-{a3@RyQ!NsZ*02{6Eq-*H@no9?Lbc`xB)@T?v_i#Tv_Q7}~j`L&XIYY2IC=t!OVK_h*nysq6_xVllnOWZFBPiV6U`K`e z_J4mrc(5juojoY^0}QFb>IB75q=+)eGS>yZYv9Ae#YZ=8ZJ|5`<^B_R{}wJPIU)f1 zJ?^wD(0bh>z=IBj!^>R8hL!P7xe&kddi5l_(>a?TJuQZOuc?-nNuxJ}!2P-Z0nG?; z$nXY<5;o{=9d??wjrwN$tu6XIanb10OT4S4-VOaU6p|SNi) zE+F!ZgE^yL49xJp7*0!ucBJ)#q4u~w(JOt;stTG;s58C(6On*0F_sexFlFDujY~ub zz2-O-Klazn32@8%ldUBDz@P4cA0ysEw;$yS9k2Hxfuq%mxv`V6Ud?Rc116HNzoR0H&L}%X|Jv{kyxk zWRcgcZ1Ex~R`zq%A{uuF$woio&K?aCW~gF!8Ng%Z@}q6^ju^qJ6s`5^dtI;fnJ!R? zHKoxHWHx9-hu0wf5!It?W%ApFNBBpfZ7s)npe2ZjqDsn+f=g)_m6vM>Y-Qa)%1<7*(}9v{5%ja IwadEOq%bqFZwu2z z?B#JX6!ZhN%50>l2-Sd$qwPT`_+9%1DYAV_V~Sd4lmZ~U63mW6p=Vmh5}lJX3BX$j zzkzsyhRMMD9eqFVgVvTv+pl+HC=H*fJ&9fJ)vd!d2aQ+?-ki|od&7ILKthJhU4Xto zzl2X|&YtyU%oXlO)GtRM%@Mvgi0W0vLDoMED7%v2MlovjLcGos1A1UX<=AQBY6OD+ zPOJaCq_p;OZtcdi@$0jjQ$o|2Q!I&BAi?YX?P7q0hVMuADc_pJN+yIz5MKwxxirjTP!LP_aV?$td{_pKc>XS2CLCggT?~7%2@mg9KQSrS;Y=6wpT|&nhr9N`WnTbV z%%NcOAKN+Wmm6fCue+nQBDh9c^@dMXfD4@*rX<`PaFk((8Nr>in{A?9X_%do7_h(c zAr}D+`b%sXLt+gAI+f7O>9d!X=7E6i-g+`hb_$gi*$ZMQl z?R|cP!;4@0cEAkB?n6G24omFAgio6Qqt4ZWd2B48NqyxOt?PRHM!RZcjUb-fu!mF| z>5WSI`{<4gac%+d<`a@Aw;@Co%V0lqrxpkDRmWVe{i=FcQ$bvm<@J%36$FjNS=r=w z_lF>2kgjz_NQjhB-wlE)$-l^aU0y9ZlnGACW_|^;-A~G~LQP!fq3Cfc3^T#(=`9#V zoYSYi);%J;uJkcFj)@Bz85B~L11+WJlnZ Date: Fri, 6 Sep 2024 12:05:47 -0700 Subject: [PATCH 020/258] [Auth] Better keychain error descriptions (#13600) --- .../Swift/Utilities/AuthErrorUtils.swift | 3 ++- .../SettingsViewController.swift | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index e4997d07c2d..dbf2dcb9a1b 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -371,7 +371,8 @@ class AuthErrorUtils: NSObject { } static func keychainError(function: String, status: OSStatus) -> Error { - let reason = "\(function) (\(status))" + let message = SecCopyErrorMessageString(status, nil) as String? ?? "" + let reason = "\(function) (\(status)) \(message)" return error(code: .keychainError, userInfo: [NSLocalizedFailureReasonErrorKey: reason]) } diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift index e7f3b95181e..77f6a6f7d8b 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift @@ -113,14 +113,19 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { } private func toggleAccessGroup() { - if AppManager.shared.auth().userAccessGroup == nil { - guard let bundleDictionary = Bundle.main.infoDictionary, - let group = bundleDictionary["AppIdentifierPrefix"] as? String else { - fatalError("Configure AppIdentifierPrefix in the plist") + do { + if AppManager.shared.auth().userAccessGroup == nil { + guard let bundleDictionary = Bundle.main.infoDictionary, + let group = bundleDictionary["AppIdentifierPrefix"] as? String else { + fatalError("Configure AppIdentifierPrefix in the plist") + } + try AppManager.shared.auth().useUserAccessGroup(group + + "com.google.firebase.auth.keychainGroup1") + } else { + try AppManager.shared.auth().useUserAccessGroup(nil) } - AppManager.shared.auth().userAccessGroup = group + "com.google.firebase.auth.keychainGroup1" - } else { - AppManager.shared.auth().userAccessGroup = nil + } catch { + fatalError("Failed to set userAccessGroup with error \(error)") } } From 424516b5ab552a07720927b39294e6cbe99f1306 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:34:28 -0400 Subject: [PATCH 021/258] [Auth] Forward secure coding calls for TOTPMultiFactorInfo (#13592) --- FirebaseAuth/CHANGELOG.md | 3 ++ .../RPC/Proto/AuthProtoMFAEnrollment.swift | 2 ++ .../TOTP/TOTPMultiFactorInfo.swift | 11 +++---- FirebaseAuth/Tests/Unit/UserTests.swift | 33 +++++++++++++------ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 96771bafd05..81388ecef86 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -7,6 +7,9 @@ in methods and a `nil` error. In such cases, an empty array is instead returned with the `nil` error. (#13550) - [Fixed] Fixed user session persistence in multi tenant projects. Introduced in 11.0.0. (#13565) +- [Fixed] Fixed encoding crash that occurs when using TOTP multi-factor + authentication. Note that this fix will not be in the 11.2.0 zip and Carthage + distributions, but will be included from 11.3.0 onwards. (#13591) # 11.1.0 - [fixed] Fixed `Swift.error` conformance for `AuthErrorCode`. (#13430) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift index e742cf8cf77..494252ac56e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift @@ -16,6 +16,8 @@ import Foundation class AuthProtoMFAEnrollment: NSObject, AuthProto { let phoneInfo: String? + // In practice, this will be an empty dictionary. The presence of which + // indicates TOTP MFA enrollment rather than phone MFA enrollment. let totpInfo: NSObject? let mfaEnrollmentID: String? let displayName: String? diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift index 2273b2aea9e..b1c3f7b7d95 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift @@ -22,19 +22,18 @@ import Foundation /// /// This class is available on iOS only. class TOTPMultiFactorInfo: MultiFactorInfo { - /// This is the totp info for the second factor. - let totpInfo: NSObject? - /// Initialize the AuthProtoMFAEnrollment instance with proto. /// - Parameter proto: AuthProtoMFAEnrollment proto object. init(proto: AuthProtoMFAEnrollment) { - totpInfo = proto.totpInfo super.init(proto: proto, factorID: PhoneMultiFactorInfo.TOTPMultiFactorID) } - @available(*, unavailable) required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init(coder: coder) + } + + override class var supportsSecureCoding: Bool { + super.supportsSecureCoding } } #endif diff --git a/FirebaseAuth/Tests/Unit/UserTests.swift b/FirebaseAuth/Tests/Unit/UserTests.swift index 619efac790a..77c1449792f 100644 --- a/FirebaseAuth/Tests/Unit/UserTests.swift +++ b/FirebaseAuth/Tests/Unit/UserTests.swift @@ -141,12 +141,21 @@ class UserTests: RPCBaseTests { "phoneNumber": kPhoneNumber, "createdAt": String(Int(kCreationDateTimeIntervalInSeconds) * 1000), // to nanoseconds "lastLoginAt": String(Int(kLastSignInDateTimeIntervalInSeconds) * 1000), - "mfaInfo": [[ - "phoneInfo": kPhoneInfo, - "mfaEnrollmentId": kEnrollmentID, - "displayName": kDisplayName, - "enrolledAt": kEnrolledAt, - ]], + "mfaInfo": [ + [ + "phoneInfo": kPhoneInfo, + "mfaEnrollmentId": kEnrollmentID, + "displayName": kDisplayName, + "enrolledAt": kEnrolledAt, + ], + [ + // In practice, this will be an empty dictionary. + "totpInfo": [AnyHashable: AnyHashable](), + "mfaEnrollmentId": kEnrollmentID, + "displayName": kDisplayName, + "enrolledAt": kEnrolledAt, + ] as [AnyHashable: AnyHashable], + ], ]] let expectation = self.expectation(description: #function) @@ -346,11 +355,15 @@ class UserTests: RPCBaseTests { // Verify MultiFactorInfo properties. let enrolledFactors = try XCTUnwrap(user.multiFactor.enrolledFactors) + XCTAssertEqual(enrolledFactors.count, 2) XCTAssertEqual(enrolledFactors[0].factorID, PhoneMultiFactorInfo.PhoneMultiFactorID) - XCTAssertEqual(enrolledFactors[0].uid, kEnrollmentID) - XCTAssertEqual(enrolledFactors[0].displayName, self.kDisplayName) - let date = try XCTUnwrap(enrolledFactors[0].enrollmentDate) - XCTAssertEqual("\(date)", kEnrolledAtMatch) + XCTAssertEqual(enrolledFactors[1].factorID, PhoneMultiFactorInfo.TOTPMultiFactorID) + for enrolledFactor in enrolledFactors { + XCTAssertEqual(enrolledFactor.uid, kEnrollmentID) + XCTAssertEqual(enrolledFactor.displayName, self.kDisplayName) + let date = try XCTUnwrap(enrolledFactor.enrollmentDate) + XCTAssertEqual("\(date)", kEnrolledAtMatch) + } #endif } catch { XCTFail("Caught an error in \(#function): \(error)") From 3e2c79dd62289dc0c503eabbdb262ad6118432c8 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 9 Sep 2024 15:36:30 -0400 Subject: [PATCH 022/258] [Vertex AI] Add `Decodable` conformance for `FunctionResponse` (#13606) --- FirebaseVertexAI/CHANGELOG.md | 3 ++ .../Sources/FunctionCalling.swift | 2 +- FirebaseVertexAI/Sources/ModelContent.swift | 9 ++++-- .../Tests/Unit/ModelContentTests.swift | 31 ++++++++++++++++++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 7390ef0c57c..c048c49ebc4 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) + # 11.2.0 - [fixed] Resolved a decoding error for citations without a `uri` and added support for decoding `title` fields, which were previously ignored. (#13518) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 95087f16ce1..3c88279c6df 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -193,4 +193,4 @@ extension FunctionCallingConfig.Mode: Encodable {} extension ToolConfig: Encodable {} -extension FunctionResponse: Encodable {} +extension FunctionResponse: Codable {} diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index f8695aafc47..3262a4eba15 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -179,10 +179,13 @@ extension ModelContent.Part: Codable { self = .data(mimetype: mimetype, bytes) } else if values.contains(.functionCall) { self = try .functionCall(values.decode(FunctionCall.self, forKey: .functionCall)) + } else if values.contains(.functionResponse) { + self = try .functionResponse(values.decode(FunctionResponse.self, forKey: .functionResponse)) } else { - throw DecodingError.dataCorrupted(.init( - codingPath: [CodingKeys.text, CodingKeys.inlineData], - debugDescription: "No text, inline data or function call was found." + let unexpectedKeys = values.allKeys.map { $0.stringValue } + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: values.codingPath, + debugDescription: "Unexpected ModelContent.Part type(s): \(unexpectedKeys)" )) } } diff --git a/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift b/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift index 8eb02045361..67175af739b 100644 --- a/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift @@ -19,6 +19,7 @@ import XCTest @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ModelContentTests: XCTestCase { + let decoder = JSONDecoder() let encoder = JSONEncoder() override func setUp() { @@ -27,7 +28,35 @@ final class ModelContentTests: XCTestCase { ) } - // MARK: ModelContent.Part Encoding + // MARK: - ModelContent.Part Decoding + + func testDecodeFunctionResponsePart() throws { + let functionName = "test-function-name" + let resultParameter = "test-result-parameter" + let resultValue = "test-result-value" + let json = """ + { + "functionResponse" : { + "name" : "\(functionName)", + "response" : { + "\(resultParameter)" : "\(resultValue)" + } + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(ModelContent.Part.self, from: jsonData) + + guard case let .functionResponse(functionResponse) = part else { + XCTFail("Decoded Part was not a FunctionResponse.") + return + } + XCTAssertEqual(functionResponse.name, functionName) + XCTAssertEqual(functionResponse.response, [resultParameter: .string(resultValue)]) + } + + // MARK: - ModelContent.Part Encoding func testEncodeFileDataPart() throws { let mimeType = "image/jpeg" From 3d035c11995a0b27a9f7b4ef528fe416a722e4ad Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 9 Sep 2024 16:33:11 -0400 Subject: [PATCH 023/258] [Vertex AI] Add pod to FirebaseManifest (#13586) --- ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json | 1 + .../Sources/FirebaseManifest/FirebaseManifest.swift | 1 + ReleaseTooling/Sources/ZipBuilder/Platform.swift | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json diff --git a/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json b/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json @@ -0,0 +1 @@ +{} diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 805eaa6045d..0e6e2057831 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -53,6 +53,7 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), + Pod("FirebaseVertexAI", isBeta: true, allowWarnings: true, zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), ] ) diff --git a/ReleaseTooling/Sources/ZipBuilder/Platform.swift b/ReleaseTooling/Sources/ZipBuilder/Platform.swift index 3746d1b9334..d9281b11331 100644 --- a/ReleaseTooling/Sources/ZipBuilder/Platform.swift +++ b/ReleaseTooling/Sources/ZipBuilder/Platform.swift @@ -68,9 +68,9 @@ enum PlatformMinimum { /// Useful to disable minimum version checking on pod installation. Pods still get built with /// for the minimum version specified in the podspec. static func useRecentVersions() { - minimumIOSVersion = "14.0" + minimumIOSVersion = "15.0" minimumMacOSVersion = "11.0" - minimumTVOSVersion = "14.0" + minimumTVOSVersion = "15.0" minimumWatchOSVersion = "8.0" } } From b2a33ce3b0ffcc70f6b96220b2c9ad398019759e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:40:25 -0400 Subject: [PATCH 024/258] [Infra] Reduce test flakes due to async variable assignment (#13610) --- .../Tests/Unit/AuthBackendRPCImplentationTests.swift | 12 +++--------- .../Tests/Unit/Fakes/FakeBackendRPCIssuer.swift | 11 +++++------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift index 7fa7f4dec76..e3e6748c887 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift @@ -590,12 +590,10 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withJSON: [:]) } _ = try? await rpcImplementation.call(with: request) - // Make sure completeRequest updates. - usleep(10000) // Then let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue() - let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest) + let completeRequest = await rpcIssuer.completeRequest.value let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client") XCTAssertEqual(headerValue, expectedHeader) } @@ -619,10 +617,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withJSON: [:]) } _ = try? await rpcImplementation.call(with: request) - // Make sure completeRequest updates. - usleep(10000) - let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest) + let completeRequest = await rpcIssuer.completeRequest.value let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck") XCTAssertEqual(headerValue, fakeAppCheck.fakeAppCheckToken) } @@ -651,11 +647,9 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withJSON: [:]) } _ = try? await rpcImplementation.call(with: request) - // Make sure completRequest updates. - usleep(10000) // Then - let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest) + let completeRequest = await rpcIssuer.completeRequest.value XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")) } #endif // COCOAPODS || SWIFT_PACKAGE diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift index f8a1001436b..b6ec2c13ffa 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift @@ -52,7 +52,7 @@ class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { /** @property completeRequest @brief The last request to be processed by the backend. */ - var completeRequest: URLRequest? + var completeRequest: Task! /** @var _handler @brief A block we must invoke when @c respondWithError or @c respondWithJSON are called. @@ -148,11 +148,10 @@ class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { requestData = body // Use the real implementation so that the complete request can // be verified during testing. - Task { - self.completeRequest = await AuthBackend.request(withURL: requestURL!, - contentType: contentType, - requestConfiguration: request - .requestConfiguration()) + completeRequest = Task { + await AuthBackend.request(withURL: requestURL!, + contentType: contentType, + requestConfiguration: request.requestConfiguration()) } decodedRequest = try? JSONSerialization.jsonObject(with: body) as? [String: Any] } From f18a459b214c7f152e4eb1f161ce7b0b42855851 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:21:41 -0400 Subject: [PATCH 025/258] [Infra] Silence extensions warnings in Sessions SDK (#13611) --- .../Development/NanoPB+CustomStringConvertible.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseSessions/Sources/Development/NanoPB+CustomStringConvertible.swift b/FirebaseSessions/Sources/Development/NanoPB+CustomStringConvertible.swift index 331ddd3b6d0..28e927ab91f 100644 --- a/FirebaseSessions/Sources/Development/NanoPB+CustomStringConvertible.swift +++ b/FirebaseSessions/Sources/Development/NanoPB+CustomStringConvertible.swift @@ -25,7 +25,7 @@ import Foundation /// on each field manually. Instead you can read `.description`. /// -extension firebase_appquality_sessions_EventType: CustomStringConvertible { +extension firebase_appquality_sessions_EventType: Swift.CustomStringConvertible { public var description: String { switch self { case firebase_appquality_sessions_EventType_SESSION_START: @@ -38,7 +38,7 @@ extension firebase_appquality_sessions_EventType: CustomStringConvertible { } } -extension firebase_appquality_sessions_DataCollectionState: CustomStringConvertible { +extension firebase_appquality_sessions_DataCollectionState: Swift.CustomStringConvertible { public var description: String { switch self { case firebase_appquality_sessions_DataCollectionState_COLLECTION_ENABLED: @@ -59,7 +59,7 @@ extension firebase_appquality_sessions_DataCollectionState: CustomStringConverti } } -extension firebase_appquality_sessions_OsName: CustomStringConvertible { +extension firebase_appquality_sessions_OsName: Swift.CustomStringConvertible { public var description: String { switch self { case firebase_appquality_sessions_OsName_IOS: @@ -86,7 +86,7 @@ extension firebase_appquality_sessions_OsName: CustomStringConvertible { } } -extension firebase_appquality_sessions_LogEnvironment: CustomStringConvertible { +extension firebase_appquality_sessions_LogEnvironment: Swift.CustomStringConvertible { public var description: String { switch self { case firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_PROD: @@ -106,7 +106,7 @@ extension firebase_appquality_sessions_LogEnvironment: CustomStringConvertible { // This is written like this for Swift backwards-compatibility. // Once we upgrade to Xcode 14, this can be written as // UnsafeMutablePointer -extension UnsafeMutablePointer: CustomStringConvertible where Pointee == pb_bytes_array_t { +extension UnsafeMutablePointer: Swift.CustomStringConvertible where Pointee == pb_bytes_array_t { public var description: String { let decoded = FIRSESDecodeString(self) if decoded.count == 0 { @@ -120,7 +120,7 @@ extension UnsafeMutablePointer: CustomStringConvertible where Pointee == pb_byte // This is written like this for Swift backwards-compatibility. // Once we upgrade to Xcode 14, this can be written as // UnsafeMutablePointer? -extension Optional: CustomStringConvertible +extension Optional: Swift.CustomStringConvertible where Wrapped == UnsafeMutablePointer { public var description: String { guard let this = self else { From 9384b9fb8a65ff52cdeb088db255efa3e77cdacd Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Tue, 10 Sep 2024 20:27:09 +0200 Subject: [PATCH 026/258] `FunctionsError` (#13601) --- FirebaseFunctions/Sources/Functions.swift | 18 +- .../Sources/FunctionsError.swift | 164 +++++++++-------- .../Tests/Unit/FunctionsErrorTests.swift | 172 ++++++++++++++++++ 3 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 FirebaseFunctions/Tests/Unit/FunctionsErrorTests.swift diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index aa9daaa543b..60bb9032f4b 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -509,13 +509,13 @@ enum FunctionsConstants { if let error = error as NSError? { let localError: (any Error)? if error.domain == kGTMSessionFetcherStatusDomain { - localError = FunctionsErrorCode.errorForResponse( - status: error.code, + localError = FunctionsError( + httpStatusCode: error.code, body: data, serializer: serializer ) } else if error.domain == NSURLErrorDomain, error.code == NSURLErrorTimedOut { - localError = FunctionsErrorCode.deadlineExceeded.generatedError(userInfo: nil) + localError = FunctionsError(.deadlineExceeded) } else { localError = nil } @@ -525,15 +525,11 @@ enum FunctionsConstants { // Case 2: `data` is `nil` -> always throws guard let data else { - throw FunctionsErrorCode.internal.generatedError(userInfo: nil) + throw FunctionsError(.internal) } // Case 3: `data` is not `nil` but might specify a custom error -> throws conditionally - if let bodyError = FunctionsErrorCode.errorForResponse( - status: 200, - body: data, - serializer: serializer - ) { + if let bodyError = FunctionsError(httpStatusCode: 200, body: data, serializer: serializer) { throw bodyError } @@ -546,13 +542,13 @@ enum FunctionsConstants { guard let responseJSON = responseJSONObject as? NSDictionary else { let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."] - throw FunctionsErrorCode.internal.generatedError(userInfo: userInfo) + throw FunctionsError(.internal, userInfo: userInfo) } // `result` is checked for backwards compatibility: guard let dataJSON = responseJSON["data"] ?? responseJSON["result"] else { let userInfo = [NSLocalizedDescriptionKey: "Response is missing data field."] - throw FunctionsErrorCode.internal.generatedError(userInfo: userInfo) + throw FunctionsError(.internal, userInfo: userInfo) } return dataJSON diff --git a/FirebaseFunctions/Sources/FunctionsError.swift b/FirebaseFunctions/Sources/FunctionsError.swift index 8755b362bd1..f8815b3ce60 100644 --- a/FirebaseFunctions/Sources/FunctionsError.swift +++ b/FirebaseFunctions/Sources/FunctionsError.swift @@ -101,16 +101,16 @@ public let FunctionsErrorDetailsKey: String = "details" case unauthenticated = 16 } -extension FunctionsErrorCode { +private extension FunctionsErrorCode { /// Takes an HTTP status code and returns the corresponding `FIRFunctionsErrorCode` error code. /// /// + This is the standard HTTP status code -> error mapping defined in: /// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto /// - /// - Parameter status: An HTTP status code. + /// - Parameter httpStatusCode: An HTTP status code. /// - Returns: A `FunctionsErrorCode`. Falls back to `internal` for unknown status codes. - static func errorCode(forHTTPStatus status: Int) -> Self { - switch status { + init(httpStatusCode: Int) { + self = switch httpStatusCode { case 200: .OK case 400: .invalidArgument case 401: .unauthenticated @@ -127,102 +127,86 @@ extension FunctionsErrorCode { } } - static func errorCode(forName name: String) -> FunctionsErrorCode { - switch name { - case "OK": return .OK - case "CANCELLED": return .cancelled - case "UNKNOWN": return .unknown - case "INVALID_ARGUMENT": return .invalidArgument - case "DEADLINE_EXCEEDED": return .deadlineExceeded - case "NOT_FOUND": return .notFound - case "ALREADY_EXISTS": return .alreadyExists - case "PERMISSION_DENIED": return .permissionDenied - case "RESOURCE_EXHAUSTED": return .resourceExhausted - case "FAILED_PRECONDITION": return .failedPrecondition - case "ABORTED": return .aborted - case "OUT_OF_RANGE": return .outOfRange - case "UNIMPLEMENTED": return .unimplemented - case "INTERNAL": return .internal - case "UNAVAILABLE": return .unavailable - case "DATA_LOSS": return .dataLoss - case "UNAUTHENTICATED": return .unauthenticated - default: return .internal + init(errorName: String) { + self = switch errorName { + case "OK": .OK + case "CANCELLED": .cancelled + case "UNKNOWN": .unknown + case "INVALID_ARGUMENT": .invalidArgument + case "DEADLINE_EXCEEDED": .deadlineExceeded + case "NOT_FOUND": .notFound + case "ALREADY_EXISTS": .alreadyExists + case "PERMISSION_DENIED": .permissionDenied + case "RESOURCE_EXHAUSTED": .resourceExhausted + case "FAILED_PRECONDITION": .failedPrecondition + case "ABORTED": .aborted + case "OUT_OF_RANGE": .outOfRange + case "UNIMPLEMENTED": .unimplemented + case "INTERNAL": .internal + case "UNAVAILABLE": .unavailable + case "DATA_LOSS": .dataLoss + case "UNAUTHENTICATED": .unauthenticated + default: .internal } } +} - var descriptionForErrorCode: String { - switch self { - case .OK: - return "OK" - case .cancelled: - return "CANCELLED" - case .unknown: - return "UNKNOWN" - case .invalidArgument: - return "INVALID ARGUMENT" - case .deadlineExceeded: - return "DEADLINE EXCEEDED" - case .notFound: - return "NOT FOUND" - case .alreadyExists: - return "ALREADY EXISTS" - case .permissionDenied: - return "PERMISSION DENIED" - case .resourceExhausted: - return "RESOURCE EXHAUSTED" - case .failedPrecondition: - return "FAILED PRECONDITION" - case .aborted: - return "ABORTED" - case .outOfRange: - return "OUT OF RANGE" - case .unimplemented: - return "UNIMPLEMENTED" - case .internal: - return "INTERNAL" - case .unavailable: - return "UNAVAILABLE" - case .dataLoss: - return "DATA LOSS" - case .unauthenticated: - return "UNAUTHENTICATED" - } - } +/// The object used to report errors that occur during a function’s execution. +struct FunctionsError: CustomNSError { + static let errorDomain = FunctionsErrorDomain + + let code: FunctionsErrorCode + let errorUserInfo: [String: Any] + var errorCode: FunctionsErrorCode.RawValue { code.rawValue } - func generatedError(userInfo: [String: Any]? = nil) -> NSError { - return NSError(domain: FunctionsErrorDomain, - code: rawValue, - userInfo: userInfo ?? [NSLocalizedDescriptionKey: descriptionForErrorCode]) + init(_ code: FunctionsErrorCode, userInfo: [String: Any]? = nil) { + self.code = code + errorUserInfo = userInfo ?? [NSLocalizedDescriptionKey: Self.errorDescription(from: code)] } - static func errorForResponse(status: Int, - body: Data?, - serializer: FunctionsSerializer) -> NSError? { + /// Initializes a `FunctionsError` from the HTTP status code and response body. + /// + /// - Parameters: + /// - httpStatusCode: The HTTP status code reported during a function’s execution. Only a subset + /// of codes are supported. + /// - body: The optional response data which may contain information about the error. The + /// following schema is expected: + /// ``` + /// { + /// "error": { + /// "status": "PERMISSION_DENIED", + /// "message": "You are not allowed to perform this operation", + /// "details": 123 // Any value supported by `FunctionsSerializer` + /// } + /// ``` + /// - serializer: The `FunctionsSerializer` used to decode `details` in the error body. + init?(httpStatusCode: Int, body: Data?, serializer: FunctionsSerializer) { // Start with reasonable defaults from the status code. - var code = FunctionsErrorCode.errorCode(forHTTPStatus: status) - var description = code.descriptionForErrorCode - var details: AnyObject? + var code = FunctionsErrorCode(httpStatusCode: httpStatusCode) + var description = Self.errorDescription(from: code) + var details: Any? // Then look through the body for explicit details. if let body, - let json = try? JSONSerialization.jsonObject(with: body) as? NSDictionary, - let errorDetails = json["error"] as? NSDictionary { + let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any], + let errorDetails = json["error"] as? [String: Any] { if let status = errorDetails["status"] as? String { - code = .errorCode(forName: status) + code = FunctionsErrorCode(errorName: status) // If the code in the body is invalid, treat the whole response as malformed. guard code != .internal else { - return code.generatedError(userInfo: nil) + self.init(code) + return } } if let message = errorDetails["message"] as? String { description = message } else { - description = code.descriptionForErrorCode + description = Self.errorDescription(from: code) } - details = errorDetails["details"] as AnyObject? + details = errorDetails["details"] as Any? // Update `details` only if decoding succeeds; // otherwise, keep the original object. if let innerDetails = details, @@ -243,6 +227,28 @@ extension FunctionsErrorCode { if let details { userInfo[FunctionsErrorDetailsKey] = details } - return code.generatedError(userInfo: userInfo) + self.init(code, userInfo: userInfo) + } + + private static func errorDescription(from code: FunctionsErrorCode) -> String { + switch code { + case .OK: "OK" + case .cancelled: "CANCELLED" + case .unknown: "UNKNOWN" + case .invalidArgument: "INVALID ARGUMENT" + case .deadlineExceeded: "DEADLINE EXCEEDED" + case .notFound: "NOT FOUND" + case .alreadyExists: "ALREADY EXISTS" + case .permissionDenied: "PERMISSION DENIED" + case .resourceExhausted: "RESOURCE EXHAUSTED" + case .failedPrecondition: "FAILED PRECONDITION" + case .aborted: "ABORTED" + case .outOfRange: "OUT OF RANGE" + case .unimplemented: "UNIMPLEMENTED" + case .internal: "INTERNAL" + case .unavailable: "UNAVAILABLE" + case .dataLoss: "DATA LOSS" + case .unauthenticated: "UNAUTHENTICATED" + } } } diff --git a/FirebaseFunctions/Tests/Unit/FunctionsErrorTests.swift b/FirebaseFunctions/Tests/Unit/FunctionsErrorTests.swift new file mode 100644 index 00000000000..99b4c8334b3 --- /dev/null +++ b/FirebaseFunctions/Tests/Unit/FunctionsErrorTests.swift @@ -0,0 +1,172 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@testable import FirebaseFunctions + +import XCTest + +final class FunctionsErrorTests: XCTestCase { + func testInitWithCode() { + let error = FunctionsError(.permissionDenied) + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 7) + XCTAssertEqual(nsError.localizedDescription, "PERMISSION DENIED") + XCTAssertEqual(nsError.userInfo.count, 1) + } + + func testInitWithCodeAndUserInfo() { + let error = FunctionsError(.unimplemented, userInfo: ["TEST_Key": "TEST_Value"]) + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 12) + XCTAssertEqual( + nsError.localizedDescription, + "The operation couldn’t be completed. (com.firebase.functions error 12.)" + ) + XCTAssertEqual(nsError.userInfo.count, 1) + XCTAssertEqual(nsError.userInfo["TEST_Key"] as? String, "TEST_Value") + } + + func testInitWithOKStatusCodeAndNoErrorBody() { + // The error should be `nil`. + let error = FunctionsError( + httpStatusCode: 200, + body: nil, + serializer: FunctionsSerializer() + ) + + XCTAssertNil(error) + } + + func testInitWithErrorStatusCodeAndNoErrorBody() { + // The error should be inferred from the HTTP status code. + let error = FunctionsError( + httpStatusCode: 429, + body: nil, + serializer: FunctionsSerializer() + ) + + guard let error else { return XCTFail("Unexpected `nil` value") } + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 8) + XCTAssertEqual(nsError.localizedDescription, "RESOURCE EXHAUSTED") + XCTAssertEqual(nsError.userInfo.count, 1) + } + + func testInitWithOKStatusCodeAndIncompleteErrorBody() { + // The status code in the error body takes precedence over the HTTP status code. + let responseData = #"{ "error": { "status": "OUT_OF_RANGE" } }"#.data(using: .utf8)! + + let error = FunctionsError( + httpStatusCode: 200, + body: responseData, + serializer: FunctionsSerializer() + ) + + guard let error else { return XCTFail("Unexpected `nil` value") } + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 11) + XCTAssertEqual(nsError.localizedDescription, "OUT OF RANGE") + XCTAssertEqual(nsError.userInfo.count, 1) + } + + func testInitWithErrorStatusCodeAndErrorBody() { + // The status code in the error body takes precedence over the HTTP status code. + let responseData = + #"{ "error": { "status": "OUT_OF_RANGE", "message": "TEST_ErrorMessage", "details": 123 } }"# + .data(using: .utf8)! + + let error = FunctionsError( + httpStatusCode: 499, + body: responseData, + serializer: FunctionsSerializer() + ) + + guard let error else { return XCTFail("Unexpected `nil` value") } + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 11) + XCTAssertEqual(nsError.localizedDescription, "TEST_ErrorMessage") + XCTAssertEqual(nsError.userInfo.count, 2) + XCTAssertEqual(nsError.userInfo["details"] as? Int, 123) + } + + func testInitWithErrorStatusCodeAndOKErrorBody() { + // When the status code in the error body is `OK`, error should be `nil` regardless of the HTTP + // status code. + let responseData = + #"{ "error": { "status": "OK", "message": "TEST_ErrorMessage", "details": 123 } }"# + .data(using: .utf8)! + + let error = FunctionsError( + httpStatusCode: 401, + body: responseData, + serializer: FunctionsSerializer() + ) + + XCTAssertNil(error) + } + + func testInitWithErrorStatusCodeAndIncompleteErrorBody() { + // The error name is not in the body; it should be inferred from the HTTP status code. + let responseData = #"{ "error": { "message": "TEST_ErrorMessage", "details": null } }"# + .data(using: .utf8)! + + let error = FunctionsError( + httpStatusCode: 403, + body: responseData, + serializer: FunctionsSerializer() + ) + + guard let error else { return XCTFail("Unexpected `nil` value") } + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + XCTAssertEqual(nsError.code, 7) // `permissionDenied`, inferred from the HTTP status code + XCTAssertEqual(nsError.localizedDescription, "TEST_ErrorMessage") + XCTAssertEqual(nsError.userInfo.count, 2) + XCTAssertEqual(nsError.userInfo["details"] as? NSNull, NSNull()) + } + + func testInitWithErrorStatusCodeAndInvalidErrorBody() { + // An unsupported status code in the error body should result in the rest of the body ignored. + let responseData = + #"{ "error": { "status": "TEST_UNKNOWN_ERROR", "message": "TEST_ErrorMessage", "details": 123 } }"# + .data(using: .utf8)! + + let error = FunctionsError( + httpStatusCode: 503, + body: responseData, + serializer: FunctionsSerializer() + ) + + guard let error else { return XCTFail("Unexpected `nil` value") } + + let nsError = error as NSError + XCTAssertEqual(nsError.domain, "com.firebase.functions") + // Currently, `internal` is used as the fallback error code. Is this correct? + // Seems like we could get more information from the HTTP status code in such cases. + XCTAssertEqual(nsError.code, 13) + XCTAssertEqual(nsError.localizedDescription, "INTERNAL") + XCTAssertEqual(nsError.userInfo.count, 1) + } +} From b7838f507f6495226aa3b7589195234a1da34b44 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:40:47 -0400 Subject: [PATCH 027/258] [Infra] Update notice_generation.yml (#13617) --- .github/workflows/notice_generation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/notice_generation.yml b/.github/workflows/notice_generation.yml index cd5025e1881..579db732cbc 100644 --- a/.github/workflows/notice_generation.yml +++ b/.github/workflows/notice_generation.yml @@ -36,7 +36,8 @@ jobs: with: pods: ${{ env.PODS }} sources: "https://github.com/firebase/SpecsTesting,https://github.com/firebase/SpecsStaging,https://cdn.cocoapods.org" - min-ios-version: "13.0" + # This should match the highest minimum supported iOS version. + min-ios-version: "15.0" search-local-pod-version: true notices-path: ${{ env.NOTICES_PATH }} - name: Create a pull request From 85a6cc25bf1288e9f72ae3a7d6c6914b6b72e96d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 10 Sep 2024 14:58:38 -0400 Subject: [PATCH 028/258] [Release] Carthage updates for M153 / 11.2.0 (#13620) --- ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json | 1 + .../CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json | 1 + 19 files changed, 19 insertions(+) diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index 1277d975b67..05532cadfa0 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseABTesting-0d992d73cce9103c.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseABTesting-bd865e6158ecfeaa.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseABTesting-1bc00d2361fabe31.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseABTesting-0d51fde82d49f9e8.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/ABTesting-d0fdf10c43e985b1.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/ABTesting-d0fdf10c43e985b1.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/ABTesting-a71d17cadc209af9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json index ffe76133a55..b936a37bead 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/Google-Mobile-Ads-SDK-89717d4c95277aa0.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/Google-Mobile-Ads-SDK-8208c48cf9486f31.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/Google-Mobile-Ads-SDK-35e22051f01c0eaa.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/Google-Mobile-Ads-SDK-4f24527af297e7f1.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/AdMob-8a654a42c33bbcc8.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/AdMob-63dab3b525b94cd9.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/AdMob-134752c6180a2a41.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index ab3c4da582a..d21c13833d5 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAnalytics-0ada8ccba1c9ac8a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalytics-640e51a50e0916d4.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalytics-c0c45b49d7c16d39.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalytics-a93a6c81da535385.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Analytics-2468c231ebeb7922.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Analytics-bc8101d420b896c5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Analytics-d2b6a6b0242db786.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json index f2574073563..7c65254e7ee 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAnalyticsOnDeviceConversion-56efb7cef86436f2.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalyticsOnDeviceConversion-d34b43045f6de5d9.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalyticsOnDeviceConversion-e0b5f6e47b71efce.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalyticsOnDeviceConversion-09d94624a2de0ac8.zip", "9.0.0": "https://dl.google.com/dl/firebase/ios/carthage/9.0.0/FirebaseAnalyticsOnDeviceConversion-31aedde70a736b8a.zip", "9.1.0": "https://dl.google.com/dl/firebase/ios/carthage/9.1.0/FirebaseAnalyticsOnDeviceConversion-f13b5a47d1e3978d.zip", "9.2.0": "https://dl.google.com/dl/firebase/ios/carthage/9.2.0/FirebaseAnalyticsOnDeviceConversion-2ebf567c4d97de12.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index 1af267343e7..583de62ae4e 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAppCheck-696b7147e94c4910.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppCheck-2391378293607ac0.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppCheck-b44ecc329b8672d0.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppCheck-d0c5f46e6a2bf4a3.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseAppCheck-9ef1d217cf057203.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseAppCheck-fc03215d9fe45d3a.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseAppCheck-6ebe9e9539f06003.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index f9e790d78f8..8ad9902a4db 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAppDistribution-11e51d1485259968.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppDistribution-6e4980b915e8e59e.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppDistribution-9415636f92f6e4be.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppDistribution-9b05f4873b275347.zip", "6.31.0": "https://dl.google.com/dl/firebase/ios/carthage/6.31.0/FirebaseAppDistribution-07f6a2cf7f576a8a.zip", "6.32.0": "https://dl.google.com/dl/firebase/ios/carthage/6.32.0/FirebaseAppDistribution-a9c4f5db794508ca.zip", "6.33.0": "https://dl.google.com/dl/firebase/ios/carthage/6.33.0/FirebaseAppDistribution-448a96d2ade54581.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index ba331ba0cfe..179cda304c9 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAuth-d62857535fd583f9.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAuth-6880d36dd83051b8.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAuth-41423c3255e3355e.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAuth-eade26b5390baf84.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Auth-0fa76ba0f7956220.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Auth-5ddd2b4351012c7a.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Auth-5e248984d78d7284.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index 0b6ac5019a5..4e9b8a2062c 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseCrashlytics-d78fb9954cb2041a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseCrashlytics-23e5ee21eff49370.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseCrashlytics-573b0427dec2b08b.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseCrashlytics-13851523ad6df088.zip", "6.15.0": "https://dl.google.com/dl/firebase/ios/carthage/6.15.0/FirebaseCrashlytics-1c6d22d5b73c84fd.zip", "6.16.0": "https://dl.google.com/dl/firebase/ios/carthage/6.16.0/FirebaseCrashlytics-938e5fd0e2eab3b3.zip", "6.17.0": "https://dl.google.com/dl/firebase/ios/carthage/6.17.0/FirebaseCrashlytics-fa09f0c8f31ed5d9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index 7637a5a3fd1..ee1b97dcee6 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseDatabase-b129760f802187e1.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDatabase-5e45f5e1fd19258b.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDatabase-64e6eeeecc70e513.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDatabase-06dbb1f7d3c8a3e1.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Database-1f7a820452722c7d.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Database-1f7a820452722c7d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Database-59a12d87456b3e1c.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json index 9d1bcf74a33..843856f3089 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseDynamicLinks-3c0298de3e855025.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDynamicLinks-9e4c79d39080bef1.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDynamicLinks-09b22cea086a30bc.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDynamicLinks-e61c61fa80e5ea8a.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/DynamicLinks-6a76740211df73f5.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/DynamicLinks-6a76740211df73f5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/DynamicLinks-6a76740211df73f5.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index 3413f3801f6..627e8785ae5 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseFirestore-9fb513238a7cadf9.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFirestore-f19512c3374d5feb.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFirestore-5f76b2878966eea4.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFirestore-43af85b854ac842e.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Firestore-68fc02c229d0cc69.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Firestore-87a804ab561d91db.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Firestore-ecb3eea7bde7e8e8.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index 6eb04342f48..c77f37b026c 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseFunctions-92bb6b6dfaf293e3.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFunctions-5249dd848af2df99.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFunctions-87cffbdd6cbb9512.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFunctions-307f00117c2efc62.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Functions-f4c426016dd41e38.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Functions-c6c44427c3034736.zip", "5.0.0": "https://dl.google.com/dl/firebase/ios/carthage/5.0.0/Functions-146f34c401bd459b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index 3466e4fbaa8..215269e4e52 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/GoogleSignIn-633815a56a62ad0f.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/GoogleSignIn-3287d323d4a251ea.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/GoogleSignIn-dabfaced725377c4.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/GoogleSignIn-4e8837ef9594b57b.zip", "6.0.0": "https://dl.google.com/dl/firebase/ios/carthage/6.0.0/GoogleSignIn-de9c5d5e8eb6d6ea.zip", "6.1.0": "https://dl.google.com/dl/firebase/ios/carthage/6.1.0/GoogleSignIn-8c82f2870573a793.zip", "6.10.0": "https://dl.google.com/dl/firebase/ios/carthage/6.10.0/GoogleSignIn-ff3aef61c4a55b05.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 235170070e5..7e526558654 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseInAppMessaging-dcbb85b38f4032d1.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseInAppMessaging-076c8e5a966eb715.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseInAppMessaging-7aa1595a55b0f2bd.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseInAppMessaging-6fae0a778e9d3efa.zip", "5.10.0": "https://dl.google.com/dl/firebase/ios/carthage/5.10.0/InAppMessaging-a7a3f933362f6e95.zip", "5.11.0": "https://dl.google.com/dl/firebase/ios/carthage/5.11.0/InAppMessaging-fa28ce1b88fbca93.zip", "5.12.0": "https://dl.google.com/dl/firebase/ios/carthage/5.12.0/InAppMessaging-fa28ce1b88fbca93.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index ac55ce22630..f5db13bb816 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseMLModelDownloader-2656725e56950b58.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMLModelDownloader-587e66639052095f.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMLModelDownloader-4029775a5484e3d2.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMLModelDownloader-d8649822e63fbf7f.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseMLModelDownloader-8f972757fb181320.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseMLModelDownloader-058ad59fa6dc0111.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseMLModelDownloader-286479a966d2fb37.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index 148af1f81e8..520032de482 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseMessaging-908395d688c14419.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMessaging-31823941cc0a4e8c.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMessaging-3edcc27744f3aa8e.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMessaging-70e63bb9d9590ded.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Messaging-a22ef2b5f2f30f82.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Messaging-94fa4e090c7e9185.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Messaging-2a00a1c64a19d176.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 2652495dcc9..5da68b8ff2e 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebasePerformance-ab8a5b884aef1d5e.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebasePerformance-cbde910ed7498ae3.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebasePerformance-e983e4ab114b6122.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebasePerformance-aa174ee3102722d9.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Performance-d8693eb892bfa05b.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Performance-0a400f9460f7a71d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Performance-f5b4002ab96523e4.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index 82501ed3580..43cb10c953f 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseRemoteConfig-b613ce2eed93cfc5.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseRemoteConfig-cda0d6b61b66f8e2.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseRemoteConfig-bf6cbcdd97aa9c46.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseRemoteConfig-9a298869ce3cc6db.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/RemoteConfig-7e9635365ccd4a17.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/RemoteConfig-e7928fcb6311c439.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/RemoteConfig-9ab1ca5f360a1780.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index fc861558d0e..647c17f60fd 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseStorage-98af3136b87c351a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseStorage-baee7d21e3743cf6.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseStorage-f483c715e48ec023.zip", + "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseStorage-b9b969b0d1254065.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Storage-6b3e77e1a7fdbc61.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Storage-4721c35d2b90a569.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Storage-821299369b9d0fb2.zip", From 8b320b7db3c2cf96cf3a077755115ca5608390fb Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:18:01 -0400 Subject: [PATCH 029/258] [Auth] Revoke SiwA token when unlinking Apple provider (#13621) --- .../Utility/Extensions.swift | 2 +- .../AccountLinkingViewController.swift | 124 +++++++++++------- 2 files changed, 79 insertions(+), 47 deletions(-) diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Utility/Extensions.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Utility/Extensions.swift index d4b0fad3d6f..a004534ad7d 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Utility/Extensions.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Utility/Extensions.swift @@ -77,7 +77,7 @@ public extension UIViewController { } } - func displayError(_ error: (any Error)?, from function: StaticString = #function) { + @MainActor func displayError(_ error: (any Error)?, from function: StaticString = #function) { guard let error = error else { return } print("ⓧ Error in \(function): \(error.localizedDescription)") let message = "\(error.localizedDescription)\n\n Occurred in \(function)" diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift index 4f2342d4b6d..1d0e9510d0a 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift @@ -77,7 +77,7 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate // If the item's affiliated provider is currently linked with the user, // unlink the provider from the user's account. if item.isChecked { - unlinkFromProvider(provider.id) + Task { await unlinkFromProvider(provider.id) } return } @@ -86,7 +86,7 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate performGoogleAccountLink() case .apple: - performAppleAccountLink() + Task { await performAppleAccountLink() } case .facebook: performFacebookAccountLink() @@ -124,15 +124,53 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate } } + /// Used for Sign in with Apple token revocation flow. + private var continuation: CheckedContinuation? + /// Wrapper method that uses Firebase's `unlink(fromProvider:)` API to unlink a user from an auth /// provider. /// This method will update the UI upon the unlinking's completion. /// - Parameter providerID: The string id of the auth provider. - private func unlinkFromProvider(_ providerID: String) { - user.unlink(fromProvider: providerID) { user, error in - guard error == nil else { return self.displayError(error) } - print("Unlinked user from auth provider: \(providerID)") - self.updateUI() + private func unlinkFromProvider(_ providerID: String) async { + if providerID == AuthProviderID.apple.rawValue { + // Needs SiwA token revocation. + do { + let needsTokenRevocation = user.providerData + .contains { $0.providerID == AuthProviderID.apple.rawValue } + if needsTokenRevocation { + let appleIDCredential = try await signInWithApple() + + guard let appleIDToken = appleIDCredential.identityToken else { + print("Unable to fetch identify token.") + return + } + guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { + print("Unable to serialise token string from data: \(appleIDToken.debugDescription)") + return + } + + let nonce = try CryptoUtils.randomNonceString() + let credential = OAuthProvider.credential(providerID: .apple, + idToken: idTokenString, + rawNonce: nonce) + + try await user.reauthenticate(with: credential) + if + let authorizationCode = appleIDCredential.authorizationCode, + let authCodeString = String(data: authorizationCode, encoding: .utf8) { + try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString) + } + } + } catch { + displayError(error) + } + } + + do { + _ = try await user.unlink(fromProvider: providerID) + updateUI() + } catch { + displayError(error) } } @@ -179,27 +217,26 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate // MARK: - Sign in with Apple Account Linking 🔥 - // For Sign in with Apple - var currentNonce: String? - /// This method will initate the Sign In with Apple flow. /// See this class's conformance to `ASAuthorizationControllerDelegate` below for /// context on how the linking is made. - private func performAppleAccountLink() { + private func performAppleAccountLink() async { do { - let nonce = try CryptoUtils.randomNonceString() - currentNonce = nonce - let appleIDProvider = ASAuthorizationAppleIDProvider() - let request = appleIDProvider.createRequest() - request.requestedScopes = [.fullName, .email] - request.nonce = CryptoUtils.sha256(nonce) + let appleIDCredential = try await signInWithApple() - let authorizationController = ASAuthorizationController(authorizationRequests: [request]) - authorizationController.delegate = self - authorizationController.presentationContextProvider = self - authorizationController.performRequests() + guard let appleIDToken = appleIDCredential.identityToken else { + fatalError("Unable to fetch identify token.") + } + guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { + fatalError("Unable to serialise token string from data: \(appleIDToken.debugDescription)") + } + + let nonce = try CryptoUtils.randomNonceString() + let credential = OAuthProvider.credential(providerID: .apple, + idToken: idTokenString, + rawNonce: nonce) + linkAccount(authCredential: credential) } catch { - // In the unlikely case that nonce generation fails, show error view. displayError(error) } } @@ -448,7 +485,7 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate dataSourceProvider.delegate = self } - private func updateUI() { + @MainActor private func updateUI() { configureDataSourceProvider() animateUpdates(for: tableView) } @@ -488,31 +525,26 @@ extension AccountLinkingViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { // MARK: ASAuthorizationControllerDelegate - func authorizationController(controller: ASAuthorizationController, - didCompleteWithAuthorization authorization: ASAuthorization) { - guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential - else { - print("Unable to retrieve AppleIDCredential") - return - } + func signInWithApple() async throws -> ASAuthorizationAppleIDCredential { + return try await withCheckedThrowingContinuation { continuation in + self.continuation = continuation + let appleIDProvider = ASAuthorizationAppleIDProvider() + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] - guard let nonce = currentNonce else { - fatalError("Invalid state: A login callback was received, but no login request was sent.") - } - guard let appleIDToken = appleIDCredential.identityToken else { - print("Unable to fetch identity token") - return - } - guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { - print("Unable to serialize token string from data: \(appleIDToken.debugDescription)") - return + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.performRequests() } + } - let credential = OAuthProvider.credential(providerID: .apple, - idToken: idTokenString, - rawNonce: nonce) - // Once we have created the above `credential`, we can link accounts to it. - linkAccount(authCredential: credential) + func authorizationController(controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization) { + if case let appleIDCredential as ASAuthorizationAppleIDCredential = authorization.credential { + continuation?.resume(returning: appleIDCredential) + } else { + fatalError("Unexpected authorization credential type.") + } } func authorizationController(controller: ASAuthorizationController, @@ -520,7 +552,7 @@ extension AccountLinkingViewController: ASAuthorizationControllerDelegate, // Ensure that you have: // - enabled `Sign in with Apple` on the Firebase console // - added the `Sign in with Apple` capability for this project - print("Sign in with Apple errored: \(error)") + continuation?.resume(throwing: error) } // MARK: ASAuthorizationControllerPresentationContextProviding From 12ec5a1f34e5bb6383f5c3cd3b69f06a88862643 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 11 Sep 2024 10:27:33 -0400 Subject: [PATCH 030/258] Update versions for Release 11.3.0 (#13622) --- Firebase.podspec | 48 +++++++++---------- FirebaseABTesting.podspec | 2 +- FirebaseAnalytics.podspec | 6 +-- FirebaseAnalyticsOnDeviceConversion.podspec | 4 +- FirebaseAppCheck.podspec | 2 +- FirebaseAppCheckInterop.podspec | 2 +- FirebaseAppDistribution.podspec | 2 +- FirebaseAuth.podspec | 2 +- FirebaseAuthInterop.podspec | 2 +- FirebaseCore.podspec | 2 +- FirebaseCoreExtension.podspec | 2 +- FirebaseCoreInternal.podspec | 2 +- FirebaseCrashlytics.podspec | 2 +- FirebaseDatabase.podspec | 2 +- FirebaseDynamicLinks.podspec | 2 +- FirebaseFirestore.podspec | 4 +- FirebaseFirestoreInternal.podspec | 2 +- FirebaseFunctions.podspec | 2 +- FirebaseInAppMessaging.podspec | 2 +- FirebaseInstallations.podspec | 2 +- FirebaseMLModelDownloader.podspec | 2 +- FirebaseMessaging.podspec | 2 +- FirebaseMessagingInterop.podspec | 2 +- FirebasePerformance.podspec | 2 +- FirebaseRemoteConfig.podspec | 2 +- FirebaseRemoteConfigInterop.podspec | 2 +- FirebaseSessions.podspec | 2 +- FirebaseSharedSwift.podspec | 2 +- FirebaseStorage.podspec | 2 +- FirebaseVertexAI.podspec | 2 +- GoogleAppMeasurement.podspec | 4 +- ...leAppMeasurementOnDeviceConversion.podspec | 2 +- Package.swift | 2 +- .../FirebaseManifest/FirebaseManifest.swift | 2 +- 34 files changed, 62 insertions(+), 62 deletions(-) diff --git a/Firebase.podspec b/Firebase.podspec index 75dd6212b16..bd9fb53a69d 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase' s.description = <<-DESC @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 11.2.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 11.2.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 11.2.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 11.3.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 11.3.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 11.3.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '11.2.0' + ss.dependency 'FirebaseCore', '11.3.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -79,13 +79,13 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.2.0' + ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.3.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 11.2.0' + ss.dependency 'FirebaseABTesting', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -95,13 +95,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'AppDistribution' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseAppDistribution', '~> 11.2.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 11.3.0-beta' ss.ios.deployment_target = '13.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 11.2.0' + ss.dependency 'FirebaseAppCheck', '~> 11.3.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -110,7 +110,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Auth' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAuth', '~> 11.2.0' + ss.dependency 'FirebaseAuth', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -120,7 +120,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Crashlytics' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseCrashlytics', '~> 11.2.0' + ss.dependency 'FirebaseCrashlytics', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' @@ -130,7 +130,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Database' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseDatabase', '~> 11.2.0' + ss.dependency 'FirebaseDatabase', '~> 11.3.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -140,13 +140,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'DynamicLinks' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.2.0' + ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.3.0' ss.ios.deployment_target = '13.0' end s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 11.2.0' + ss.dependency 'FirebaseFirestore', '~> 11.3.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -154,7 +154,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Functions' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFunctions', '~> 11.2.0' + ss.dependency 'FirebaseFunctions', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -164,20 +164,20 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'InAppMessaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.2.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.2.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.3.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.3.0-beta' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 11.2.0' + ss.dependency 'FirebaseInstallations', '~> 11.3.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 11.2.0' + ss.dependency 'FirebaseMessaging', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -187,7 +187,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'MLModelDownloader' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMLModelDownloader', '~> 11.2.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 11.3.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -197,15 +197,15 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Performance' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebasePerformance', '~> 11.2.0' - ss.tvos.dependency 'FirebasePerformance', '~> 11.2.0' + ss.ios.dependency 'FirebasePerformance', '~> 11.3.0' + ss.tvos.dependency 'FirebasePerformance', '~> 11.3.0' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 11.2.0' + ss.dependency 'FirebaseRemoteConfig', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -215,7 +215,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Storage' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseStorage', '~> 11.2.0' + ss.dependency 'FirebaseStorage', '~> 11.3.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index caedc46bab5..e1c6a25f29e 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 2daa7ed2935..840bbf1fb04 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -37,12 +37,12 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement', '11.2.0' + ss.dependency 'GoogleAppMeasurement', '11.3.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'WithoutAdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.2.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.3.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAnalyticsOnDeviceConversion.podspec b/FirebaseAnalyticsOnDeviceConversion.podspec index a36e59f766a..72b1ef42be3 100644 --- a/FirebaseAnalyticsOnDeviceConversion.podspec +++ b/FirebaseAnalyticsOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalyticsOnDeviceConversion' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'On device conversion measurement plugin for FirebaseAnalytics. Not intended for direct use.' s.description = <<-DESC @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.cocoapods_version = '>= 1.12.0' - s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.2.0' + s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.3.0' s.static_framework = true diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 125f6ab9bfb..077d186b0ab 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 8d17d1e6480..91590ec04a4 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Interfaces that allow other Firebase SDKs to use AppCheck functionality.' s.description = <<-DESC diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index 9199346e624..a15f313b197 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '11.2.0-beta' + s.version = '11.3.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 70454dde33e..5bb6a6b1eec 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index 388171a16ff..08120eaf25c 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 35ef886de13..a69f318c4ce 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Core' s.description = <<-DESC diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index e882c286d17..eb426d343ed 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index f15563f7d1b..86b0a42bf9c 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 94bb7fc3499..b7b5ba079b9 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index 85034b98650..02cbb08406a 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index 89ad46e4baa..4129f573f63 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Dynamic Links' s.description = <<-DESC diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 782a4341b2b..6da4590e11e 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. @@ -37,7 +37,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.dependency 'FirebaseCore', '~> 11.0' s.dependency 'FirebaseCoreExtension', '~> 11.0' - s.dependency 'FirebaseFirestoreInternal', '11.2.0' + s.dependency 'FirebaseFirestoreInternal', '11.3.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' end diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 98e5d8e055e..2f25d91e6fe 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index ac6be385709..44ed34cda2e 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index ac29ca4a8d3..1822c89d08c 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '11.2.0-beta' + s.version = '11.3.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 39e856a0370..9ef28c929cf 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Installations' s.description = <<-DESC diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index f58d79a3322..008ff3b1918 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '11.2.0-beta' + s.version = '11.3.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 7da5f1d61ca..5fe1da25749 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Messaging' s.description = <<-DESC diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 5f7017b1cab..7ccc417d808 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Messaging functionality.' s.description = <<-DESC diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 68060017351..0a559ea3e53 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Performance' s.description = <<-DESC diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index fcd404fa48f..50fc76bcfe3 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index 7d6e2870829..2bb293c1d85 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfigInterop' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' s.description = <<-DESC diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 0bb3c1b096c..51932c3826f 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Sessions' s.description = <<-DESC diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index 786ca4b7a32..94623570949 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 9fcf1641751..d2c2763aac9 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Firebase Storage' s.description = <<-DESC diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index a5251b4dc1f..184abbe8e45 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseVertexAI' - s.version = '11.2.0-beta' + s.version = '11.3.0-beta' s.summary = 'Vertex AI in Firebase - Public Preview' s.description = <<-DESC diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index f12fe582f44..5d8e9c7f469 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -37,7 +37,7 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.2.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.3.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index e9c8fe8e599..8fa41d287e3 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/GoogleAppMeasurementOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurementOnDeviceConversion' - s.version = '11.2.0' + s.version = '11.3.0' s.summary = <<-SUMMARY On device conversion measurement plugin for Google App Measurement. Not intended for direct use. diff --git a/Package.swift b/Package.swift index 57f58043df8..c770b7e047f 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -let firebaseVersion = "11.2.0" +let firebaseVersion = "11.3.0" let package = Package( name: "Firebase", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 0e6e2057831..32dd30d5b99 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -21,7 +21,7 @@ import Foundation /// The version and releasing fields of the non-Firebase pods should be reviewed every release. /// The array should be ordered so that any pod's dependencies precede it in the list. public let shared = Manifest( - version: "11.2.0", + version: "11.3.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), From b8ef6d72f0fb39dc66f664028d3b643edca03ac6 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:12:35 -0400 Subject: [PATCH 031/258] [Infra] Update NOTICES (#13625) --- CoreOnly/NOTICES | 1 + 1 file changed, 1 insertion(+) diff --git a/CoreOnly/NOTICES b/CoreOnly/NOTICES index 6ee4a9aa089..84b2455db55 100644 --- a/CoreOnly/NOTICES +++ b/CoreOnly/NOTICES @@ -21,6 +21,7 @@ FirebaseRemoteConfig FirebaseRemoteConfigInterop FirebaseSessions FirebaseStorage +FirebaseVertexAI GTMSessionFetcher GoogleDataTransport PromisesObjC From 0d401358fe9b4930b01827791be28c56fc187452 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:53:37 -0400 Subject: [PATCH 032/258] [Infra] Fix Swift 6 warning in FirebaseAppTests.swift (#13627) --- FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift b/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift index ee23b54df5f..61922fbe740 100644 --- a/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift +++ b/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift @@ -340,7 +340,7 @@ class FirebaseAppTests: XCTestCase { // MARK: - Helpers private func expectAppConfigurationNotification(appName: String, isDefaultApp: Bool) { - let expectedUserInfo: NSDictionary = [ + let expectedUserInfo: [String: Any] = [ "FIRAppNameKey": appName, "FIRAppIsDefaultAppKey": NSNumber(value: isDefaultApp), "FIRGoogleAppIDKey": Constants.Options.googleAppID, @@ -348,14 +348,12 @@ class FirebaseAppTests: XCTestCase { expectation(forNotification: NSNotification.Name.firAppReadyToConfigureSDK, object: FirebaseApp.self, handler: { notification -> Bool in - if let userInfo = notification.userInfo { - if expectedUserInfo.isEqual(to: userInfo) { - return true - } - } else { + guard let userInfo = notification.userInfo else { XCTFail("Failed to unwrap notification user info") + return false } - return false + return NSDictionary(dictionary: expectedUserInfo) == + NSDictionary(dictionary: userInfo) }) } } From 16dd4eed59bc6828d5319a1e75d3aa239430a6a6 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:27:04 -0400 Subject: [PATCH 033/258] [Infra] Silence extensions warnings in Sessions SDK tests (#13629) --- FirebaseSessions/Tests/Unit/Error+EquatableTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSessions/Tests/Unit/Error+EquatableTests.swift b/FirebaseSessions/Tests/Unit/Error+EquatableTests.swift index 46e870b81be..9308d31d093 100644 --- a/FirebaseSessions/Tests/Unit/Error+EquatableTests.swift +++ b/FirebaseSessions/Tests/Unit/Error+EquatableTests.swift @@ -23,7 +23,7 @@ import XCTest /// This class exists for unit testing purposes only. The SDK should use switch internally /// when handling errors, because equating errors is prone to issues (eg. we're just comparing /// the types, but not the values). -extension FirebaseSessionsError: Equatable { +extension FirebaseSessionsError: Swift.Equatable { public static func == (lhs: FirebaseSessions.FirebaseSessionsError, rhs: FirebaseSessions.FirebaseSessionsError) -> Bool { return String(reflecting: lhs) == String(reflecting: rhs) From bf29b4b8448a12c83ecaedea159876b3382d780e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:07:43 -0400 Subject: [PATCH 034/258] [Infra] Address Sendable warnings in RC's URLSessionPartialMock.swift (#13632) --- .../Tests/Swift/FakeUtils/URLSessionPartialMock.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/Tests/Swift/FakeUtils/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/Swift/FakeUtils/URLSessionPartialMock.swift index 93a8047f7d0..f6809a0560e 100644 --- a/FirebaseRemoteConfig/Tests/Swift/FakeUtils/URLSessionPartialMock.swift +++ b/FirebaseRemoteConfig/Tests/Swift/FakeUtils/URLSessionPartialMock.swift @@ -19,7 +19,7 @@ import Foundation #endif // Create a partial mock by subclassing the URLSessionDataTask. -class URLSessionDataTaskMock: URLSessionDataTask { +class URLSessionDataTaskMock: URLSessionDataTask, @unchecked Sendable { private let closure: () -> Void init(closure: @escaping () -> Void) { @@ -31,7 +31,7 @@ class URLSessionDataTaskMock: URLSessionDataTask { } } -class URLSessionMock: URLSession { +class URLSessionMock: URLSession, @unchecked Sendable { typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void private let fakeConsole: FakeConsole From 9e537325bf36a7c053154b685134a8bf99542c11 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:46:18 -0400 Subject: [PATCH 035/258] [Infra] Fix warning in Auth's 'SwiftAPI.swift' tests (#13633) --- FirebaseAuth/Tests/Unit/SwiftAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/Tests/Unit/SwiftAPI.swift b/FirebaseAuth/Tests/Unit/SwiftAPI.swift index 05193b27dfa..c508199088f 100644 --- a/FirebaseAuth/Tests/Unit/SwiftAPI.swift +++ b/FirebaseAuth/Tests/Unit/SwiftAPI.swift @@ -353,7 +353,7 @@ class AuthAPI_hOnlyTests: XCTestCase { } } let obj = FederatedAuthImplementation() - try await _ = obj.credential(with: nil) + _ = try await obj.credential(with: nil) } func FIRFederatedAuthProvider_h() { From e81ea8e2f3b36e95447fafd3a1321d8b9b880041 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 13 Sep 2024 14:56:38 -0400 Subject: [PATCH 036/258] [Vertex AI] Silence `SafetyRating: Comparable` warning in tests (#13635) --- FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index f112168151a..b01d62b90f0 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1375,7 +1375,7 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { struct AppCheckErrorFake: Error {} @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetyRating: Comparable { +extension SafetyRating: Swift.Comparable { public static func < (lhs: FirebaseVertexAI.SafetyRating, rhs: FirebaseVertexAI.SafetyRating) -> Bool { return lhs.category.rawValue < rhs.category.rawValue From e3822e67d93f1b7f6228892b600433528e6928bc Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sun, 15 Sep 2024 09:59:04 -0400 Subject: [PATCH 037/258] [Auth] Query with `kSecAttrSynchronizable` when auth sharing enabled (#13642) --- FirebaseAuth/CHANGELOG.md | 5 +++++ .../Sources/Swift/SystemService/AuthStoredUserManager.swift | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 81388ecef86..60e625d55de 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased +- [Fixed] Restore Firebase 10 behavior by querying with the + `kSecAttrSynchronizable` key when auth state is set to be shared across + devices. (#13584) + # 11.2.0 - [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 betas. (#13480) diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift index ae2cb1d7d0a..b9471dc891f 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift @@ -161,6 +161,10 @@ class AuthStoredUserManager { ] query[kSecUseDataProtectionKeychain as String] = true + if shareAuthStateAcrossDevices { + query[kSecAttrSynchronizable as String] = true + } + return query } } From f1f01c7f99847a12166f984fbef38bd1ebc20500 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:26:07 -0400 Subject: [PATCH 038/258] [Auth] Await header value from underlying queue (#13647) --- FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index b0e9a93fca5..f84a87d3e07 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -109,7 +109,11 @@ class AuthBackend: NSObject { request.setValue(Bundle.main.bundleIdentifier, forHTTPHeaderField: "X-Ios-Bundle-Identifier") request.setValue(requestConfiguration.appID, forHTTPHeaderField: "X-Firebase-GMPID") if let heartbeatLogger = requestConfiguration.heartbeatLogger { - request.setValue(heartbeatLogger.headerValue(), forHTTPHeaderField: "X-Firebase-Client") + // The below call synchronously dispatches to a queue. To avoid blocking + // the shared concurrency queue, `async let` will spawn the process on + // a separate thread. + async let heartbeatsHeaderValue = heartbeatLogger.headerValue() + await request.setValue(heartbeatsHeaderValue, forHTTPHeaderField: "X-Firebase-Client") } request.httpMethod = requestConfiguration.httpMethod let preferredLocalizations = Bundle.main.preferredLocalizations From 546a39bf4d8cb700e968da051d777bda4f28720a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 16 Sep 2024 16:18:20 -0700 Subject: [PATCH 039/258] Skip copyright check for data-connect ProtoGen files (#13651) --- scripts/check_copyright.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check_copyright.sh b/scripts/check_copyright.sh index 8ccc634c5c7..aecc286f7a6 100755 --- a/scripts/check_copyright.sh +++ b/scripts/check_copyright.sh @@ -24,6 +24,7 @@ options=( list=$(git grep "${options[@]}" -- \ '*.'{c,cc,cmake,h,js,m,mm,py,rb,sh,swift} \ CMakeLists.txt '**/CMakeLists.txt' \ + ':(exclude)**/Sources/ProtoGen' \ ':(exclude)third_party/**' \ ':(exclude)**/third_party/**') From a70e2e3cba4381559d98fcc672e0ba0cafd57885 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 16 Sep 2024 16:31:38 -0700 Subject: [PATCH 040/258] Update check_copyright.sh (#13652) --- scripts/check_copyright.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_copyright.sh b/scripts/check_copyright.sh index aecc286f7a6..3995c449e6f 100755 --- a/scripts/check_copyright.sh +++ b/scripts/check_copyright.sh @@ -24,7 +24,7 @@ options=( list=$(git grep "${options[@]}" -- \ '*.'{c,cc,cmake,h,js,m,mm,py,rb,sh,swift} \ CMakeLists.txt '**/CMakeLists.txt' \ - ':(exclude)**/Sources/ProtoGen' \ + ':(exclude)Sources/ProtoGen' \ ':(exclude)third_party/**' \ ':(exclude)**/third_party/**') From a7590c7a8aab623b8c2a953095ff6412d0b4fbfc Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 17 Sep 2024 11:47:00 -0400 Subject: [PATCH 041/258] [Infra] Upgrade to `clang-format@19` (#13656) --- .../Sources/Analytics/FIRIAMClearcutUploader.m | 4 ++-- Firestore/core/src/immutable/keys_view.h | 4 ++-- Firestore/core/src/util/filesystem_posix.cc | 4 ++-- Firestore/core/src/util/hashing.h | 8 ++++---- scripts/setup_check.sh | 2 +- scripts/style.sh | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m b/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m index f424f51f878..841cdeee1b3 100644 --- a/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m +++ b/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m @@ -111,8 +111,8 @@ - (instancetype)initWithRequestSender:(FIRIAMClearcutHttpRequestSender *)request _userDefaults = userDefaults ? userDefaults : [GULUserDefaults standardUserDefaults]; // it would be 0 if it does not exist, which is equvilent to saying that // you can send now - _nextValidSendTimeInMills = (int64_t) - [_userDefaults doubleForKey:FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills]; + _nextValidSendTimeInMills = (int64_t)[_userDefaults + doubleForKey:FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills]; NSArray *availableLogs = [logStorage popStillValidRecordsForUpTo:strategy.batchSendSize]; diff --git a/Firestore/core/src/immutable/keys_view.h b/Firestore/core/src/immutable/keys_view.h index c866ba5ef6c..fad8510cd59 100644 --- a/Firestore/core/src/immutable/keys_view.h +++ b/Firestore/core/src/immutable/keys_view.h @@ -47,8 +47,8 @@ auto KeysView(const Range& range) -> KeysRange { } template -auto KeysViewFrom(const Range& range, - const K& key) -> KeysRange { +auto KeysViewFrom(const Range& range, const K& key) + -> KeysRange { auto keys_begin = util::make_iterator_first(range.lower_bound(key)); auto keys_end = util::make_iterator_first(std::end(range)); return util::make_range(keys_begin, keys_end); diff --git a/Firestore/core/src/util/filesystem_posix.cc b/Firestore/core/src/util/filesystem_posix.cc index 8688ce5897c..8a2058c0809 100644 --- a/Firestore/core/src/util/filesystem_posix.cc +++ b/Firestore/core/src/util/filesystem_posix.cc @@ -132,7 +132,7 @@ Path Filesystem::TempDir() { #if !__APPLE__ Status Filesystem::IsDirectory(const Path& path) { - struct stat buffer {}; + struct stat buffer{}; if (::stat(path.c_str(), &buffer)) { if (errno == ENOENT) { // Expected common error case. @@ -168,7 +168,7 @@ Status Filesystem::IsDirectory(const Path& path) { } StatusOr Filesystem::FileSize(const Path& path) { - struct stat st {}; + struct stat st{}; if (::stat(path.c_str(), &st) == 0) { return st.st_size; } else { diff --git a/Firestore/core/src/util/hashing.h b/Firestore/core/src/util/hashing.h index 33375f03a63..7286c44cc32 100644 --- a/Firestore/core/src/util/hashing.h +++ b/Firestore/core/src/util/hashing.h @@ -190,8 +190,8 @@ auto RankedInvokeHash(const Range& range, HashChoice<3>) * value can itself be hashed. */ template -auto RankedInvokeHash(const absl::optional& option, - HashChoice<4>) -> decltype(InvokeHash(*option)) { +auto RankedInvokeHash(const absl::optional& option, HashChoice<4>) + -> decltype(InvokeHash(*option)) { return option ? InvokeHash(*option) : -1171; } @@ -202,8 +202,8 @@ size_t RankedInvokeHash(K value, HashChoice<5>) { } template -auto RankedInvokeHash(const std::unique_ptr& ptr, - HashChoice<6>) -> decltype(InvokeHash(*ptr)) { +auto RankedInvokeHash(const std::unique_ptr& ptr, HashChoice<6>) + -> decltype(InvokeHash(*ptr)) { return ptr ? InvokeHash(*ptr) : 23631; } diff --git a/scripts/setup_check.sh b/scripts/setup_check.sh index eb323eb0473..a1e6ad64ecc 100755 --- a/scripts/setup_check.sh +++ b/scripts/setup_check.sh @@ -35,7 +35,7 @@ fi # install clang-format brew update -brew install clang-format@18 +brew install clang-format@19 # mint installs tools from Mintfile on demand. brew install mint diff --git a/scripts/style.sh b/scripts/style.sh index 324903331d2..5caf8e54296 100755 --- a/scripts/style.sh +++ b/scripts/style.sh @@ -56,7 +56,7 @@ version="${version/ (*)/}" version="${version/.*/}" case "$version" in - 18) + 19) ;; google3-trunk) echo "Please use a publicly released clang-format; a recent LLVM release" From b5e2c182c83db543829f2a712f5cff7e6ad1ce1e Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 17 Sep 2024 09:02:53 -0700 Subject: [PATCH 042/258] Add Data Connect to issue product list selector (#13657) --- .github/ISSUE_TEMPLATE/BUG_REPORT.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index 06f35e92362..83e260c83f1 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -74,6 +74,7 @@ body: - Authentication - Crashlytics - Database + - Data Connect - DynamicLinks - Firestore - Functions From 9b06b3f5cdd1db0b4318860b0e00bba083900fd1 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:22:14 -0400 Subject: [PATCH 043/258] [Auth] Use native Swift types in Auth backend types (#13658) --- FirebaseAuth/CHANGELOG.md | 3 +++ .../Sources/Swift/Backend/AuthBackend.swift | 15 ++++++--------- .../Tests/Unit/Fakes/FakeBackendRPCIssuer.swift | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 60e625d55de..57a5987609d 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -2,6 +2,9 @@ - [Fixed] Restore Firebase 10 behavior by querying with the `kSecAttrSynchronizable` key when auth state is set to be shared across devices. (#13584) +- [Fixed] Prevent a bad memory access crash by using non-ObjC, native Swift + types in the SDK's networking layer, and moving synchronous work off of + the shared Swift concurrency queue. (#13650) # 11.2.0 - [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index f84a87d3e07..444a97c1c07 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -22,7 +22,7 @@ import Foundation #endif @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthBackendRPCIssuer: NSObjectProtocol { +protocol AuthBackendRPCIssuer { /// Asynchronously send a HTTP request. /// - Parameter request: The request to be made. /// - Parameter body: Request body. @@ -35,10 +35,10 @@ protocol AuthBackendRPCIssuer: NSObjectProtocol { } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackendRPCIssuerImplementation: NSObject, AuthBackendRPCIssuer { +class AuthBackendRPCIssuerImplementation: AuthBackendRPCIssuer { let fetcherService: GTMSessionFetcherService - override init() { + init() { fetcherService = GTMSessionFetcherService() fetcherService.userAgent = AuthBackend.authUserAgent() fetcherService.callbackQueue = kAuthGlobalWorkQueue @@ -71,7 +71,7 @@ class AuthBackendRPCIssuerImplementation: NSObject, AuthBackendRPCIssuer { } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackend: NSObject { +class AuthBackend { static func authUserAgent() -> String { return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))" } @@ -143,11 +143,8 @@ protocol AuthBackendImplementation { } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation { - var rpcIssuer: AuthBackendRPCIssuer - override init() { - rpcIssuer = AuthBackendRPCIssuerImplementation() - } +private class AuthBackendRPCImplementation: AuthBackendImplementation { + var rpcIssuer: AuthBackendRPCIssuer = AuthBackendRPCIssuerImplementation() /// Calls the RPC using HTTP request. /// Possible error responses: diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift index b6ec2c13ffa..f1b4cee7a5d 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift @@ -23,7 +23,7 @@ import XCTest response, and glue logic. */ @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { +class FakeBackendRPCIssuer: AuthBackendRPCIssuer { /** @property requestURL @brief The URL which was requested. */ From d39335cf23bef492b5f3c06463e35b8860b5586d Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 17 Sep 2024 14:33:46 -0700 Subject: [PATCH 044/258] Expose FirebaseCore via SPM (#13659) --- Package.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Package.swift b/Package.swift index c770b7e047f..26d896416d1 100644 --- a/Package.swift +++ b/Package.swift @@ -69,6 +69,10 @@ let package = Package( name: "FirebaseStorageCombine-Community", targets: ["FirebaseStorageCombineSwift"] ), + .library( + name: "FirebaseCore", + targets: ["FirebaseCore"] + ), .library( name: "FirebaseCrashlytics", targets: ["FirebaseCrashlytics"] From 602f3e76be073a98e62076ac417c6b9d2b36c590 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:12:06 -0400 Subject: [PATCH 045/258] [Auth] Handle corrupt keychain value resulting from incomplete v11 port (#13643) --- .../SystemService/AuthStoredUserManager.swift | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift index b9471dc891f..5517ddc3fa1 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthStoredUserManager.swift @@ -119,7 +119,87 @@ class AuthStoredUserManager { archiver.encode(user, forKey: Self.storedUserCoderKey) archiver.finishEncoding() - try keychainServices.setItem(archiver.encodedData, withQuery: query) + // In Firebase 10, the below query contained the `kSecAttrSynchronizable` + // key set to `true` when `shareAuthStateAcrossDevices == true`. This + // allows a user entry to be shared across devices via the iCloud keychain. + // For the purpose of this discussion, such a user entry will be referred + // to as a "iCloud entry". Conversely, a "non-iCloud entry" will refer to a + // user entry stored when `shareAuthStateAcrossDevices == false`. Keep in + // mind that this class exclusively manages user entries stored in + // device-specific keychain access groups, so both iCloud and non-iCloud + // entries are implicitly available at the device level to apps that + // have access rights to the specific keychain access group used. + // + // The iCloud/non-iCloud distinction is important because entries stored + // with `kSecAttrSynchronizable == true` can only be retrieved when the + // search query includes `kSecAttrSynchronizable == true`. Likewise, + // entries stored without the `kSecAttrSynchronizable` key (or + // `kSecAttrSynchronizable == false`) can only be retrieved when + // the search query omits `kSecAttrSynchronizable` or sets it to `false`. + // + // So for each access group, the SDK manages up to two buckets in the + // keychain, one for iCloud entries and one for non-iCloud entries. + // + // From Firebase 11.0.0 up to but not including 11.3.0, the + // `kSecAttrSynchronizable` key was *not* included in the query when + // `shareAuthStateAcrossDevices == true`. This had the effect of the iCloud + // bucket being inaccessible, and iCloud and non-iCloud entries attempting + // to be written to the same bucket. This was problematic because the + // two types of entries use another flag, the `kSecAttrAccessible` flag, + // with different values. If two queries are identical apart from different + // values for their `kSecAttrAccessible` key, whichever query written to + // the keychain first won't be accessible for reading or updating via the + // other query (resulting in a OSStatus of -25300 indicating the queried + // item cannot be found). And worse, attempting to write the other query to + // the keychain won't work because the write will conflict with the + // previously written query (resulting in a OSStatus of -25299 indicating a + // duplicate item already exists in the keychain). This formed the basis + // for the issues this bug caused. + // + // The missing key was added back in 11.3, but adding back the key + // introduced a new issue. If the buggy version succeeded at writing an + // iCloud entry to the non-iCloud bucket (e.g. keychain was empty before + // iCloud entry was written), then all future non-iCloud writes would fail + // due to the mismatching `kSecAttrAccessible` flag and throw an + // unrecoverable error. To address this the below error handling is used to + // detect such cases, remove the "corrupt" iCloud entry stored by the buggy + // version in the non-iCloud bucket, and retry writing the current + // non-iCloud entry again. + do { + try keychainServices.setItem(archiver.encodedData, withQuery: query) + } catch let error as NSError { + guard shareAuthStateAcrossDevices == false, + error.localizedFailureReason == "SecItemAdd (-25299)" else { + // The error is not related to the 11.0 - 11.2 issue described above, + // and should be rethrown. + throw error + } + // We are trying to write a non-iCloud entry but a corrupt iCloud entry + // is likely preventing it from happening. + // + // The corrupt query was supposed to contain the following keys: + // { + // kSecAttrSynchronizable: true, + // kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock + // } + // Instead, it contained: + // { + // kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock + // } + // + // Excluding `kSecAttrSynchronizable` treats the query as if it's false + // and the entry won't be shared in iCloud across devices. It is instead + // written to the non-iCloud bucket. This query is corrupting the + // non-iCloud bucket because its `kSecAttrAccessible` value is not + // compatible with the value used for non-iCloud entries. To delete it, + // a compatible query is formed by swapping the accessibility flag + // out for `kSecAttrAccessibleAfterFirstUnlock`. This frees up the bucket + // so the non-iCloud entry can attempt to be written again. + let corruptQuery = query + .merging([kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock]) { $1 } + try keychainServices.removeItem(query: corruptQuery) + try keychainServices.setItem(archiver.encodedData, withQuery: query) + } } /// Remove the user that stored locally. From 18c90b899ccbbab206592b27325a2cee9eec8681 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 18 Sep 2024 20:15:43 -0400 Subject: [PATCH 046/258] [Vertex AI] Remove `SafetyFeedback` struct (#13666) --- FirebaseVertexAI/Sources/Safety.swift | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index ad78e96243d..240dd75a7a3 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -59,22 +59,6 @@ public struct SafetyRating: Equatable, Hashable, Sendable { } } -/// Safety feedback for an entire request. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct SafetyFeedback { - /// Safety rating evaluated from content. - public let rating: SafetyRating - - /// Safety settings applied to the request. - public let setting: SafetySetting - - /// Internal initializer. - init(rating: SafetyRating, setting: SafetySetting) { - self.rating = rating - self.setting = setting - } -} - /// A type used to specify a threshold for harmful content, beyond which the model will return a /// fallback response instead of generated content. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) @@ -143,7 +127,7 @@ public struct SafetySetting { // MARK: - Codable Conformances @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetyRating.HarmProbability: Codable { +extension SafetyRating.HarmProbability: Decodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedProbability = SafetyRating.HarmProbability(rawValue: value) else { @@ -160,9 +144,6 @@ extension SafetyRating.HarmProbability: Codable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetyRating: Decodable {} -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetyFeedback: Decodable {} - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting.HarmCategory: Codable { public init(from decoder: Decoder) throws { @@ -179,7 +160,7 @@ extension SafetySetting.HarmCategory: Codable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetySetting.BlockThreshold: Codable { +extension SafetySetting.BlockThreshold: Encodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedThreshold = SafetySetting.BlockThreshold(rawValue: value) else { @@ -194,4 +175,4 @@ extension SafetySetting.BlockThreshold: Codable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetySetting: Codable {} +extension SafetySetting: Encodable {} From c282b6c0080ec0817f7e69c7414ee848a35e7039 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 19 Sep 2024 12:30:48 -0400 Subject: [PATCH 047/258] [Vertex AI] Cleanup Decodable impl from BlockThreshold (#13673) --- FirebaseVertexAI/Sources/Safety.swift | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 240dd75a7a3..6ad99bbb5e5 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -87,12 +87,6 @@ public struct SafetySetting { /// Block at and beyond a specified ``SafetyRating/HarmProbability``. public enum BlockThreshold: String, Sendable { - /// Unknown. A new server value that isn't recognized by the SDK. - case unknown = "UNKNOWN" - - /// Threshold is unspecified. - case unspecified = "HARM_BLOCK_THRESHOLD_UNSPECIFIED" - // Content with `.negligible` will be allowed. case blockLowAndAbove = "BLOCK_LOW_AND_ABOVE" @@ -160,19 +154,7 @@ extension SafetySetting.HarmCategory: Codable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetySetting.BlockThreshold: Encodable { - public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedThreshold = SafetySetting.BlockThreshold(rawValue: value) else { - Logging.default - .error("[FirebaseVertexAI] Unrecognized BlockThreshold with value \"\(value)\".") - self = .unknown - return - } - - self = decodedThreshold - } -} +extension SafetySetting.BlockThreshold: Encodable {} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting: Encodable {} From 9f274681a59108a333e8aef2f3b82c331b0cf6d4 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:13:45 -0400 Subject: [PATCH 048/258] [Core] Add ability to load version from product SDK (#13639) --- FirebaseCore/Sources/FIRApp.m | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/FirebaseCore/Sources/FIRApp.m b/FirebaseCore/Sources/FIRApp.m index abec9669f9b..0fc8691b3e2 100644 --- a/FirebaseCore/Sources/FIRApp.m +++ b/FirebaseCore/Sources/FIRApp.m @@ -821,11 +821,24 @@ + (void)registerSwiftComponents { @"FIRFunctions" : @"fire-fun", @"FIRStorage" : @"fire-str", @"FIRVertexAIComponent" : @"fire-vertex", + @"FIRDataConnectComponent" : @"fire-dc", }; for (NSString *className in swiftLibraries.allKeys) { Class klass = NSClassFromString(className); if (klass) { - [FIRApp registerLibrary:swiftLibraries[className] withVersion:FIRFirebaseVersion()]; + NSString *version = FIRFirebaseVersion(); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + SEL sdkVersionSelector = @selector(sdkVersion); +#pragma clang diagnostic pop + if ([klass respondsToSelector:sdkVersionSelector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + NSString *sdkVersion = (NSString *)[klass performSelector:sdkVersionSelector]; + if (sdkVersion) version = sdkVersion; +#pragma clang diagnostic pop + } + [FIRApp registerLibrary:swiftLibraries[className] withVersion:version]; } } } From dfa5ed8946a02a2b62dbf9dc3e77983569092172 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:03:15 -0400 Subject: [PATCH 049/258] [Infra] Limit concurrent matrix jobs --- .github/workflows/spm.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 21d0930dad0..5557f1b2865 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -26,6 +26,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14, macos-13] include: From f3908332def9ab1604a9d029ebfa1e590dfc12d8 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:04:25 -0400 Subject: [PATCH 050/258] [Revert] Limit concurrent runner jobs --- .github/workflows/spm.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 5557f1b2865..21d0930dad0 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -26,7 +26,6 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: - max-parallel: 1 matrix: os: [macos-14, macos-13] include: From 976393a2a82577e773748eadb3e0da1444331c23 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:29:38 -0400 Subject: [PATCH 051/258] [Auth] Move applicable non-public types away from subclassing NSObject (#13676) --- .../Sources/Swift/Backend/AuthRequestConfiguration.swift | 2 +- .../Sources/Swift/Backend/RPC/DeleteAccountResponse.swift | 4 ++-- .../Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift | 4 ++-- .../Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift | 4 ++-- .../Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift | 2 +- FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift | 2 +- .../Sources/Swift/SystemService/AuthAPNSTokenManager.swift | 2 +- .../Sources/Swift/SystemService/AuthAppCredential.swift | 3 ++- .../Swift/SystemService/AuthAppCredentialManager.swift | 2 +- .../Sources/Swift/SystemService/AuthNotificationManager.swift | 2 +- FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift | 4 +--- FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift | 2 +- 12 files changed, 16 insertions(+), 17 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift index 9aa53d7e68f..47aff1be47e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift @@ -19,7 +19,7 @@ import FirebaseCoreExtension /// Defines configurations to be added to a request to Firebase Auth's backend. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthRequestConfiguration: NSObject { +class AuthRequestConfiguration { /// The Firebase Auth API key used in the request. let apiKey: String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift index 92208e7581b..868d36d25d2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift @@ -17,8 +17,8 @@ import Foundation /// Represents the response from the deleteAccount endpoint. /// /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount -class DeleteAccountResponse: NSObject, AuthRPCResponse { - override required init() {} +class DeleteAccountResponse: AuthRPCResponse { + required init() {} func setFields(dictionary: [String: AnyHashable]) throws {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index 4147c4962e0..107c0859ac7 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -15,8 +15,8 @@ import Foundation /// Represents the response from the emailLinkSignin endpoint. -class EmailLinkSignInResponse: NSObject, AuthRPCResponse, AuthMFAResponse { - override required init() {} +class EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { + required init() {} /// The ID token in the email link sign-in response. private(set) var idToken: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift index 2f39fdfe68d..8b4ae17d627 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift @@ -19,7 +19,7 @@ private let kErrorKey = "error" /// Represents the provider user info part of the response from the getAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseProviderUserInfo: NSObject { +class GetAccountInfoResponseProviderUserInfo { /// The ID of the identity provider. let providerID: String? @@ -57,7 +57,7 @@ class GetAccountInfoResponseProviderUserInfo: NSObject { /// Represents the firebase user info part of the response from the getAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseUser: NSObject { +class GetAccountInfoResponseUser { /// The ID of the user. let localID: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift index d6bd97ccd70..4990e0dc0a0 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift @@ -16,7 +16,7 @@ import Foundation /// Represents the provider user info part of the response from the setAccountInfo endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo -class SetAccountInfoResponseProviderUserInfo: NSObject { +class SetAccountInfoResponseProviderUserInfo { /// The ID of the identity provider. var providerID: String? diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift index 5031a0e212e..3867f661e89 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift @@ -16,7 +16,7 @@ import Foundation /// A data structure for an APNs token. - class AuthAPNSToken: NSObject { + class AuthAPNSToken { let data: Data let type: AuthAPNSTokenType diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift index 006efc5ddc1..ff677b8771b 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift @@ -31,7 +31,7 @@ /// A class to manage APNs token in memory. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - class AuthAPNSTokenManager: NSObject { + class AuthAPNSTokenManager { /// The timeout for registering for remote notification. /// /// Only tests should access this property. diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift index 38fdcb06e34..39abc59e12f 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift @@ -15,7 +15,8 @@ import Foundation /// A class represents a credential that proves the identity of the app. -@objc(FIRAuthAppCredential) class AuthAppCredential: NSObject, NSSecureCoding { +@objc(FIRAuthAppCredential) // objc Needed for decoding old versions +class AuthAppCredential: NSObject, NSSecureCoding { /// The server acknowledgement of receiving client's claim of identity. var receipt: String diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift index 511d354e2fd..0c83875a8d1 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift @@ -17,7 +17,7 @@ /// A class to manage app credentials backed by iOS Keychain. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - class AuthAppCredentialManager: NSObject { + class AuthAppCredentialManager { let kKeychainDataKey = "app_credentials" let kFullCredentialKey = "full_credential" let kPendingReceiptsKey = "pending_receipts" diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift index 1d95153f42a..2cfd76ca2de 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift @@ -18,7 +18,7 @@ /// A class represents a credential that proves the identity of the app. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - class AuthNotificationManager: NSObject { + class AuthNotificationManager { /// The key to locate payload data in the remote notification. private let kNotificationDataKey = "com.google.firebase.auth" diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index dbf2dcb9a1b..6e6a1a74353 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -31,7 +31,7 @@ private let kFIRAuthErrorMessageMalformedJWT = "Failed to parse JWT. Check the userInfo dictionary for the full token." @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthErrorUtils: NSObject { +class AuthErrorUtils { static let internalErrorDomain = "FIRAuthInternalErrorDomain" static let userInfoDeserializedResponseKey = "FIRAuthErrorUserInfoDeserializedResponseKey" static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey" @@ -563,5 +563,3 @@ class AuthErrorUtils: NSObject { return error(code: .recaptchaActionCreationFailed, message: message) } } - -protocol MultiFactorResolverWrapper: NSObjectProtocol {} diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift index e7c64d39d49..2231c040023 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift @@ -15,7 +15,7 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthWebUtils: NSObject { +class AuthWebUtils { static func randomString(withLength length: Int) -> String { var randomString = "" for _ in 0 ..< length { From c4e1088365fa691d12a118f65d9f0072c5d0b570 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:30:05 -0400 Subject: [PATCH 052/258] [Infra] Limit concurrnet matrix jobs in spm.yml (#13677) --- .github/workflows/spm.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 21d0930dad0..5557f1b2865 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -26,6 +26,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14, macos-13] include: From 3ae46c051533cdf2632e4fa6b332df5a4e075c6a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:51:39 -0400 Subject: [PATCH 053/258] [Infra] Limit concurrent matrix jobs in 'spm' jobs (#13679) --- .github/workflows/abtesting.yml | 1 + .github/workflows/appdistribution.yml | 1 + .github/workflows/auth.yml | 1 + .github/workflows/core.yml | 1 + .github/workflows/core_internal.yml | 1 + .github/workflows/crashlytics.yml | 1 + .github/workflows/database.yml | 1 + .github/workflows/dynamiclinks.yml | 1 + .github/workflows/firebase_app_check.yml | 1 + .github/workflows/functions.yml | 1 + .github/workflows/inappmessaging.yml | 1 + .github/workflows/installations.yml | 1 + .github/workflows/messaging.yml | 1 + .github/workflows/mlmodeldownloader.yml | 1 + .github/workflows/performance.yml | 1 + .github/workflows/remoteconfig.yml | 1 + .github/workflows/sessions.yml | 1 + .github/workflows/shared-swift.yml | 1 + .github/workflows/storage.yml | 1 + .github/workflows/vertexai.yml | 2 ++ 20 files changed, 21 insertions(+) diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index 9c7bd5a1417..a1a50434bcc 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -50,6 +50,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index 74670a14f2e..072ee73b9f4 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -46,6 +46,7 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14, macos-13] include: diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index d3c3cdbb214..ecdafa27904 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -103,6 +103,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 5df6aad888a..d4913b5a428 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -43,6 +43,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/core_internal.yml b/.github/workflows/core_internal.yml index f05eb0cfd0f..916e4d2a6b0 100644 --- a/.github/workflows/core_internal.yml +++ b/.github/workflows/core_internal.yml @@ -39,6 +39,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index e7b78447c08..2abf8a6736b 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -57,6 +57,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 698f52ea6b4..4ff6742cfc2 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -68,6 +68,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/dynamiclinks.yml b/.github/workflows/dynamiclinks.yml index f2679cc3fda..06b6d593664 100644 --- a/.github/workflows/dynamiclinks.yml +++ b/.github/workflows/dynamiclinks.yml @@ -43,6 +43,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14, macos-13] include: diff --git a/.github/workflows/firebase_app_check.yml b/.github/workflows/firebase_app_check.yml index c2775348cb3..a4b03fe4a64 100644 --- a/.github/workflows/firebase_app_check.yml +++ b/.github/workflows/firebase_app_check.yml @@ -100,6 +100,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-14, macos-13] diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index bac9f2c2457..db67ea823f4 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -56,6 +56,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14] include: diff --git a/.github/workflows/inappmessaging.yml b/.github/workflows/inappmessaging.yml index efec2295431..76eea952f23 100644 --- a/.github/workflows/inappmessaging.yml +++ b/.github/workflows/inappmessaging.yml @@ -70,6 +70,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: os: [macos-14, macos-13] include: diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index 2b103346226..744e11c10a6 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -61,6 +61,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [iOS, tvOS, macOS, watchOS, catalyst] diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index 93aa3669a03..6aa8fdc0091 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -82,6 +82,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, watchOS, tvOS, macOS, catalyst] os: [macos-13, macos-14] diff --git a/.github/workflows/mlmodeldownloader.yml b/.github/workflows/mlmodeldownloader.yml index 24b59960440..a0d2cd992cc 100644 --- a/.github/workflows/mlmodeldownloader.yml +++ b/.github/workflows/mlmodeldownloader.yml @@ -73,6 +73,7 @@ jobs: spm: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ce0a4ac8790..059253fabfb 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -127,6 +127,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS] include: diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 0792a706f77..382068cd980 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -58,6 +58,7 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] diff --git a/.github/workflows/sessions.yml b/.github/workflows/sessions.yml index 11ffd2f942d..a80387bab95 100644 --- a/.github/workflows/sessions.yml +++ b/.github/workflows/sessions.yml @@ -53,6 +53,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/shared-swift.yml b/.github/workflows/shared-swift.yml index 4b1cbcb3383..4b6ea4a12e1 100644 --- a/.github/workflows/shared-swift.yml +++ b/.github/workflows/shared-swift.yml @@ -45,6 +45,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index 4409dadc0ca..50fb4f7b2ef 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -63,6 +63,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: + max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-13, macos-14] diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 3e9bf41e292..3a33c8ded29 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -17,6 +17,7 @@ concurrency: jobs: spm-unit: strategy: + max-parallel: 1 matrix: target: [iOS, macOS, catalyst, tvOS, visionOS, watchOS] os: [macos-14] @@ -44,6 +45,7 @@ jobs: spm-integration: strategy: + max-parallel: 1 matrix: target: [iOS] os: [macos-14] From 95eb9dfe8bb2cca361522ec93436afebcb998f2a Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 20 Sep 2024 19:45:22 -0400 Subject: [PATCH 054/258] [Vertex AI] Use `actions/cache` in workflow (#13687) --- .github/workflows/vertexai.yml | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 3a33c8ded29..cc97d6bf32f 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -15,9 +15,31 @@ concurrency: cancel-in-progress: true jobs: + spm-package-resolved: + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm-unit: strategy: - max-parallel: 1 matrix: target: [iOS, macOS, catalyst, tvOS, visionOS, watchOS] os: [macos-14] @@ -25,10 +47,15 @@ jobs: - os: macos-14 xcode: Xcode_15.2 runs-on: ${{ matrix.os }} + needs: spm-package-resolved env: FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 steps: - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Clone mock responses run: scripts/update_vertexai_responses.sh - name: Xcode @@ -45,7 +72,6 @@ jobs: spm-integration: strategy: - max-parallel: 1 matrix: target: [iOS] os: [macos-14] @@ -53,12 +79,17 @@ jobs: - os: macos-14 xcode: Xcode_15.2 runs-on: ${{ matrix.os }} + needs: spm-package-resolved env: TEST_RUNNER_VertexAIRunIntegrationTests: 1 FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} steps: - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Install Secret GoogleService-Info.plist run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/vertexai-integration.plist.gpg \ FirebaseVertexAI/Tests/Integration/Resources/GoogleService-Info.plist "$plist_secret" @@ -108,14 +139,17 @@ jobs: - os: macos-14 xcode: Xcode_15.2 runs-on: ${{ matrix.os }} + needs: spm-package-resolved env: FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 steps: - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Initialize xcodebuild - run: xcodebuild -list - name: Placeholder GoogleService-Info.plist for build testing run: cp FirebaseCore/Tests/Unit/Resources/GoogleService-Info.plist FirebaseVertexAI/Sample/ - uses: nick-fields/retry@v3 From 59c570371b40610d835b0342f5dbcce0cab1fe01 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 10:35:23 -0400 Subject: [PATCH 055/258] [Vertex AI] Remove pod from `FirebaseManifest` (#13663) --- ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 32dd30d5b99..39dbc952214 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -53,7 +53,8 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), - Pod("FirebaseVertexAI", isBeta: true, allowWarnings: true, zip: true), + // TODO(andrewheard): Re-add FirebaseVertexAI when ready for release as zip. + // Pod("FirebaseVertexAI", isBeta: true, allowWarnings: true, zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), ] ) From 846a41f9e5591679ad2a9c885d6376b38878cb41 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:44:29 -0400 Subject: [PATCH 056/258] [Messaging] Prep `FIRMessagingExtensionHelper` usage for refactored 1P target (#13691) --- FirebaseMessaging/Sources/FIRMessaging.m | 7 +++++++ .../Sources/Public/FirebaseMessaging/FIRMessaging.h | 4 ++++ .../Sources/Public/FirebaseMessaging/FirebaseMessaging.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/FirebaseMessaging/Sources/FIRMessaging.m b/FirebaseMessaging/Sources/FIRMessaging.m index af18ea77fea..6f160682e0a 100644 --- a/FirebaseMessaging/Sources/FIRMessaging.m +++ b/FirebaseMessaging/Sources/FIRMessaging.m @@ -40,7 +40,10 @@ #import "FirebaseMessaging/Sources/FIRMessagingUtilities.h" #import "FirebaseMessaging/Sources/FIRMessaging_Private.h" #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h" +#if __has_include( \ + "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") #import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h" +#endif // __has_include("FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") #import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenInfo.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h" @@ -131,6 +134,8 @@ + (FIRMessaging *)messaging { return (FIRMessaging *)instance; } +#if __has_include( \ + "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") + (FIRMessagingExtensionHelper *)extensionHelper { static dispatch_once_t once; static FIRMessagingExtensionHelper *extensionHelper; @@ -139,6 +144,8 @@ + (FIRMessagingExtensionHelper *)extensionHelper { }); return extensionHelper; } +#endif // __has_include("FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") + - (instancetype)initWithAnalytics:(nullable id)analytics userDefaults:(GULUserDefaults *)defaults heartbeatLogger:(FIRHeartbeatLogger *)heartbeatLogger { diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h index 4f5209bb935..5a7243cbacc 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h @@ -136,7 +136,9 @@ NS_SWIFT_NAME(MessagingMessageInfo) @end @class FIRMessaging; +#if __has_include("FIRMessagingExtensionHelper.h") @class FIRMessagingExtensionHelper; +#endif // __has_include("FIRMessagingExtensionHelper.h") /** * A protocol to handle token update or data message delivery from FCM. @@ -184,6 +186,7 @@ NS_SWIFT_NAME(Messaging) */ + (instancetype)messaging NS_SWIFT_NAME(messaging()); +#if __has_include("FIRMessagingExtensionHelper.h") /** * Use the MessagingExtensionHelper to populate rich UI content for your notifications. * For example, if an image URL is set in your notification payload or on the console, @@ -194,6 +197,7 @@ NS_SWIFT_NAME(Messaging) */ + (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) NS_AVAILABLE(10.14, 10.0); +#endif // __has_include("FIRMessagingExtensionHelper.h") /** * Unavailable. Use +messaging instead. diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h index c5d0bd05046..a46524b399c 100755 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h @@ -15,4 +15,6 @@ */ #import "FIRMessaging.h" +#if __has_include("FIRMessagingExtensionHelper.h") #import "FIRMessagingExtensionHelper.h" +#endif // __has_include("FIRMessagingExtensionHelper.h") From e084de1240b7c22b61ddd361900302073fd1e3ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 07:05:10 -0700 Subject: [PATCH 057/258] NOTICES Change (#13705) Co-authored-by: Anka --- CoreOnly/NOTICES | 1 - 1 file changed, 1 deletion(-) diff --git a/CoreOnly/NOTICES b/CoreOnly/NOTICES index 84b2455db55..6ee4a9aa089 100644 --- a/CoreOnly/NOTICES +++ b/CoreOnly/NOTICES @@ -21,7 +21,6 @@ FirebaseRemoteConfig FirebaseRemoteConfigInterop FirebaseSessions FirebaseStorage -FirebaseVertexAI GTMSessionFetcher GoogleDataTransport PromisesObjC From 2bff4d436293758e770ebdf994252ddbb6f76ebd Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 24 Sep 2024 10:50:55 -0400 Subject: [PATCH 058/258] Revert "Make GenerativeModel and Chat into Swift actors (#13545)" (#13703) --- FirebaseVertexAI/CHANGELOG.md | 6 ++- .../Screens/ConversationScreen.swift | 4 +- .../ViewModels/ConversationViewModel.swift | 42 +++++++----------- .../Screens/FunctionCallingScreen.swift | 4 +- .../ViewModels/FunctionCallingViewModel.swift | 33 +++++--------- .../ViewModels/PhotoReasoningViewModel.swift | 2 +- .../ViewModels/SummarizeViewModel.swift | 2 +- FirebaseVertexAI/Sources/Chat.swift | 4 +- .../Sources/GenerativeModel.swift | 44 ++++++++++--------- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 11 +++-- .../Tests/Unit/GenerativeModelTests.swift | 36 +++++++-------- .../Tests/Unit/VertexAIAPITests.swift | 4 +- .../Tests/Unit/VertexComponentTests.swift | 15 +++---- 13 files changed, 90 insertions(+), 117 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index c048c49ebc4..fc54c727370 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,5 +1,9 @@ -# Unreleased +# 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) +- [changed] **Breaking Change**: Reverted refactor of `GenerativeModel` and + `Chat` as Swift actors (#13545) introduced in 11.2; The methods + `generateContentStream`, `startChat` and `sendMessageStream` no longer need to + be called with `await`. (#13703) # 11.2.0 - [fixed] Resolved a decoding error for citations without a `uri` and added diff --git a/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift index 43da223aa78..78c903e3412 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Screens/ConversationScreen.swift @@ -104,9 +104,7 @@ struct ConversationScreen: View { } private func newChat() { - Task { - await viewModel.startNewChat() - } + viewModel.startNewChat() } } diff --git a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift index 3db45eb8d19..65e1c940de9 100644 --- a/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift +++ b/FirebaseVertexAI/Sample/ChatSample/ViewModels/ConversationViewModel.swift @@ -21,8 +21,8 @@ class ConversationViewModel: ObservableObject { /// This array holds both the user's and the system's chat messages @Published var messages = [ChatMessage]() - /// Indicates we're waiting for the model to finish or the UI is loading - @Published var busy = true + /// Indicates we're waiting for the model to finish + @Published var busy = false @Published var error: Error? var hasError: Bool { @@ -30,20 +30,18 @@ class ConversationViewModel: ObservableObject { } private var model: GenerativeModel - private var chat: Chat? = nil + private var chat: Chat private var stopGenerating = false private var chatTask: Task? init() { model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") - Task { - await startNewChat() - } + chat = model.startChat() } func sendMessage(_ text: String, streaming: Bool = true) async { - stop() + error = nil if streaming { await internalSendMessageStreaming(text) } else { @@ -51,14 +49,11 @@ class ConversationViewModel: ObservableObject { } } - func startNewChat() async { - busy = true - defer { - busy = false - } + func startNewChat() { stop() + error = nil + chat = model.startChat() messages.removeAll() - chat = await model.startChat() } func stop() { @@ -67,6 +62,8 @@ class ConversationViewModel: ObservableObject { } private func internalSendMessageStreaming(_ text: String) async { + chatTask?.cancel() + chatTask = Task { busy = true defer { @@ -82,10 +79,7 @@ class ConversationViewModel: ObservableObject { messages.append(systemMessage) do { - guard let chat else { - throw ChatError.notInitialized - } - let responseStream = try await chat.sendMessageStream(text) + let responseStream = try chat.sendMessageStream(text) for try await chunk in responseStream { messages[messages.count - 1].pending = false if let text = chunk.text { @@ -101,6 +95,8 @@ class ConversationViewModel: ObservableObject { } private func internalSendMessage(_ text: String) async { + chatTask?.cancel() + chatTask = Task { busy = true defer { @@ -116,12 +112,10 @@ class ConversationViewModel: ObservableObject { messages.append(systemMessage) do { - guard let chat = chat else { - throw ChatError.notInitialized - } - let response = try await chat.sendMessage(text) + var response: GenerateContentResponse? + response = try await chat.sendMessage(text) - if let responseText = response.text { + if let responseText = response?.text { // replace pending message with backend response messages[messages.count - 1].message = responseText messages[messages.count - 1].pending = false @@ -133,8 +127,4 @@ class ConversationViewModel: ObservableObject { } } } - - enum ChatError: Error { - case notInitialized - } } diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift index dbfd04eb52c..f16da39e22f 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/Screens/FunctionCallingScreen.swift @@ -106,9 +106,7 @@ struct FunctionCallingScreen: View { } private func newChat() { - Task { - await viewModel.startNewChat() - } + viewModel.startNewChat() } } diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 36b53f2e2da..ac2ea5a1fcc 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -33,7 +33,7 @@ class FunctionCallingViewModel: ObservableObject { private var functionCalls = [FunctionCall]() private var model: GenerativeModel - private var chat: Chat? = nil + private var chat: Chat private var chatTask: Task? @@ -62,13 +62,13 @@ class FunctionCallingViewModel: ObservableObject { ), ])] ) - Task { - await startNewChat() - } + chat = model.startChat() } func sendMessage(_ text: String, streaming: Bool = true) async { - stop() + error = nil + chatTask?.cancel() + chatTask = Task { busy = true defer { @@ -100,14 +100,11 @@ class FunctionCallingViewModel: ObservableObject { } } - func startNewChat() async { - busy = true - defer { - busy = false - } + func startNewChat() { stop() + error = nil + chat = model.startChat() messages.removeAll() - chat = await model.startChat() } func stop() { @@ -117,17 +114,14 @@ class FunctionCallingViewModel: ObservableObject { private func internalSendMessageStreaming(_ text: String) async throws { let functionResponses = try await processFunctionCalls() - guard let chat else { - throw ChatError.notInitialized - } let responseStream: AsyncThrowingStream if functionResponses.isEmpty { - responseStream = try await chat.sendMessageStream(text) + responseStream = try chat.sendMessageStream(text) } else { for functionResponse in functionResponses { messages.insert(functionResponse.chatMessage(), at: messages.count - 1) } - responseStream = try await chat.sendMessageStream(functionResponses.modelContent()) + responseStream = try chat.sendMessageStream(functionResponses.modelContent()) } for try await chunk in responseStream { processResponseContent(content: chunk) @@ -136,9 +130,6 @@ class FunctionCallingViewModel: ObservableObject { private func internalSendMessage(_ text: String) async throws { let functionResponses = try await processFunctionCalls() - guard let chat else { - throw ChatError.notInitialized - } let response: GenerateContentResponse if functionResponses.isEmpty { response = try await chat.sendMessage(text) @@ -190,10 +181,6 @@ class FunctionCallingViewModel: ObservableObject { return functionResponses } - enum ChatError: Error { - case notInitialized - } - // MARK: - Callable Functions func getExchangeRate(args: JSONObject) -> JSONObject { diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift index 4eee954a68d..3a85da05102 100644 --- a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -84,7 +84,7 @@ class PhotoReasoningViewModel: ObservableObject { } } - let outputContentStream = try await model.generateContentStream(prompt, images) + let outputContentStream = try model.generateContentStream(prompt, images) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift index 4467b85fe3d..540ff893ba8 100644 --- a/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift @@ -50,7 +50,7 @@ class SummarizeViewModel: ObservableObject { let prompt = "Summarize the following text for me: \(inputText)" - let outputContentStream = try await model.generateContentStream(prompt) + let outputContentStream = try model.generateContentStream(prompt) // stream response for try await outputContent in outputContentStream { diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index 0c0239ed54e..ff7a8aa9a09 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -17,7 +17,7 @@ import Foundation /// An object that represents a back-and-forth chat with a model, capturing the history and saving /// the context in memory between each message sent. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public actor Chat { +public class Chat { private let model: GenerativeModel /// Initializes a new chat representing a 1:1 conversation between model and user. @@ -116,7 +116,7 @@ public actor Chat { // Send the history alongside the new message as context. let request = history + newContent - let stream = try await model.generateContentStream(request) + let stream = try model.generateContentStream(request) do { for try await chunk in stream { // Capture any content that's streaming. This should be populated if there's no error. diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 64c97729177..28d3ca4ba88 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -19,7 +19,7 @@ import Foundation /// A type that represents a remote multimodal model (like Gemini), with the ability to generate /// content based on various input types. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public final actor GenerativeModel { +public final class GenerativeModel { /// The resource name of the model in the backend; has the format "models/model-name". let modelResourceName: String @@ -212,31 +212,33 @@ public final actor GenerativeModel { isStreaming: true, options: requestOptions) - let responseStream = generativeAIService.loadRequestStream(request: generateContentRequest) - + var responseIterator = generativeAIService.loadRequestStream(request: generateContentRequest) + .makeAsyncIterator() return AsyncThrowingStream { + let response: GenerateContentResponse? do { - for try await response in responseStream { - // Check the prompt feedback to see if the prompt was blocked. - if response.promptFeedback?.blockReason != nil { - throw GenerateContentError.promptBlocked(response: response) - } + response = try await responseIterator.next() + } catch { + throw GenerativeModel.generateContentError(from: error) + } - // If the stream ended early unexpectedly, throw an error. - if let finishReason = response.candidates.first?.finishReason, finishReason != .stop { - throw GenerateContentError.responseStoppedEarly( - reason: finishReason, - response: response - ) - } else { - // Response was valid content, pass it along and continue. - return response - } - } + // The responseIterator will return `nil` when it's done. + guard let response = response else { // This is the end of the stream! Signal it by sending `nil`. return nil - } catch { - throw GenerativeModel.generateContentError(from: error) + } + + // Check the prompt feedback to see if the prompt was blocked. + if response.promptFeedback?.blockReason != nil { + throw GenerateContentError.promptBlocked(response: response) + } + + // If the stream ended early unexpectedly, throw an error. + if let finishReason = response.candidates.first?.finishReason, finishReason != .stop { + throw GenerateContentError.responseStoppedEarly(reason: finishReason, response: response) + } else { + // Response was valid content, pass it along and continue. + return response } } } diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 2e55b73020f..614559fe011 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -69,20 +69,19 @@ final class ChatTests: XCTestCase { ) let chat = Chat(model: model, history: []) let input = "Test input" - let stream = try await chat.sendMessageStream(input) + let stream = try chat.sendMessageStream(input) // Ensure the values are parsed correctly for try await value in stream { XCTAssertNotNil(value.text) } - let history = await chat.history - XCTAssertEqual(history.count, 2) - XCTAssertEqual(history[0].parts[0].text, input) + XCTAssertEqual(chat.history.count, 2) + XCTAssertEqual(chat.history[0].parts[0].text, input) let finalText = "1 2 3 4 5 6 7 8" let assembledExpectation = ModelContent(role: "model", parts: finalText) - XCTAssertEqual(history[0].parts[0].text, input) - XCTAssertEqual(history[1], assembledExpectation) + XCTAssertEqual(chat.history[0].parts[0].text, input) + XCTAssertEqual(chat.history[1], assembledExpectation) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index b01d62b90f0..dc76123d028 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -760,7 +760,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -784,7 +784,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -807,7 +807,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } @@ -827,7 +827,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -847,7 +847,7 @@ final class GenerativeModelTests: XCTestCase { ) do { - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } @@ -866,7 +866,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") do { for try await content in stream { XCTAssertNotNil(content.text) @@ -887,7 +887,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -904,7 +904,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responses += 1 @@ -921,7 +921,7 @@ final class GenerativeModelTests: XCTestCase { ) var hadUnknown = false - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) if let ratings = content.candidates.first?.safetyRatings, @@ -940,7 +940,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") var citations = [Citation]() var responses = [GenerateContentResponse]() for try await content in stream { @@ -996,7 +996,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: appCheckToken ) - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1018,7 +1018,7 @@ final class GenerativeModelTests: XCTestCase { appCheckToken: AppCheckInteropFake.placeholderTokenValue ) - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) for try await _ in stream {} } @@ -1030,7 +1030,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = [GenerateContentResponse]() - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) for try await response in stream { responses.append(response) } @@ -1056,7 +1056,7 @@ final class GenerativeModelTests: XCTestCase { var responseCount = 0 do { - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) responseCount += 1 @@ -1076,7 +1076,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_nonHTTPResponse() async throws { MockURLProtocol.requestHandler = try nonHTTPRequestHandler() - let stream = try await model.generateContentStream("Hi") + let stream = try model.generateContentStream("Hi") do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1096,7 +1096,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1120,7 +1120,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "txt" ) - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) do { for try await content in stream { XCTFail("Unexpected content in stream: \(content)") @@ -1159,7 +1159,7 @@ final class GenerativeModelTests: XCTestCase { ) var responses = 0 - let stream = try await model.generateContentStream(testPrompt) + let stream = try model.generateContentStream(testPrompt) for try await content in stream { XCTAssertNotNil(content.text) responses += 1 diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index f2c38a03e61..c68b69b03ec 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -170,8 +170,8 @@ final class VertexAIAPITests: XCTestCase { #endif // Chat - _ = await genAI.startChat() - _ = await genAI.startChat(history: [ModelContent(parts: "abc")]) + _ = genAI.startChat() + _ = genAI.startChat(history: [ModelContent(parts: "abc")]) } // Public API tests for GenerateContentResponse. diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift index a6f77467c24..64a3edcc6b8 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift @@ -107,20 +107,15 @@ class VertexComponentTests: XCTestCase { let app = try XCTUnwrap(VertexComponentTests.app) let vertex = VertexAI.vertexAI(app: app, location: location) let modelName = "test-model-name" - let expectedModelResourceName = vertex.modelResourceName(modelName: modelName) - let expectedSystemInstruction = ModelContent( - role: "system", - parts: "test-system-instruction-prompt" - ) + let modelResourceName = vertex.modelResourceName(modelName: modelName) + let systemInstruction = ModelContent(role: "system", parts: "test-system-instruction-prompt") let generativeModel = vertex.generativeModel( modelName: modelName, - systemInstruction: expectedSystemInstruction + systemInstruction: systemInstruction ) - let modelResourceName = await generativeModel.modelResourceName - let systemInstruction = await generativeModel.systemInstruction - XCTAssertEqual(modelResourceName, expectedModelResourceName) - XCTAssertEqual(systemInstruction, expectedSystemInstruction) + XCTAssertEqual(generativeModel.modelResourceName, modelResourceName) + XCTAssertEqual(generativeModel.systemInstruction, systemInstruction) } } From bc2c2d42a1a80cade1a815d9f4a7d01b8734b5ca Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Sep 2024 08:31:33 -0700 Subject: [PATCH 059/258] GHA updates: visionOS and Xcode 16 - Part 1 (#13697) --- .github/workflows/abtesting.yml | 51 ++++++----- .github/workflows/api_diff_report.yml | 2 +- .github/workflows/appdistribution.yml | 45 ++++++---- .github/workflows/auth.yml | 89 +++++++++++++------ .github/workflows/check.yml | 4 +- .github/workflows/crashlytics.yml | 2 +- .github/workflows/dynamiclinks.yml | 2 +- .github/workflows/firestore-nightly.yml | 8 +- .github/workflows/firestore.yml | 22 ++--- .github/workflows/functions.yml | 2 +- .github/workflows/installations.yml | 2 +- .github/workflows/messaging.yml | 2 +- .github/workflows/performance.yml | 2 +- .github/workflows/remoteconfig.yml | 2 +- .github/workflows/storage.yml | 2 +- .../workflows/update-cpp-sdk-on-release.yml | 2 +- .github/workflows/vertexai.yml | 6 +- FirebaseAuth.podspec | 4 +- scripts/setup_spm_tests.sh | 1 - 19 files changed, 152 insertions(+), 98 deletions(-) diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index a1a50434bcc..00cb6e22b6b 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -24,12 +24,8 @@ jobs: matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -46,30 +42,43 @@ jobs: retry_wait_seconds: 120 command: scripts/pod_lib_lint.rb FirebaseABTesting.podspec --platforms=${{ matrix.target }} + spm-package-resolved: + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.2 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild @@ -137,7 +146,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/api_diff_report.yml b/.github/workflows/api_diff_report.yml index 8ca8c385b81..1e5f18a15b9 100644 --- a/.github/workflows/api_diff_report.yml +++ b/.github/workflows/api_diff_report.yml @@ -34,7 +34,7 @@ jobs: echo "file_list=$(git diff --name-only -r HEAD^1 HEAD | tr '\n' ' ')" >> $GITHUB_OUTPUT - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index 072ee73b9f4..1c34f2e97eb 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -22,12 +22,8 @@ jobs: strategy: matrix: target: [ios] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -41,25 +37,42 @@ jobs: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAppDistribution.podspec \ --platforms=${{ matrix.target }} + spm-package-resolved: + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' - + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index ecdafa27904..6e6058dc0f3 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -28,16 +28,9 @@ jobs: strategy: matrix: podspec: [FirebaseAuthInterop.podspec, FirebaseAuth.podspec] - # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - tests: --skip-tests - - os: macos-13 - xcode: Xcode_15.2 - tests: + os: [macos-14] + xcode: [Xcode_15.2] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -48,7 +41,6 @@ jobs: run: scripts/configure_test_keychain.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer -#TODO: Restore warnings check after resolution of #11693 - uses: nick-fields/retry@v3 with: timeout_minutes: 120 @@ -57,6 +49,35 @@ jobs: retry_wait_seconds: 120 command: scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} ${{ matrix.tests }} + # TODO: Fix warnings on Xcode 16 and move into matrix above. + pod-lib-lint-xc16: + # Don't run on private repo unless it is a PR. + if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + + strategy: + matrix: + podspec: [FirebaseAuthInterop.podspec, FirebaseAuth.podspec] + target: [ios, tvos, macos --skip-tests --allow-warnings, watchos] + os: [macos-14] + xcode: [Xcode_16] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + - name: Setup Bundler + run: scripts/setup_bundler.sh + - name: Configure test keychain + run: scripts/configure_test_keychain.sh + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} ${{ matrix.tests }} --allow-warnings + integration-tests: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' @@ -99,31 +120,43 @@ jobs: retry_wait_seconds: 120 command: ([ -z $plist_secret ] || scripts/build.sh Auth iOS ${{ matrix.scheme }}) + spm-package-resolved: + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - test: spmbuildonly - - os: macos-14 - xcode: Xcode_15.3 - test: spmbuildonly - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS - test: spm + target: [iOS spm, tvOS spm, macOS spmbuildonly, catalyst spm, watchOS spm] + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild @@ -134,7 +167,7 @@ jobs: max_attempts: 3 retry_on: error retry_wait_seconds: 120 - command: scripts/third_party/travis/retry.sh ./scripts/build.sh AuthUnit ${{ matrix.target }} ${{ matrix.test }} + command: scripts/third_party/travis/retry.sh ./scripts/build.sh AuthUnit ${{ matrix.target }} catalyst: # Don't run on private repo unless it is a PR. @@ -187,7 +220,7 @@ jobs: # steps: # - uses: actions/checkout@v4 # - uses: ruby/setup-ruby@v1 - # - uses: actions/setup-python@v4 + # - uses: actions/setup-python@v5 # with: # python-version: '3.11' # - name: Setup quickstart diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 169ff7a2752..b2a6a56a984 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,12 +21,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.11 - name: Cache Mint packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index 2abf8a6736b..124e2121b8f 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -147,7 +147,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/dynamiclinks.yml b/.github/workflows/dynamiclinks.yml index 06b6d593664..589825221e8 100644 --- a/.github/workflows/dynamiclinks.yml +++ b/.github/workflows/dynamiclinks.yml @@ -121,7 +121,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/firestore-nightly.yml b/.github/workflows/firestore-nightly.yml index 98c73dbf322..dca4a833f19 100644 --- a/.github/workflows/firestore-nightly.yml +++ b/.github/workflows/firestore-nightly.yml @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.11 @@ -55,7 +55,7 @@ jobs: - uses: actions/checkout@v3 - name: Prepare ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache key: firestore-ccache-${{ matrix.databaseId }}-${{ runner.os }}-${{ github.sha }} @@ -63,13 +63,13 @@ jobs: firestore-ccache-${{ matrix.databaseId }}-${{ runner.os }}- - name: Cache Mint packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} restore-keys: ${{ runner.os }}-mint- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.7' diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 4bf75ec6279..9bff2362ab1 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -86,7 +86,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.11 @@ -114,7 +114,7 @@ jobs: - uses: actions/checkout@v4 - name: Prepare ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache key: firestore-ccache-${{ runner.os }}-${{ github.sha }} @@ -122,13 +122,13 @@ jobs: firestore-ccache-${{ runner.os }}- - name: Cache Mint packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} restore-keys: ${{ runner.os }}-mint- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' @@ -164,7 +164,7 @@ jobs: - uses: actions/checkout@v4 - name: Prepare ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache key: firestore-ccache-${{ matrix.databaseId }}-${{ runner.os }}-${{ github.sha }} @@ -172,13 +172,13 @@ jobs: firestore-ccache-${{ matrix.databaseId }}-${{ runner.os }}- - name: Cache Mint packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} restore-keys: ${{ runner.os }}-mint- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' @@ -250,14 +250,14 @@ jobs: - uses: actions/checkout@v4 - name: Prepare ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache key: ${{ matrix.sanitizer }}-firestore-ccache-${{ runner.os }}-${{ github.sha }} restore-keys: | ${{ matrix.sanitizer }}-firestore-ccache-${{ runner.os }}- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' @@ -296,14 +296,14 @@ jobs: - uses: actions/checkout@v3 - name: Prepare ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ runner.temp }}/ccache key: ${{ matrix.sanitizer }}-firestore-ccache-${{ runner.os }}-${{ github.sha }} restore-keys: | ${{ matrix.sanitizer }}-firestore-ccache-${{ runner.os }}- - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.7' diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index db67ea823f4..32e9dffcb01 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -152,7 +152,7 @@ jobs: # steps: # - uses: actions/checkout@v4 # - uses: ruby/setup-ruby@v1 - # - uses: actions/setup-python@v4 + # - uses: actions/setup-python@v5 # with: # python-version: '3.11' # - name: Setup quickstart diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index 744e11c10a6..94e7c7831e0 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -126,7 +126,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index 6aa8fdc0091..151166c5715 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -161,7 +161,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 059253fabfb..29aa3176bb6 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -104,7 +104,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 382068cd980..fe576403f5d 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -163,7 +163,7 @@ jobs: # steps: # - uses: actions/checkout@v4 # - uses: ruby/setup-ruby@v1 - # - uses: actions/setup-python@v4 + # - uses: actions/setup-python@v5 # with: # python-version: '3.11' # - name: Setup quickstart diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index 50fb4f7b2ef..1c1bb861e30 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -131,7 +131,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Setup quickstart diff --git a/.github/workflows/update-cpp-sdk-on-release.yml b/.github/workflows/update-cpp-sdk-on-release.yml index ac824cf4c53..192444ef9d0 100644 --- a/.github/workflows/update-cpp-sdk-on-release.yml +++ b/.github/workflows/update-cpp-sdk-on-release.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index cc97d6bf32f..2e427be3bb7 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -41,11 +41,9 @@ jobs: spm-unit: strategy: matrix: - target: [iOS, macOS, catalyst, tvOS, visionOS, watchOS] + target: [iOS, tvOS, macOS, catalyst, watchOS] os: [macos-14] - include: - - os: macos-14 - xcode: Xcode_15.2 + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} needs: spm-package-resolved env: diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 5bb6a6b1eec..7c35e277736 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -48,11 +48,13 @@ supports email and password accounts, as well as several 3rd party authenticatio 'FirebaseAuth/README.md', 'FirebaseAuth/CHANGELOG.md' ] + # TODO(#13704) Restore warnings-as-errors checking. + # 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI -warnings-as-errors' : ''}" s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', # The second path is to find FirebaseAuth-Swift.h from a pod gen project 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}" "${OBJECT_FILE_DIR_normal}/${NATIVE_ARCH_ACTUAL}"', - 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI -warnings-as-errors' : ''}" + 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI' : ''}" } s.framework = 'Security' s.ios.framework = 'SafariServices' diff --git a/scripts/setup_spm_tests.sh b/scripts/setup_spm_tests.sh index 88771005172..0834c17b7bd 100755 --- a/scripts/setup_spm_tests.sh +++ b/scripts/setup_spm_tests.sh @@ -32,4 +32,3 @@ sed -i '' 's#exact:[[:space:]]*"[0-9.]*"#branch: "main"#' Package.swift mkdir -p .swiftpm/xcode/xcshareddata/xcschemes cp scripts/spm_test_schemes/* .swiftpm/xcode/xcshareddata/xcschemes/ -xcodebuild -list From c07397cc4f82ff81410bcdccfafa3c408e44b6fa Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:54:27 -0400 Subject: [PATCH 060/258] [Infra] Require firestore status check only if firestore code changes (#13678) --- .github/workflows/firestore.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 9bff2362ab1..1c684ee5c50 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -143,11 +143,11 @@ jobs: cmake-prod-db: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') - needs: check strategy: matrix: @@ -230,11 +230,11 @@ jobs: sanitizers-mac: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') - needs: check strategy: matrix: @@ -272,11 +272,11 @@ jobs: sanitizers-ubuntu: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') - needs: check strategy: matrix: @@ -318,12 +318,12 @@ jobs: xcodebuild: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || - (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') + (github.event_name == 'pull_request') runs-on: macos-14 - needs: check strategy: matrix: @@ -347,12 +347,12 @@ jobs: pod-lib-lint: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || - (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') + (github.event_name == 'pull_request') runs-on: macos-13 - needs: check strategy: matrix: podspec: [ @@ -379,8 +379,8 @@ jobs: # `pod lib lint` takes a long time so only run the other platforms and static frameworks build in the cron. pod-lib-lint-cron: - if: github.event_name == 'schedule' && github.repository == 'Firebase/firebase-ios-sdk' needs: check + if: github.event_name == 'schedule' && github.repository == 'Firebase/firebase-ios-sdk' strategy: matrix: podspec: [ @@ -430,10 +430,11 @@ jobs: --no-analyze spm-source: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || - (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') + (github.event_name == 'pull_request') strategy: matrix: target: [iOS, tvOS, macOS] @@ -447,7 +448,6 @@ jobs: xcode: Xcode_15.3 target: visionOS runs-on: ${{ matrix.os }} - needs: check env: FIREBASE_SOURCE_FIRESTORE: 1 steps: @@ -463,12 +463,12 @@ jobs: run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFirestore ${{ matrix.target }} spmbuildonly spm-binary: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') runs-on: macos-14 - needs: check steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 @@ -480,12 +480,12 @@ jobs: run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFirestore iOS spmbuildonly check-firestore-internal-public-headers: + needs: check # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request' && needs.changes.outputs.changed == 'true') runs-on: macos-14 - needs: check steps: - uses: actions/checkout@v4 - name: Assert that Firestore and FirestoreInternal have identically named headers. @@ -551,15 +551,13 @@ jobs: # to be used as a required check for merging. check-required-tests: runs-on: ubuntu-latest - if: always() name: Check all required Firestore tests results needs: [cmake, cmake-prod-db, xcodebuild, spm-source, spm-binary] steps: - name: Check test matrix - if: needs.cmake.result == 'failure' || needs.cmake-prod-db.result == 'failure' || needs.xcodebuild.result == 'failure' || needs.spm.result == 'failure' + if: needs.*.result == 'failure' run: exit 1 - # Disable until FirebaseUI is updated to accept Firebase 9 and quickstart is updated to accept # Firebase UI 12 # quickstart: From 3cab646fe1c5d775c3f2574eb2e2f64f7e9d9660 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 10:53:11 -0400 Subject: [PATCH 061/258] [Vertex AI] Refactor `HarmCategory` enum (#13686) # Conflicts: # FirebaseVertexAI/CHANGELOG.md --- FirebaseVertexAI/CHANGELOG.md | 5 ++ .../ChatSample/Views/ErrorDetailsView.swift | 3 +- FirebaseVertexAI/Sources/Safety.swift | 63 +++++++++---------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index fc54c727370..7d5fe4575cb 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,3 +1,8 @@ +# 11.4.0 +- [changed] **Breaking Change**: The `HarmCategory` enum is no longer nested + inside the `SafetySetting` struct and the `unspecified` case has been + removed. (#13686) + # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) - [changed] **Breaking Change**: Reverted refactor of `GenerativeModel` and diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index dc5ce8f9561..8e3fbc1fbad 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -16,7 +16,7 @@ import FirebaseVertexAI import MarkdownUI import SwiftUI -extension SafetySetting.HarmCategory: CustomStringConvertible { +extension HarmCategory: CustomStringConvertible { public var description: String { switch self { case .dangerousContent: "Dangerous content" @@ -24,7 +24,6 @@ extension SafetySetting.HarmCategory: CustomStringConvertible { case .hateSpeech: "Hate speech" case .sexuallyExplicit: "Sexually explicit" case .unknown: "Unknown" - case .unspecified: "Unspecified" } } } diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 6ad99bbb5e5..244532b3ac7 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -19,18 +19,21 @@ import Foundation /// responses that exceed a certain threshold. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetyRating: Equatable, Hashable, Sendable { - /// The category describing the potential harm a piece of content may pose. See - /// ``SafetySetting/HarmCategory`` for a list of possible values. - public let category: SafetySetting.HarmCategory - - /// The model-generated probability that a given piece of content falls under the harm category - /// described in ``SafetySetting/HarmCategory``. This does not indicate the severity of harm for a - /// piece of content. See ``HarmProbability`` for a list of possible values. + /// The category describing the potential harm a piece of content may pose. + /// + /// See ``HarmCategory`` for a list of possible values. + public let category: HarmCategory + + /// The model-generated probability that the content falls under the specified harm ``category``. + /// + /// See ``HarmProbability`` for a list of possible values. + /// + /// > Important: This does not indicate the severity of harm for a piece of content. public let probability: HarmProbability /// Initializes a new `SafetyRating` instance with the given category and probability. /// Use this initializer for SwiftUI previews or tests. - public init(category: SafetySetting.HarmCategory, probability: HarmProbability) { + public init(category: HarmCategory, probability: HarmProbability) { self.category = category self.probability = probability } @@ -63,28 +66,6 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// fallback response instead of generated content. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetySetting { - /// A type describing safety attributes, which include harmful categories and topics that can - /// be considered sensitive. - public enum HarmCategory: String, Sendable { - /// Unknown. A new server value that isn't recognized by the SDK. - case unknown = "HARM_CATEGORY_UNKNOWN" - - /// Unspecified by the server. - case unspecified = "HARM_CATEGORY_UNSPECIFIED" - - /// Harassment content. - case harassment = "HARM_CATEGORY_HARASSMENT" - - /// Negative or harmful comments targeting identity and/or protected attributes. - case hateSpeech = "HARM_CATEGORY_HATE_SPEECH" - - /// Contains references to sexual acts or other lewd content. - case sexuallyExplicit = "HARM_CATEGORY_SEXUALLY_EXPLICIT" - - /// Promotes or enables access to harmful goods, services, or activities. - case dangerousContent = "HARM_CATEGORY_DANGEROUS_CONTENT" - } - /// Block at and beyond a specified ``SafetyRating/HarmProbability``. public enum BlockThreshold: String, Sendable { // Content with `.negligible` will be allowed. @@ -118,6 +99,24 @@ public struct SafetySetting { } } +/// Categories describing the potential harm a piece of content may pose. +public enum HarmCategory: String, Sendable { + /// Unknown. A new server value that isn't recognized by the SDK. + case unknown = "HARM_CATEGORY_UNKNOWN" + + /// Harassment content. + case harassment = "HARM_CATEGORY_HARASSMENT" + + /// Negative or harmful comments targeting identity and/or protected attributes. + case hateSpeech = "HARM_CATEGORY_HATE_SPEECH" + + /// Contains references to sexual acts or other lewd content. + case sexuallyExplicit = "HARM_CATEGORY_SEXUALLY_EXPLICIT" + + /// Promotes or enables access to harmful goods, services, or activities. + case dangerousContent = "HARM_CATEGORY_DANGEROUS_CONTENT" +} + // MARK: - Codable Conformances @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) @@ -139,10 +138,10 @@ extension SafetyRating.HarmProbability: Decodable { extension SafetyRating: Decodable {} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetySetting.HarmCategory: Codable { +extension HarmCategory: Codable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedCategory = SafetySetting.HarmCategory(rawValue: value) else { + guard let decodedCategory = HarmCategory(rawValue: value) else { Logging.default .error("[FirebaseVertexAI] Unrecognized HarmCategory with value \"\(value)\".") self = .unknown From aab0282a50cbf52fb7f4e657c44a541242390127 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 14:02:33 -0400 Subject: [PATCH 062/258] [Vertex AI] Rename `BlockThreshold` to `HarmBlockThreshold` (#13696) --- FirebaseVertexAI/CHANGELOG.md | 2 ++ FirebaseVertexAI/Sources/Safety.swift | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 7d5fe4575cb..73fc8db9dfd 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -2,6 +2,8 @@ - [changed] **Breaking Change**: The `HarmCategory` enum is no longer nested inside the `SafetySetting` struct and the `unspecified` case has been removed. (#13686) +- [changed] **Breaking Change**: The `BlockThreshold` enum in `SafetySetting` + has been renamed to `HarmBlockThreshold`. (#13696) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 244532b3ac7..3fa99626198 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -67,7 +67,7 @@ public struct SafetyRating: Equatable, Hashable, Sendable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetySetting { /// Block at and beyond a specified ``SafetyRating/HarmProbability``. - public enum BlockThreshold: String, Sendable { + public enum HarmBlockThreshold: String, Sendable { // Content with `.negligible` will be allowed. case blockLowAndAbove = "BLOCK_LOW_AND_ABOVE" @@ -90,10 +90,10 @@ public struct SafetySetting { public let harmCategory: HarmCategory /// The threshold describing what content should be blocked. - public let threshold: BlockThreshold + public let threshold: HarmBlockThreshold /// Initializes a new safety setting with the given category and threshold. - public init(harmCategory: HarmCategory, threshold: BlockThreshold) { + public init(harmCategory: HarmCategory, threshold: HarmBlockThreshold) { self.harmCategory = harmCategory self.threshold = threshold } @@ -153,7 +153,7 @@ extension HarmCategory: Codable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetySetting.BlockThreshold: Encodable {} +extension SafetySetting.HarmBlockThreshold: Encodable {} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting: Encodable {} From 5fb5a5ba91f1281db647064042643a6cdaafab16 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 15:21:22 -0400 Subject: [PATCH 063/258] [Vertex AI] Remove `unspecified` enum cases from response types (#13699) --- FirebaseVertexAI/CHANGELOG.md | 3 +++ .../Sample/ChatSample/Views/ErrorDetailsView.swift | 1 - FirebaseVertexAI/Sources/GenerateContentResponse.swift | 6 +----- FirebaseVertexAI/Sources/Safety.swift | 3 --- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 73fc8db9dfd..d697c413ad1 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -4,6 +4,9 @@ removed. (#13686) - [changed] **Breaking Change**: The `BlockThreshold` enum in `SafetySetting` has been renamed to `HarmBlockThreshold`. (#13696) +- [changed] **Breaking Change**: The `unspecified` case has been removed from + the `FinishReason`, `PromptFeedback` and `HarmProbability` enums; this + scenario is now handled by the existing `unknown` case. (#13699) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index 8e3fbc1fbad..b8febfe0e40 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -36,7 +36,6 @@ extension SafetyRating.HarmProbability: CustomStringConvertible { case .medium: "Medium" case .negligible: "Negligible" case .unknown: "Unknown" - case .unspecified: "Unspecified" } } } diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index c9f085971ea..d1aeb58bd56 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -138,10 +138,9 @@ public struct Citation: Sendable { /// A value enumerating possible reasons for a model to terminate a content generation request. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public enum FinishReason: String, Sendable { + /// The finish reason is unknown. case unknown = "FINISH_REASON_UNKNOWN" - case unspecified = "FINISH_REASON_UNSPECIFIED" - /// Natural stop point of the model or provided stop sequence. case stop = "STOP" @@ -168,9 +167,6 @@ public struct PromptFeedback: Sendable { /// The block reason is unknown. case unknown = "UNKNOWN" - /// The block reason was not specified in the server response. - case unspecified = "BLOCK_REASON_UNSPECIFIED" - /// The prompt was blocked because it was deemed unsafe. case safety = "SAFETY" diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 3fa99626198..2bea6eda5a9 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -44,9 +44,6 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// Unknown. A new server value that isn't recognized by the SDK. case unknown = "UNKNOWN" - /// The probability was not specified in the server response. - case unspecified = "HARM_PROBABILITY_UNSPECIFIED" - /// The probability is zero or close to zero. For benign content, the probability across all /// categories will be this value. case negligible = "NEGLIGIBLE" From 4d71da87faefadc5d96f1bfd12bbc7a9e044aa96 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 20:08:21 -0400 Subject: [PATCH 064/258] [Vertex AI] Rename `Part` enum case `data` to `inlineData` (#13700) --- FirebaseVertexAI/CHANGELOG.md | 6 ++++-- .../ViewModels/FunctionCallingViewModel.swift | 2 +- FirebaseVertexAI/Sources/Chat.swift | 2 +- FirebaseVertexAI/Sources/ModelContent.swift | 10 +++++----- .../Sources/PartsRepresentable+Image.swift | 8 ++++---- FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 2 +- FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index d697c413ad1..33e85e1ed5f 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -5,8 +5,10 @@ - [changed] **Breaking Change**: The `BlockThreshold` enum in `SafetySetting` has been renamed to `HarmBlockThreshold`. (#13696) - [changed] **Breaking Change**: The `unspecified` case has been removed from - the `FinishReason`, `PromptFeedback` and `HarmProbability` enums; this - scenario is now handled by the existing `unknown` case. (#13699) + the `FinishReason`, `BlockReason` and `HarmProbability` enums; this scenario + is now handled by the existing `unknown` case. (#13699) +- [changed] **Breaking Change**: The `data` case in the `Part` enum has been + renamed to `inlineData`; no functionality changes. (#13700) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index ac2ea5a1fcc..7adcadc123a 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -156,7 +156,7 @@ class FunctionCallingViewModel: ObservableObject { case let .functionCall(functionCall): messages.insert(functionCall.chatMessage(), at: messages.count - 1) functionCalls.append(functionCall) - case .data, .fileData, .functionResponse: + case .inlineData, .fileData, .functionResponse: fatalError("Unsupported response content.") } } diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index ff7a8aa9a09..10df040ab30 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -155,7 +155,7 @@ public class Chat { case let .text(str): combinedText += str - case .data, .fileData, .functionCall, .functionResponse: + case .inlineData, .fileData, .functionCall, .functionResponse: // Don't combine it, just add to the content. If there's any text pending, add that as // a part. if !combinedText.isEmpty { diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index 3262a4eba15..f5699a600fb 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -26,7 +26,7 @@ public struct ModelContent: Equatable, Sendable { case text(String) /// Data with a specified media type. Not all media types may be supported by the AI model. - case data(mimetype: String, Data) + case inlineData(mimetype: String, Data) /// File data stored in Cloud Storage for Firebase, referenced by URI. /// @@ -53,12 +53,12 @@ public struct ModelContent: Equatable, Sendable { /// Convenience function for populating a Part with JPEG data. public static func jpeg(_ data: Data) -> Self { - return .data(mimetype: "image/jpeg", data) + return .inlineData(mimetype: "image/jpeg", data) } /// Convenience function for populating a Part with PNG data. public static func png(_ data: Data) -> Self { - return .data(mimetype: "image/png", data) + return .inlineData(mimetype: "image/png", data) } /// Returns the text contents of this ``Part``, if it contains text. @@ -144,7 +144,7 @@ extension ModelContent.Part: Codable { switch self { case let .text(a0): try container.encode(a0, forKey: .text) - case let .data(mimetype, bytes): + case let .inlineData(mimetype, bytes): var inlineDataContainer = container.nestedContainer( keyedBy: InlineDataKeys.self, forKey: .inlineData @@ -176,7 +176,7 @@ extension ModelContent.Part: Codable { ) let mimetype = try dataContainer.decode(String.self, forKey: .mimeType) let bytes = try dataContainer.decode(Data.self, forKey: .bytes) - self = .data(mimetype: mimetype, bytes) + self = .inlineData(mimetype: mimetype, bytes) } else if values.contains(.functionCall) { self = try .functionCall(values.decode(FunctionCall.self, forKey: .functionCall)) } else if values.contains(.functionResponse) { diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift index 1991503a224..4e1dc2fea5e 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift @@ -57,7 +57,7 @@ public enum ImageConversionError: Error { guard let data = jpegData(compressionQuality: imageCompressionQuality) else { throw ImageConversionError.couldNotConvertToJPEG(.uiImage(self)) } - return [ModelContent.Part.data(mimetype: "image/jpeg", data)] + return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] } } @@ -74,7 +74,7 @@ public enum ImageConversionError: Error { else { throw ImageConversionError.couldNotConvertToJPEG(.nsImage(self)) } - return [ModelContent.Part.data(mimetype: "image/jpeg", data)] + return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] } } #endif @@ -95,7 +95,7 @@ public enum ImageConversionError: Error { kCGImageDestinationLossyCompressionQuality: imageCompressionQuality, ] as CFDictionary) if CGImageDestinationFinalize(imageDestination) { - return [.data(mimetype: "image/jpeg", output as Data)] + return [.inlineData(mimetype: "image/jpeg", output as Data)] } throw ImageConversionError.couldNotConvertToJPEG(.cgImage(self)) } @@ -116,7 +116,7 @@ public enum ImageConversionError: Error { context.jpegRepresentation(of: self, colorSpace: $0, options: [:]) } if let jpegData = jpegData { - return [.data(mimetype: "image/jpeg", jpegData)] + return [.inlineData(mimetype: "image/jpeg", jpegData)] } throw ImageConversionError.couldNotConvertToJPEG(.ciImage(self)) } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index dc76123d028..affa548f002 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1188,7 +1188,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "json" ) - let response = try await model.countTokens(ModelContent.Part.data( + let response = try await model.countTokens(ModelContent.Part.inlineData( mimetype: "image/jpeg", Data() )) diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index c68b69b03ec..1c469867f76 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -123,7 +123,7 @@ final class VertexAIAPITests: XCTestCase { // convert value of type 'String' to expected element type // 'Array.ArrayLiteralElement'. Not sure if there's a way we can get it to // work. - let _ = try ModelContent(parts: [str, ModelContent.Part.data( + let _ = try ModelContent(parts: [str, ModelContent.Part.inlineData( mimetype: "foo", Data() )] as [any ThrowingPartsRepresentable]) From 5fe65b97389c4eb1c9762743cf802b3f6bac36db Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 23 Sep 2024 21:27:12 -0400 Subject: [PATCH 065/258] [Vertex AI] Rename `citationSources` to `citations` (#13702) --- FirebaseVertexAI/CHANGELOG.md | 2 ++ FirebaseVertexAI/Sources/GenerateContentResponse.swift | 8 ++------ FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 10 +++++----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 33e85e1ed5f..6057cf42d90 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -9,6 +9,8 @@ is now handled by the existing `unknown` case. (#13699) - [changed] **Breaking Change**: The `data` case in the `Part` enum has been renamed to `inlineData`; no functionality changes. (#13700) +- [changed] **Breaking Change**: The property `citationSources` of + `CitationMetadata` has been renamed to `citations`. (#13702) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index d1aeb58bd56..31566889a0c 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -113,7 +113,7 @@ public struct CandidateResponse: Sendable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct CitationMetadata: Sendable { /// A list of individual cited sources and the parts of the content to which they apply. - public let citationSources: [Citation] + public let citations: [Citation] } /// A struct describing a source attribution. @@ -290,11 +290,7 @@ extension CandidateResponse: Decodable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension CitationMetadata: Decodable { - enum CodingKeys: String, CodingKey { - case citationSources = "citations" - } -} +extension CitationMetadata: Decodable {} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension Citation: Decodable { diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index affa548f002..5b30a56039c 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -113,20 +113,20 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(candidate.content.parts.count, 1) XCTAssertEqual(response.text, "Some information cited from an external source") let citationMetadata = try XCTUnwrap(candidate.citationMetadata) - XCTAssertEqual(citationMetadata.citationSources.count, 3) - let citationSource1 = try XCTUnwrap(citationMetadata.citationSources[0]) + XCTAssertEqual(citationMetadata.citations.count, 3) + let citationSource1 = try XCTUnwrap(citationMetadata.citations[0]) XCTAssertEqual(citationSource1.uri, "https://www.example.com/some-citation-1") XCTAssertEqual(citationSource1.startIndex, 0) XCTAssertEqual(citationSource1.endIndex, 128) XCTAssertNil(citationSource1.title) XCTAssertNil(citationSource1.license) - let citationSource2 = try XCTUnwrap(citationMetadata.citationSources[1]) + let citationSource2 = try XCTUnwrap(citationMetadata.citations[1]) XCTAssertEqual(citationSource2.title, "some-citation-2") XCTAssertEqual(citationSource2.startIndex, 130) XCTAssertEqual(citationSource2.endIndex, 265) XCTAssertNil(citationSource2.uri) XCTAssertNil(citationSource2.license) - let citationSource3 = try XCTUnwrap(citationMetadata.citationSources[2]) + let citationSource3 = try XCTUnwrap(citationMetadata.citations[2]) XCTAssertEqual(citationSource3.uri, "https://www.example.com/some-citation-3") XCTAssertEqual(citationSource3.startIndex, 272) XCTAssertEqual(citationSource3.endIndex, 431) @@ -947,7 +947,7 @@ final class GenerativeModelTests: XCTestCase { responses.append(content) XCTAssertNotNil(content.text) let candidate = try XCTUnwrap(content.candidates.first) - if let sources = candidate.citationMetadata?.citationSources { + if let sources = candidate.citationMetadata?.citations { citations.append(contentsOf: sources) } } From 77ac61ca4dfdd3c59a5de7bcdf16fb05dd6226f4 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Sep 2024 09:53:41 -0700 Subject: [PATCH 066/258] Changelog updates for 11.3.0 (#13711) --- FirebaseAuth/CHANGELOG.md | 2 +- Firestore/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 57a5987609d..fd8371d582d 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.3.0 - [Fixed] Restore Firebase 10 behavior by querying with the `kSecAttrSynchronizable` key when auth state is set to be shared across devices. (#13584) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 0b72c4d470b..092e66027f8 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.3.0 - [changed] Improve efficiency of memory persistence when processing a large number of writes. (#13572) # 11.2.0 From e1d297258184202ceff39b4409d33da8f34a685d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:41:19 -0400 Subject: [PATCH 067/258] [Infra] Fix warning in Auth's AuthWebViewController.swift (#13634) --- .../Swift/Utilities/AuthWebViewController.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift index f476c0e06f5..c247eead731 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift @@ -96,17 +96,14 @@ // MARK: - WKNavigationDelegate - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction) async + -> WKNavigationActionPolicy { let canHandleURL = delegate?.webViewController( self, canHandle: navigationAction.request.url ?? url ) ?? false - if canHandleURL { - decisionHandler(.allow) - } else { - decisionHandler(.cancel) - } + return canHandleURL ? .allow : .cancel } func webView(_ webView: WKWebView, From 89ea60229e3f05114870c9b26c6d2b94f234f506 Mon Sep 17 00:00:00 2001 From: tsunghung <78230356+tsunghung@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:51:12 -0700 Subject: [PATCH 068/258] Analytics 11.3.0 (#13709) --- FirebaseAnalytics.podspec | 2 +- GoogleAppMeasurement.podspec | 2 +- Package.swift | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 840bbf1fb04..8e33b6ff5a5 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/ad70ce75cb9d5945/FirebaseAnalytics-11.1.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/7c5cc65f2b583ee1/FirebaseAnalytics-11.3.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index 5d8e9c7f469..f04df1e7e4e 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/f60e4b9034ff4db0/GoogleAppMeasurement-11.1.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/d86efe4f4164be5a/GoogleAppMeasurement-11.3.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/Package.swift b/Package.swift index 26d896416d1..ce9d632e330 100644 --- a/Package.swift +++ b/Package.swift @@ -303,8 +303,8 @@ let package = Package( ), .binaryTarget( name: "FirebaseAnalytics", - url: "https://dl.google.com/firebase/ios/swiftpm/11.1.0/FirebaseAnalytics.zip", - checksum: "7477b92093cc001e713a3442bcf8725b83764294452c04f36164c8706d224510" + url: "https://dl.google.com/firebase/ios/swiftpm/11.3.0/FirebaseAnalytics.zip", + checksum: "1d4c06ccb6ffbf44e80934ab9190d2473421f94e7116c95c13cb2744c3873852" ), .testTarget( name: "AnalyticsSwiftUnit", @@ -1342,7 +1342,7 @@ func googleAppMeasurementDependency() -> Package.Dependency { return .package(url: appMeasurementURL, branch: "main") } - return .package(url: appMeasurementURL, exact: "11.1.0") + return .package(url: appMeasurementURL, exact: "11.3.0") } func abseilDependency() -> Package.Dependency { From 56b1bda7031801b1950e48d74b51b3f8f6926691 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Sep 2024 10:51:54 -0700 Subject: [PATCH 069/258] Ensure test of recent TagManager (#13710) --- SymbolCollisionTest/Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SymbolCollisionTest/Podfile b/SymbolCollisionTest/Podfile index 7a7f0122b6a..2d92ae6638e 100644 --- a/SymbolCollisionTest/Podfile +++ b/SymbolCollisionTest/Podfile @@ -49,7 +49,7 @@ target 'SymbolCollisionTest' do pod 'GooglePlaces', '> 8' pod 'GoogleSignIn', '> 6' pod 'GTMAppAuth' - pod 'GoogleTagManager' + pod 'GoogleTagManager', '>= 7.4.6' pod 'GoogleToolboxForMac' pod 'GoogleUtilities' # pod 'GTMHTTPFetcher' - conflicts with GTMSessionFetcher From 70558c913016bf468a54e507723b9cb2e8e802bc Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Sep 2024 11:21:23 -0700 Subject: [PATCH 070/258] RC: fix internal API nullability issue caught by analyzer (#13712) --- FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h | 3 ++- FirebaseRemoteConfig/Sources/RCNConfigFetch.m | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h index dbef87d206a..58113944c18 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h @@ -53,7 +53,8 @@ typedef void (^RCNConfigFetchCompletion)(FIRRemoteConfigFetchStatus status, /// @param expirationDuration Expiration duration, in seconds. /// @param completionHandler Callback handler. - (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration - completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler; + completionHandler: + (_Nullable FIRRemoteConfigFetchCompletion)completionHandler; /// Fetches config data immediately, keyed by namespace. Completion block will be called on the main /// queue. diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m index dbc4b9bec56..3fad2694b02 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m @@ -131,7 +131,8 @@ - (void)dealloc { #pragma mark - Fetch Config API - (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration - completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler { + completionHandler: + (_Nullable FIRRemoteConfigFetchCompletion)completionHandler { // Note: We expect the googleAppID to always be available. BOOL hasDeviceContextChanged = FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID); From ce468fadee5ec2591c89e3bed9667c217bd8b995 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:22:42 -0400 Subject: [PATCH 071/258] [Auth] nil out SafariViewController when presentation finishes (#13715) --- FirebaseAuth/CHANGELOG.md | 2 ++ FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift | 1 + 2 files changed, 3 insertions(+) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index fd8371d582d..71cb75a1d05 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -5,6 +5,8 @@ - [Fixed] Prevent a bad memory access crash by using non-ObjC, native Swift types in the SDK's networking layer, and moving synchronous work off of the shared Swift concurrency queue. (#13650) +- [Fixed] Restore Firebase 10 behavior by forwarding errors from interrupted + reCAPTCHA or OIDC login flows. (#13645) # 11.2.0 - [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift index a9a62bf88b2..c59ced1cfe1 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift @@ -89,6 +89,7 @@ func safariViewControllerDidFinish(_ controller: SFSafariViewController) { kAuthGlobalWorkQueue.async { if controller == self.safariViewController { + self.safariViewController = nil // TODO: Ensure that the SFSafariViewController is actually removed from the screen // before invoking finishPresentation self.finishPresentation(withURL: nil, From 335f3d27b82af895a04ab3f2b9a519ed537fb4cc Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:34:24 -0400 Subject: [PATCH 072/258] [Auth] Match Firebase 10 implementation in WKNavigationDelegate conformance (#13714) --- .../Sources/Swift/Utilities/AuthWebViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift index c247eead731..ee954b0029f 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift @@ -99,11 +99,11 @@ func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { - let canHandleURL = delegate?.webViewController( + _ = delegate?.webViewController( self, canHandle: navigationAction.request.url ?? url - ) ?? false - return canHandleURL ? .allow : .cancel + ) + return .allow } func webView(_ webView: WKWebView, From 3d61c54be01913f2ce895befefbbb55468e355c3 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:46:43 -0400 Subject: [PATCH 073/258] [Infra] Switch back to building Auth with `-warnings-as-errors` (#13713) --- FirebaseAuth.podspec | 4 +--- FirebaseAuth/Tests/Unit/SwiftAPI.swift | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 7c35e277736..5bb6a6b1eec 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -48,13 +48,11 @@ supports email and password accounts, as well as several 3rd party authenticatio 'FirebaseAuth/README.md', 'FirebaseAuth/CHANGELOG.md' ] - # TODO(#13704) Restore warnings-as-errors checking. - # 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI -warnings-as-errors' : ''}" s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', # The second path is to find FirebaseAuth-Swift.h from a pod gen project 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}" "${OBJECT_FILE_DIR_normal}/${NATIVE_ARCH_ACTUAL}"', - 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI' : ''}" + 'OTHER_SWIFT_FLAGS' => "$(inherited) #{ENV.key?('FIREBASE_CI') ? '-D FIREBASE_CI -warnings-as-errors' : ''}" } s.framework = 'Security' s.ios.framework = 'SafariServices' diff --git a/FirebaseAuth/Tests/Unit/SwiftAPI.swift b/FirebaseAuth/Tests/Unit/SwiftAPI.swift index c508199088f..481d5bd92c8 100644 --- a/FirebaseAuth/Tests/Unit/SwiftAPI.swift +++ b/FirebaseAuth/Tests/Unit/SwiftAPI.swift @@ -365,7 +365,7 @@ class AuthAPI_hOnlyTests: XCTestCase { @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) func FIRFedederatedAuthProvider_hAsync() async throws { let obj = FederatedAuthImplementation() - try await _ = obj.credential(with: nil) + _ = try await obj.credential(with: nil) } } #endif From 96ca3cd67deadcf565ed10fc4102458b5370d9ae Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Sep 2024 15:29:57 -0700 Subject: [PATCH 074/258] CI updates for visionOS and Xcode 16: part 2 (#13708) --- .github/workflows/abtesting.yml | 2 + .github/workflows/appdistribution.yml | 2 + .github/workflows/auth.yml | 85 ++++++++++++------------ .github/workflows/core.yml | 49 +++++++++----- .github/workflows/core_extension.yml | 8 +-- .github/workflows/core_internal.yml | 48 ++++++++----- .github/workflows/crashlytics.yml | 52 +++++++++------ .github/workflows/database.yml | 54 +++++++++------ .github/workflows/dynamiclinks.yml | 46 ++++++++----- .github/workflows/firebase_app_check.yml | 47 ++++++++----- .github/workflows/firestore.yml | 41 ++++++++---- .github/workflows/functions.yml | 60 ++++++++++------- .github/workflows/inappmessaging.yml | 46 ++++++++----- .github/workflows/installations.yml | 49 +++++++++----- .github/workflows/messaging.yml | 53 ++++++++++----- .github/workflows/mlmodeldownloader.yml | 49 +++++++++----- .github/workflows/performance.yml | 44 ++++++++---- .github/workflows/remoteconfig.yml | 54 +++++++++------ .github/workflows/sessions.yml | 47 ++++++++----- .github/workflows/shared-swift.yml | 49 +++++++++----- .github/workflows/spm.yml | 64 ++++++++++++------ .github/workflows/storage.yml | 55 +++++++++------ 22 files changed, 642 insertions(+), 362 deletions(-) diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index 00cb6e22b6b..e16cb3ddb82 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -43,6 +43,8 @@ jobs: command: scripts/pod_lib_lint.rb FirebaseABTesting.podspec --platforms=${{ matrix.target }} spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 runs-on: macos-14 outputs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index 1c34f2e97eb..8c80ed4116c 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -38,6 +38,8 @@ jobs: --platforms=${{ matrix.target }} spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 runs-on: macos-14 outputs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index 6e6058dc0f3..e9169e19701 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -78,50 +78,10 @@ jobs: retry_wait_seconds: 120 command: scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} ${{ matrix.tests }} --allow-warnings - integration-tests: - # Don't run on private repo unless it is a PR. - if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' - strategy: - matrix: - scheme: [ObjCApiTests, SwiftApiTests, AuthenticationExampleUITests] - + spm-package-resolved: env: - plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 runs-on: macos-14 - steps: - - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 - with: - cache_key: integration-tests${{ matrix.os }} - - name: Install Secrets - run: | - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthCredentials.h.gpg \ - FirebaseAuth/Tests/SampleSwift/ObjCApiTests/AuthCredentials.h "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/SwiftApplication.plist.gpg \ - FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/AuthCredentials.h.gpg \ - FirebaseAuth/Tests/SampleSwift/AuthCredentials.h "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/GoogleService-Info.plist.gpg \ - FirebaseAuth/Tests/SampleSwift/GoogleService-Info.plist "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/GoogleService-Info_multi.plist.gpg \ - FirebaseAuth/Tests/SampleSwift/GoogleService-Info_multi.plist "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Sample.entitlements.gpg \ - FirebaseAuth/Tests/SampleSwift/Sample.entitlements "$plist_secret" - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Credentials.swift.gpg \ - FirebaseAuth/Tests/SampleSwift/SwiftApiTests/Credentials.swift "$plist_secret" - - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - - uses: nick-fields/retry@v3 - with: - timeout_minutes: 120 - max_attempts: 3 - retry_on: error - retry_wait_seconds: 120 - command: ([ -z $plist_secret ] || scripts/build.sh Auth iOS ${{ matrix.scheme }}) - - spm-package-resolved: - runs-on: macos-14 outputs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} steps: @@ -169,6 +129,49 @@ jobs: retry_wait_seconds: 120 command: scripts/third_party/travis/retry.sh ./scripts/build.sh AuthUnit ${{ matrix.target }} + integration-tests: + # Don't run on private repo unless it is a PR. + if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] + strategy: + matrix: + scheme: [ObjCApiTests, SwiftApiTests, AuthenticationExampleUITests] + env: + plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} + - name: Install Secrets + run: | + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthCredentials.h.gpg \ + FirebaseAuth/Tests/SampleSwift/ObjCApiTests/AuthCredentials.h "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/SwiftApplication.plist.gpg \ + FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/AuthCredentials.h.gpg \ + FirebaseAuth/Tests/SampleSwift/AuthCredentials.h "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/GoogleService-Info.plist.gpg \ + FirebaseAuth/Tests/SampleSwift/GoogleService-Info.plist "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/GoogleService-Info_multi.plist.gpg \ + FirebaseAuth/Tests/SampleSwift/GoogleService-Info_multi.plist "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Sample.entitlements.gpg \ + FirebaseAuth/Tests/SampleSwift/Sample.entitlements "$plist_secret" + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Credentials.swift.gpg \ + FirebaseAuth/Tests/SampleSwift/SwiftApiTests/Credentials.swift "$plist_secret" + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: ([ -z $plist_secret ] || scripts/build.sh Auth iOS ${{ matrix.scheme }}) + catalyst: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index d4913b5a428..d8f420f2af3 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -22,12 +22,8 @@ jobs: matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -39,28 +35,45 @@ jobs: - name: Build and test run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseCore.podspec --platforms=${{ matrix.target }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/core_extension.yml b/.github/workflows/core_extension.yml index 1afaa4bb3de..b728bbc3bff 100644 --- a/.github/workflows/core_extension.yml +++ b/.github/workflows/core_extension.yml @@ -20,12 +20,8 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/core_internal.yml b/.github/workflows/core_internal.yml index 916e4d2a6b0..c1b38b07422 100644 --- a/.github/workflows/core_internal.yml +++ b/.github/workflows/core_internal.yml @@ -18,12 +18,8 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -35,25 +31,45 @@ jobs: - name: Build and test run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseCoreInternal.podspec --platforms=${{ matrix.target }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: Xcode diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index 124e2121b8f..ef4c0ba650c 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -25,18 +25,12 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos --skip-tests] - os: [macos-14, macos-13] + os: [macos-14] flags: [ '--use-modular-headers', '' ] - include: - - os: macos-14 - xcode: Xcode_15.3 - tests: --skip-tests - - os: macos-13 - xcode: Xcode_15.2 - tests: + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -53,28 +47,46 @@ jobs: retry_wait_seconds: 120 command: scripts/pod_lib_lint.rb FirebaseCrashlytics.podspec --platforms=${{ matrix.target }} ${{ matrix.tests }} ${{ matrix.flags }} + + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 4ff6742cfc2..17b1db109e6 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -25,16 +25,9 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: matrix: - # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - tests: --skip-tests - - os: macos-13 - xcode: Xcode_15.2 - tests: --test-specs=unit + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -44,7 +37,7 @@ jobs: - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Build and test - run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseDatabase.podspec ${{ matrix.tests }} --platforms=${{ matrix.target }} + run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseDatabase.podspec --test-specs=unit --platforms=${{ matrix.target }} integration: # Don't run on private repo unless it is a PR. @@ -64,28 +57,45 @@ jobs: # Only iOS to mitigate flakes. run: scripts/third_party/travis/retry.sh scripts/build.sh Database iOS integration + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/dynamiclinks.yml b/.github/workflows/dynamiclinks.yml index 589825221e8..4fcd9624616 100644 --- a/.github/workflows/dynamiclinks.yml +++ b/.github/workflows/dynamiclinks.yml @@ -22,12 +22,8 @@ jobs: strategy: matrix: - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -39,24 +35,44 @@ jobs: - name: FirebaseDynamicLinks run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseDynamicLinks.podspec --allow-warnings + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/firebase_app_check.yml b/.github/workflows/firebase_app_check.yml index a4b03fe4a64..d001709535a 100644 --- a/.github/workflows/firebase_app_check.yml +++ b/.github/workflows/firebase_app_check.yml @@ -21,14 +21,9 @@ jobs: strategy: matrix: podspec: [FirebaseAppCheckInterop.podspec, FirebaseAppCheck.podspec] - # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -96,25 +91,45 @@ jobs: # TODO: Remove --allow-warnings when stabilized. run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAppCheck.podspec --platforms=ios ${{ matrix.flags }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 1c684ee5c50..4c98182f859 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -352,7 +352,7 @@ jobs: if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || (github.event_name == 'pull_request') - runs-on: macos-13 + runs-on: macos-14 strategy: matrix: podspec: [ @@ -406,7 +406,7 @@ jobs: platforms: 'ios' include: - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 - os: macos-13 xcode: Xcode_15.2 runs-on: ${{ matrix.os }} @@ -429,8 +429,32 @@ jobs: --allow-warnings \ --no-analyze + spm-package-resolved: + runs-on: macos-14 + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + FIREBASE_SOURCE_FIRESTORE: 1 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm-source: - needs: check + needs: [check, spm-package-resolved] # Either a scheduled run from public repo, or a pull request with firestore changes. if: | (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || @@ -438,15 +462,8 @@ jobs: strategy: matrix: target: [iOS, tvOS, macOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} env: FIREBASE_SOURCE_FIRESTORE: 1 diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index 32e9dffcb01..d4207217be3 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -30,12 +30,8 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -52,24 +48,47 @@ jobs: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseFunctions.podspec \ --test-specs=unit --platforms=${{ matrix.target }} + + spm-package-resolved: + runs-on: macos-14 + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm-integration: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: os: [macos-14] - include: - - os: macos-14 - xcode: Xcode_15.3 + xcode: [Xcode_15.4] runs-on: ${{ matrix.os }} env: FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: Integration Test Server @@ -86,24 +105,19 @@ jobs: spm-unit: # Don't run on private repo. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/inappmessaging.yml b/.github/workflows/inappmessaging.yml index 76eea952f23..c766fd89f31 100644 --- a/.github/workflows/inappmessaging.yml +++ b/.github/workflows/inappmessaging.yml @@ -24,12 +24,8 @@ jobs: strategy: matrix: podspec: [FirebaseInAppMessaging.podspec] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -66,24 +62,44 @@ jobs: - name: Build and test run: scripts/third_party/travis/retry.sh scripts/build.sh InAppMessaging ${{ matrix.platform }} xcodebuild + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index 94e7c7831e0..5fe5174fa9a 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -25,13 +25,13 @@ jobs: matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 test-specs: unit,integration - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 test-specs: unit runs-on: ${{ matrix.os }} steps: @@ -57,29 +57,44 @@ jobs: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseInstallations.podspec \ --platforms=${{ matrix.target }} --test-specs=--platforms=${{ matrix.test-specs }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 - target: [iOS, tvOS, macOS, watchOS, catalyst] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + target: [iOS, tvOS, macOS, catalyst, watchOS] + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index 151166c5715..acc9d6d8c2c 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -29,7 +29,7 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 @@ -59,13 +59,13 @@ jobs: matrix: podspec: [FirebaseMessagingInterop.podspec, FirebaseMessaging.podspec] target: [ios, tvos, macos --skip-tests, watchos --skip-tests] # skipping tests on mac because of keychain access - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 tests: --test-specs=unit - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -78,34 +78,51 @@ jobs: - name: Build and test run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec }} ${{ matrix.tests }} --platforms=${{ matrix.target }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - target: [iOS, watchOS, tvOS, macOS, catalyst] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + target: [iOS spm, tvOS spmbuildonly, macOS spmbuildonly, catalyst spmbuildonly, watchOS spmbuildonly] + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Unit Tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh MessagingUnit ${{ matrix.target }} spm + run: scripts/third_party/travis/retry.sh ./scripts/build.sh MessagingUnit ${{ matrix.target }} catalyst: # Don't run on private repo unless it is a PR. diff --git a/.github/workflows/mlmodeldownloader.yml b/.github/workflows/mlmodeldownloader.yml index a0d2cd992cc..feae6c76383 100644 --- a/.github/workflows/mlmodeldownloader.yml +++ b/.github/workflows/mlmodeldownloader.yml @@ -22,12 +22,8 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -70,27 +66,44 @@ jobs: - name: PodLibLint MLModelDownloader Cron run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseMLModelDownloader.podspec --platforms=${{ matrix.target }} --use-static-frameworks + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 29aa3176bb6..f58bb912a18 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -55,12 +55,8 @@ jobs: strategy: matrix: target: [ios, tvos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -123,24 +119,44 @@ jobs: testapp_dir: quickstart-ios/build-for-testing test_type: "xctest" + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index fe576403f5d..200c19801cf 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -58,20 +58,19 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: - max-parallel: 1 matrix: # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 target: [ios, tvos, macos --skip-tests, watchos] podspec: [FirebaseRemoteConfig.podspec] - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 # TODO(#13078): Fix testing infra to enforce warnings again. tests: --allow-warnings # Flaky tests on CI - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -86,38 +85,53 @@ jobs: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} \ ${{ matrix.tests }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - test: spmbuildonly - - os: macos-14 - xcode: Xcode_15.3 - test: spmbuildonly - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS - test: spm + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: Unit Tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigUnit ${{ matrix.target }} ${{ matrix.test }} + run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigUnit ${{ matrix.target }} spm - name: Fake Console tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigFakeConsole ${{ matrix.target }} ${{ matrix.test }} + run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigFakeConsole ${{ matrix.target }} spm catalyst: # Don't run on private repo unless it is a PR. diff --git a/.github/workflows/sessions.yml b/.github/workflows/sessions.yml index a80387bab95..c4edb685e1f 100644 --- a/.github/workflows/sessions.yml +++ b/.github/workflows/sessions.yml @@ -24,14 +24,14 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 tests: # Flaky tests on CI - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -49,28 +49,45 @@ jobs: retry_wait_seconds: 120 command: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseSessions.podspec --platforms=${{ matrix.target }} ${{ matrix.tests }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/shared-swift.yml b/.github/workflows/shared-swift.yml index 4b6ea4a12e1..40f1d46fa19 100644 --- a/.github/workflows/shared-swift.yml +++ b/.github/workflows/shared-swift.yml @@ -24,12 +24,8 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] - include: - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -41,28 +37,45 @@ jobs: - name: Build and test run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseSharedSwift.podspec --platforms=${{ matrix.target }} + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 5557f1b2865..fec6410ca5d 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -22,27 +22,50 @@ concurrency: cancel-in-progress: true jobs: + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + swift-build-run: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: - os: [macos-14, macos-13] + os: [macos-14] include: - # The integration tests are slow and flaky on Xcode 15, so just build. - - os: macos-13 - xcode: Xcode_15.2 - test: spmbuildonly + - os: macos-14 + xcode: Xcode_16 + test: spm - os: macos-14 xcode: Xcode_15.3 test: spm runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Clone mock responses for Vertex AI unit tests run: scripts/update_vertexai_responses.sh - name: Xcode @@ -63,20 +86,22 @@ jobs: iOS-Device: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: matrix: - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: device${{ matrix.os }}${{ matrix.xcode }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Clone mock responses for Vertex AI unit tests run: scripts/update_vertexai_responses.sh - name: Xcode @@ -89,25 +114,26 @@ jobs: platforms: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' - + needs: [spm-package-resolved] strategy: matrix: # Full set of Firebase-Package tests only run on iOS. Run subset on other platforms. # visionOS isn't buildable from here (even with Firestore source) because the test # targets need Analytics. target: [tvOS, macOS, catalyst] - os: [macos-13, macos-14] + os: [macos-14] include: - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 - os: macos-14 xcode: Xcode_15.3 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: platforms${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index 1c1bb861e30..b93f4b9141d 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -25,7 +25,7 @@ jobs: language: [Swift, ObjC] include: - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_15.4 env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} runs-on: ${{ matrix.os }} @@ -59,28 +59,45 @@ jobs: retry_wait_seconds: 120 command: ([ -z $plist_secret ] || scripts/build.sh Storage${{ matrix.language }} all) + spm-package-resolved: + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + spm: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + needs: [spm-package-resolved] strategy: - max-parallel: 1 matrix: target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-13, macos-14] - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.3 - - os: macos-14 - xcode: Xcode_15.3 - target: visionOS + os: [macos-14] + xcode: [Xcode_15.2, Xcode_16] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 + - uses: actions/cache/restore@v4 with: - cache_key: spm-cron${{ matrix.os }}-${{ matrix.xcode }}-${{ matrix.target }} + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcodes run: ls -l /Applications/Xcode* - name: Xcode @@ -156,13 +173,13 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 tests: --skip-tests - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 tests: --test-specs=unit runs-on: ${{ matrix.os }} steps: @@ -185,12 +202,12 @@ jobs: strategy: matrix: target: [ios, tvos, macos, watchos] - os: [macos-14, macos-13] + os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 - - os: macos-13 - xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_16 runs-on: ${{ matrix.os }} needs: pod-lib-lint steps: From 5822291dff54d236adfec5fc541bbd6031cdf2ca Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 24 Sep 2024 20:36:50 -0400 Subject: [PATCH 075/258] [Vertex AI] Refactor `Schema` declarations (#13616) --- FirebaseVertexAI/CHANGELOG.md | 7 + .../ViewModels/FunctionCallingViewModel.swift | 19 +- .../Sources/FunctionCalling.swift | 14 +- FirebaseVertexAI/Sources/Schema.swift | 324 +++++++++++++++++- .../Tests/Unit/GenerationConfigTests.swift | 26 +- 5 files changed, 345 insertions(+), 45 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 6057cf42d90..9950b1c0190 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -11,6 +11,13 @@ renamed to `inlineData`; no functionality changes. (#13700) - [changed] **Breaking Change**: The property `citationSources` of `CitationMetadata` has been renamed to `citations`. (#13702) +- [changed] **Breaking Change**: The constructor for `Schema` is now deprecated; + use the new static methods `Schema.string(...)`, `Schema.object(...)`, etc., + instead. (#13616) +- [changed] **Breaking Change**: The constructor for `FunctionDeclaration` now + accepts an array of *optional* parameters instead of a list of *required* + parameters; if a parameter is not listed as optional it is assumed to be + required. (#13616) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 7adcadc123a..110cab9ce27 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -45,20 +45,15 @@ class FunctionCallingViewModel: ObservableObject { name: "get_exchange_rate", description: "Get the exchange rate for currencies between countries", parameters: [ - "currency_from": Schema( - type: .string, - format: "enum", - description: "The currency to convert from in ISO 4217 format", - enumValues: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"] + "currency_from": .enumeration( + values: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"], + description: "The currency to convert from in ISO 4217 format" ), - "currency_to": Schema( - type: .string, - format: "enum", - description: "The currency to convert to in ISO 4217 format", - enumValues: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"] + "currency_to": .enumeration( + values: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"], + description: "The currency to convert to in ISO 4217 format" ), - ], - requiredParameters: ["currency_from", "currency_to"] + ] ), ])] ) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 3c88279c6df..4641a6f400f 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -43,17 +43,15 @@ public struct FunctionDeclaration { /// - name: The name of the function; must be a-z, A-Z, 0-9, or contain underscores and dashes, /// with a maximum length of 63. /// - description: A brief description of the function. - /// - parameters: Describes the parameters to this function; the keys are parameter names and - /// the values are ``Schema`` objects describing them. - /// - requiredParameters: A list of required parameters by name. - public init(name: String, description: String, parameters: [String: Schema]?, - requiredParameters: [String]? = nil) { + /// - parameters: Describes the parameters to this function. + public init(name: String, description: String, parameters: [String: Schema], + optionalParameters: [String] = []) { self.name = name self.description = description - self.parameters = Schema( - type: .object, + self.parameters = Schema.object( properties: parameters, - requiredProperties: requiredParameters + optionalProperties: optionalParameters, + nullable: false ) } } diff --git a/FirebaseVertexAI/Sources/Schema.swift b/FirebaseVertexAI/Sources/Schema.swift index 6ffadfd9025..f24a4d203b3 100644 --- a/FirebaseVertexAI/Sources/Schema.swift +++ b/FirebaseVertexAI/Sources/Schema.swift @@ -19,29 +19,45 @@ import Foundation /// These types can be objects, but also primitives and arrays. Represents a select subset of an /// [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema). public class Schema { + /// Modifiers describing the expected format of a string `Schema`. + public enum StringFormat { + /// A custom string format. + case custom(String) + } + + /// Modifiers describing the expected format of an integer `Schema`. + public enum IntegerFormat { + /// A 32-bit signed integer. + case int32 + /// A 64-bit signed integer. + case int64 + /// A custom integer format. + case custom(String) + } + /// The data type. - let type: DataType + public let type: DataType /// The format of the data. - let format: String? + public let format: String? /// A brief description of the parameter. - let description: String? + public let description: String? /// Indicates if the value may be null. - let nullable: Bool? + public let nullable: Bool? /// Possible values of the element of type ``DataType/string`` with "enum" format. - let enumValues: [String]? + public let enumValues: [String]? /// Schema of the elements of type ``DataType/array``. - let items: Schema? + public let items: Schema? /// Properties of type ``DataType/object``. - let properties: [String: Schema]? + public let properties: [String: Schema]? /// Required properties of type ``DataType/object``. - let requiredProperties: [String]? + public let requiredProperties: [String]? /// Constructs a new `Schema`. /// @@ -59,11 +75,29 @@ public class Schema { /// - items: Schema of the elements of type ``DataType/array``. /// - properties: Properties of type ``DataType/object``. /// - requiredProperties: Required properties of type ``DataType/object``. - public init(type: DataType, format: String? = nil, description: String? = nil, - nullable: Bool? = nil, - enumValues: [String]? = nil, items: Schema? = nil, - properties: [String: Schema]? = nil, - requiredProperties: [String]? = nil) { + @available(*, deprecated, message: """ + Use static methods `string(description:format:nullable:)`, `number(description:format:nullable:)`, + etc., instead. + """) + public convenience init(type: DataType, format: String? = nil, description: String? = nil, + nullable: Bool? = nil, enumValues: [String]? = nil, items: Schema? = nil, + properties: [String: Schema]? = nil, + requiredProperties: [String]? = nil) { + self.init( + type: type, + format: format, + description: description, + nullable: nullable ?? false, + enumValues: enumValues, + items: items, + properties: properties, + requiredProperties: requiredProperties + ) + } + + required init(type: DataType, format: String? = nil, description: String? = nil, + nullable: Bool = false, enumValues: [String]? = nil, items: Schema? = nil, + properties: [String: Schema]? = nil, requiredProperties: [String]? = nil) { self.type = type self.format = format self.description = description @@ -73,6 +107,228 @@ public class Schema { self.properties = properties self.requiredProperties = requiredProperties } + + /// Returns a `Schema` representing a string value. + /// + /// This schema instructs the model to produce data of type ``DataType/string``, which is suitable + /// for decoding into a Swift `String` (or `String?`, if `nullable` is set to `true`). + /// + /// > Tip: If a specific set of string values should be generated by the model (for example, + /// > "north", "south", "east", or "west"), use ``enumeration(values:description:nullable:)`` + /// > instead to constrain the generated values. + /// + /// - Parameters: + /// - description: An optional description of what the string should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it *may* generate `null` instead of a + /// string; defaults to `false`, enforcing that a string value is generated. + /// - format: An optional modifier describing the expected format of the string. Currently no + /// formats are officially supported for strings but custom values may be specified using + /// ``StringFormat/custom(_:)``, for example `.custom("email")` or `.custom("byte")`; these + /// provide additional hints for how the model should respond but are not guaranteed to be + /// adhered to. + public static func string(description: String? = nil, nullable: Bool = false, + format: StringFormat? = nil) -> Schema { + return self.init( + type: .string, + format: format?.rawValue, + description: description, + nullable: nullable + ) + } + + /// Returns a `Schema` representing an enumeration of string values. + /// + /// This schema instructs the model to produce data of type ``DataType/string`` with the + /// `format` `"enum"`. This data is suitable for decoding into a Swift `String` (or `String?`, + /// if `nullable` is set to `true`), or an `enum` with strings as raw values. + /// + /// **Example:** + /// The values `["north", "south", "east", "west"]` for an enumation of directions. + /// ``` + /// enum Direction: String, Decodable { + /// case north, south, east, west + /// } + /// ``` + /// + /// - Parameters: + /// - values: The list of string values that may be generated by the model. + /// - description: An optional description of what the `values` contain or represent; may use + /// Markdown format. + /// - nullable: If `true`, instructs the model that it *may* generate `null` instead of one of + /// the strings specified in `values`; defaults to `false`, enforcing that one of the string + /// values is generated. + public static func enumeration(values: [String], description: String? = nil, + nullable: Bool = false) -> Schema { + return self.init( + type: .string, + format: "enum", + description: description, + nullable: nullable, + enumValues: values + ) + } + + /// Returns a `Schema` representing a single-precision floating-point number. + /// + /// This schema instructs the model to produce data of type ``DataType/number`` with the + /// `format` `"float"`, which is suitable for decoding into a Swift `Float` (or `Float?`, if + /// `nullable` is set to `true`). + /// + /// > Important: This `Schema` provides a hint to the model that it should generate a + /// > single-precision floating-point number, a `float`, but only guarantees that the value will + /// > be a number. + /// + /// - Parameters: + /// - description: An optional description of what the number should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may generate `null` instead of a number; + /// defaults to `false`, enforcing that a number is generated. + public static func float(description: String? = nil, nullable: Bool = false) -> Schema { + return self.init( + type: .number, + format: "float", + description: description, + nullable: nullable + ) + } + + /// Returns a `Schema` representing a double-precision floating-point number. + /// + /// This schema instructs the model to produce data of type ``DataType/number`` with the + /// `format` `"double"`, which is suitable for decoding into a Swift `Double` (or `Double?`, if + /// `nullable` is set to `true`). + /// + /// > Important: This `Schema` provides a hint to the model that it should generate a + /// > double-precision floating-point number, a `double`, but only guarantees that the value will + /// > be a number. + /// + /// - Parameters: + /// - description: An optional description of what the number should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may return `null` instead of a number; + /// defaults to `false`, enforcing that a number is returned. + public static func double(description: String? = nil, nullable: Bool = false) -> Schema { + return self.init( + type: .number, + format: "double", + description: description, + nullable: nullable + ) + } + + /// Returns a `Schema` representing an integer value. + /// + /// This schema instructs the model to produce data of type ``DataType/integer``, which is + /// suitable for decoding into a Swift `Int` (or `Int?`, if `nullable` is set to `true`) or other + /// integer types (such as `Int32`) based on the expected size of values being generated. + /// + /// > Important: If a `format` of ``IntegerFormat/int32`` or ``IntegerFormat/int64`` is + /// > specified, this provides a hint to the model that it should generate 32-bit or 64-bit + /// > integers but this `Schema` only guarantees that the value will be an integer. Therefore, it + /// > is *possible* that decoding into an `Int32` could overflow even if a `format` of + /// > ``IntegerFormat/int32`` is specified. + /// + /// - Parameters: + /// - description: An optional description of what the integer should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may return `null` instead of an integer; + /// defaults to `false`, enforcing that an integer is returned. + /// - format: An optional modifier describing the expected format of the integer. Currently the + /// formats ``IntegerFormat/int32`` and ``IntegerFormat/int64`` are supported; custom values + /// may be specified using ``IntegerFormat/custom(_:)`` but may be ignored by the model. + public static func integer(description: String? = nil, nullable: Bool = false, + format: IntegerFormat? = nil) -> Schema { + return self.init( + type: .integer, + format: format?.rawValue, + description: description, + nullable: nullable + ) + } + + /// Returns a `Schema` representing a boolean value. + /// + /// This schema instructs the model to produce data of type ``DataType/boolean``, which is + /// suitable for decoding into a Swift `Bool` (or `Bool?`, if `nullable` is set to `true`). + /// + /// - Parameters: + /// - description: An optional description of what the boolean should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may return `null` instead of a boolean; + /// defaults to `false`, enforcing that a boolean is returned. + public static func boolean(description: String? = nil, nullable: Bool = false) -> Schema { + return self.init(type: .boolean, description: description, nullable: nullable) + } + + /// Returns a `Schema` representing an array. + /// + /// This schema instructs the model to produce data of type ``DataType/array``, which has elements + /// of any other ``DataType`` (including nested ``DataType/array``s). This data is suitable for + /// decoding into many Swift collection types, including `Array`, holding elements of types + /// suitable for decoding from the respective `items` type. + /// + /// - Parameters: + /// - items: The `Schema` of the elements that the array will hold. + /// - description: An optional description of what the array should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may return `null` instead of an array; + /// defaults to `false`, enforcing that an array is returned. + public static func array(items: Schema, description: String? = nil, + nullable: Bool = false) -> Schema { + return self.init(type: .array, description: description, nullable: nullable, items: items) + } + + /// Returns a `Schema` representing an object. + /// + /// This schema instructs the model to produce data of type ``DataType/object``, which has keys + /// of type ``DataType/string`` and values of any other ``DataType`` (including nested + /// ``DataType/object``s). This data is suitable for decoding into Swift keyed collection types, + /// including `Dictionary`, or other custom `struct` or `class` types. + /// + /// **Example:** A `City` could be represented with the following object `Schema`. + /// ``` + /// Schema.object(properties: [ + /// "name" : .string(), + /// "population": .integer() + /// ]) + /// ``` + /// The generated data could be decoded into a Swift native type: + /// ``` + /// struct City: Decodable { + /// let name: String + /// let population: Int + /// } + /// ``` + /// + /// - Parameters: + /// - properties: A dictionary containing the object's property names as keys and their + /// respective `Schema`s as values. + /// - optionalProperties: A list of property names that may be be omitted in objects generated + /// by the model; these names must correspond to the keys provided in the `properties` + /// dictionary and may be an empty list. + /// - description: An optional description of what the object should contain or represent; may + /// use Markdown format. + /// - nullable: If `true`, instructs the model that it may return `null` instead of an object; + /// defaults to `false`, enforcing that an object is returned. + public static func object(properties: [String: Schema], optionalProperties: [String] = [], + description: String? = nil, nullable: Bool = false) -> Schema { + var requiredProperties = Set(properties.keys) + for optionalProperty in optionalProperties { + guard properties.keys.contains(optionalProperty) else { + fatalError("Optional property \"\(optionalProperty)\" not defined in object properties.") + } + requiredProperties.remove(optionalProperty) + } + + return self.init( + type: .object, + description: description, + nullable: nullable, + properties: properties, + requiredProperties: requiredProperties.sorted() + ) + } } /// A data type. @@ -114,3 +370,45 @@ extension Schema: Encodable { } extension DataType: Encodable {} + +// MARK: - RawRepresentable Conformance + +extension Schema.IntegerFormat: RawRepresentable { + public init?(rawValue: String) { + switch rawValue { + case "int32": + self = .int32 + case "int64": + self = .int64 + default: + self = .custom(rawValue) + } + } + + public var rawValue: String { + switch self { + case .int32: + return "int32" + case .int64: + return "int64" + case let .custom(format): + return format + } + } +} + +extension Schema.StringFormat: RawRepresentable { + public init?(rawValue: String) { + switch rawValue { + default: + self = .custom(rawValue) + } + } + + public var rawValue: String { + switch self { + case let .custom(format): + return format + } + } +} diff --git a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift index 35450c03758..43cbfe6dd96 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift @@ -57,7 +57,7 @@ final class GenerationConfigTests: XCTestCase { maxOutputTokens: maxOutputTokens, stopSequences: stopSequences, responseMIMEType: responseMIMEType, - responseSchema: Schema(type: .array, items: Schema(type: .string)) + responseSchema: .array(items: .string()) ) let jsonData = try encoder.encode(generationConfig) @@ -70,8 +70,10 @@ final class GenerationConfigTests: XCTestCase { "responseMIMEType" : "\(responseMIMEType)", "responseSchema" : { "items" : { + "nullable" : false, "type" : "STRING" }, + "nullable" : false, "type" : "ARRAY" }, "stopSequences" : [ @@ -89,15 +91,11 @@ final class GenerationConfigTests: XCTestCase { let mimeType = "application/json" let generationConfig = GenerationConfig( responseMIMEType: mimeType, - responseSchema: Schema( - type: .object, - properties: [ - "firstName": Schema(type: .string), - "lastName": Schema(type: .string), - "age": Schema(type: .integer), - ], - requiredProperties: ["firstName", "lastName", "age"] - ) + responseSchema: .object(properties: [ + "firstName": .string(), + "lastName": .string(), + "age": .integer(), + ]) ) let jsonData = try encoder.encode(generationConfig) @@ -107,21 +105,25 @@ final class GenerationConfigTests: XCTestCase { { "responseMIMEType" : "\(mimeType)", "responseSchema" : { + "nullable" : false, "properties" : { "age" : { + "nullable" : false, "type" : "INTEGER" }, "firstName" : { + "nullable" : false, "type" : "STRING" }, "lastName" : { + "nullable" : false, "type" : "STRING" } }, "required" : [ + "age", "firstName", - "lastName", - "age" + "lastName" ], "type" : "OBJECT" } From 2893101579e85ea93790a7f2e7a39b323401bb7e Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Wed, 25 Sep 2024 09:08:58 -0400 Subject: [PATCH 076/258] SessionToken persistence implementation (#13684) Co-authored-by: Wu-Hui --- Firestore/CHANGELOG.md | 1 + .../Firestore.xcodeproj/project.pbxproj | 44 ++++++++++++ Firestore/core/src/local/globals_cache.h | 57 +++++++++++++++ .../core/src/local/leveldb_globals_cache.cc | 57 +++++++++++++++ .../core/src/local/leveldb_globals_cache.h | 52 ++++++++++++++ Firestore/core/src/local/leveldb_key.cc | 36 ++++++++++ Firestore/core/src/local/leveldb_key.h | 35 +++++++++ .../core/src/local/leveldb_persistence.cc | 5 ++ .../core/src/local/leveldb_persistence.h | 4 ++ .../core/src/local/memory_globals_cache.cc | 33 +++++++++ .../core/src/local/memory_globals_cache.h | 49 +++++++++++++ .../core/src/local/memory_persistence.cc | 4 ++ Firestore/core/src/local/memory_persistence.h | 5 ++ Firestore/core/src/local/persistence.h | 6 ++ .../test/unit/local/globals_cache_test.cc | 71 +++++++++++++++++++ .../core/test/unit/local/globals_cache_test.h | 59 +++++++++++++++ .../unit/local/leveldb_globals_cache_test.cc | 38 ++++++++++ .../unit/local/memory_globals_cache_test.cc | 38 ++++++++++ 18 files changed, 594 insertions(+) create mode 100644 Firestore/core/src/local/globals_cache.h create mode 100644 Firestore/core/src/local/leveldb_globals_cache.cc create mode 100644 Firestore/core/src/local/leveldb_globals_cache.h create mode 100644 Firestore/core/src/local/memory_globals_cache.cc create mode 100644 Firestore/core/src/local/memory_globals_cache.h create mode 100644 Firestore/core/test/unit/local/globals_cache_test.cc create mode 100644 Firestore/core/test/unit/local/globals_cache_test.h create mode 100644 Firestore/core/test/unit/local/leveldb_globals_cache_test.cc create mode 100644 Firestore/core/test/unit/local/memory_globals_cache_test.cc diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 092e66027f8..dac0c7dbcdc 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,5 +1,6 @@ # 11.3.0 - [changed] Improve efficiency of memory persistence when processing a large number of writes. (#13572) +- [changed] Prepare Firestore cache to support session token. # 11.2.0 - [fixed] Marked all public classes with only readonly properties as `Sendable` to address diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index d90023b76d6..7b6e8450bf1 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 00A5761CD97E26A0EF4D47ED /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; 00F1CB487E8E0DA48F2E8FEC /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; + 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 0131DEDEF2C3CCAB2AB918A5 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 01C66732ECCB83AB1D896026 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; @@ -50,6 +51,7 @@ 06E0914D76667F1345EC17F5 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C939D1789E38C09F9A0C1157 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json */; }; 070B9CCDD759E66E6E10CC68 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 076465DFEEEAA4CAF5A0595A /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; }; 077292C9797D97D3851F15CE /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; 0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; @@ -110,7 +112,9 @@ 0F99BB63CE5B3CFE35F9027E /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; + 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; + 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */; }; 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; @@ -148,6 +152,7 @@ 15576E9A23A1C6678D5D7DE1 /* bloom_filter.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C7C0DCD2790019E66D8CC /* bloom_filter.pb.cc */; }; 155B7B54FFC72C14530BC4D4 /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; 156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = D872D754B8AD88E28AF28B28 /* aggregation_result.pb.cc */; }; + 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 15A5DEC8430E71D64424CBFD /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; 15A5F95DA733FD89A1E4147D /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; 15BF63DFF3A7E9A5376C4233 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; @@ -274,6 +279,7 @@ 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; 2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; + 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 284A5280F868B2B4B5A1C848 /* leveldb_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */; }; 28691225046DF9DF181B3350 /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; 28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -371,6 +377,7 @@ 392966346DA5EB3165E16A22 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 392F527F144BADDAC69C5485 /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; 394259BB091E1DB5994B91A2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; + 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */; }; 39CDC9EC5FD2E891D6D49151 /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; 3A307F319553A977258BB3D6 /* view_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */; }; @@ -389,6 +396,8 @@ 3BA4EEA6153B3833F86B8104 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; 3BAFCABA851AE1865D904323 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 3C5D441E7D5C140F0FB14D91 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; + 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; @@ -679,6 +688,7 @@ 5DA343D28AE05B0B2FE9FFB3 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 5DA741B0B90DB8DAB0AAE53C /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 5DDEC1A08F13226271FE636E /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; + 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 5E53122E4214FC4EA3B3DC1E /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; }; 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; @@ -688,6 +698,7 @@ 5ECE040F87E9FCD0A5D215DB /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; 5EDF0D63EAD6A65D4F8CDF45 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; 5EE21E86159A1911E9503BC1 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; + 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 5EFBAD082CB0F86CD0711979 /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 5F05A801B1EA44BC1264E55A /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; 5F096E8A16A3FAC824E194D1 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -798,6 +809,7 @@ 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 6E59498D20F55BA800ECD9A5 /* FuzzingResources in Resources */ = {isa = PBXBuildFile; fileRef = 6ED6DEA120F5502700FC6076 /* FuzzingResources */; }; + 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 6E7603BC1D8011A5D6F62072 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; }; 6E8302E021022309003E1EA3 /* FSTFuzzTestFieldPath.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */; }; 6E8CD8F545C8EDA84918977C /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -956,6 +968,7 @@ 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; + 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 87B5972F1C67CB8D53ADA024 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; 87B5AC3EBF0E83166B142FA4 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 881E55152AB34465412F8542 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; @@ -1234,9 +1247,11 @@ B9706A5CD29195A613CF4147 /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; B99452AB7E16B72D1C01FBBC /* datastore_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3167BD972EFF8EC636530E59 /* datastore_test.cc */; }; B998971CE6D0D1DD2AD9250A /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */; }; + B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; BA0BB02821F1949783C8AA50 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; BA3C0BA8082A6FB2546E47AC /* CodableTimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B65C996438B84DBC7616640 /* CodableTimestampTests.swift */; }; + BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; BACBBF4AF2F5455673AEAB35 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; @@ -1298,6 +1313,7 @@ C4548D8C790387C8E64F0FC4 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; }; C4C7A8D11DC394EF81B7B1FA /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; }; + C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* objc_type_traits_apple_test.mm */; }; C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; C57B15CADD8C3E806B154C19 /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; @@ -1336,6 +1352,7 @@ CCE596E8654A4D2EEA75C219 /* index_backfiller_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */; }; CD1E2F356FC71D7E74FCD26C /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; CD226D868CEFA9D557EF33A1 /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; + CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; CD78EEAA1CD36BE691CA3427 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; CDB5816537AB1B209C2B72A4 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; @@ -1604,6 +1621,7 @@ FB3D9E01547436163C456A3C /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; FBBB13329D3B5827C21AE7AB /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; FC1D22B6EC4E5F089AE39B8C /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; }; + FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; FCF8E7F5268F6842C07B69CF /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; FD365D6DFE9511D3BA2C74DF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -1746,6 +1764,7 @@ 4334F87873015E3763954578 /* status_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = status_testing.h; sourceTree = ""; }; 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; sourceTree = ""; }; 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = ""; }; + 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = globals_cache_test.cc; sourceTree = ""; }; 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; sourceTree = ""; }; 48D0915834C3D234E5A875A9 /* grpc_stream_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = grpc_stream_tester.h; sourceTree = ""; }; 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; sourceTree = ""; }; @@ -1863,6 +1882,7 @@ 5B5414D28802BC76FDADABD6 /* stream_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = stream_test.cc; sourceTree = ""; }; 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; sourceTree = ""; }; 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; sourceTree = ""; }; + 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = memory_globals_cache_test.cc; sourceTree = ""; }; 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_mutation_queue_test.cc; sourceTree = ""; }; 5CAE131920FFFED600BE9A4A /* Firestore_Benchmarks_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_Benchmarks_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5CAE131D20FFFED600BE9A4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1974,6 +1994,7 @@ 9B0B005A79E765AF02793DCE /* schedule_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = schedule_test.cc; sourceTree = ""; }; 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; path = recovery_spec_test.json; sourceTree = ""; }; 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_format_apple_test.mm; sourceTree = ""; }; + 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = globals_cache_test.h; sourceTree = ""; }; A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = testing_hooks_test.cc; sourceTree = ""; }; A082AFDD981B07B5AD78FDE8 /* token_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = token_test.cc; path = credentials/token_test.cc; sourceTree = ""; }; A20BAA3D2F994384279727EC /* md5_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = md5_testing.h; sourceTree = ""; }; @@ -2105,6 +2126,7 @@ F848C41C03A25C42AD5A4BC2 /* target_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = target_cache_test.h; sourceTree = ""; }; F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; name = firebase_auth_credentials_provider_test.mm; path = credentials/firebase_auth_credentials_provider_test.mm; sourceTree = ""; }; FA2E9952BA2B299C1156C43C /* Pods-Firestore_Benchmarks_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; sourceTree = ""; }; + FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = leveldb_globals_cache_test.cc; sourceTree = ""; }; FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.release.xcconfig"; sourceTree = ""; }; FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQueryUnitTests.mm; sourceTree = ""; }; FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = document_overlay_cache_test.cc; sourceTree = ""; }; @@ -2448,11 +2470,14 @@ 75E24C5CD7BC423D48713100 /* counting_query_engine.h */, FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */, DF445D5201750281F1817387 /* document_overlay_cache_test.h */, + 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */, + 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */, 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */, AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */, 73F1F73A2210F3D800E1F692 /* index_manager_test.h */, 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */, AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */, + FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */, 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */, 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */, 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */, @@ -2474,6 +2499,7 @@ CB7B2D4691C380DE3EB59038 /* lru_garbage_collector_test.h */, AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */, 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */, + 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */, DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */, F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */, 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */, @@ -4197,6 +4223,7 @@ 9B9BFC16E26BDE4AE0CDFF4B /* firebase_auth_credentials_provider_test.mm in Sources */, C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */, B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */, + B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */, 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */, 4D98894EB5B3D778F5628456 /* grpc_stream_test.cc in Sources */, 0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */, @@ -4213,6 +4240,7 @@ 49C04B97AB282FFA82FD98CD /* latlng.pb.cc in Sources */, 292BCC76AF1B916752764A8F /* leveldb_bundle_cache_test.cc in Sources */, 095A878BB33211AB52BFAD9F /* leveldb_document_overlay_cache_test.cc in Sources */, + 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */, 8B3EB33933D11CF897EAF4C3 /* leveldb_index_manager_test.cc in Sources */, 568EC1C0F68A7B95E57C8C6C /* leveldb_key_test.cc in Sources */, 843EE932AA9A8F43721F189E /* leveldb_local_store_test.cc in Sources */, @@ -4238,6 +4266,7 @@ FE20E696E014CDCE918E91D6 /* md5_testing.cc in Sources */, FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */, 8F2055702DB5EE8DA4BACD7C /* memory_document_overlay_cache_test.cc in Sources */, + 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */, CFF1EBC60A00BA5109893C6E /* memory_index_manager_test.cc in Sources */, 49774EBBC8496FE1E43AEE29 /* memory_local_store_test.cc in Sources */, 66D9F8E8A65F97F436B1EE5E /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4415,6 +4444,7 @@ 0E17927CE45F5E3FC6691E24 /* firebase_auth_credentials_provider_test.mm in Sources */, 8683BBC3AC7B01937606A83B /* firestore.pb.cc in Sources */, F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */, + 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */, BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */, D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */, 336E415DD06E719F9C9E2A14 /* grpc_stream_tester.cc in Sources */, @@ -4431,6 +4461,7 @@ 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */, 513D34C9964E8C60C5C2EE1C /* leveldb_bundle_cache_test.cc in Sources */, A6BDA28DBC85BC1BAB7061F4 /* leveldb_document_overlay_cache_test.cc in Sources */, + 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */, A215078DBFBB5A4F4DADE8A9 /* leveldb_index_manager_test.cc in Sources */, B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */, E63342115B1DA65DB6F2C59A /* leveldb_local_store_test.cc in Sources */, @@ -4456,6 +4487,7 @@ 169EDCF15637580BA79B61AD /* md5_testing.cc in Sources */, 9611A0FAA2E10A6B1C1AC2EA /* memory_bundle_cache_test.cc in Sources */, 75C6CECF607CA94F56260BAB /* memory_document_overlay_cache_test.cc in Sources */, + 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */, 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */, B15D17049414E2F5AE72C9C6 /* memory_local_store_test.cc in Sources */, D4D8BA32ACC5C2B1B29711C0 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4657,6 +4689,7 @@ F7EE3CCC821975B71E834453 /* firebase_auth_credentials_provider_test.mm in Sources */, 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */, 6ABB82D43C0728EB095947AF /* geo_point_test.cc in Sources */, + 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */, D9DA467E7903412DC6AECDE4 /* grpc_connection_test.cc in Sources */, B7DD5FC63A78FF00E80332C0 /* grpc_stream_test.cc in Sources */, 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */, @@ -4673,6 +4706,7 @@ CBC891BEEC525F4D8F40A319 /* latlng.pb.cc in Sources */, 2E76BC76BBCE5FCDDCF5EEBE /* leveldb_bundle_cache_test.cc in Sources */, 6711E75A10EBA662341F5C9D /* leveldb_document_overlay_cache_test.cc in Sources */, + 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */, A602E6C7C8B243BB767D251C /* leveldb_index_manager_test.cc in Sources */, 8AA7A1FCEE6EC309399978AD /* leveldb_key_test.cc in Sources */, 55E84644D385A70E607A0F91 /* leveldb_local_store_test.cc in Sources */, @@ -4698,6 +4732,7 @@ E2AC3BDAAFFF9A45C916708B /* md5_testing.cc in Sources */, FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */, 5F6FD840AC2D729B50991CCB /* memory_document_overlay_cache_test.cc in Sources */, + 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */, E6B825EE85BF20B88AF3E3CD /* memory_index_manager_test.cc in Sources */, 7ACA8D967438B5CD9DA4C884 /* memory_local_store_test.cc in Sources */, 444298A613D027AC67F7E977 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4899,6 +4934,7 @@ B6BEB7AF975FA31E169B7DD2 /* firebase_auth_credentials_provider_test.mm in Sources */, D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */, 8B31F63673F3B5238DE95AFB /* geo_point_test.cc in Sources */, + FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */, 5958E3E3A0446A88B815CB70 /* grpc_connection_test.cc in Sources */, 0C18678CE7E355B17C34F2EE /* grpc_stream_test.cc in Sources */, B83A1416C3922E2F3EBA77FE /* grpc_stream_tester.cc in Sources */, @@ -4915,6 +4951,7 @@ 4173B61CB74EB4CD1D89EE68 /* latlng.pb.cc in Sources */, 1E8F5F37052AB0C087D69DF9 /* leveldb_bundle_cache_test.cc in Sources */, 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */, + 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */, 839D8B502026706419FE09D6 /* leveldb_index_manager_test.cc in Sources */, A4AD189BDEF7A609953457A6 /* leveldb_key_test.cc in Sources */, 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */, @@ -4940,6 +4977,7 @@ E72A77095FF6814267DF0F6D /* md5_testing.cc in Sources */, 94854FAEAEA75A1AC77A0515 /* memory_bundle_cache_test.cc in Sources */, 053C11420E49AE1A77E21C20 /* memory_document_overlay_cache_test.cc in Sources */, + BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */, 4D8367018652104A8803E8DB /* memory_index_manager_test.cc in Sources */, 91AEFFEE35FBE15FEC42A1F4 /* memory_local_store_test.cc in Sources */, 3B23E21D5D7ACF54EBD8CF67 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5127,6 +5165,7 @@ C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */, 544129DB21C2DDC800EFB9CC /* firestore.pb.cc in Sources */, AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */, + 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */, B6D9649121544D4F00EB9CFB /* grpc_connection_test.cc in Sources */, B6BBE43121262CF400C6A53E /* grpc_stream_test.cc in Sources */, 34202A37E0B762386967AF3D /* grpc_stream_tester.cc in Sources */, @@ -5143,6 +5182,7 @@ 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */, 0EDFC8A6593477E1D17CDD8F /* leveldb_bundle_cache_test.cc in Sources */, E962CA641FB1312638593131 /* leveldb_document_overlay_cache_test.cc in Sources */, + 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */, B743F4E121E879EF34536A51 /* leveldb_index_manager_test.cc in Sources */, 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */, 04887E378B39FB86A8A5B52B /* leveldb_local_store_test.cc in Sources */, @@ -5168,6 +5208,7 @@ 723BBD713478BB26CEFA5A7D /* md5_testing.cc in Sources */, A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */, E56EEC9DAC455E2BE77D110A /* memory_document_overlay_cache_test.cc in Sources */, + 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */, 3B47CC43DBA24434E215B8ED /* memory_index_manager_test.cc in Sources */, C6BF529243414C53DF5F1012 /* memory_local_store_test.cc in Sources */, 72B25B2D698E4746143D5B74 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5388,6 +5429,7 @@ 58693C153EC597BC25EE9648 /* firebase_auth_credentials_provider_test.mm in Sources */, 920B6ABF76FDB3547F1CCD84 /* firestore.pb.cc in Sources */, 5FE84472E5369DA866193C45 /* geo_point_test.cc in Sources */, + C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */, 0DDEE9FE08845BB7CA4607DE /* grpc_connection_test.cc in Sources */, 549CEDA0519BA5F2508794E1 /* grpc_stream_test.cc in Sources */, DE50F1D39D34F867BC750957 /* grpc_stream_tester.cc in Sources */, @@ -5404,6 +5446,7 @@ 23C04A637090E438461E4E70 /* latlng.pb.cc in Sources */, 77C459976DCF7503AEE18F7F /* leveldb_bundle_cache_test.cc in Sources */, 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */, + 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */, 2C5C612B26168BA9286290AE /* leveldb_index_manager_test.cc in Sources */, 7731E564468645A4A62E2A3C /* leveldb_key_test.cc in Sources */, 380A137B785A5A6991BEDF4B /* leveldb_local_store_test.cc in Sources */, @@ -5429,6 +5472,7 @@ 1DCDED1F94EBC7F72FDBFC98 /* md5_testing.cc in Sources */, 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */, 5CEB0E83DA68652927D2CF07 /* memory_document_overlay_cache_test.cc in Sources */, + CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */, 90FE088B8FD9EC06EEED1F39 /* memory_index_manager_test.cc in Sources */, 1CC56DCA513B98CE39A6ED45 /* memory_local_store_test.cc in Sources */, 264AAB492E24318C5EEB0649 /* memory_lru_garbage_collector_test.cc in Sources */, diff --git a/Firestore/core/src/local/globals_cache.h b/Firestore/core/src/local/globals_cache.h new file mode 100644 index 00000000000..78800470292 --- /dev/null +++ b/Firestore/core/src/local/globals_cache.h @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ +#define FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ + +#include "Firestore/core/src/nanopb/byte_string.h" + +using firebase::firestore::nanopb::ByteString; + +namespace firebase { +namespace firestore { +namespace local { + +/** + * General purpose cache for global values. + * + * Global state that cuts across components should be saved here. Following are + * contained herein: + * + * `sessionToken` tracks server interaction across Listen and Write streams. + * This facilitates cache synchronization and invalidation. + */ +class GlobalsCache { + public: + virtual ~GlobalsCache() = default; + + /** + * Gets session token. + */ + virtual ByteString GetSessionToken() const = 0; + + /** + * Sets session token. + */ + virtual void SetSessionToken(const ByteString& session_token) = 0; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_globals_cache.cc b/Firestore/core/src/local/leveldb_globals_cache.cc new file mode 100644 index 00000000000..366d0c811ea --- /dev/null +++ b/Firestore/core/src/local/leveldb_globals_cache.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "Firestore/core/src/local/leveldb_globals_cache.h" +#include "Firestore/core/src/local/leveldb_key.h" +#include "Firestore/core/src/local/leveldb_persistence.h" + +namespace firebase { +namespace firestore { +namespace local { + +namespace { + +const char* kSessionToken = "session_token"; + +} + +LevelDbGlobalsCache::LevelDbGlobalsCache(LevelDbPersistence* db) + : db_(NOT_NULL(db)) { +} + +ByteString LevelDbGlobalsCache::GetSessionToken() const { + auto key = LevelDbGlobalKey::Key(kSessionToken); + + std::string encoded; + auto done = db_->current_transaction()->Get(key, &encoded); + + if (!done.ok()) { + return ByteString(); + } + + return ByteString(encoded); +} + +void LevelDbGlobalsCache::SetSessionToken(const ByteString& session_token) { + auto key = LevelDbGlobalKey::Key(kSessionToken); + db_->current_transaction()->Put(key, session_token.ToString()); +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/local/leveldb_globals_cache.h b/Firestore/core/src/local/leveldb_globals_cache.h new file mode 100644 index 00000000000..4b41df5705e --- /dev/null +++ b/Firestore/core/src/local/leveldb_globals_cache.h @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ +#define FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ + +#include "Firestore/core/src/local/globals_cache.h" + +namespace firebase { +namespace firestore { +namespace local { + +class LevelDbPersistence; + +class LevelDbGlobalsCache : public GlobalsCache { + public: + /** Creates a new bundle cache in the given LevelDB. */ + explicit LevelDbGlobalsCache(LevelDbPersistence* db); + + /** + * Gets session token. + */ + ByteString GetSessionToken() const override; + + /** + * Sets session token. + */ + void SetSessionToken(const ByteString& session_token) override; + + private: + // The LevelDbGlobalsCache is owned by LevelDbPersistence. + LevelDbPersistence* db_ = nullptr; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_key.cc b/Firestore/core/src/local/leveldb_key.cc index e8819650df4..fd0fa8dacd1 100644 --- a/Firestore/core/src/local/leveldb_key.cc +++ b/Firestore/core/src/local/leveldb_key.cc @@ -39,6 +39,7 @@ namespace local { namespace { const char* kVersionGlobalTable = "version"; +const char* kGlobalsTable = "globals"; const char* kMutationsTable = "mutation"; const char* kDocumentMutationsTable = "document_mutation"; const char* kMutationQueuesTable = "mutation_queue"; @@ -159,6 +160,11 @@ enum ComponentLabel { */ DataMigrationName = 25, + /** + * The name of a global. + */ + GlobalName = 26, + /** * A path segment describes just a single segment in a resource path. Path * segments that occur sequentially in a key represent successive segments in @@ -245,6 +251,10 @@ class Reader { return ReadLabeledString(ComponentLabel::BundleId); } + std::string ReadGlobalName() { + return ReadLabeledString(ComponentLabel::GlobalName); + } + std::string ReadQueryName() { return ReadLabeledString(ComponentLabel::QueryName); } @@ -718,6 +728,10 @@ class Writer { WriteLabeledString(ComponentLabel::TableName, table_name); } + void WriteGlobalName(absl::string_view global_name) { + WriteLabeledString(ComponentLabel::GlobalName, global_name); + } + void WriteBatchId(model::BatchId batch_id) { WriteLabeledInt32(ComponentLabel::BatchId, batch_id); } @@ -1206,6 +1220,28 @@ bool LevelDbRemoteDocumentReadTimeKey::Decode(absl::string_view key) { return reader.ok(); } +std::string LevelDbGlobalKey::KeyPrefix() { + Writer writer; + writer.WriteTableName(kGlobalsTable); + return writer.result(); +} + +std::string LevelDbGlobalKey::Key(absl::string_view global_name) { + Writer writer; + writer.WriteTableName(kGlobalsTable); + writer.WriteGlobalName(global_name); + writer.WriteTerminator(); + return writer.result(); +} + +bool LevelDbGlobalKey::Decode(absl::string_view key) { + Reader reader{key}; + reader.ReadTableNameMatching(kGlobalsTable); + global_name_ = reader.ReadGlobalName(); + reader.ReadTerminator(); + return reader.ok(); +} + std::string LevelDbBundleKey::KeyPrefix() { Writer writer; writer.WriteTableName(kBundlesTable); diff --git a/Firestore/core/src/local/leveldb_key.h b/Firestore/core/src/local/leveldb_key.h index 51505b9c5c6..14ecd809ea2 100644 --- a/Firestore/core/src/local/leveldb_key.h +++ b/Firestore/core/src/local/leveldb_key.h @@ -768,6 +768,41 @@ class LevelDbNamedQueryKey { std::string name_; }; +/** + * A key in the globals table, storing the name of the global value. + */ +class LevelDbGlobalKey { + public: + /** + * Creates a key prefix that points just before the first key of the table. + */ + static std::string KeyPrefix(); + + /** + * Creates a key that points to the key for the given name of global value. + */ + static std::string Key(absl::string_view global_name); + + /** + * Decodes the given complete key, storing the decoded values in this + * instance. + * + * @return true if the key successfully decoded, false otherwise. If false is + * returned, this instance is in an undefined state until the next call to + * `Decode()`. + */ + ABSL_MUST_USE_RESULT + bool Decode(absl::string_view key); + + /** The name that serves as identifier for global value for this entry. */ + const std::string& global_name() const { + return global_name_; + } + + private: + std::string global_name_; +}; + /** * A key in the index_configuration table, storing the index definition proto, * and the collection (group) it applies to. diff --git a/Firestore/core/src/local/leveldb_persistence.cc b/Firestore/core/src/local/leveldb_persistence.cc index c5ce4c60c2d..9725e267356 100644 --- a/Firestore/core/src/local/leveldb_persistence.cc +++ b/Firestore/core/src/local/leveldb_persistence.cc @@ -126,6 +126,7 @@ LevelDbPersistence::LevelDbPersistence(std::unique_ptr db, reference_delegate_ = absl::make_unique(this, lru_params); bundle_cache_ = absl::make_unique(this, &serializer_); + globals_cache_ = absl::make_unique(this); // TODO(gsoltis): set up a leveldb transaction for these operations. target_cache_->Start(); @@ -250,6 +251,10 @@ LevelDbTargetCache* LevelDbPersistence::target_cache() { return target_cache_.get(); } +LevelDbGlobalsCache* LevelDbPersistence::globals_cache() { + return globals_cache_.get(); +} + LevelDbRemoteDocumentCache* LevelDbPersistence::remote_document_cache() { return document_cache_.get(); } diff --git a/Firestore/core/src/local/leveldb_persistence.h b/Firestore/core/src/local/leveldb_persistence.h index 1374d91a08e..5ca6491bccd 100644 --- a/Firestore/core/src/local/leveldb_persistence.h +++ b/Firestore/core/src/local/leveldb_persistence.h @@ -25,6 +25,7 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/leveldb_bundle_cache.h" #include "Firestore/core/src/local/leveldb_document_overlay_cache.h" +#include "Firestore/core/src/local/leveldb_globals_cache.h" #include "Firestore/core/src/local/leveldb_index_manager.h" #include "Firestore/core/src/local/leveldb_lru_reference_delegate.h" #include "Firestore/core/src/local/leveldb_migrations.h" @@ -84,6 +85,8 @@ class LevelDbPersistence : public Persistence { LevelDbBundleCache* bundle_cache() override; + LevelDbGlobalsCache* globals_cache() override; + LevelDbDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; LevelDbOverlayMigrationManager* GetOverlayMigrationManager( @@ -154,6 +157,7 @@ class LevelDbPersistence : public Persistence { bool started_ = false; std::unique_ptr bundle_cache_; + std::unique_ptr globals_cache_; std::unordered_map> document_overlay_caches_; std::unordered_map + +#include "Firestore/core/src/local/globals_cache.h" + +namespace firebase { +namespace firestore { +namespace local { + +class MemoryGlobalsCache : public GlobalsCache { + public: + /** + * Gets session token. + */ + ByteString GetSessionToken() const override; + + /** + * Sets session token. + */ + void SetSessionToken(const ByteString& session_token) override; + + private: + ByteString session_token_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_MEMORY_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/memory_persistence.cc b/Firestore/core/src/local/memory_persistence.cc index 009036e1d8b..f1946661ba5 100644 --- a/Firestore/core/src/local/memory_persistence.cc +++ b/Firestore/core/src/local/memory_persistence.cc @@ -102,6 +102,10 @@ MemoryBundleCache* MemoryPersistence::bundle_cache() { return &bundle_cache_; } +MemoryGlobalsCache* MemoryPersistence::globals_cache() { + return &globals_cache_; +} + MemoryDocumentOverlayCache* MemoryPersistence::GetDocumentOverlayCache( const User& user) { auto iter = document_overlay_caches_.find(user); diff --git a/Firestore/core/src/local/memory_persistence.h b/Firestore/core/src/local/memory_persistence.h index 674577bbac2..bebadb86790 100644 --- a/Firestore/core/src/local/memory_persistence.h +++ b/Firestore/core/src/local/memory_persistence.h @@ -27,6 +27,7 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/memory_bundle_cache.h" #include "Firestore/core/src/local/memory_document_overlay_cache.h" +#include "Firestore/core/src/local/memory_globals_cache.h" #include "Firestore/core/src/local/memory_index_manager.h" #include "Firestore/core/src/local/memory_mutation_queue.h" #include "Firestore/core/src/local/memory_remote_document_cache.h" @@ -90,6 +91,8 @@ class MemoryPersistence : public Persistence { MemoryBundleCache* bundle_cache() override; + MemoryGlobalsCache* globals_cache() override; + MemoryDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; @@ -138,6 +141,8 @@ class MemoryPersistence : public Persistence { MemoryBundleCache bundle_cache_; + MemoryGlobalsCache globals_cache_; + DocumentOverlayCaches document_overlay_caches_; MemoryOverlayMigrationManager overlay_migration_manager_; diff --git a/Firestore/core/src/local/persistence.h b/Firestore/core/src/local/persistence.h index b1f7ebde74f..3e184e4bac8 100644 --- a/Firestore/core/src/local/persistence.h +++ b/Firestore/core/src/local/persistence.h @@ -36,6 +36,7 @@ namespace local { class BundleCache; class DocumentOverlayCache; +class GlobalsCache; class IndexManager; class MutationQueue; class OverlayMigrationManager; @@ -86,6 +87,11 @@ class Persistence { /** Releases any resources held during eager shutdown. */ virtual void Shutdown() = 0; + /** + * Returns GlobalCache representing a general purpose cache for global values. + */ + virtual GlobalsCache* globals_cache() = 0; + /** * Returns a MutationQueue representing the persisted mutations for the given * user. diff --git a/Firestore/core/test/unit/local/globals_cache_test.cc b/Firestore/core/test/unit/local/globals_cache_test.cc new file mode 100644 index 00000000000..0945a3a7b35 --- /dev/null +++ b/Firestore/core/test/unit/local/globals_cache_test.cc @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/local/globals_cache_test.h" + +#include "Firestore/core/src/local/globals_cache.h" +#include "Firestore/core/src/local/persistence.h" +#include "Firestore/core/test/unit/testutil/testutil.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { + +using util::ComparisonResult; + +namespace local { + +GlobalsCacheTest::GlobalsCacheTest(std::unique_ptr persistence) + : persistence_(std::move(NOT_NULL(persistence))), + cache_(persistence_->globals_cache()) { +} + +GlobalsCacheTest::GlobalsCacheTest() : GlobalsCacheTest(GetParam()()) { +} + +namespace { + +TEST_P(GlobalsCacheTest, ReturnsEmptyBytestringWhenSessionTokenNotFound) { + persistence_->Run( + "test_returns_empty_bytestring_when_session_token_not_found", [&] { + auto expected = ByteString(); + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + }); +} + +TEST_P(GlobalsCacheTest, ReturnsSavedSessionToken) { + persistence_->Run("test_returns_saved_session_token", [&] { + auto expected = ByteString("magic"); + cache_->SetSessionToken(expected); + + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + + // Overwrite + expected = ByteString("science"); + cache_->SetSessionToken(expected); + + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + }); +} + +} // namespace + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/local/globals_cache_test.h b/Firestore/core/test/unit/local/globals_cache_test.h new file mode 100644 index 00000000000..aad64844339 --- /dev/null +++ b/Firestore/core/test/unit/local/globals_cache_test.h @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ +#define FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ + +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace local { + +class Persistence; +class GlobalsCache; + +using FactoryFunc = std::unique_ptr (*)(); + +/** + * These are tests for any implementation of the GlobalsCache interface. + * + * To test a specific implementation of GlobalsCache: + * + * - Write a persistence factory function + * - Call INSTANTIATE_TEST_SUITE_P(MyNewGlobalsCacheTest, + * GlobalsCacheTest, + * testing::Values(PersistenceFactory)); + */ +class GlobalsCacheTest : public testing::Test, + public testing::WithParamInterface { + public: + GlobalsCacheTest(); + explicit GlobalsCacheTest(std::unique_ptr persistence); + ~GlobalsCacheTest() = default; + + protected: + std::unique_ptr persistence_; + GlobalsCache* cache_ = nullptr; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ diff --git a/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc b/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc new file mode 100644 index 00000000000..4b6f11e5a48 --- /dev/null +++ b/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/local/leveldb_persistence.h" +#include "Firestore/core/test/unit/local/globals_cache_test.h" +#include "Firestore/core/test/unit/local/persistence_testing.h" + +namespace firebase { +namespace firestore { +namespace local { +namespace { + +std::unique_ptr PersistenceFactory() { + return LevelDbPersistenceForTesting(); +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(LevelDbGlobalsCacheTest, + GlobalsCacheTest, + testing::Values(PersistenceFactory)); + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/local/memory_globals_cache_test.cc b/Firestore/core/test/unit/local/memory_globals_cache_test.cc new file mode 100644 index 00000000000..e3bcde58d96 --- /dev/null +++ b/Firestore/core/test/unit/local/memory_globals_cache_test.cc @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/local/memory_persistence.h" +#include "Firestore/core/test/unit/local/globals_cache_test.h" +#include "Firestore/core/test/unit/local/persistence_testing.h" + +namespace firebase { +namespace firestore { +namespace local { +namespace { + +std::unique_ptr PersistenceFactory() { + return MemoryPersistenceWithEagerGcForTesting(); +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(MemoryGloablsCacheTest, + GlobalsCacheTest, + testing::Values(PersistenceFactory)); + +} // namespace local +} // namespace firestore +} // namespace firebase From fb09f937affa36f4656e91ea2ab9f5d8659f5c33 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Wed, 25 Sep 2024 11:17:37 -0400 Subject: [PATCH 077/258] Revert "SessionToken persistence implementation (#13684)" (#13719) --- Firestore/CHANGELOG.md | 1 - .../Firestore.xcodeproj/project.pbxproj | 44 ------------ Firestore/core/src/local/globals_cache.h | 57 --------------- .../core/src/local/leveldb_globals_cache.cc | 57 --------------- .../core/src/local/leveldb_globals_cache.h | 52 -------------- Firestore/core/src/local/leveldb_key.cc | 36 ---------- Firestore/core/src/local/leveldb_key.h | 35 --------- .../core/src/local/leveldb_persistence.cc | 5 -- .../core/src/local/leveldb_persistence.h | 4 -- .../core/src/local/memory_globals_cache.cc | 33 --------- .../core/src/local/memory_globals_cache.h | 49 ------------- .../core/src/local/memory_persistence.cc | 4 -- Firestore/core/src/local/memory_persistence.h | 5 -- Firestore/core/src/local/persistence.h | 6 -- .../test/unit/local/globals_cache_test.cc | 71 ------------------- .../core/test/unit/local/globals_cache_test.h | 59 --------------- .../unit/local/leveldb_globals_cache_test.cc | 38 ---------- .../unit/local/memory_globals_cache_test.cc | 38 ---------- 18 files changed, 594 deletions(-) delete mode 100644 Firestore/core/src/local/globals_cache.h delete mode 100644 Firestore/core/src/local/leveldb_globals_cache.cc delete mode 100644 Firestore/core/src/local/leveldb_globals_cache.h delete mode 100644 Firestore/core/src/local/memory_globals_cache.cc delete mode 100644 Firestore/core/src/local/memory_globals_cache.h delete mode 100644 Firestore/core/test/unit/local/globals_cache_test.cc delete mode 100644 Firestore/core/test/unit/local/globals_cache_test.h delete mode 100644 Firestore/core/test/unit/local/leveldb_globals_cache_test.cc delete mode 100644 Firestore/core/test/unit/local/memory_globals_cache_test.cc diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index dac0c7dbcdc..092e66027f8 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,6 +1,5 @@ # 11.3.0 - [changed] Improve efficiency of memory persistence when processing a large number of writes. (#13572) -- [changed] Prepare Firestore cache to support session token. # 11.2.0 - [fixed] Marked all public classes with only readonly properties as `Sendable` to address diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 7b6e8450bf1..d90023b76d6 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 00A5761CD97E26A0EF4D47ED /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; 00F1CB487E8E0DA48F2E8FEC /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; - 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 0131DEDEF2C3CCAB2AB918A5 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 01C66732ECCB83AB1D896026 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; @@ -51,7 +50,6 @@ 06E0914D76667F1345EC17F5 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C939D1789E38C09F9A0C1157 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json */; }; 070B9CCDD759E66E6E10CC68 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; - 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 076465DFEEEAA4CAF5A0595A /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; }; 077292C9797D97D3851F15CE /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; 0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; @@ -112,9 +110,7 @@ 0F99BB63CE5B3CFE35F9027E /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; - 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; - 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */; }; 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; @@ -152,7 +148,6 @@ 15576E9A23A1C6678D5D7DE1 /* bloom_filter.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C7C0DCD2790019E66D8CC /* bloom_filter.pb.cc */; }; 155B7B54FFC72C14530BC4D4 /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; 156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = D872D754B8AD88E28AF28B28 /* aggregation_result.pb.cc */; }; - 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 15A5DEC8430E71D64424CBFD /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; 15A5F95DA733FD89A1E4147D /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; 15BF63DFF3A7E9A5376C4233 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; @@ -279,7 +274,6 @@ 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; 2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; - 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 284A5280F868B2B4B5A1C848 /* leveldb_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */; }; 28691225046DF9DF181B3350 /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; 28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -377,7 +371,6 @@ 392966346DA5EB3165E16A22 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 392F527F144BADDAC69C5485 /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; 394259BB091E1DB5994B91A2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; - 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */; }; 39CDC9EC5FD2E891D6D49151 /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; 3A307F319553A977258BB3D6 /* view_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */; }; @@ -396,8 +389,6 @@ 3BA4EEA6153B3833F86B8104 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; 3BAFCABA851AE1865D904323 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 3C5D441E7D5C140F0FB14D91 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; - 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; - 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; @@ -688,7 +679,6 @@ 5DA343D28AE05B0B2FE9FFB3 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 5DA741B0B90DB8DAB0AAE53C /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 5DDEC1A08F13226271FE636E /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; - 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 5E53122E4214FC4EA3B3DC1E /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; }; 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; @@ -698,7 +688,6 @@ 5ECE040F87E9FCD0A5D215DB /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; 5EDF0D63EAD6A65D4F8CDF45 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; 5EE21E86159A1911E9503BC1 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; - 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 5EFBAD082CB0F86CD0711979 /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 5F05A801B1EA44BC1264E55A /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; 5F096E8A16A3FAC824E194D1 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -809,7 +798,6 @@ 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 6E59498D20F55BA800ECD9A5 /* FuzzingResources in Resources */ = {isa = PBXBuildFile; fileRef = 6ED6DEA120F5502700FC6076 /* FuzzingResources */; }; - 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 6E7603BC1D8011A5D6F62072 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; }; 6E8302E021022309003E1EA3 /* FSTFuzzTestFieldPath.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */; }; 6E8CD8F545C8EDA84918977C /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -968,7 +956,6 @@ 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; - 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 87B5972F1C67CB8D53ADA024 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; 87B5AC3EBF0E83166B142FA4 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 881E55152AB34465412F8542 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; @@ -1247,11 +1234,9 @@ B9706A5CD29195A613CF4147 /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; B99452AB7E16B72D1C01FBBC /* datastore_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3167BD972EFF8EC636530E59 /* datastore_test.cc */; }; B998971CE6D0D1DD2AD9250A /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */; }; - B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; BA0BB02821F1949783C8AA50 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; BA3C0BA8082A6FB2546E47AC /* CodableTimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B65C996438B84DBC7616640 /* CodableTimestampTests.swift */; }; - BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; BACBBF4AF2F5455673AEAB35 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; @@ -1313,7 +1298,6 @@ C4548D8C790387C8E64F0FC4 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; }; C4C7A8D11DC394EF81B7B1FA /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; }; - C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* objc_type_traits_apple_test.mm */; }; C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; C57B15CADD8C3E806B154C19 /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; @@ -1352,7 +1336,6 @@ CCE596E8654A4D2EEA75C219 /* index_backfiller_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */; }; CD1E2F356FC71D7E74FCD26C /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; CD226D868CEFA9D557EF33A1 /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; - CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; CD78EEAA1CD36BE691CA3427 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; CDB5816537AB1B209C2B72A4 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; @@ -1621,7 +1604,6 @@ FB3D9E01547436163C456A3C /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; FBBB13329D3B5827C21AE7AB /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; FC1D22B6EC4E5F089AE39B8C /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; }; - FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; FCF8E7F5268F6842C07B69CF /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; FD365D6DFE9511D3BA2C74DF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -1764,7 +1746,6 @@ 4334F87873015E3763954578 /* status_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = status_testing.h; sourceTree = ""; }; 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; sourceTree = ""; }; 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = ""; }; - 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = globals_cache_test.cc; sourceTree = ""; }; 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; sourceTree = ""; }; 48D0915834C3D234E5A875A9 /* grpc_stream_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = grpc_stream_tester.h; sourceTree = ""; }; 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; sourceTree = ""; }; @@ -1882,7 +1863,6 @@ 5B5414D28802BC76FDADABD6 /* stream_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = stream_test.cc; sourceTree = ""; }; 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; sourceTree = ""; }; 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; sourceTree = ""; }; - 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = memory_globals_cache_test.cc; sourceTree = ""; }; 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_mutation_queue_test.cc; sourceTree = ""; }; 5CAE131920FFFED600BE9A4A /* Firestore_Benchmarks_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_Benchmarks_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5CAE131D20FFFED600BE9A4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1994,7 +1974,6 @@ 9B0B005A79E765AF02793DCE /* schedule_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = schedule_test.cc; sourceTree = ""; }; 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; path = recovery_spec_test.json; sourceTree = ""; }; 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_format_apple_test.mm; sourceTree = ""; }; - 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = globals_cache_test.h; sourceTree = ""; }; A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = testing_hooks_test.cc; sourceTree = ""; }; A082AFDD981B07B5AD78FDE8 /* token_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = token_test.cc; path = credentials/token_test.cc; sourceTree = ""; }; A20BAA3D2F994384279727EC /* md5_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = md5_testing.h; sourceTree = ""; }; @@ -2126,7 +2105,6 @@ F848C41C03A25C42AD5A4BC2 /* target_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = target_cache_test.h; sourceTree = ""; }; F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; name = firebase_auth_credentials_provider_test.mm; path = credentials/firebase_auth_credentials_provider_test.mm; sourceTree = ""; }; FA2E9952BA2B299C1156C43C /* Pods-Firestore_Benchmarks_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; sourceTree = ""; }; - FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = leveldb_globals_cache_test.cc; sourceTree = ""; }; FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.release.xcconfig"; sourceTree = ""; }; FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQueryUnitTests.mm; sourceTree = ""; }; FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = document_overlay_cache_test.cc; sourceTree = ""; }; @@ -2470,14 +2448,11 @@ 75E24C5CD7BC423D48713100 /* counting_query_engine.h */, FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */, DF445D5201750281F1817387 /* document_overlay_cache_test.h */, - 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */, - 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */, 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */, AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */, 73F1F73A2210F3D800E1F692 /* index_manager_test.h */, 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */, AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */, - FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */, 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */, 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */, 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */, @@ -2499,7 +2474,6 @@ CB7B2D4691C380DE3EB59038 /* lru_garbage_collector_test.h */, AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */, 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */, - 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */, DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */, F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */, 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */, @@ -4223,7 +4197,6 @@ 9B9BFC16E26BDE4AE0CDFF4B /* firebase_auth_credentials_provider_test.mm in Sources */, C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */, B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */, - B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */, 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */, 4D98894EB5B3D778F5628456 /* grpc_stream_test.cc in Sources */, 0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */, @@ -4240,7 +4213,6 @@ 49C04B97AB282FFA82FD98CD /* latlng.pb.cc in Sources */, 292BCC76AF1B916752764A8F /* leveldb_bundle_cache_test.cc in Sources */, 095A878BB33211AB52BFAD9F /* leveldb_document_overlay_cache_test.cc in Sources */, - 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */, 8B3EB33933D11CF897EAF4C3 /* leveldb_index_manager_test.cc in Sources */, 568EC1C0F68A7B95E57C8C6C /* leveldb_key_test.cc in Sources */, 843EE932AA9A8F43721F189E /* leveldb_local_store_test.cc in Sources */, @@ -4266,7 +4238,6 @@ FE20E696E014CDCE918E91D6 /* md5_testing.cc in Sources */, FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */, 8F2055702DB5EE8DA4BACD7C /* memory_document_overlay_cache_test.cc in Sources */, - 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */, CFF1EBC60A00BA5109893C6E /* memory_index_manager_test.cc in Sources */, 49774EBBC8496FE1E43AEE29 /* memory_local_store_test.cc in Sources */, 66D9F8E8A65F97F436B1EE5E /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4444,7 +4415,6 @@ 0E17927CE45F5E3FC6691E24 /* firebase_auth_credentials_provider_test.mm in Sources */, 8683BBC3AC7B01937606A83B /* firestore.pb.cc in Sources */, F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */, - 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */, BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */, D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */, 336E415DD06E719F9C9E2A14 /* grpc_stream_tester.cc in Sources */, @@ -4461,7 +4431,6 @@ 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */, 513D34C9964E8C60C5C2EE1C /* leveldb_bundle_cache_test.cc in Sources */, A6BDA28DBC85BC1BAB7061F4 /* leveldb_document_overlay_cache_test.cc in Sources */, - 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */, A215078DBFBB5A4F4DADE8A9 /* leveldb_index_manager_test.cc in Sources */, B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */, E63342115B1DA65DB6F2C59A /* leveldb_local_store_test.cc in Sources */, @@ -4487,7 +4456,6 @@ 169EDCF15637580BA79B61AD /* md5_testing.cc in Sources */, 9611A0FAA2E10A6B1C1AC2EA /* memory_bundle_cache_test.cc in Sources */, 75C6CECF607CA94F56260BAB /* memory_document_overlay_cache_test.cc in Sources */, - 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */, 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */, B15D17049414E2F5AE72C9C6 /* memory_local_store_test.cc in Sources */, D4D8BA32ACC5C2B1B29711C0 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4689,7 +4657,6 @@ F7EE3CCC821975B71E834453 /* firebase_auth_credentials_provider_test.mm in Sources */, 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */, 6ABB82D43C0728EB095947AF /* geo_point_test.cc in Sources */, - 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */, D9DA467E7903412DC6AECDE4 /* grpc_connection_test.cc in Sources */, B7DD5FC63A78FF00E80332C0 /* grpc_stream_test.cc in Sources */, 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */, @@ -4706,7 +4673,6 @@ CBC891BEEC525F4D8F40A319 /* latlng.pb.cc in Sources */, 2E76BC76BBCE5FCDDCF5EEBE /* leveldb_bundle_cache_test.cc in Sources */, 6711E75A10EBA662341F5C9D /* leveldb_document_overlay_cache_test.cc in Sources */, - 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */, A602E6C7C8B243BB767D251C /* leveldb_index_manager_test.cc in Sources */, 8AA7A1FCEE6EC309399978AD /* leveldb_key_test.cc in Sources */, 55E84644D385A70E607A0F91 /* leveldb_local_store_test.cc in Sources */, @@ -4732,7 +4698,6 @@ E2AC3BDAAFFF9A45C916708B /* md5_testing.cc in Sources */, FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */, 5F6FD840AC2D729B50991CCB /* memory_document_overlay_cache_test.cc in Sources */, - 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */, E6B825EE85BF20B88AF3E3CD /* memory_index_manager_test.cc in Sources */, 7ACA8D967438B5CD9DA4C884 /* memory_local_store_test.cc in Sources */, 444298A613D027AC67F7E977 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4934,7 +4899,6 @@ B6BEB7AF975FA31E169B7DD2 /* firebase_auth_credentials_provider_test.mm in Sources */, D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */, 8B31F63673F3B5238DE95AFB /* geo_point_test.cc in Sources */, - FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */, 5958E3E3A0446A88B815CB70 /* grpc_connection_test.cc in Sources */, 0C18678CE7E355B17C34F2EE /* grpc_stream_test.cc in Sources */, B83A1416C3922E2F3EBA77FE /* grpc_stream_tester.cc in Sources */, @@ -4951,7 +4915,6 @@ 4173B61CB74EB4CD1D89EE68 /* latlng.pb.cc in Sources */, 1E8F5F37052AB0C087D69DF9 /* leveldb_bundle_cache_test.cc in Sources */, 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */, - 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */, 839D8B502026706419FE09D6 /* leveldb_index_manager_test.cc in Sources */, A4AD189BDEF7A609953457A6 /* leveldb_key_test.cc in Sources */, 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */, @@ -4977,7 +4940,6 @@ E72A77095FF6814267DF0F6D /* md5_testing.cc in Sources */, 94854FAEAEA75A1AC77A0515 /* memory_bundle_cache_test.cc in Sources */, 053C11420E49AE1A77E21C20 /* memory_document_overlay_cache_test.cc in Sources */, - BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */, 4D8367018652104A8803E8DB /* memory_index_manager_test.cc in Sources */, 91AEFFEE35FBE15FEC42A1F4 /* memory_local_store_test.cc in Sources */, 3B23E21D5D7ACF54EBD8CF67 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5165,7 +5127,6 @@ C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */, 544129DB21C2DDC800EFB9CC /* firestore.pb.cc in Sources */, AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */, - 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */, B6D9649121544D4F00EB9CFB /* grpc_connection_test.cc in Sources */, B6BBE43121262CF400C6A53E /* grpc_stream_test.cc in Sources */, 34202A37E0B762386967AF3D /* grpc_stream_tester.cc in Sources */, @@ -5182,7 +5143,6 @@ 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */, 0EDFC8A6593477E1D17CDD8F /* leveldb_bundle_cache_test.cc in Sources */, E962CA641FB1312638593131 /* leveldb_document_overlay_cache_test.cc in Sources */, - 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */, B743F4E121E879EF34536A51 /* leveldb_index_manager_test.cc in Sources */, 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */, 04887E378B39FB86A8A5B52B /* leveldb_local_store_test.cc in Sources */, @@ -5208,7 +5168,6 @@ 723BBD713478BB26CEFA5A7D /* md5_testing.cc in Sources */, A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */, E56EEC9DAC455E2BE77D110A /* memory_document_overlay_cache_test.cc in Sources */, - 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */, 3B47CC43DBA24434E215B8ED /* memory_index_manager_test.cc in Sources */, C6BF529243414C53DF5F1012 /* memory_local_store_test.cc in Sources */, 72B25B2D698E4746143D5B74 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5429,7 +5388,6 @@ 58693C153EC597BC25EE9648 /* firebase_auth_credentials_provider_test.mm in Sources */, 920B6ABF76FDB3547F1CCD84 /* firestore.pb.cc in Sources */, 5FE84472E5369DA866193C45 /* geo_point_test.cc in Sources */, - C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */, 0DDEE9FE08845BB7CA4607DE /* grpc_connection_test.cc in Sources */, 549CEDA0519BA5F2508794E1 /* grpc_stream_test.cc in Sources */, DE50F1D39D34F867BC750957 /* grpc_stream_tester.cc in Sources */, @@ -5446,7 +5404,6 @@ 23C04A637090E438461E4E70 /* latlng.pb.cc in Sources */, 77C459976DCF7503AEE18F7F /* leveldb_bundle_cache_test.cc in Sources */, 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */, - 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */, 2C5C612B26168BA9286290AE /* leveldb_index_manager_test.cc in Sources */, 7731E564468645A4A62E2A3C /* leveldb_key_test.cc in Sources */, 380A137B785A5A6991BEDF4B /* leveldb_local_store_test.cc in Sources */, @@ -5472,7 +5429,6 @@ 1DCDED1F94EBC7F72FDBFC98 /* md5_testing.cc in Sources */, 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */, 5CEB0E83DA68652927D2CF07 /* memory_document_overlay_cache_test.cc in Sources */, - CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */, 90FE088B8FD9EC06EEED1F39 /* memory_index_manager_test.cc in Sources */, 1CC56DCA513B98CE39A6ED45 /* memory_local_store_test.cc in Sources */, 264AAB492E24318C5EEB0649 /* memory_lru_garbage_collector_test.cc in Sources */, diff --git a/Firestore/core/src/local/globals_cache.h b/Firestore/core/src/local/globals_cache.h deleted file mode 100644 index 78800470292..00000000000 --- a/Firestore/core/src/local/globals_cache.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ -#define FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ - -#include "Firestore/core/src/nanopb/byte_string.h" - -using firebase::firestore::nanopb::ByteString; - -namespace firebase { -namespace firestore { -namespace local { - -/** - * General purpose cache for global values. - * - * Global state that cuts across components should be saved here. Following are - * contained herein: - * - * `sessionToken` tracks server interaction across Listen and Write streams. - * This facilitates cache synchronization and invalidation. - */ -class GlobalsCache { - public: - virtual ~GlobalsCache() = default; - - /** - * Gets session token. - */ - virtual ByteString GetSessionToken() const = 0; - - /** - * Sets session token. - */ - virtual void SetSessionToken(const ByteString& session_token) = 0; -}; - -} // namespace local -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_globals_cache.cc b/Firestore/core/src/local/leveldb_globals_cache.cc deleted file mode 100644 index 366d0c811ea..00000000000 --- a/Firestore/core/src/local/leveldb_globals_cache.cc +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "Firestore/core/src/local/leveldb_globals_cache.h" -#include "Firestore/core/src/local/leveldb_key.h" -#include "Firestore/core/src/local/leveldb_persistence.h" - -namespace firebase { -namespace firestore { -namespace local { - -namespace { - -const char* kSessionToken = "session_token"; - -} - -LevelDbGlobalsCache::LevelDbGlobalsCache(LevelDbPersistence* db) - : db_(NOT_NULL(db)) { -} - -ByteString LevelDbGlobalsCache::GetSessionToken() const { - auto key = LevelDbGlobalKey::Key(kSessionToken); - - std::string encoded; - auto done = db_->current_transaction()->Get(key, &encoded); - - if (!done.ok()) { - return ByteString(); - } - - return ByteString(encoded); -} - -void LevelDbGlobalsCache::SetSessionToken(const ByteString& session_token) { - auto key = LevelDbGlobalKey::Key(kSessionToken); - db_->current_transaction()->Put(key, session_token.ToString()); -} - -} // namespace local -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/local/leveldb_globals_cache.h b/Firestore/core/src/local/leveldb_globals_cache.h deleted file mode 100644 index 4b41df5705e..00000000000 --- a/Firestore/core/src/local/leveldb_globals_cache.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ -#define FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ - -#include "Firestore/core/src/local/globals_cache.h" - -namespace firebase { -namespace firestore { -namespace local { - -class LevelDbPersistence; - -class LevelDbGlobalsCache : public GlobalsCache { - public: - /** Creates a new bundle cache in the given LevelDB. */ - explicit LevelDbGlobalsCache(LevelDbPersistence* db); - - /** - * Gets session token. - */ - ByteString GetSessionToken() const override; - - /** - * Sets session token. - */ - void SetSessionToken(const ByteString& session_token) override; - - private: - // The LevelDbGlobalsCache is owned by LevelDbPersistence. - LevelDbPersistence* db_ = nullptr; -}; - -} // namespace local -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_key.cc b/Firestore/core/src/local/leveldb_key.cc index fd0fa8dacd1..e8819650df4 100644 --- a/Firestore/core/src/local/leveldb_key.cc +++ b/Firestore/core/src/local/leveldb_key.cc @@ -39,7 +39,6 @@ namespace local { namespace { const char* kVersionGlobalTable = "version"; -const char* kGlobalsTable = "globals"; const char* kMutationsTable = "mutation"; const char* kDocumentMutationsTable = "document_mutation"; const char* kMutationQueuesTable = "mutation_queue"; @@ -160,11 +159,6 @@ enum ComponentLabel { */ DataMigrationName = 25, - /** - * The name of a global. - */ - GlobalName = 26, - /** * A path segment describes just a single segment in a resource path. Path * segments that occur sequentially in a key represent successive segments in @@ -251,10 +245,6 @@ class Reader { return ReadLabeledString(ComponentLabel::BundleId); } - std::string ReadGlobalName() { - return ReadLabeledString(ComponentLabel::GlobalName); - } - std::string ReadQueryName() { return ReadLabeledString(ComponentLabel::QueryName); } @@ -728,10 +718,6 @@ class Writer { WriteLabeledString(ComponentLabel::TableName, table_name); } - void WriteGlobalName(absl::string_view global_name) { - WriteLabeledString(ComponentLabel::GlobalName, global_name); - } - void WriteBatchId(model::BatchId batch_id) { WriteLabeledInt32(ComponentLabel::BatchId, batch_id); } @@ -1220,28 +1206,6 @@ bool LevelDbRemoteDocumentReadTimeKey::Decode(absl::string_view key) { return reader.ok(); } -std::string LevelDbGlobalKey::KeyPrefix() { - Writer writer; - writer.WriteTableName(kGlobalsTable); - return writer.result(); -} - -std::string LevelDbGlobalKey::Key(absl::string_view global_name) { - Writer writer; - writer.WriteTableName(kGlobalsTable); - writer.WriteGlobalName(global_name); - writer.WriteTerminator(); - return writer.result(); -} - -bool LevelDbGlobalKey::Decode(absl::string_view key) { - Reader reader{key}; - reader.ReadTableNameMatching(kGlobalsTable); - global_name_ = reader.ReadGlobalName(); - reader.ReadTerminator(); - return reader.ok(); -} - std::string LevelDbBundleKey::KeyPrefix() { Writer writer; writer.WriteTableName(kBundlesTable); diff --git a/Firestore/core/src/local/leveldb_key.h b/Firestore/core/src/local/leveldb_key.h index 14ecd809ea2..51505b9c5c6 100644 --- a/Firestore/core/src/local/leveldb_key.h +++ b/Firestore/core/src/local/leveldb_key.h @@ -768,41 +768,6 @@ class LevelDbNamedQueryKey { std::string name_; }; -/** - * A key in the globals table, storing the name of the global value. - */ -class LevelDbGlobalKey { - public: - /** - * Creates a key prefix that points just before the first key of the table. - */ - static std::string KeyPrefix(); - - /** - * Creates a key that points to the key for the given name of global value. - */ - static std::string Key(absl::string_view global_name); - - /** - * Decodes the given complete key, storing the decoded values in this - * instance. - * - * @return true if the key successfully decoded, false otherwise. If false is - * returned, this instance is in an undefined state until the next call to - * `Decode()`. - */ - ABSL_MUST_USE_RESULT - bool Decode(absl::string_view key); - - /** The name that serves as identifier for global value for this entry. */ - const std::string& global_name() const { - return global_name_; - } - - private: - std::string global_name_; -}; - /** * A key in the index_configuration table, storing the index definition proto, * and the collection (group) it applies to. diff --git a/Firestore/core/src/local/leveldb_persistence.cc b/Firestore/core/src/local/leveldb_persistence.cc index 9725e267356..c5ce4c60c2d 100644 --- a/Firestore/core/src/local/leveldb_persistence.cc +++ b/Firestore/core/src/local/leveldb_persistence.cc @@ -126,7 +126,6 @@ LevelDbPersistence::LevelDbPersistence(std::unique_ptr db, reference_delegate_ = absl::make_unique(this, lru_params); bundle_cache_ = absl::make_unique(this, &serializer_); - globals_cache_ = absl::make_unique(this); // TODO(gsoltis): set up a leveldb transaction for these operations. target_cache_->Start(); @@ -251,10 +250,6 @@ LevelDbTargetCache* LevelDbPersistence::target_cache() { return target_cache_.get(); } -LevelDbGlobalsCache* LevelDbPersistence::globals_cache() { - return globals_cache_.get(); -} - LevelDbRemoteDocumentCache* LevelDbPersistence::remote_document_cache() { return document_cache_.get(); } diff --git a/Firestore/core/src/local/leveldb_persistence.h b/Firestore/core/src/local/leveldb_persistence.h index 5ca6491bccd..1374d91a08e 100644 --- a/Firestore/core/src/local/leveldb_persistence.h +++ b/Firestore/core/src/local/leveldb_persistence.h @@ -25,7 +25,6 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/leveldb_bundle_cache.h" #include "Firestore/core/src/local/leveldb_document_overlay_cache.h" -#include "Firestore/core/src/local/leveldb_globals_cache.h" #include "Firestore/core/src/local/leveldb_index_manager.h" #include "Firestore/core/src/local/leveldb_lru_reference_delegate.h" #include "Firestore/core/src/local/leveldb_migrations.h" @@ -85,8 +84,6 @@ class LevelDbPersistence : public Persistence { LevelDbBundleCache* bundle_cache() override; - LevelDbGlobalsCache* globals_cache() override; - LevelDbDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; LevelDbOverlayMigrationManager* GetOverlayMigrationManager( @@ -157,7 +154,6 @@ class LevelDbPersistence : public Persistence { bool started_ = false; std::unique_ptr bundle_cache_; - std::unique_ptr globals_cache_; std::unordered_map> document_overlay_caches_; std::unordered_map - -#include "Firestore/core/src/local/globals_cache.h" - -namespace firebase { -namespace firestore { -namespace local { - -class MemoryGlobalsCache : public GlobalsCache { - public: - /** - * Gets session token. - */ - ByteString GetSessionToken() const override; - - /** - * Sets session token. - */ - void SetSessionToken(const ByteString& session_token) override; - - private: - ByteString session_token_; -}; - -} // namespace local -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_LOCAL_MEMORY_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/memory_persistence.cc b/Firestore/core/src/local/memory_persistence.cc index f1946661ba5..009036e1d8b 100644 --- a/Firestore/core/src/local/memory_persistence.cc +++ b/Firestore/core/src/local/memory_persistence.cc @@ -102,10 +102,6 @@ MemoryBundleCache* MemoryPersistence::bundle_cache() { return &bundle_cache_; } -MemoryGlobalsCache* MemoryPersistence::globals_cache() { - return &globals_cache_; -} - MemoryDocumentOverlayCache* MemoryPersistence::GetDocumentOverlayCache( const User& user) { auto iter = document_overlay_caches_.find(user); diff --git a/Firestore/core/src/local/memory_persistence.h b/Firestore/core/src/local/memory_persistence.h index bebadb86790..674577bbac2 100644 --- a/Firestore/core/src/local/memory_persistence.h +++ b/Firestore/core/src/local/memory_persistence.h @@ -27,7 +27,6 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/memory_bundle_cache.h" #include "Firestore/core/src/local/memory_document_overlay_cache.h" -#include "Firestore/core/src/local/memory_globals_cache.h" #include "Firestore/core/src/local/memory_index_manager.h" #include "Firestore/core/src/local/memory_mutation_queue.h" #include "Firestore/core/src/local/memory_remote_document_cache.h" @@ -91,8 +90,6 @@ class MemoryPersistence : public Persistence { MemoryBundleCache* bundle_cache() override; - MemoryGlobalsCache* globals_cache() override; - MemoryDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; @@ -141,8 +138,6 @@ class MemoryPersistence : public Persistence { MemoryBundleCache bundle_cache_; - MemoryGlobalsCache globals_cache_; - DocumentOverlayCaches document_overlay_caches_; MemoryOverlayMigrationManager overlay_migration_manager_; diff --git a/Firestore/core/src/local/persistence.h b/Firestore/core/src/local/persistence.h index 3e184e4bac8..b1f7ebde74f 100644 --- a/Firestore/core/src/local/persistence.h +++ b/Firestore/core/src/local/persistence.h @@ -36,7 +36,6 @@ namespace local { class BundleCache; class DocumentOverlayCache; -class GlobalsCache; class IndexManager; class MutationQueue; class OverlayMigrationManager; @@ -87,11 +86,6 @@ class Persistence { /** Releases any resources held during eager shutdown. */ virtual void Shutdown() = 0; - /** - * Returns GlobalCache representing a general purpose cache for global values. - */ - virtual GlobalsCache* globals_cache() = 0; - /** * Returns a MutationQueue representing the persisted mutations for the given * user. diff --git a/Firestore/core/test/unit/local/globals_cache_test.cc b/Firestore/core/test/unit/local/globals_cache_test.cc deleted file mode 100644 index 0945a3a7b35..00000000000 --- a/Firestore/core/test/unit/local/globals_cache_test.cc +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/test/unit/local/globals_cache_test.h" - -#include "Firestore/core/src/local/globals_cache.h" -#include "Firestore/core/src/local/persistence.h" -#include "Firestore/core/test/unit/testutil/testutil.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { - -using util::ComparisonResult; - -namespace local { - -GlobalsCacheTest::GlobalsCacheTest(std::unique_ptr persistence) - : persistence_(std::move(NOT_NULL(persistence))), - cache_(persistence_->globals_cache()) { -} - -GlobalsCacheTest::GlobalsCacheTest() : GlobalsCacheTest(GetParam()()) { -} - -namespace { - -TEST_P(GlobalsCacheTest, ReturnsEmptyBytestringWhenSessionTokenNotFound) { - persistence_->Run( - "test_returns_empty_bytestring_when_session_token_not_found", [&] { - auto expected = ByteString(); - EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), - ComparisonResult::Same); - }); -} - -TEST_P(GlobalsCacheTest, ReturnsSavedSessionToken) { - persistence_->Run("test_returns_saved_session_token", [&] { - auto expected = ByteString("magic"); - cache_->SetSessionToken(expected); - - EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), - ComparisonResult::Same); - - // Overwrite - expected = ByteString("science"); - cache_->SetSessionToken(expected); - - EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), - ComparisonResult::Same); - }); -} - -} // namespace - -} // namespace local -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/local/globals_cache_test.h b/Firestore/core/test/unit/local/globals_cache_test.h deleted file mode 100644 index aad64844339..00000000000 --- a/Firestore/core/test/unit/local/globals_cache_test.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ -#define FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ - -#include - -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { -namespace local { - -class Persistence; -class GlobalsCache; - -using FactoryFunc = std::unique_ptr (*)(); - -/** - * These are tests for any implementation of the GlobalsCache interface. - * - * To test a specific implementation of GlobalsCache: - * - * - Write a persistence factory function - * - Call INSTANTIATE_TEST_SUITE_P(MyNewGlobalsCacheTest, - * GlobalsCacheTest, - * testing::Values(PersistenceFactory)); - */ -class GlobalsCacheTest : public testing::Test, - public testing::WithParamInterface { - public: - GlobalsCacheTest(); - explicit GlobalsCacheTest(std::unique_ptr persistence); - ~GlobalsCacheTest() = default; - - protected: - std::unique_ptr persistence_; - GlobalsCache* cache_ = nullptr; -}; - -} // namespace local -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ diff --git a/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc b/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc deleted file mode 100644 index 4b6f11e5a48..00000000000 --- a/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/local/leveldb_persistence.h" -#include "Firestore/core/test/unit/local/globals_cache_test.h" -#include "Firestore/core/test/unit/local/persistence_testing.h" - -namespace firebase { -namespace firestore { -namespace local { -namespace { - -std::unique_ptr PersistenceFactory() { - return LevelDbPersistenceForTesting(); -} - -} // namespace - -INSTANTIATE_TEST_SUITE_P(LevelDbGlobalsCacheTest, - GlobalsCacheTest, - testing::Values(PersistenceFactory)); - -} // namespace local -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/local/memory_globals_cache_test.cc b/Firestore/core/test/unit/local/memory_globals_cache_test.cc deleted file mode 100644 index e3bcde58d96..00000000000 --- a/Firestore/core/test/unit/local/memory_globals_cache_test.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/local/memory_persistence.h" -#include "Firestore/core/test/unit/local/globals_cache_test.h" -#include "Firestore/core/test/unit/local/persistence_testing.h" - -namespace firebase { -namespace firestore { -namespace local { -namespace { - -std::unique_ptr PersistenceFactory() { - return MemoryPersistenceWithEagerGcForTesting(); -} - -} // namespace - -INSTANTIATE_TEST_SUITE_P(MemoryGloablsCacheTest, - GlobalsCacheTest, - testing::Values(PersistenceFactory)); - -} // namespace local -} // namespace firestore -} // namespace firebase From ca6ea18bad87718c8fe5650f796a5a9e6b13f62f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 25 Sep 2024 09:30:29 -0700 Subject: [PATCH 078/258] Revert "[Messaging] Prep #13691 (#13720) --- FirebaseMessaging/Sources/FIRMessaging.m | 7 ------- .../Sources/Public/FirebaseMessaging/FIRMessaging.h | 4 ---- .../Sources/Public/FirebaseMessaging/FirebaseMessaging.h | 2 -- 3 files changed, 13 deletions(-) diff --git a/FirebaseMessaging/Sources/FIRMessaging.m b/FirebaseMessaging/Sources/FIRMessaging.m index 6f160682e0a..af18ea77fea 100644 --- a/FirebaseMessaging/Sources/FIRMessaging.m +++ b/FirebaseMessaging/Sources/FIRMessaging.m @@ -40,10 +40,7 @@ #import "FirebaseMessaging/Sources/FIRMessagingUtilities.h" #import "FirebaseMessaging/Sources/FIRMessaging_Private.h" #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h" -#if __has_include( \ - "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") #import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h" -#endif // __has_include("FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") #import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenInfo.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h" @@ -134,8 +131,6 @@ + (FIRMessaging *)messaging { return (FIRMessaging *)instance; } -#if __has_include( \ - "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") + (FIRMessagingExtensionHelper *)extensionHelper { static dispatch_once_t once; static FIRMessagingExtensionHelper *extensionHelper; @@ -144,8 +139,6 @@ + (FIRMessagingExtensionHelper *)extensionHelper { }); return extensionHelper; } -#endif // __has_include("FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h") - - (instancetype)initWithAnalytics:(nullable id)analytics userDefaults:(GULUserDefaults *)defaults heartbeatLogger:(FIRHeartbeatLogger *)heartbeatLogger { diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h index 5a7243cbacc..4f5209bb935 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h @@ -136,9 +136,7 @@ NS_SWIFT_NAME(MessagingMessageInfo) @end @class FIRMessaging; -#if __has_include("FIRMessagingExtensionHelper.h") @class FIRMessagingExtensionHelper; -#endif // __has_include("FIRMessagingExtensionHelper.h") /** * A protocol to handle token update or data message delivery from FCM. @@ -186,7 +184,6 @@ NS_SWIFT_NAME(Messaging) */ + (instancetype)messaging NS_SWIFT_NAME(messaging()); -#if __has_include("FIRMessagingExtensionHelper.h") /** * Use the MessagingExtensionHelper to populate rich UI content for your notifications. * For example, if an image URL is set in your notification payload or on the console, @@ -197,7 +194,6 @@ NS_SWIFT_NAME(Messaging) */ + (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) NS_AVAILABLE(10.14, 10.0); -#endif // __has_include("FIRMessagingExtensionHelper.h") /** * Unavailable. Use +messaging instead. diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h index a46524b399c..c5d0bd05046 100755 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h @@ -15,6 +15,4 @@ */ #import "FIRMessaging.h" -#if __has_include("FIRMessagingExtensionHelper.h") #import "FIRMessagingExtensionHelper.h" -#endif // __has_include("FIRMessagingExtensionHelper.h") From b1a19b44f30a2a0288687b9f85b7fd436f42a82e Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 14:43:36 -0400 Subject: [PATCH 079/258] [Vertex AI] Optional `CountTokensResponse.totalBillableCharacters` (#13721) --- FirebaseVertexAI/CHANGELOG.md | 4 ++++ .../Sources/CountTokensRequest.swift | 18 ++---------------- .../Tests/Unit/GenerativeModelTests.swift | 2 +- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 9950b1c0190..f3d471969c3 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -18,6 +18,10 @@ accepts an array of *optional* parameters instead of a list of *required* parameters; if a parameter is not listed as optional it is assumed to be required. (#13616) +- [changed] **Breaking Change**: `CountTokensResponse.totalBillableCharacters` + is now optional (`Int?`); it may be `null` in cases such as when a + `GenerateContentRequest` contains only images or other non-text content. + (#13721) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/CountTokensRequest.swift b/FirebaseVertexAI/Sources/CountTokensRequest.swift index feddf9b2a05..6b052da19e6 100644 --- a/FirebaseVertexAI/Sources/CountTokensRequest.swift +++ b/FirebaseVertexAI/Sources/CountTokensRequest.swift @@ -40,7 +40,7 @@ public struct CountTokensResponse { /// /// > Important: This does not include billable image, video or other non-text input. See /// [Vertex AI pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing) for details. - public let totalBillableCharacters: Int + public let totalBillableCharacters: Int? } // MARK: - Codable Conformances @@ -53,18 +53,4 @@ extension CountTokensRequest: Encodable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension CountTokensResponse: Decodable { - enum CodingKeys: CodingKey { - case totalTokens - case totalBillableCharacters - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - totalTokens = try container.decode(Int.self, forKey: .totalTokens) - totalBillableCharacters = try container.decodeIfPresent( - Int.self, - forKey: .totalBillableCharacters - ) ?? 0 - } -} +extension CountTokensResponse: Decodable {} diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 5b30a56039c..c7cfe36df70 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1194,7 +1194,7 @@ final class GenerativeModelTests: XCTestCase { )) XCTAssertEqual(response.totalTokens, 258) - XCTAssertEqual(response.totalBillableCharacters, 0) + XCTAssertNil(response.totalBillableCharacters) } func testCountTokens_modelNotFound() async throws { From 7bb68adb8532a07901209820c7e0955a8469980b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 17:31:55 -0400 Subject: [PATCH 080/258] [Vertex AI] Migrate logging to `FirebaseLogger` (#13638) --- .../Sources/GenerateContentResponse.swift | 24 ++-- .../Sources/GenerativeAIService.swift | 54 ++++++--- .../Sources/GenerativeModel.swift | 18 +-- FirebaseVertexAI/Sources/Logging.swift | 71 ----------- FirebaseVertexAI/Sources/Safety.swift | 12 +- FirebaseVertexAI/Sources/VertexLog.swift | 112 ++++++++++++++++++ 6 files changed, 178 insertions(+), 113 deletions(-) delete mode 100644 FirebaseVertexAI/Sources/Logging.swift create mode 100644 FirebaseVertexAI/Sources/VertexLog.swift diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 31566889a0c..66dd83aec16 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -42,8 +42,10 @@ public struct GenerateContentResponse: Sendable { /// The response's content as text, if it exists. public var text: String? { guard let candidate = candidates.first else { - Logging.default - .error("[FirebaseVertexAI] Could not get text from a response that had no candidates.") + VertexLog.error( + code: .generateContentResponseNoCandidates, + "Could not get text from a response that had no candidates." + ) return nil } let textValues: [String] = candidate.content.parts.compactMap { part in @@ -53,8 +55,10 @@ public struct GenerateContentResponse: Sendable { return text } guard textValues.count > 0 else { - Logging.default - .error("[FirebaseVertexAI] Could not get a text part from the first candidate.") + VertexLog.error( + code: .generateContentResponseNoText, + "Could not get a text part from the first candidate." + ) return nil } return textValues.joined(separator: " ") @@ -330,8 +334,10 @@ extension FinishReason: Decodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedFinishReason = FinishReason(rawValue: value) else { - Logging.default - .error("[FirebaseVertexAI] Unrecognized FinishReason with value \"\(value)\".") + VertexLog.error( + code: .generateContentResponseUnrecognizedFinishReason, + "Unrecognized FinishReason with value \"\(value)\"." + ) self = .unknown return } @@ -345,8 +351,10 @@ extension PromptFeedback.BlockReason: Decodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedBlockReason = PromptFeedback.BlockReason(rawValue: value) else { - Logging.default - .error("[FirebaseVertexAI] Unrecognized BlockReason with value \"\(value)\".") + VertexLog.error( + code: .generateContentResponseUnrecognizedBlockReason, + "Unrecognized BlockReason with value \"\(value)\"." + ) self = .unknown return } diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index 3ebbf69f102..e8f7525d3fd 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -16,6 +16,7 @@ import FirebaseAppCheckInterop import FirebaseAuthInterop import FirebaseCore import Foundation +import os.log @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct GenerativeAIService { @@ -60,9 +61,15 @@ struct GenerativeAIService { // Verify the status code is 200 guard response.statusCode == 200 else { - Logging.network.error("[FirebaseVertexAI] The server responded with an error: \(response)") + VertexLog.error( + code: .loadRequestResponseError, + "The server responded with an error: \(response)" + ) if let responseString = String(data: data, encoding: .utf8) { - Logging.default.error("[FirebaseVertexAI] Response payload: \(responseString)") + VertexLog.error( + code: .loadRequestResponseErrorPayload, + "Response payload: \(responseString)" + ) } throw parseError(responseData: data) @@ -108,14 +115,19 @@ struct GenerativeAIService { // Verify the status code is 200 guard response.statusCode == 200 else { - Logging.network - .error("[FirebaseVertexAI] The server responded with an error: \(response)") + VertexLog.error( + code: .loadRequestStreamResponseError, + "The server responded with an error: \(response)" + ) var responseBody = "" for try await line in stream.lines { responseBody += line + "\n" } - Logging.default.error("[FirebaseVertexAI] Response payload: \(responseBody)") + VertexLog.error( + code: .loadRequestStreamResponseErrorPayload, + "Response payload: \(responseBody)" + ) continuation.finish(throwing: parseError(responseBody: responseBody)) return @@ -127,7 +139,7 @@ struct GenerativeAIService { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase for try await line in stream.lines { - Logging.network.debug("[FirebaseVertexAI] Stream response: \(line)") + VertexLog.debug(code: .loadRequestStreamResponseLine, "Stream response: \(line)") if line.hasPrefix("data:") { // We can assume 5 characters since it's utf-8 encoded, removing `data:`. @@ -179,8 +191,10 @@ struct GenerativeAIService { let tokenResult = await appCheck.getToken(forcingRefresh: false) urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") if let error = tokenResult.error { - Logging.default - .debug("[FirebaseVertexAI] Failed to fetch AppCheck token. Error: \(error)") + VertexLog.error( + code: .appCheckTokenFetchFailed, + "Failed to fetch AppCheck token. Error: \(error)" + ) } } @@ -202,10 +216,10 @@ struct GenerativeAIService { private func httpResponse(urlResponse: URLResponse) throws -> HTTPURLResponse { // Verify the status code is 200 guard let response = urlResponse as? HTTPURLResponse else { - Logging.default - .error( - "[FirebaseVertexAI] Response wasn't an HTTP response, internal error \(urlResponse)" - ) + VertexLog.error( + code: .generativeAIServiceNonHTTPResponse, + "Response wasn't an HTTP response, internal error \(urlResponse)" + ) throw NSError( domain: "com.google.generative-ai", code: -1, @@ -253,7 +267,7 @@ struct GenerativeAIService { // These errors do not produce specific GenerateContentError or CountTokensError cases. private func logRPCError(_ error: RPCError) { if error.isFirebaseMLServiceDisabledError() { - Logging.default.error(""" + VertexLog.error(code: .firebaseMLAPIDisabled, """ The Vertex AI for Firebase SDK requires the Firebase ML API `firebaseml.googleapis.com` to \ be enabled for your project. Get started in the Firebase Console \ (https://console.firebase.google.com/project/\(projectID)/genai/vertex) or verify that the \ @@ -269,9 +283,12 @@ struct GenerativeAIService { return try JSONDecoder().decode(type, from: data) } catch { if let json = String(data: data, encoding: .utf8) { - Logging.network.error("[FirebaseVertexAI] JSON response: \(json)") + VertexLog.error(code: .loadRequestParseResponseFailedJSON, "JSON response: \(json)") } - Logging.default.error("[FirebaseVertexAI] Error decoding server JSON: \(error)") + VertexLog.error( + code: .loadRequestParseResponseFailedJSONError, + "Error decoding server JSON: \(error)" + ) throw error } } @@ -297,9 +314,12 @@ struct GenerativeAIService { } private func printCURLCommand(from request: URLRequest) { + guard VertexLog.additionalLoggingEnabled() else { + return + } let command = cURLCommand(from: request) - Logging.verbose.debug(""" - [FirebaseVertexAI] Creating request with the equivalent cURL command: + os_log(.debug, log: VertexLog.logObject, """ + \(VertexLog.service) Creating request with the equivalent cURL command: ----- cURL command ----- \(command, privacy: .private) ------------------------ diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 28d3ca4ba88..25e859aebac 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -85,23 +85,15 @@ public final class GenerativeModel { self.systemInstruction = systemInstruction self.requestOptions = requestOptions - if Logging.additionalLoggingEnabled() { - if ProcessInfo.processInfo.arguments.contains(Logging.migrationEnableArgumentKey) { - Logging.verbose.debug(""" - [FirebaseVertexAI] Verbose logging enabled with the \ - \(Logging.migrationEnableArgumentKey, privacy: .public) launch argument; please migrate to \ - the \(Logging.enableArgumentKey, privacy: .public) argument to ensure future compatibility. - """) - } else { - Logging.verbose.debug("[FirebaseVertexAI] Verbose logging enabled.") - } + if VertexLog.additionalLoggingEnabled() { + VertexLog.debug(code: .verboseLoggingEnabled, "Verbose logging enabled.") } else { - Logging.default.info(""" + VertexLog.info(code: .verboseLoggingDisabled, """ [FirebaseVertexAI] To enable additional logging, add \ - `\(Logging.enableArgumentKey, privacy: .public)` as a launch argument in Xcode. + `\(VertexLog.enableArgumentKey)` as a launch argument in Xcode. """) } - Logging.default.debug("[FirebaseVertexAI] Model \(name, privacy: .public) initialized.") + VertexLog.debug(code: .generativeModelInitialized, "Model \(name) initialized.") } /// Generates content from String and/or image inputs, given to the model as a prompt, that are diff --git a/FirebaseVertexAI/Sources/Logging.swift b/FirebaseVertexAI/Sources/Logging.swift deleted file mode 100644 index 5806ac2368a..00000000000 --- a/FirebaseVertexAI/Sources/Logging.swift +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Foundation -import OSLog - -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -struct Logging { - /// Subsystem that should be used for all Loggers. - static let subsystem = "com.google.firebase.vertex-ai" - - /// Default category used for most loggers, unless specialized. - static let defaultCategory = "" - - /// The argument required to enable additional logging. - static let enableArgumentKey = "-FIRDebugEnabled" - - /// The argument required to enable additional logging in the Google AI SDK; used for migration. - /// - /// To facilitate migration between the SDKs, this launch argument is also accepted to enable - /// additional logging at this time, though it is expected to be removed in the future. - static let migrationEnableArgumentKey = "-GoogleGenerativeAIDebugLogEnabled" - - // No initializer available. - @available(*, unavailable) - private init() {} - - /// The default logger that is visible for all users. Note: we shouldn't be using anything lower - /// than `.notice`. - static let `default` = Logger(subsystem: subsystem, category: defaultCategory) - - /// A non default - static let network: Logger = { - if additionalLoggingEnabled() { - return Logger(subsystem: subsystem, category: "NetworkResponse") - } else { - // Return a valid logger that's using `OSLog.disabled` as the logger, hiding everything. - return Logger(.disabled) - } - }() - - /// - static let verbose: Logger = { - if additionalLoggingEnabled() { - return Logger(subsystem: subsystem, category: defaultCategory) - } else { - // Return a valid logger that's using `OSLog.disabled` as the logger, hiding everything. - return Logger(.disabled) - } - }() - - /// Returns `true` if additional logging has been enabled via a launch argument. - static func additionalLoggingEnabled() -> Bool { - let arguments = ProcessInfo.processInfo.arguments - if arguments.contains(enableArgumentKey) || arguments.contains(migrationEnableArgumentKey) { - return true - } - return false - } -} diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 2bea6eda5a9..a57900e7317 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -121,8 +121,10 @@ extension SafetyRating.HarmProbability: Decodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedProbability = SafetyRating.HarmProbability(rawValue: value) else { - Logging.default - .error("[FirebaseVertexAI] Unrecognized HarmProbability with value \"\(value)\".") + VertexLog.error( + code: .generateContentResponseUnrecognizedHarmProbability, + "Unrecognized HarmProbability with value \"\(value)\"." + ) self = .unknown return } @@ -139,8 +141,10 @@ extension HarmCategory: Codable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) guard let decodedCategory = HarmCategory(rawValue: value) else { - Logging.default - .error("[FirebaseVertexAI] Unrecognized HarmCategory with value \"\(value)\".") + VertexLog.error( + code: .generateContentResponseUnrecognizedHarmCategory, + "Unrecognized HarmCategory with value \"\(value)\"." + ) self = .unknown return } diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift new file mode 100644 index 00000000000..c69d019975f --- /dev/null +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -0,0 +1,112 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +import os.log + +@_implementationOnly import FirebaseCoreExtension + +enum VertexLog { + /// Log message codes for the Vertex AI SDK + /// + /// These codes should ideally not be re-used in order to facilitate matching error codes in + /// support requests to lines in the SDK. These codes should range between 0 and 999999 to avoid + /// being truncated in log messages. + enum MessageCode: Int { + // Logging Configuration + case verboseLoggingDisabled = 100 + case verboseLoggingEnabled = 101 + + // API Enablement Errors + case firebaseMLAPIDisabled = 200 + + // Model Configuration + case generativeModelInitialized = 1000 + + // Network Errors + case generativeAIServiceNonHTTPResponse = 2000 + case loadRequestResponseError = 2001 + case loadRequestResponseErrorPayload = 2002 + case loadRequestStreamResponseError = 2003 + case loadRequestStreamResponseErrorPayload = 2004 + + // Parsing Errors + case loadRequestParseResponseFailedJSON = 3000 + case loadRequestParseResponseFailedJSONError = 3001 + case generateContentResponseUnrecognizedFinishReason = 3002 + case generateContentResponseUnrecognizedBlockReason = 3003 + case generateContentResponseUnrecognizedBlockThreshold = 3004 + case generateContentResponseUnrecognizedHarmProbability = 3005 + case generateContentResponseUnrecognizedHarmCategory = 3006 + + // SDK State Errors + case generateContentResponseNoCandidates = 4000 + case generateContentResponseNoText = 4001 + case appCheckTokenFetchFailed = 4002 + + // SDK Debugging + case loadRequestStreamResponseLine = 5000 + } + + /// Subsystem that should be used for all Loggers. + static let subsystem = "com.google.firebase" + + /// Log identifier for the Vertex AI SDK. + /// + /// > Note: This corresponds to the `category` in `OSLog`. + static let service = "[FirebaseVertexAI]" + + /// The raw `OSLog` log object. + /// + /// > Important: This is only needed for direct `os_log` usage. + static let logObject = OSLog(subsystem: subsystem, category: service) + + /// The argument required to enable additional logging. + static let enableArgumentKey = "-FIRDebugEnabled" + + static func log(level: FirebaseLoggerLevel, code: MessageCode, _ message: String) { + let messageCode = String(format: "I-VTX%06d", code.rawValue) + FirebaseLogger.log( + level: level, + service: VertexLog.service, + code: messageCode, + message: message + ) + } + + static func error(code: MessageCode, _ message: String) { + log(level: .error, code: code, message) + } + + static func warning(code: MessageCode, _ message: String) { + log(level: .warning, code: code, message) + } + + static func notice(code: MessageCode, _ message: String) { + log(level: .notice, code: code, message) + } + + static func info(code: MessageCode, _ message: String) { + log(level: .info, code: code, message) + } + + static func debug(code: MessageCode, _ message: String) { + log(level: .debug, code: code, message) + } + + /// Returns `true` if additional logging has been enabled via a launch argument. + static func additionalLoggingEnabled() -> Bool { + return ProcessInfo.processInfo.arguments.contains(enableArgumentKey) + } +} From 801cbb3cea33f272d75a744673f895bac1203885 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 18:22:56 -0400 Subject: [PATCH 081/258] [Vertex AI] Add Vertex AI in Firebase API enablement logging (#13724) --- FirebaseVertexAI/Sources/Constants.swift | 3 ++ FirebaseVertexAI/Sources/Errors.swift | 22 ++++++-- .../Sources/GenerativeAIRequest.swift | 2 + .../Sources/GenerativeAIService.swift | 12 ++++- FirebaseVertexAI/Sources/VertexLog.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 52 +++++++++++++++++++ 6 files changed, 87 insertions(+), 6 deletions(-) diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseVertexAI/Sources/Constants.swift index 8f1768d7082..e3f2d45d6df 100644 --- a/FirebaseVertexAI/Sources/Constants.swift +++ b/FirebaseVertexAI/Sources/Constants.swift @@ -21,5 +21,8 @@ import Foundation /// Constants associated with the Vertex AI for Firebase SDK. enum Constants { /// The Vertex AI backend endpoint URL. + /// + /// TODO(andrewheard): Update to "https://firebasevertexai.googleapis.com" after the Vertex AI in + /// Firebase API launch. static let baseURL = "https://firebaseml.googleapis.com" } diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseVertexAI/Sources/Errors.swift index 17a4dd9e201..57fbe826580 100644 --- a/FirebaseVertexAI/Sources/Errors.swift +++ b/FirebaseVertexAI/Sources/Errors.swift @@ -31,9 +31,14 @@ struct RPCError: Error { self.details = details } + // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. func isFirebaseMLServiceDisabledError() -> Bool { return details.contains { $0.isFirebaseMLServiceDisabledErrorDetails() } } + + func isVertexAIInFirebaseServiceDisabledError() -> Bool { + return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() } + } } extension RPCError: Decodable { @@ -86,17 +91,26 @@ struct ErrorDetails { return type == ErrorDetails.errorInfoType } + func isServiceDisabledError() -> Bool { + return isErrorInfo() && reason == "SERVICE_DISABLED" && domain == "googleapis.com" + } + + // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. func isFirebaseMLServiceDisabledErrorDetails() -> Bool { - guard isErrorInfo() else { + guard isServiceDisabledError() else { return false } - guard reason == "SERVICE_DISABLED" else { + guard let metadata, metadata["service"] == "firebaseml.googleapis.com" else { return false } - guard domain == "googleapis.com" else { + return true + } + + func isVertexAIInFirebaseServiceDisabledErrorDetails() -> Bool { + guard isServiceDisabledError() else { return false } - guard let metadata, metadata["service"] == "firebaseml.googleapis.com" else { + guard let metadata, metadata["service"] == "firebasevertexai.googleapis.com" else { return false } return true diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index 08b178d5808..327b7d4f1b4 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -31,6 +31,8 @@ public struct RequestOptions { let timeout: TimeInterval? /// The API version to use in requests to the backend. + /// + /// TODO(andrewheard): Update to "v1beta" after the Vertex AI in Firebase API launch. let apiVersion = "v2beta" /// Initializes a request options object. diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index e8f7525d3fd..02995926abb 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -266,8 +266,9 @@ struct GenerativeAIService { // Log specific RPC errors that cannot be mitigated or handled by user code. // These errors do not produce specific GenerateContentError or CountTokensError cases. private func logRPCError(_ error: RPCError) { + // TODO(andrewheard): Remove this check after the Vertex AI in Firebase API launch. if error.isFirebaseMLServiceDisabledError() { - VertexLog.error(code: .firebaseMLAPIDisabled, """ + VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ The Vertex AI for Firebase SDK requires the Firebase ML API `firebaseml.googleapis.com` to \ be enabled for your project. Get started in the Firebase Console \ (https://console.firebase.google.com/project/\(projectID)/genai/vertex) or verify that the \ @@ -276,6 +277,15 @@ struct GenerativeAIService { \(projectID)). """) } + + if error.isVertexAIInFirebaseServiceDisabledError() { + VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ + The Vertex AI for Firebase SDK requires the Firebase Vertex AI API \ + `firebasevertexai.googleapis.com` to be enabled for your project. Get started by visiting \ + the Firebase Console at: \ + https://console.firebase.google.com/project/\(projectID)/genai/vertex + """) + } } private func parseResponse(_ type: T.Type, from data: Data) throws -> T { diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index c69d019975f..bd400c200c2 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -29,7 +29,7 @@ enum VertexLog { case verboseLoggingEnabled = 101 // API Enablement Errors - case firebaseMLAPIDisabled = 200 + case vertexAIInFirebaseAPIDisabled = 200 // Model Configuration case generativeModelInitialized = 1000 diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index c7cfe36df70..e7bd91dcff1 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -453,6 +453,7 @@ final class GenerativeModelTests: XCTestCase { } } + // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. func testGenerateContent_failure_firebaseMLAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -476,6 +477,30 @@ final class GenerativeModelTests: XCTestCase { } } + func testGenerateContent_failure_firebaseVertexAIAPINotEnabled() async throws { + let expectedStatusCode = 403 + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-failure-firebasevertexai-api-not-enabled", + withExtension: "json", + statusCode: expectedStatusCode + ) + + do { + _ = try await model.generateContent(testPrompt) + XCTFail("Should throw GenerateContentError.internalError; no error thrown.") + } catch let GenerateContentError.internalError(error as RPCError) { + XCTAssertEqual(error.httpResponseCode, expectedStatusCode) + XCTAssertEqual(error.status, .permissionDenied) + XCTAssertTrue(error.message + .starts(with: "Vertex AI in Firebase API has not been used in project")) + XCTAssertTrue(error.isVertexAIInFirebaseServiceDisabledError()) + return + } catch { + XCTFail("Should throw GenerateContentError.internalError(RPCError); error thrown: \(error)") + } + } + func testGenerateContent_failure_emptyContent() async throws { MockURLProtocol .requestHandler = try httpRequestHandler( @@ -774,6 +799,7 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should have caught an error.") } + // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. func testGenerateContentStream_failure_firebaseMLAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -799,6 +825,32 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should have caught an error.") } + func testGenerateContentStream_failure_vertexAIInFirebaseAPINotEnabled() async throws { + let expectedStatusCode = 403 + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-failure-firebasevertexai-api-not-enabled", + withExtension: "json", + statusCode: expectedStatusCode + ) + + do { + let stream = try model.generateContentStream(testPrompt) + for try await _ in stream { + XCTFail("No content is there, this shouldn't happen.") + } + } catch let GenerateContentError.internalError(error as RPCError) { + XCTAssertEqual(error.httpResponseCode, expectedStatusCode) + XCTAssertEqual(error.status, .permissionDenied) + XCTAssertTrue(error.message + .starts(with: "Vertex AI in Firebase API has not been used in project")) + XCTAssertTrue(error.isVertexAIInFirebaseServiceDisabledError()) + return + } + + XCTFail("Should have caught an error.") + } + func testGenerateContentStream_failureEmptyContent() async throws { MockURLProtocol .requestHandler = try httpRequestHandler( From 5ed86cdada5fd7e2aab7ee2d2b7249a2a25bfbc8 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 26 Sep 2024 11:35:04 -0400 Subject: [PATCH 082/258] [Vertex AI] Set default request timeout to 180 seconds (#13722) --- FirebaseVertexAI/CHANGELOG.md | 3 +++ FirebaseVertexAI/Sources/GenerativeAIRequest.swift | 7 +++---- FirebaseVertexAI/Sources/GenerativeAIService.swift | 5 +---- FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 10 +--------- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index f3d471969c3..21271329704 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -22,6 +22,9 @@ is now optional (`Int?`); it may be `null` in cases such as when a `GenerateContentRequest` contains only images or other non-text content. (#13721) +- [changed] The default request timeout is now 180 seconds instead of the + platform-default value of 60 seconds for a `URLRequest`; this timeout may + still be customized in `RequestOptions`. (#13722) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index 327b7d4f1b4..08ac22eaa5f 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -28,7 +28,7 @@ protocol GenerativeAIRequest: Encodable { public struct RequestOptions { /// The request’s timeout interval in seconds; if not specified uses the default value for a /// `URLRequest`. - let timeout: TimeInterval? + let timeout: TimeInterval /// The API version to use in requests to the backend. /// @@ -38,9 +38,8 @@ public struct RequestOptions { /// Initializes a request options object. /// /// - Parameters: - /// - timeout The request’s timeout interval in seconds; if not specified uses the default value - /// for a `URLRequest`. - public init(timeout: TimeInterval? = nil) { + /// - timeout The request’s timeout interval in seconds; defaults to 180 seconds. + public init(timeout: TimeInterval = 180.0) { self.timeout = timeout } } diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index 02995926abb..0e2e39169ea 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -205,10 +205,7 @@ struct GenerativeAIService { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase urlRequest.httpBody = try encoder.encode(request) - - if let timeoutInterval = request.options.timeout { - urlRequest.timeoutInterval = timeoutInterval - } + urlRequest.timeoutInterval = request.options.timeout return urlRequest } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index e7bd91dcff1..4af77cf3811 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1319,7 +1319,7 @@ final class GenerativeModelTests: XCTestCase { private func httpRequestHandler(forResource name: String, withExtension ext: String, statusCode: Int = 200, - timeout: TimeInterval = URLRequest.defaultTimeoutInterval(), + timeout: TimeInterval = RequestOptions().timeout, appCheckToken: String? = nil, authToken: String? = nil) throws -> ((URLRequest) throws -> ( URLResponse, @@ -1368,14 +1368,6 @@ private extension String { } } -private extension URLRequest { - /// Returns the default `timeoutInterval` for a `URLRequest`. - static func defaultTimeoutInterval() -> TimeInterval { - let placeholderURL = URL(string: "https://example.com")! - return URLRequest(url: placeholderURL).timeoutInterval - } -} - @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) class AppCheckInteropFake: NSObject, AppCheckInterop { /// The placeholder token value returned when an error occurs From f909f901bfba9e27e4e9da83242a4915d6dd64bb Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 26 Sep 2024 18:02:23 -0700 Subject: [PATCH 083/258] Firestore SPM binary for 11.3.0 (#13730) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index ce9d632e330..86d77b45682 100644 --- a/Package.swift +++ b/Package.swift @@ -1511,8 +1511,8 @@ func firestoreTargets() -> [Target] { } else { return .binaryTarget( name: "FirebaseFirestoreInternal", - url: "https://dl.google.com/firebase/ios/bin/firestore/11.2.0/rc0/FirebaseFirestoreInternal.zip", - checksum: "821acae8d3b79c6d35539d87926da8aeae4a63878bec2987b01cb885b5120df2" + url: "https://dl.google.com/firebase/ios/bin/firestore/11.3.0/rc0/FirebaseFirestoreInternal.zip", + checksum: "214f91ae3ad87fce55155ea3e6dacda4f237e703ea3935353c7c6819eddb1c17" ) } }() From 4c9cc644732884134d914db3cf0cda22f52bc2ab Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 27 Sep 2024 17:33:14 -0400 Subject: [PATCH 084/258] [Vertex AI] Remove `ImageConversionError` from public API (#13735) --- FirebaseVertexAI/CHANGELOG.md | 3 ++ .../Sources/GenerateContentError.swift | 6 +-- .../Sources/PartsRepresentable+Image.swift | 28 ++++--------- .../Tests/Unit/PartsRepresentableTests.swift | 41 ++++++------------- 4 files changed, 25 insertions(+), 53 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 21271329704..c904c0cfbc5 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -22,6 +22,9 @@ is now optional (`Int?`); it may be `null` in cases such as when a `GenerateContentRequest` contains only images or other non-text content. (#13721) +- [changed] **Breaking Change**: The `ImageConversionError` enum is no longer + public; image conversion errors are still reported as + `GenerateContentError.promptImageContentError`. (#13735) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sources/GenerateContentError.swift b/FirebaseVertexAI/Sources/GenerateContentError.swift index 5428223853f..b5b52d0acd5 100644 --- a/FirebaseVertexAI/Sources/GenerateContentError.swift +++ b/FirebaseVertexAI/Sources/GenerateContentError.swift @@ -17,12 +17,12 @@ import Foundation /// Errors that occur when generating content from a model. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public enum GenerateContentError: Error { - /// An error occurred when constructing the prompt. Examine the related error for details. - case promptImageContentError(underlying: ImageConversionError) - /// An internal error occurred. See the underlying error for more context. case internalError(underlying: Error) + /// An error occurred when constructing the prompt. Examine the related error for details. + case promptImageContentError(underlying: Error) + /// A prompt was blocked. See the response's `promptFeedback.blockReason` for more information. case promptBlocked(response: GenerateContentResponse) diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift index 4e1dc2fea5e..6b2cc977889 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift @@ -24,29 +24,15 @@ private let imageCompressionQuality: CGFloat = 0.8 /// An enum describing failures that can occur when converting image types to model content data. /// For some image types like `CIImage`, creating valid model content requires creating a JPEG /// representation of the image that may not yet exist, which may be computationally expensive. -public enum ImageConversionError: Error { - /// The image that could not be converted. - public enum SourceImage { - #if canImport(UIKit) - case uiImage(UIImage) - #elseif canImport(AppKit) - case nsImage(NSImage) - #endif // canImport(UIKit) - case cgImage(CGImage) - #if canImport(CoreImage) - case ciImage(CIImage) - #endif // canImport(CoreImage) - } - +enum ImageConversionError: Error { /// The image (the receiver of the call `toModelContentParts()`) was invalid. case invalidUnderlyingImage /// A valid image destination could not be allocated. case couldNotAllocateDestination - /// JPEG image data conversion failed, accompanied by the original image, which may be an - /// instance of `NSImage`, `UIImage`, `CGImage`, or `CIImage`. - case couldNotConvertToJPEG(SourceImage) + /// JPEG image data conversion failed. + case couldNotConvertToJPEG } #if canImport(UIKit) @@ -55,7 +41,7 @@ public enum ImageConversionError: Error { extension UIImage: ThrowingPartsRepresentable { public func tryPartsValue() throws -> [ModelContent.Part] { guard let data = jpegData(compressionQuality: imageCompressionQuality) else { - throw ImageConversionError.couldNotConvertToJPEG(.uiImage(self)) + throw ImageConversionError.couldNotConvertToJPEG } return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] } @@ -72,7 +58,7 @@ public enum ImageConversionError: Error { let bmp = NSBitmapImageRep(cgImage: cgImage) guard let data = bmp.representation(using: .jpeg, properties: [.compressionFactor: 0.8]) else { - throw ImageConversionError.couldNotConvertToJPEG(.nsImage(self)) + throw ImageConversionError.couldNotConvertToJPEG } return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] } @@ -97,7 +83,7 @@ public enum ImageConversionError: Error { if CGImageDestinationFinalize(imageDestination) { return [.inlineData(mimetype: "image/jpeg", output as Data)] } - throw ImageConversionError.couldNotConvertToJPEG(.cgImage(self)) + throw ImageConversionError.couldNotConvertToJPEG } } #endif // !os(watchOS) @@ -118,7 +104,7 @@ public enum ImageConversionError: Error { if let jpegData = jpegData { return [.inlineData(mimetype: "image/jpeg", jpegData)] } - throw ImageConversionError.couldNotConvertToJPEG(.ciImage(self)) + throw ImageConversionError.couldNotConvertToJPEG } } #endif // canImport(CoreImage) diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift index bd539d825a8..073f6582721 100644 --- a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift @@ -13,7 +13,6 @@ // limitations under the License. import CoreGraphics -import FirebaseVertexAI import XCTest #if canImport(UIKit) import UIKit @@ -24,6 +23,8 @@ import XCTest import CoreImage #endif // canImport(CoreImage) +@testable import FirebaseVertexAI + @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartsRepresentableTests: XCTestCase { #if !os(watchOS) @@ -61,22 +62,13 @@ final class PartsRepresentableTests: XCTestCase { do { _ = try image.tryPartsValue() XCTFail("Expected model content from invalid image to error") - } catch { - guard let imageError = (error as? ImageConversionError) else { - XCTFail("Got unexpected error type: \(error)") - return - } - switch imageError { - case let .couldNotConvertToJPEG(source): - guard case let .ciImage(ciImage) = source else { - XCTFail("Unexpected image source: \(source)") - return - } - XCTAssertEqual(ciImage, image) - default: - XCTFail("Expected image conversion error, got \(imageError) instead") + } catch let imageError as ImageConversionError { + guard case .couldNotConvertToJPEG = imageError else { + XCTFail("Expected JPEG conversion error, got \(imageError) instead.") return } + } catch { + XCTFail("Got unexpected error type: \(error)") } } #endif // canImport(CoreImage) @@ -87,22 +79,13 @@ final class PartsRepresentableTests: XCTestCase { do { _ = try image.tryPartsValue() XCTFail("Expected model content from invalid image to error") - } catch { - guard let imageError = (error as? ImageConversionError) else { - XCTFail("Got unexpected error type: \(error)") - return - } - switch imageError { - case let .couldNotConvertToJPEG(source): - guard case let .uiImage(uiImage) = source else { - XCTFail("Unexpected image source: \(source)") - return - } - XCTAssertEqual(uiImage, image) - default: - XCTFail("Expected image conversion error, got \(imageError) instead") + } catch let imageError as ImageConversionError { + guard case .couldNotConvertToJPEG = imageError else { + XCTFail("Expected JPEG conversion error, got \(imageError) instead.") return } + } catch { + XCTFail("Got unexpected error type: \(error)") } } From 78c341f1bd054b2bba94f445e8f9c27e61fa28bc Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 27 Sep 2024 18:09:06 -0400 Subject: [PATCH 085/258] [Vertex AI] Remove `CountTokensError` enum (#13736) --- FirebaseVertexAI/CHANGELOG.md | 3 +++ .../Sources/GenerativeModel.swift | 22 +++++-------------- .../Tests/Unit/GenerativeModelTests.swift | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index c904c0cfbc5..25f3f3181e0 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -25,6 +25,9 @@ - [changed] **Breaking Change**: The `ImageConversionError` enum is no longer public; image conversion errors are still reported as `GenerateContentError.promptImageContentError`. (#13735) +- [changed] **Breaking Change**: The `CountTokensError` enum has been removed; + errors occurring in `GenerativeModel.countTokens(...)` are now thrown directly + instead of being wrapped in a `CountTokensError.internalError`. (#13736) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 25e859aebac..a5a8933e435 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -269,16 +269,12 @@ public final class GenerativeModel { /// invalid. public func countTokens(_ content: @autoclosure () throws -> [ModelContent]) async throws -> CountTokensResponse { - do { - let countTokensRequest = try CountTokensRequest( - model: modelResourceName, - contents: content(), - options: requestOptions - ) - return try await generativeAIService.loadRequest(request: countTokensRequest) - } catch { - throw CountTokensError.internalError(underlying: error) - } + let countTokensRequest = try CountTokensRequest( + model: modelResourceName, + contents: content(), + options: requestOptions + ) + return try await generativeAIService.loadRequest(request: countTokensRequest) } /// Returns a `GenerateContentError` (for public consumption) from an internal error. @@ -291,9 +287,3 @@ public final class GenerativeModel { return GenerateContentError.internalError(underlying: error) } } - -/// An error thrown in `GenerativeModel.countTokens(_:)`. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public enum CountTokensError: Error { - case internalError(underlying: Error) -} diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 4af77cf3811..6956160b072 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1258,7 +1258,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.countTokens("Why is the sky blue?") XCTFail("Request should not have succeeded.") - } catch let CountTokensError.internalError(rpcError as RPCError) { + } catch let rpcError as RPCError { XCTAssertEqual(rpcError.httpResponseCode, 404) XCTAssertEqual(rpcError.status, .notFound) XCTAssert(rpcError.message.hasPrefix("models/test-model-name is not found")) From ad2536a14cc497db907f960ea1b9dee3888f53e1 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 27 Sep 2024 15:48:42 -0700 Subject: [PATCH 086/258] Run zip testing with Xcode 16 (#13731) --- .github/workflows/zip.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index bd49ad23063..0059e7b5c70 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -117,7 +117,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -179,7 +179,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -233,7 +233,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -285,7 +285,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -412,7 +412,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -472,7 +472,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -557,7 +557,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -614,7 +614,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -670,7 +670,7 @@ jobs: - os: macos-13 xcode: Xcode_15.2 - os: macos-14 - xcode: Xcode_15.3 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 From 292f1bb417528ef40476d64285fa9cc2a4d4f9d4 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 27 Sep 2024 15:49:56 -0700 Subject: [PATCH 087/258] Remove GoogleUtilitiesComponents from the repo (#13732) --- .../workflows/google-utilities-components.yml | 37 --- GoogleUtilitiesComponents.podspec | 39 ---- .../Sources/GULCCComponent.m | 65 ------ .../Sources/GULCCComponentContainer.m | 208 ----------------- .../Sources/GULCCComponentType.m | 28 --- .../Sources/GULCCDependency.m | 44 ---- .../Private/GULCCComponentContainerInternal.h | 48 ---- .../Sources/Public/GULCCComponent.h | 90 -------- .../Sources/Public/GULCCComponentContainer.h | 41 ---- .../Sources/Public/GULCCComponentType.h | 34 --- .../Sources/Public/GULCCDependency.h | 45 ---- .../Sources/Public/GULCCLibrary.h | 37 --- .../Tests/GULCCComponentContainerTest.m | 217 ------------------ .../Tests/GULCCComponentTypeTest.m | 61 ----- .../Tests/GULCCTestComponents.h | 77 ------- .../Tests/GULCCTestComponents.m | 167 -------------- SymbolCollisionTest/Podfile | 1 - scripts/check_imports.swift | 1 - scripts/health_metrics/file_patterns.json | 7 - 19 files changed, 1247 deletions(-) delete mode 100644 .github/workflows/google-utilities-components.yml delete mode 100644 GoogleUtilitiesComponents.podspec delete mode 100644 GoogleUtilitiesComponents/Sources/GULCCComponent.m delete mode 100644 GoogleUtilitiesComponents/Sources/GULCCComponentContainer.m delete mode 100644 GoogleUtilitiesComponents/Sources/GULCCComponentType.m delete mode 100644 GoogleUtilitiesComponents/Sources/GULCCDependency.m delete mode 100644 GoogleUtilitiesComponents/Sources/Private/GULCCComponentContainerInternal.h delete mode 100644 GoogleUtilitiesComponents/Sources/Public/GULCCComponent.h delete mode 100644 GoogleUtilitiesComponents/Sources/Public/GULCCComponentContainer.h delete mode 100644 GoogleUtilitiesComponents/Sources/Public/GULCCComponentType.h delete mode 100644 GoogleUtilitiesComponents/Sources/Public/GULCCDependency.h delete mode 100644 GoogleUtilitiesComponents/Sources/Public/GULCCLibrary.h delete mode 100644 GoogleUtilitiesComponents/Tests/GULCCComponentContainerTest.m delete mode 100644 GoogleUtilitiesComponents/Tests/GULCCComponentTypeTest.m delete mode 100644 GoogleUtilitiesComponents/Tests/GULCCTestComponents.h delete mode 100644 GoogleUtilitiesComponents/Tests/GULCCTestComponents.m diff --git a/.github/workflows/google-utilities-components.yml b/.github/workflows/google-utilities-components.yml deleted file mode 100644 index 36b703b7b4d..00000000000 --- a/.github/workflows/google-utilities-components.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: google-utilities-components - -on: - pull_request: - paths: - - 'GoogleUtilitiesComponents**' - - '.github/workflows/google-utilities-components.yml' - - 'Gemfile*' - schedule: - # Run every day at 10pm (PST) - cron uses UTC times - - cron: '0 6 * * *' - - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - pod-lib-lint: - # Don't run on private repo unless it is a PR. - if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' - - runs-on: macos-14 - strategy: - matrix: - flags: [ - '', - '--use-static-frameworks' - ] - steps: - - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - - name: Setup Bundler - run: scripts/setup_bundler.sh - - name: Build and test - run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb GoogleUtilitiesComponents.podspec ${{ matrix.flags }} diff --git a/GoogleUtilitiesComponents.podspec b/GoogleUtilitiesComponents.podspec deleted file mode 100644 index 227bbe5a86d..00000000000 --- a/GoogleUtilitiesComponents.podspec +++ /dev/null @@ -1,39 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'GoogleUtilitiesComponents' - s.version = '2.0.0' - s.summary = 'Google Utilities Component Container for Apple platforms.' - - s.description = <<-DESC -An internal Google utility that is a dependency injection system for libraries to depend on other -libraries in a type safe and potentially weak manner. -Not intended for direct public usage. - DESC - - s.homepage = 'https://developers.google.com/' - s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } - s.authors = 'Google, Inc.' - - s.source = { - :git => 'https://github.com/firebase/firebase-ios-sdk.git', - :tag => 'UtilitiesComponents-' + s.version.to_s - } - - s.ios.deployment_target = '12.0' - - s.cocoapods_version = '>= 1.12.0' - s.prefix_header_file = false - s.static_framework = true - - s.source_files = 'GoogleUtilitiesComponents/Sources/**/*.[mh]' - s.public_header_files = 'GoogleUtilitiesComponents/Sources/Public/*.h', 'GoogleUtilitiesComponents/Sources/Private/*.h' - s.private_header_files = 'GoogleUtilitiesComponents/Sources/Private/*.h' - s.dependency 'GoogleUtilities/Logger', "~> 8.0" - - s.test_spec 'unit' do |unit_tests| - unit_tests.scheme = { :code_coverage => true } - unit_tests.source_files = 'GoogleUtilitiesComponents/Tests/**/*.[mh]' - unit_tests.requires_arc = 'GoogleUtilitiesComponents/Tests/*/*.[mh]' - unit_tests.requires_app_host = true - unit_tests.dependency 'OCMock' - end -end diff --git a/GoogleUtilitiesComponents/Sources/GULCCComponent.m b/GoogleUtilitiesComponents/Sources/GULCCComponent.m deleted file mode 100644 index 34508ce9323..00000000000 --- a/GoogleUtilitiesComponents/Sources/GULCCComponent.m +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Public/GULCCComponent.h" - -#import "Public/GULCCComponentContainer.h" -#import "Public/GULCCDependency.h" - -@interface GULCCComponent () - -- (instancetype)initWithProtocol:(Protocol *)protocol - instantiationTiming:(GULCCInstantiationTiming)instantiationTiming - dependencies:(NSArray *)dependencies - creationBlock:(GULCCComponentCreationBlock)creationBlock; - -@end - -@implementation GULCCComponent - -+ (instancetype)componentWithProtocol:(Protocol *)protocol - creationBlock:(GULCCComponentCreationBlock)creationBlock { - return [[GULCCComponent alloc] initWithProtocol:protocol - instantiationTiming:GULCCInstantiationTimingLazy - dependencies:@[] - creationBlock:creationBlock]; -} - -+ (instancetype)componentWithProtocol:(Protocol *)protocol - instantiationTiming:(GULCCInstantiationTiming)instantiationTiming - dependencies:(NSArray *)dependencies - creationBlock:(GULCCComponentCreationBlock)creationBlock { - return [[GULCCComponent alloc] initWithProtocol:protocol - instantiationTiming:instantiationTiming - dependencies:dependencies - creationBlock:creationBlock]; -} - -- (instancetype)initWithProtocol:(Protocol *)protocol - instantiationTiming:(GULCCInstantiationTiming)instantiationTiming - dependencies:(NSArray *)dependencies - creationBlock:(GULCCComponentCreationBlock)creationBlock { - self = [super init]; - if (self) { - _protocol = protocol; - _instantiationTiming = instantiationTiming; - _dependencies = [dependencies copy]; - _creationBlock = creationBlock; - } - return self; -} - -@end diff --git a/GoogleUtilitiesComponents/Sources/GULCCComponentContainer.m b/GoogleUtilitiesComponents/Sources/GULCCComponentContainer.m deleted file mode 100644 index 61d571d7f0a..00000000000 --- a/GoogleUtilitiesComponents/Sources/GULCCComponentContainer.m +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Public/GULCCComponentContainer.h" - -#import "Public/GULCCComponent.h" -#import "Public/GULCCLibrary.h" - -#import - -static NSString *kGULComponentSubsystem = @"com.google.googleutilitiescomponents"; -static NSString *kGULComponentContainer = @"[GoogleUtilitiesComponents]"; - -NS_ASSUME_NONNULL_BEGIN - -@interface GULCCComponentContainer () - -/// The dictionary of components that are registered for a particular app. The key is an `NSString` -/// of the protocol. -@property(nonatomic, strong) - NSMutableDictionary *components; - -/// Cached instances of components that requested to be cached. -@property(nonatomic, strong) NSMutableDictionary *cachedInstances; - -/// Protocols of components that have requested to be eagerly instantiated. -@property(nonatomic, strong, nullable) NSMutableArray *eagerProtocolsToInstantiate; - -@end - -@implementation GULCCComponentContainer - -// Collection of all classes that register to provide components. -static NSMutableSet *sGULComponentRegistrants; - -#pragma mark - Public Registration - -+ (void)registerAsComponentRegistrant:(Class)klass { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sGULComponentRegistrants = [[NSMutableSet alloc] init]; - }); - - [self registerAsComponentRegistrant:klass inSet:sGULComponentRegistrants]; -} - -+ (void)registerAsComponentRegistrant:(Class)klass - inSet:(NSMutableSet *)allRegistrants { - [allRegistrants addObject:klass]; -} - -#pragma mark - Internal Initialization - -- (instancetype)initWithContext:(nullable id)context { - return [self initWithContext:context registrants:sGULComponentRegistrants]; -} - -- (instancetype)initWithContext:(nullable id)context - registrants:(NSMutableSet *)allRegistrants { - self = [super init]; - if (self) { - _context = context; - _cachedInstances = [NSMutableDictionary dictionary]; - _components = [NSMutableDictionary dictionary]; - - [self populateComponentsFromRegisteredClasses:allRegistrants withContext:context]; - } - return self; -} - -- (void)populateComponentsFromRegisteredClasses:(NSSet *)classes withContext:(id)context { - // Keep track of any components that need to eagerly instantiate after all components are added. - self.eagerProtocolsToInstantiate = [[NSMutableArray alloc] init]; - - // Loop through the verified component registrants and populate the components array. - for (Class klass in classes) { - // Loop through all the components being registered and store them as appropriate. - // Classes which do not provide functionality should use a dummy GULCCComponentRegistrant - // protocol. - for (GULCCComponent *component in [klass componentsToRegister]) { - // Check if the component has been registered before, and error out if so. - NSString *protocolName = NSStringFromProtocol(component.protocol); - if (self.components[protocolName]) { - GULOSLogError(kGULComponentSubsystem, kGULComponentContainer, NO, @"I-COM000001", - @"Attempted to register protocol %@, but it already has an implementation.", - protocolName); - continue; - } - - // Store the creation block for later usage. - self.components[protocolName] = component.creationBlock; - - // Queue any protocols that should be eagerly instantiated. Don't instantiate them yet - // because they could depend on other components that haven't been added to the components - // array yet. - if (component.instantiationTiming == GULCCInstantiationTimingAlwaysEager) { - [self.eagerProtocolsToInstantiate addObject:component.protocol]; - } - } - } -} - -#pragma mark - Instance Creation - -- (void)instantiateEagerComponents { - // After all components are registered, instantiate the ones that are requesting eager - // instantiation. - @synchronized(self) { - for (Protocol *protocol in self.eagerProtocolsToInstantiate) { - // Get an instance for the protocol, which will instantiate it since it couldn't have been - // cached yet. Ignore the instance coming back since we don't need it. - __unused id unusedInstance = [self instanceForProtocol:protocol]; - } - - // All eager instantiation is complete, clear the stored property now. - self.eagerProtocolsToInstantiate = nil; - } -} - -/// Instantiate an instance of a class that conforms to the specified protocol. -/// This will: -/// - Call the block to create an instance if possible, -/// - Validate that the instance returned conforms to the protocol it claims to, -/// - Cache the instance if the block requests it -/// -/// Note that this method assumes the caller already has @synchronized on self. -- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol - withBlock:(GULCCComponentCreationBlock)creationBlock { - if (!creationBlock) { - return nil; - } - - // Create an instance using the creation block. - BOOL shouldCache = NO; - id instance = creationBlock(self, &shouldCache); - if (!instance) { - return nil; - } - - // An instance was created, validate that it conforms to the protocol it claims to. - NSString *protocolName = NSStringFromProtocol(protocol); - if (![instance conformsToProtocol:protocol]) { - GULOSLogError(kGULComponentSubsystem, kGULComponentContainer, NO, @"I-COM000002", - @"An instance conforming to %@ was requested, but the instance provided does not " - @"conform to the protocol", - protocolName); - } - - // The instance is ready to be returned, but check if it should be cached first before returning. - if (shouldCache) { - self.cachedInstances[protocolName] = instance; - } - - return instance; -} - -#pragma mark - Internal Retrieval - -- (nullable id)instanceForProtocol:(Protocol *)protocol { - // Check if there is a cached instance, and return it if so. - NSString *protocolName = NSStringFromProtocol(protocol); - - id cachedInstance; - @synchronized(self) { - cachedInstance = self.cachedInstances[protocolName]; - if (!cachedInstance) { - // Use the creation block to instantiate an instance and return it. - GULCCComponentCreationBlock creationBlock = self.components[protocolName]; - cachedInstance = [self instantiateInstanceForProtocol:protocol withBlock:creationBlock]; - } - } - return cachedInstance; -} - -#pragma mark - Lifecycle - -- (void)removeAllCachedInstances { - @synchronized(self) { - // Loop through the cache and notify each instance that is a maintainer to clean up after - // itself. - for (id instance in self.cachedInstances.allValues) { - if ([instance conformsToProtocol:@protocol(GULCCComponentLifecycleMaintainer)] && - [instance respondsToSelector:@selector(containerWillBeEmptied:)]) { - [instance containerWillBeEmptied:self]; - } - } - - // Empty the cache. - [self.cachedInstances removeAllObjects]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/GULCCComponentType.m b/GoogleUtilitiesComponents/Sources/GULCCComponentType.m deleted file mode 100644 index b1f36052671..00000000000 --- a/GoogleUtilitiesComponents/Sources/GULCCComponentType.m +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Public/GULCCComponentType.h" - -#import "Private/GULCCComponentContainerInternal.h" - -@implementation GULCCComponentType - -+ (id)instanceForProtocol:(Protocol *)protocol inContainer:(GULCCComponentContainer *)container { - // Forward the call to the container. - return [container instanceForProtocol:protocol]; -} - -@end diff --git a/GoogleUtilitiesComponents/Sources/GULCCDependency.m b/GoogleUtilitiesComponents/Sources/GULCCDependency.m deleted file mode 100644 index 9d0415153ed..00000000000 --- a/GoogleUtilitiesComponents/Sources/GULCCDependency.m +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "Public/GULCCDependency.h" - -@interface GULCCDependency () - -- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; - -@end - -@implementation GULCCDependency - -+ (instancetype)dependencyWithProtocol:(Protocol *)protocol { - return [[self alloc] initWithProtocol:protocol isRequired:YES]; -} - -+ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { - return [[self alloc] initWithProtocol:protocol isRequired:required]; -} - -- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { - self = [super init]; - if (self) { - _protocol = protocol; - _isRequired = required; - } - return self; -} - -@end diff --git a/GoogleUtilitiesComponents/Sources/Private/GULCCComponentContainerInternal.h b/GoogleUtilitiesComponents/Sources/Private/GULCCComponentContainerInternal.h deleted file mode 100644 index fdc39d562a2..00000000000 --- a/GoogleUtilitiesComponents/Sources/Private/GULCCComponentContainerInternal.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#import - -#import "GULCCComponent.h" -#import "GULCCComponentContainer.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface GULCCComponentContainer (Private) - -/// Initializes a container with a context. -- (instancetype)initWithContext:(nullable id)context; - -/// Initializes a container with a context and a given set of registered `GULLibraries`. -- (instancetype)initWithContext:(nullable id)context - registrants:(NSMutableSet *)allRegistrants; - -/// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the -/// protocol wasn't registered, or if the instance couldn't be instantiated for the provided app. -- (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:)); - -/// Instantiates all the components that have registered as "eager" after initialization. -- (void)instantiateEagerComponents; - -/// Remove all of the cached instances stored and allow them to clean up after themselves. -- (void)removeAllCachedInstances; - -/// Register a class to provide components for the interoperability system. The class should conform -/// to `GULCCComponentRegistrant` and provide an array of `GULCCComponent` objects. -+ (void)registerAsComponentRegistrant:(Class)klass; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/Public/GULCCComponent.h b/GoogleUtilitiesComponents/Sources/Public/GULCCComponent.h deleted file mode 100644 index 386a8f9ce5f..00000000000 --- a/GoogleUtilitiesComponents/Sources/Public/GULCCComponent.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class GULCCComponentContainer; - -NS_ASSUME_NONNULL_BEGIN - -/// Provides a system to clean up cached instances returned from the component system. -NS_SWIFT_NAME(ComponentLifecycleMaintainer) -@protocol GULCCComponentLifecycleMaintainer -/// Clean up any resources as they are about to be deallocated. -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container; -@end - -typedef _Nullable id (^GULCCComponentCreationBlock)(GULCCComponentContainer *container, - BOOL *isCacheable) - NS_SWIFT_NAME(ComponentCreationBlock); - -@class GULCCDependency; - -/// Describes the timing of instantiation. Note: new components should default to lazy unless there -/// is a strong reason to be eager. -typedef NS_ENUM(NSInteger, GULCCInstantiationTiming) { - GULCCInstantiationTimingLazy, - GULCCInstantiationTimingAlwaysEager -} NS_SWIFT_NAME(InstantiationTiming); - -/// A component that can be used from other components. -NS_SWIFT_NAME(Component) -@interface GULCCComponent : NSObject - -/// The protocol describing functionality provided from the Component. -@property(nonatomic, strong, readonly) Protocol *protocol; - -/// The timing of instantiation. -@property(nonatomic, readonly) GULCCInstantiationTiming instantiationTiming; - -/// An array of dependencies for the component. -@property(nonatomic, copy, readonly) NSArray *dependencies; - -/// A block to instantiate an instance of the component with the appropriate dependencies. -@property(nonatomic, copy, readonly) GULCCComponentCreationBlock creationBlock; - -// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format -// for the next two methods. -// clang-format off - -/// Creates a component with no dependencies that will be lazily initialized. -+ (instancetype)componentWithProtocol:(Protocol *)protocol - creationBlock:(GULCCComponentCreationBlock)creationBlock -NS_SWIFT_NAME(init(_:creationBlock:)); - -/// Creates a component to be registered with the component container. -/// -/// @param protocol - The protocol describing functionality provided by the component. -/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's -/// a good reason to be instantiated earlier. -/// @param dependencies - Any dependencies the `implementingClass` has, optional or required. -/// @param creationBlock - A block to instantiate the component with a container and a flag to cache -/// the instance created or not. -/// @return A component that can be registered with the component container. -+ (instancetype)componentWithProtocol:(Protocol *)protocol - instantiationTiming:(GULCCInstantiationTiming)instantiationTiming - dependencies:(NSArray *)dependencies - creationBlock:(GULCCComponentCreationBlock)creationBlock -NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:)); - -// clang-format on - -/// Unavailable. -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/Public/GULCCComponentContainer.h b/GoogleUtilitiesComponents/Sources/Public/GULCCComponentContainer.h deleted file mode 100644 index 179032b805a..00000000000 --- a/GoogleUtilitiesComponents/Sources/Public/GULCCComponentContainer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#import - -#import "GULCCComponentType.h" -#import "GULCCLibrary.h" - -NS_ASSUME_NONNULL_BEGIN - -/// A type-safe macro to retrieve a component from a container. This should be used to retrieve -/// components instead of using the container directly. -#define GUL_COMPONENT(type, container) \ - [GULCCComponentType> instanceForProtocol:@protocol(type) inContainer:container] - -/// A container that holds different components that are registered via the -/// `registerAsComponentRegistrant:` call. These classes should conform to -/// `GULCCComponentRegistrant` in order to properly register components for the container. -NS_SWIFT_NAME(GoogleComponentContainer) -@interface GULCCComponentContainer : NSObject - -/// A weak reference to an object that may provide context for the container. -@property(nonatomic, nullable, weak, readonly) id context; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/Public/GULCCComponentType.h b/GoogleUtilitiesComponents/Sources/Public/GULCCComponentType.h deleted file mode 100644 index 8fa90894087..00000000000 --- a/GoogleUtilitiesComponents/Sources/Public/GULCCComponentType.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class GULCCComponentContainer; - -NS_ASSUME_NONNULL_BEGIN - -/// Do not use directly. A placeholder type in order to provide a macro that will warn users of -/// mis-matched protocols. -NS_SWIFT_NAME(ComponentType) -@interface GULCCComponentType<__covariant T> : NSObject - -/// Do not use directly. A factory method to retrieve an instance that provides a specific -/// functionality. -+ (T)instanceForProtocol:(Protocol *)protocol inContainer:(GULCCComponentContainer *)container; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/Public/GULCCDependency.h b/GoogleUtilitiesComponents/Sources/Public/GULCCDependency.h deleted file mode 100644 index e2d7d64dd8f..00000000000 --- a/GoogleUtilitiesComponents/Sources/Public/GULCCDependency.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/// A dependency on a specific protocol's functionality. -NS_SWIFT_NAME(Dependency) -@interface GULCCDependency : NSObject - -/// The protocol describing functionality being depended on. -@property(nonatomic, strong, readonly) Protocol *protocol; - -/// A flag to specify if the dependency is required or not. -@property(nonatomic, readonly) BOOL isRequired; - -/// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for -/// the required parameter. -/// Creates a required dependency on the specified protocol's functionality. -+ (instancetype)dependencyWithProtocol:(Protocol *)protocol; - -/// Creates a dependency on the specified protocol's functionality and specify if it's required for -/// the class's functionality. -+ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; - -/// Use `dependencyWithProtocol:isRequired:` instead. -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleUtilitiesComponents/Sources/Public/GULCCLibrary.h b/GoogleUtilitiesComponents/Sources/Public/GULCCLibrary.h deleted file mode 100644 index bf8d4e72d60..00000000000 --- a/GoogleUtilitiesComponents/Sources/Public/GULCCLibrary.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef GULLibrary_h -#define GULLibrary_h - -#import -#import "GULCCComponent.h" - -NS_ASSUME_NONNULL_BEGIN - -/// Provide an interface to register a library for userAgent logging and availability to others. -NS_SWIFT_NAME(Library) -@protocol GULCCLibrary - -/// Returns one or more GULComponents that will be registered in the container and participate in -/// dependency resolution and injection. -+ (NSArray *)componentsToRegister; - -@end - -NS_ASSUME_NONNULL_END - -#endif /* GULLibrary_h */ diff --git a/GoogleUtilitiesComponents/Tests/GULCCComponentContainerTest.m b/GoogleUtilitiesComponents/Tests/GULCCComponentContainerTest.m deleted file mode 100644 index 7282bf50455..00000000000 --- a/GoogleUtilitiesComponents/Tests/GULCCComponentContainerTest.m +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2018 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import -#import - -#import "GULCCTestComponents.h" - -/// Internally exposed methods and properties for testing. -@interface GULCCComponentContainer (TestInternal) - -@property(nonatomic, strong) - NSMutableDictionary *components; -@property(nonatomic, strong) NSMutableDictionary *cachedInstances; - -+ (void)registerAsComponentRegistrant:(Class)klass - inSet:(NSMutableSet *)allRegistrants; - -@end - -@interface GULCCComponentContainer (TestInternalImplementations) -- (instancetype)initWithContext:(id)context - components:(NSDictionary *)components; -@end - -@implementation GULCCComponentContainer (TestInternalImplementations) - -- (instancetype)initWithContext:(id)context - components: - (NSDictionary *)components { - self = [self initWithContext:context registrants:[[NSMutableSet alloc] init]]; - if (self) { - self.components = [components mutableCopy]; - } - return self; -} - -@end - -@interface GULCCComponentContainerTest : XCTestCase { - /// Stored context, since the container has a `weak` reference to it. - id _context; -} - -@end - -@implementation GULCCComponentContainerTest - -- (void)tearDown { - _context = nil; - [super tearDown]; -} - -#pragma mark - Registration Tests - -- (void)testRegisteringConformingClass { - NSMutableSet *allRegistrants = [NSMutableSet set]; - Class testClass = [GULCCTestClass class]; - [GULCCComponentContainer registerAsComponentRegistrant:testClass inSet:allRegistrants]; - XCTAssertTrue([allRegistrants containsObject:testClass]); -} - -- (void)testComponentsPopulatedOnInit { - GULCCComponentContainer *container = [self containerWithRegistrants:@[ [GULCCTestClass class] ]]; - - // Verify that the block is stored. - NSString *protocolName = NSStringFromProtocol(@protocol(GULCCTestProtocol)); - GULCCComponentCreationBlock creationBlock = container.components[protocolName]; - XCTAssertNotNil(creationBlock); -} - -#pragma mark - Caching Tests - -- (void)testInstanceCached { - GULCCComponentContainer *container = - [self containerWithRegistrants:@[ [GULCCTestClassCached class] ]]; - - // Fetch an instance for `GULCCTestProtocolCached`, then fetch it again to assert it's cached. - id instance1 = GUL_COMPONENT(GULCCTestProtocolCached, container); - XCTAssertNotNil(instance1); - id instance2 = GUL_COMPONENT(GULCCTestProtocolCached, container); - XCTAssertNotNil(instance2); - XCTAssertEqual(instance1, instance2); -} - -- (void)testInstanceNotCached { - GULCCComponentContainer *container = [self containerWithRegistrants:@[ [GULCCTestClass class] ]]; - - // Retrieve an instance from the container, then fetch it again and ensure it's not the same - // instance. - id instance1 = GUL_COMPONENT(GULCCTestProtocol, container); - XCTAssertNotNil(instance1); - id instance2 = GUL_COMPONENT(GULCCTestProtocol, container); - XCTAssertNotNil(instance2); - XCTAssertNotEqual(instance1, instance2); -} - -- (void)testRemoveAllCachedInstances { - GULCCComponentContainer *container = [self containerWithRegistrants:@[ - [GULCCTestClass class], [GULCCTestClassCached class], [GULCCTestClassEagerCached class], - [GULCCTestClassCachedWithDep class] - ]]; - - // Retrieve an instance of GULCCTestClassCached to ensure it's cached. - id cachedInstance1 = GUL_COMPONENT(GULCCTestProtocolCached, container); - id eagerInstance1 = - GUL_COMPONENT(GULCCTestProtocolEagerCached, container); - - // GULCCTestClassEagerCached and GULCCTestClassCached instances should be cached at this point. - XCTAssertTrue(container.cachedInstances.count == 2); - - // Remove the instances and verify cachedInstances is empty, and that new instances returned from - // the container don't match the old ones. - [container removeAllCachedInstances]; - XCTAssertTrue(container.cachedInstances.count == 0); - - id cachedInstance2 = GUL_COMPONENT(GULCCTestProtocolCached, container); - XCTAssertNotEqual(cachedInstance1, cachedInstance2); - id eagerInstance2 = - GUL_COMPONENT(GULCCTestProtocolEagerCached, container); - XCTAssertNotEqual(eagerInstance1, eagerInstance2); -} - -#pragma mark - Instantiation Tests - -- (void)testEagerInstantiation { - // Create a container with `GULCCTestClassEagerCached` as a registrant, which provides the - // implementation for `GULCCTestProtocolEagerCached` and requires eager instantiation as well as - // caching so the test can verify it was eagerly instantiated. - GULCCComponentContainer *container = - [self containerWithRegistrants:@[ [GULCCTestClassEagerCached class] ]]; - NSString *protocolName = NSStringFromProtocol(@protocol(GULCCTestProtocolEagerCached)); - XCTAssertNotNil(container.cachedInstances[protocolName]); -} - -#pragma mark - Input Validation Tests - -- (void)testProtocolAlreadyRegistered { - // Register two classes that provide the same protocol. Only one should be stored, and there - // should be a log stating that the protocol has already been registered. Right now there's no - // guarantee which one will be registered first since it's an NSSet under the hood, but that could - // change in the future. - // TODO(wilsonryan): Assert that the log gets called warning that it's already been registered. - GULCCComponentContainer *container = - [self containerWithRegistrants:@[ [GULCCTestClass class], [GULCCTestClassDuplicate class] ]]; - XCTAssert(container.components.count == 1); -} - -#pragma mark - Dependency Tests - -- (void)testDependencyDoesntBlock { - /// Test a class that has a dependency, and fetching doesn't block the internal queue. - GULCCComponentContainer *container = [self containerWithRegistrants:@[ - [GULCCTestClassCached class], [GULCCTestClassCachedWithDep class] - ]]; - XCTAssert(container.components.count == 2); - - id instanceWithDep = - GUL_COMPONENT(GULCCTestProtocolCachedWithDep, container); - XCTAssertNotNil(instanceWithDep); -} - -- (void)testDependencyRemoveAllCachedInstancesDoesntBlock { - /// Test a class that has a dependency, and fetching doesn't block the internal queue. - GULCCComponentContainer *container = [self containerWithRegistrants:@[ - [GULCCTestClassCached class], [GULCCTestClassCachedWithDep class] - ]]; - XCTAssert(container.components.count == 2); - - id instanceWithDep = - GUL_COMPONENT(GULCCTestProtocolCachedWithDep, container); - XCTAssertNotNil(instanceWithDep); - XCTAssertNotNil(instanceWithDep.testProperty); - - // Both `instanceWithDep` and `testProperty` should be cached now. - XCTAssertTrue(container.cachedInstances.count == 2); - - // Remove the instances and verify cachedInstances is empty, and doesn't block the queue. - [container removeAllCachedInstances]; - XCTAssertTrue(container.cachedInstances.count == 0); -} - -#pragma mark - Convenience Methods - -/// Create a container that has registered the test class. -- (GULCCComponentContainer *)containerWithRegistrants:(NSArray *)registrants { - NSMutableSet *allRegistrants = [NSMutableSet set]; - - // Initialize the container with the test classes. - for (Class c in registrants) { - [GULCCComponentContainer registerAsComponentRegistrant:c inSet:allRegistrants]; - } - - GULCCComponentContainer *container = - [[GULCCComponentContainer alloc] initWithContext:nil registrants:allRegistrants]; - - // Instantiate all the components that were eagerly registered now that all other properties are - // configured. - [container instantiateEagerComponents]; - - return container; -} - -@end diff --git a/GoogleUtilitiesComponents/Tests/GULCCComponentTypeTest.m b/GoogleUtilitiesComponents/Tests/GULCCComponentTypeTest.m deleted file mode 100644 index 8d11887c0a6..00000000000 --- a/GoogleUtilitiesComponents/Tests/GULCCComponentTypeTest.m +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import -#import -#import - -#import "GULCCTestComponents.h" - -@interface GULComponentTypeTest : XCTestCase - -@property(nonatomic, strong) id componentContainerMock; -@end - -@implementation GULComponentTypeTest - -- (void)setUp { - [super setUp]; - _componentContainerMock = OCMClassMock([GULCCComponentContainer class]); -} - -- (void)tearDown { - [super tearDown]; - [_componentContainerMock stopMocking]; -} - -- (void)testForwardsCallToContainer { - Protocol *testProtocol = @protocol(GULCCTestProtocol); - OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]); - - // Grab an instance from the container, through ComponentType. - __unused id instance = - [GULCCComponentType> instanceForProtocol:@protocol(GULCCTestProtocol) - inContainer:self.componentContainerMock]; - OCMVerifyAll(self.componentContainerMock); -} - -- (void)testMacroForwardsCallToContainer { - Protocol *testProtocol = @protocol(GULCCTestProtocol); - OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]); - - // Grab an instance from the container, through the macro that uses GULCCComponentType. - __unused id instance = - GUL_COMPONENT(GULCCTestProtocol, self.componentContainerMock); - - OCMVerifyAll(self.componentContainerMock); -} -@end diff --git a/GoogleUtilitiesComponents/Tests/GULCCTestComponents.h b/GoogleUtilitiesComponents/Tests/GULCCTestComponents.h deleted file mode 100644 index 7ab19233a6a..00000000000 --- a/GoogleUtilitiesComponents/Tests/GULCCTestComponents.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import -#import -#import - -#pragma mark - Standard Component - -/// A test protocol to be used for container testing. -@protocol GULCCTestProtocol -- (void)doSomething; -@end - -/// A test class that is a component registrant. -@interface GULCCTestClass - : NSObject -@end - -/// A test class that is a component registrant, a duplicate of GULCCTestClass. -@interface GULCCTestClassDuplicate - : NSObject -@end - -#pragma mark - Eager Component - -/// A test protocol to be used for container testing. -@protocol GULCCTestProtocolEagerCached -- (void)doSomethingFaster; -@end - -/// A test class that is a component registrant that provides a component requiring eager -/// instantiation, and is cached for easier validation that it was instantiated. -@interface GULCCTestClassEagerCached - : NSObject -@end - -#pragma mark - Cached Component - -/// A test protocol to be used for container testing. -@protocol GULCCTestProtocolCached -- (void)cacheCow; -@end - -/// A test class that is a component registrant that provides a component that requests to be -/// cached. -@interface GULCCTestClassCached - : NSObject -@end - -#pragma mark - Dependency on Standard - -/// A test protocol to be used for container testing. -@protocol GULCCTestProtocolCachedWithDep -@property(nonatomic, strong) id testProperty; -@end - -/// A test class that is a component registrant that provides a component with a dependency on -// `GULCCTestProtocolCached`. -@interface GULCCTestClassCachedWithDep - : NSObject -@property(nonatomic, strong) id testProperty; -- (instancetype)initWithTest:(id)testInstance; -@end diff --git a/GoogleUtilitiesComponents/Tests/GULCCTestComponents.m b/GoogleUtilitiesComponents/Tests/GULCCTestComponents.m deleted file mode 100644 index 7a4945fb405..00000000000 --- a/GoogleUtilitiesComponents/Tests/GULCCTestComponents.m +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2019 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "GULCCTestComponents.h" - -#import -#import - -#pragma mark - Standard Component - -@implementation GULCCTestClass - -/// GULCCTestProtocol conformance. -- (void)doSomething { -} - -/// GULCCLibrary conformance. -+ (nonnull NSArray *)componentsToRegister { - GULCCComponent *testComponent = [GULCCComponent - componentWithProtocol:@protocol(GULCCTestProtocol) - creationBlock:^id _Nullable(GULCCComponentContainer *_Nonnull container, - BOOL *_Nonnull isCacheable) { - return [[GULCCTestClass alloc] init]; - }]; - return @[ testComponent ]; -} - -/// GULCCComponentLifecycleMaintainer conformance. -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container { -} - -@end - -/// A test class that is a component registrant, a duplicate of GULCCTestClass. -@implementation GULCCTestClassDuplicate - -- (void)doSomething { -} - -/// GULCCLibrary conformance. -+ (nonnull NSArray *)componentsToRegister { - GULCCComponent *testComponent = [GULCCComponent - componentWithProtocol:@protocol(GULCCTestProtocol) - creationBlock:^id _Nullable(GULCCComponentContainer *_Nonnull container, - BOOL *_Nonnull isCacheable) { - return [[GULCCTestClassDuplicate alloc] init]; - }]; - return @[ testComponent ]; -} - -/// GULCCComponentLifecycleMaintainer conformance. -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container { -} - -@end - -#pragma mark - Eager Component - -@implementation GULCCTestClassEagerCached - -/// GULCCTestProtocolEager conformance. -- (void)doSomethingFaster { -} - -/// GULCCLibrary conformance. -+ (nonnull NSArray *)componentsToRegister { - GULCCComponent *testComponent = [GULCCComponent - componentWithProtocol:@protocol(GULCCTestProtocolEagerCached) - instantiationTiming:GULCCInstantiationTimingAlwaysEager - dependencies:@[] - creationBlock:^id _Nullable(GULCCComponentContainer *_Nonnull container, - BOOL *_Nonnull isCacheable) { - GULCCTestClassEagerCached *instance = [[GULCCTestClassEagerCached alloc] init]; - *isCacheable = YES; - [instance doSomethingFaster]; - return instance; - }]; - return @[ testComponent ]; -} - -/// GULCCComponentLifecycleMaintainer conformance. -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container { -} - -@end - -#pragma mark - Cached Component - -@implementation GULCCTestClassCached - -/// GULCCLibrary conformance. -+ (nonnull NSArray *)componentsToRegister { - GULCCComponent *testComponent = [GULCCComponent - componentWithProtocol:@protocol(GULCCTestProtocolCached) - creationBlock:^id _Nullable(GULCCComponentContainer *_Nonnull container, - BOOL *_Nonnull isCacheable) { - GULCCTestClassCached *instanceToCache = [[GULCCTestClassCached alloc] init]; - *isCacheable = YES; - return instanceToCache; - }]; - return @[ testComponent ]; -} - -/// GULCCComponentLifecycleMaintainer conformance. -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container { -} - -/// GULCCTestProtocolCached conformance. -- (void)cacheCow { -} - -@end - -#pragma mark - Test Component with Dependency - -@implementation GULCCTestClassCachedWithDep - -- (instancetype)initWithTest:(id)testInstance { - self = [super init]; - if (self != nil) { - self.testProperty = testInstance; - } - return self; -} - -- (void)containerWillBeEmptied:(GULCCComponentContainer *)container { - // Do something that depends on the instance from our dependency. - [self.testProperty cacheCow]; - - // Fetch from the container in the deletion function. - id anotherInstance = GUL_COMPONENT(GULCCTestProtocolCached, container); - [anotherInstance cacheCow]; -} - -+ (nonnull NSArray *)componentsToRegister { - GULCCDependency *dep = - [GULCCDependency dependencyWithProtocol:@protocol(GULCCTestProtocolCached)]; - GULCCComponent *testComponent = [GULCCComponent - componentWithProtocol:@protocol(GULCCTestProtocolCachedWithDep) - instantiationTiming:GULCCInstantiationTimingLazy - dependencies:@[ dep ] - creationBlock:^id _Nullable(GULCCComponentContainer *_Nonnull container, - BOOL *_Nonnull isCacheable) { - // Fetch from the container in the instantiation block. - *isCacheable = YES; - - id test = - GUL_COMPONENT(GULCCTestProtocolCached, container); - GULCCTestClassCachedWithDep *instance = - [[GULCCTestClassCachedWithDep alloc] initWithTest:test]; - return instance; - }]; - return @[ testComponent ]; -} - -@end diff --git a/SymbolCollisionTest/Podfile b/SymbolCollisionTest/Podfile index 2d92ae6638e..95dfd255f4c 100644 --- a/SymbolCollisionTest/Podfile +++ b/SymbolCollisionTest/Podfile @@ -27,7 +27,6 @@ target 'SymbolCollisionTest' do pod 'FirebasePerformance', :path => '../' pod 'FirebaseRemoteConfig', :path => '../' pod 'FirebaseStorage', :path => '../' - pod 'GoogleUtilitiesComponents', :path => '../' # pod 'FirebaseUI'. - requires use_frameworks! diff --git a/scripts/check_imports.swift b/scripts/check_imports.swift index 6ddd3afc29d..89011f61b7f 100755 --- a/scripts/check_imports.swift +++ b/scripts/check_imports.swift @@ -47,7 +47,6 @@ let skipDirPatterns = ["/Sample/", "/Pods/", "FirebaseDatabase/Sources/third_party/Wrap-leveldb", // Pending SwiftPM for leveldb. "Example", "Firestore", - "GoogleUtilitiesComponents", "FirebasePerformance/ProtoSupport/", ] diff --git a/scripts/health_metrics/file_patterns.json b/scripts/health_metrics/file_patterns.json index cfda46156e5..4b488a28df3 100644 --- a/scripts/health_metrics/file_patterns.json +++ b/scripts/health_metrics/file_patterns.json @@ -108,13 +108,6 @@ "FirebaseMessaging/Interop/[^/]+\\.h" ] }, - { - "sdk": "google-utilities-components", - "podspecs": ["GoogleUtilitiesComponents.podspec"], - "filePatterns": [ - "^GoogleUtilitiesComponents.*" - ] - }, { "sdk": "inappmessaging", "podspecs": ["FirebaseInAppMessaging.podspec"], From 5632b27f05dc5934b528be9e165c42380f078478 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 30 Sep 2024 11:42:53 -0400 Subject: [PATCH 088/258] [Vertex AI] Remove supported platforms `#warning` (#13738) --- FirebaseVertexAI/Sources/Constants.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseVertexAI/Sources/Constants.swift index e3f2d45d6df..9ec76aabe95 100644 --- a/FirebaseVertexAI/Sources/Constants.swift +++ b/FirebaseVertexAI/Sources/Constants.swift @@ -14,10 +14,6 @@ import Foundation -#if !os(macOS) && !os(iOS) - #warning("Only iOS, macOS, and Catalyst targets are currently fully supported.") -#endif - /// Constants associated with the Vertex AI for Firebase SDK. enum Constants { /// The Vertex AI backend endpoint URL. From 35b001e87d76fc12e2418508d019033d52ad98cf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:00:37 -0400 Subject: [PATCH 089/258] [Messaging] Refactor Messaging.serviceExtension() API out of FIRMessaging.h (#13723) --- .../Sources/FIRMessaging+ExtensionHelper.m | 37 ++++++++++++++++++ FirebaseMessaging/Sources/FIRMessaging.m | 12 +----- .../FIRMessaging+ExtensionHelper.h | 39 +++++++++++++++++++ .../Public/FirebaseMessaging/FIRMessaging.h | 12 ------ .../FIRMessagingExtensionHelper.h | 2 + .../FirebaseMessaging/FirebaseMessaging.h | 1 + 6 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m create mode 100644 FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h mode change 100755 => 100644 FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h diff --git a/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m b/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m new file mode 100644 index 00000000000..1902d067062 --- /dev/null +++ b/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h" + +#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h" +#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h" + +@implementation FIRMessaging (ExtensionHelper) + ++ (FIRMessagingExtensionHelper *)extensionHelper { + static dispatch_once_t once; + static FIRMessagingExtensionHelper *extensionHelper; + dispatch_once(&once, ^{ + extensionHelper = [[FIRMessagingExtensionHelper alloc] init]; + }); + return extensionHelper; +} + +/// Stub used to force the linker to include the categories in this file. +void FIRInclude_FIRMessaging_ExtensionHelper_Category(void) { +} + +@end diff --git a/FirebaseMessaging/Sources/FIRMessaging.m b/FirebaseMessaging/Sources/FIRMessaging.m index af18ea77fea..590fabd042c 100644 --- a/FirebaseMessaging/Sources/FIRMessaging.m +++ b/FirebaseMessaging/Sources/FIRMessaging.m @@ -40,7 +40,6 @@ #import "FirebaseMessaging/Sources/FIRMessagingUtilities.h" #import "FirebaseMessaging/Sources/FIRMessaging_Private.h" #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h" -#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenInfo.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h" @@ -131,14 +130,6 @@ + (FIRMessaging *)messaging { return (FIRMessaging *)instance; } -+ (FIRMessagingExtensionHelper *)extensionHelper { - static dispatch_once_t once; - static FIRMessagingExtensionHelper *extensionHelper; - dispatch_once(&once, ^{ - extensionHelper = [[FIRMessagingExtensionHelper alloc] init]; - }); - return extensionHelper; -} - (instancetype)initWithAnalytics:(nullable id)analytics userDefaults:(GULUserDefaults *)defaults heartbeatLogger:(FIRHeartbeatLogger *)heartbeatLogger { @@ -1030,6 +1021,7 @@ + (NSString *)FIRMessagingSDKCurrentLocale { #pragma mark - Force Category Linking +extern void FIRInclude_FIRMessaging_ExtensionHelper_Category(void); extern void FIRInclude_NSDictionary_FIRMessaging_Category(void); extern void FIRInclude_NSError_FIRMessaging_Category(void); @@ -1038,8 +1030,8 @@ + (NSString *)FIRMessagingSDKCurrentLocale { /// This method forces the linker to include categories even if /// users do not include the '-ObjC' linker flag in their project. + (void)noop { + FIRInclude_FIRMessaging_ExtensionHelper_Category(); FIRInclude_NSDictionary_FIRMessaging_Category(); FIRInclude_NSError_FIRMessaging_Category(); } - @end diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h new file mode 100644 index 00000000000..7f0fe7c7688 --- /dev/null +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging+ExtensionHelper.h @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRMessaging.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FIRMessagingExtensionHelper; + +@interface FIRMessaging (ExtensionHelper) + +/** + * Use the MessagingExtensionHelper to populate rich UI content for your notifications. + * For example, if an image URL is set in your notification payload or on the console, + * you can use the MessagingExtensionHelper instance returned from this method to render + * the image in your notification. + * + * @return An instance of MessagingExtensionHelper that handles the extensions API. + */ ++ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()); + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h index 4f5209bb935..f8282d30b30 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h @@ -136,7 +136,6 @@ NS_SWIFT_NAME(MessagingMessageInfo) @end @class FIRMessaging; -@class FIRMessagingExtensionHelper; /** * A protocol to handle token update or data message delivery from FCM. @@ -184,17 +183,6 @@ NS_SWIFT_NAME(Messaging) */ + (instancetype)messaging NS_SWIFT_NAME(messaging()); -/** - * Use the MessagingExtensionHelper to populate rich UI content for your notifications. - * For example, if an image URL is set in your notification payload or on the console, - * you can use the MessagingExtensionHelper instance returned from this method to render - * the image in your notification. - * - * @return An instance of MessagingExtensionHelper that handles the extensions API. - */ -+ (FIRMessagingExtensionHelper *)extensionHelper NS_SWIFT_NAME(serviceExtension()) - NS_AVAILABLE(10.14, 10.0); - /** * Unavailable. Use +messaging instead. */ diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h index c383d55da3f..eb25919ced3 100644 --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h @@ -16,6 +16,8 @@ #import +#import "FIRMessaging+ExtensionHelper.h" + @class UNMutableNotificationContent, UNNotificationContent; #if __has_include() diff --git a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h old mode 100755 new mode 100644 index c5d0bd05046..d285c078c7a --- a/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h +++ b/FirebaseMessaging/Sources/Public/FirebaseMessaging/FirebaseMessaging.h @@ -14,5 +14,6 @@ * limitations under the License. */ +#import "FIRMessaging+ExtensionHelper.h" #import "FIRMessaging.h" #import "FIRMessagingExtensionHelper.h" From a14a2c5a8666ab40a91d4180f5e865d8fd111135 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 30 Sep 2024 13:05:33 -0400 Subject: [PATCH 090/258] [Vertex AI] Update log message when API is not enabled (#13733) --- .../Sources/GenerativeAIService.swift | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index 0e2e39169ea..ac429104352 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -266,21 +266,22 @@ struct GenerativeAIService { // TODO(andrewheard): Remove this check after the Vertex AI in Firebase API launch. if error.isFirebaseMLServiceDisabledError() { VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ - The Vertex AI for Firebase SDK requires the Firebase ML API `firebaseml.googleapis.com` to \ - be enabled for your project. Get started in the Firebase Console \ - (https://console.firebase.google.com/project/\(projectID)/genai/vertex) or verify that the \ - API is enabled in the Google Cloud Console \ - (https://console.developers.google.com/apis/api/firebaseml.googleapis.com/overview?project=\ - \(projectID)). + The Vertex AI in Firebase SDK requires the Firebase ML API (`firebaseml.googleapis.com`) to \ + be enabled in your Firebase project. Enable this API by visiting the Firebase Console at + https://console.firebase.google.com/project/\(projectID)/genai/ and clicking "Get started". \ + If you enabled this API recently, wait a few minutes for the action to propagate to our \ + systems and then retry. """) } if error.isVertexAIInFirebaseServiceDisabledError() { VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ - The Vertex AI for Firebase SDK requires the Firebase Vertex AI API \ - `firebasevertexai.googleapis.com` to be enabled for your project. Get started by visiting \ - the Firebase Console at: \ - https://console.firebase.google.com/project/\(projectID)/genai/vertex + The Vertex AI in Firebase SDK requires the Vertex AI in Firebase API \ + (`firebasevertexai.googleapis.com`) to be enabled in your Firebase project. Enable this API \ + by visiting the Firebase Console at + https://console.firebase.google.com/project/\(projectID)/genai/ and clicking "Get started". \ + If you enabled this API recently, wait a few minutes for the action to propagate to our \ + systems and then retry. """) } } From 91e6f2b9c4e0b98110b4f572eb07213cda8b0e5f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 30 Sep 2024 10:11:25 -0700 Subject: [PATCH 091/258] Add compatibility for GTMSessionFetcher 4 (#13734) --- FirebaseAuth.podspec | 2 +- FirebaseFunctions.podspec | 2 +- FirebaseStorage.podspec | 2 +- Package.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 5bb6a6b1eec..a78e59daac6 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -62,7 +62,7 @@ supports email and password accounts, as well as several 3rd party authenticatio s.dependency 'FirebaseCoreExtension', '~> 11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' - s.dependency 'GTMSessionFetcher/Core', '~> 3.4' + s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.ios.dependency 'RecaptchaInterop', '~> 100.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index 44ed34cda2e..dd31c1771c4 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -41,7 +41,7 @@ Cloud Functions for Firebase. s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseMessagingInterop', '~> 11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' - s.dependency 'GTMSessionFetcher/Core', '~> 3.4' + s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.test_spec 'objc' do |objc_tests| objc_tests.platforms = { diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index d2c2763aac9..cddcb89b251 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -41,7 +41,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseCore', '~> 11.0' s.dependency 'FirebaseCoreExtension', '~> 11.0' - s.dependency 'GTMSessionFetcher/Core', '~> 3.4' + s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.test_spec 'ObjCIntegration' do |objc_tests| diff --git a/Package.swift b/Package.swift index 86d77b45682..e8a81b37020 100644 --- a/Package.swift +++ b/Package.swift @@ -146,7 +146,7 @@ let package = Package( ), .package( url: "https://github.com/google/gtm-session-fetcher.git", - "3.4.1" ..< "4.0.0" + "3.4.1" ..< "5.0.0" ), .package( url: "https://github.com/firebase/nanopb.git", From 2aab14ade9d64f5bb18eb114e1de498bd630634c Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 30 Sep 2024 12:32:36 -0700 Subject: [PATCH 092/258] Carthage 11.3.0 (#13739) --- ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json | 1 + .../CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json | 1 + 19 files changed, 19 insertions(+) diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index 05532cadfa0..6ec8c9eabaf 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseABTesting-bd865e6158ecfeaa.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseABTesting-1bc00d2361fabe31.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseABTesting-0d51fde82d49f9e8.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseABTesting-2233510ff87da3b6.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/ABTesting-d0fdf10c43e985b1.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/ABTesting-d0fdf10c43e985b1.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/ABTesting-a71d17cadc209af9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json index b936a37bead..82b3a67dec8 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/Google-Mobile-Ads-SDK-8208c48cf9486f31.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/Google-Mobile-Ads-SDK-35e22051f01c0eaa.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/Google-Mobile-Ads-SDK-4f24527af297e7f1.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/Google-Mobile-Ads-SDK-80ba4cb995505158.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/AdMob-8a654a42c33bbcc8.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/AdMob-63dab3b525b94cd9.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/AdMob-134752c6180a2a41.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index d21c13833d5..b656f35e81b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalytics-640e51a50e0916d4.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalytics-c0c45b49d7c16d39.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalytics-a93a6c81da535385.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalytics-fd2c71a90d62b88a.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Analytics-2468c231ebeb7922.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Analytics-bc8101d420b896c5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Analytics-d2b6a6b0242db786.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json index 7c65254e7ee..d0f054a6012 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalyticsOnDeviceConversion-d34b43045f6de5d9.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalyticsOnDeviceConversion-e0b5f6e47b71efce.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalyticsOnDeviceConversion-09d94624a2de0ac8.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalyticsOnDeviceConversion-918bc6e0b7a2fd94.zip", "9.0.0": "https://dl.google.com/dl/firebase/ios/carthage/9.0.0/FirebaseAnalyticsOnDeviceConversion-31aedde70a736b8a.zip", "9.1.0": "https://dl.google.com/dl/firebase/ios/carthage/9.1.0/FirebaseAnalyticsOnDeviceConversion-f13b5a47d1e3978d.zip", "9.2.0": "https://dl.google.com/dl/firebase/ios/carthage/9.2.0/FirebaseAnalyticsOnDeviceConversion-2ebf567c4d97de12.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index 583de62ae4e..26f64420cff 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppCheck-2391378293607ac0.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppCheck-b44ecc329b8672d0.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppCheck-d0c5f46e6a2bf4a3.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppCheck-89c39bdcf0bb90fe.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseAppCheck-9ef1d217cf057203.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseAppCheck-fc03215d9fe45d3a.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseAppCheck-6ebe9e9539f06003.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index 8ad9902a4db..4ecc3d7eb40 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppDistribution-6e4980b915e8e59e.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppDistribution-9415636f92f6e4be.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppDistribution-9b05f4873b275347.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppDistribution-6d2eccaccfd3145f.zip", "6.31.0": "https://dl.google.com/dl/firebase/ios/carthage/6.31.0/FirebaseAppDistribution-07f6a2cf7f576a8a.zip", "6.32.0": "https://dl.google.com/dl/firebase/ios/carthage/6.32.0/FirebaseAppDistribution-a9c4f5db794508ca.zip", "6.33.0": "https://dl.google.com/dl/firebase/ios/carthage/6.33.0/FirebaseAppDistribution-448a96d2ade54581.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index 179cda304c9..973bdb437c6 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAuth-6880d36dd83051b8.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAuth-41423c3255e3355e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAuth-eade26b5390baf84.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAuth-93dd2965b3f79b98.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Auth-0fa76ba0f7956220.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Auth-5ddd2b4351012c7a.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Auth-5e248984d78d7284.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index 4e9b8a2062c..ad98d54e1d3 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseCrashlytics-23e5ee21eff49370.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseCrashlytics-573b0427dec2b08b.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseCrashlytics-13851523ad6df088.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseCrashlytics-282a6f3cf3445787.zip", "6.15.0": "https://dl.google.com/dl/firebase/ios/carthage/6.15.0/FirebaseCrashlytics-1c6d22d5b73c84fd.zip", "6.16.0": "https://dl.google.com/dl/firebase/ios/carthage/6.16.0/FirebaseCrashlytics-938e5fd0e2eab3b3.zip", "6.17.0": "https://dl.google.com/dl/firebase/ios/carthage/6.17.0/FirebaseCrashlytics-fa09f0c8f31ed5d9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index ee1b97dcee6..8ce95a163fc 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDatabase-5e45f5e1fd19258b.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDatabase-64e6eeeecc70e513.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDatabase-06dbb1f7d3c8a3e1.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDatabase-38634b55050b94fe.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Database-1f7a820452722c7d.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Database-1f7a820452722c7d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Database-59a12d87456b3e1c.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json index 843856f3089..c827bfe99a1 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDynamicLinks-9e4c79d39080bef1.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDynamicLinks-09b22cea086a30bc.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDynamicLinks-e61c61fa80e5ea8a.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDynamicLinks-95f7e222d8456304.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/DynamicLinks-6a76740211df73f5.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/DynamicLinks-6a76740211df73f5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/DynamicLinks-6a76740211df73f5.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index 627e8785ae5..28c11b4d781 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFirestore-f19512c3374d5feb.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFirestore-5f76b2878966eea4.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFirestore-43af85b854ac842e.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFirestore-e1283f8cd2e0f3ec.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Firestore-68fc02c229d0cc69.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Firestore-87a804ab561d91db.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Firestore-ecb3eea7bde7e8e8.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index c77f37b026c..aeab120b1cb 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFunctions-5249dd848af2df99.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFunctions-87cffbdd6cbb9512.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFunctions-307f00117c2efc62.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFunctions-02693a7583303912.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Functions-f4c426016dd41e38.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Functions-c6c44427c3034736.zip", "5.0.0": "https://dl.google.com/dl/firebase/ios/carthage/5.0.0/Functions-146f34c401bd459b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index 215269e4e52..7cf389b3056 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/GoogleSignIn-3287d323d4a251ea.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/GoogleSignIn-dabfaced725377c4.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/GoogleSignIn-4e8837ef9594b57b.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/GoogleSignIn-8ce1c31ca2236212.zip", "6.0.0": "https://dl.google.com/dl/firebase/ios/carthage/6.0.0/GoogleSignIn-de9c5d5e8eb6d6ea.zip", "6.1.0": "https://dl.google.com/dl/firebase/ios/carthage/6.1.0/GoogleSignIn-8c82f2870573a793.zip", "6.10.0": "https://dl.google.com/dl/firebase/ios/carthage/6.10.0/GoogleSignIn-ff3aef61c4a55b05.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 7e526558654..26058fc69d9 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseInAppMessaging-076c8e5a966eb715.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseInAppMessaging-7aa1595a55b0f2bd.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseInAppMessaging-6fae0a778e9d3efa.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseInAppMessaging-3a1a331c86520356.zip", "5.10.0": "https://dl.google.com/dl/firebase/ios/carthage/5.10.0/InAppMessaging-a7a3f933362f6e95.zip", "5.11.0": "https://dl.google.com/dl/firebase/ios/carthage/5.11.0/InAppMessaging-fa28ce1b88fbca93.zip", "5.12.0": "https://dl.google.com/dl/firebase/ios/carthage/5.12.0/InAppMessaging-fa28ce1b88fbca93.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index f5db13bb816..c42076d4160 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMLModelDownloader-587e66639052095f.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMLModelDownloader-4029775a5484e3d2.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMLModelDownloader-d8649822e63fbf7f.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMLModelDownloader-517f51af92733a7f.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseMLModelDownloader-8f972757fb181320.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseMLModelDownloader-058ad59fa6dc0111.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseMLModelDownloader-286479a966d2fb37.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index 520032de482..26df462fbc4 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMessaging-31823941cc0a4e8c.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMessaging-3edcc27744f3aa8e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMessaging-70e63bb9d9590ded.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMessaging-8a39834fead3c581.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Messaging-a22ef2b5f2f30f82.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Messaging-94fa4e090c7e9185.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Messaging-2a00a1c64a19d176.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 5da68b8ff2e..5c004ca194f 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebasePerformance-cbde910ed7498ae3.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebasePerformance-e983e4ab114b6122.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebasePerformance-aa174ee3102722d9.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebasePerformance-a489ac7a27d9b53d.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Performance-d8693eb892bfa05b.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Performance-0a400f9460f7a71d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Performance-f5b4002ab96523e4.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index 43cb10c953f..04635990e12 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseRemoteConfig-cda0d6b61b66f8e2.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseRemoteConfig-bf6cbcdd97aa9c46.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseRemoteConfig-9a298869ce3cc6db.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseRemoteConfig-940ed38696414882.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/RemoteConfig-7e9635365ccd4a17.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/RemoteConfig-e7928fcb6311c439.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/RemoteConfig-9ab1ca5f360a1780.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index 647c17f60fd..e05ef1d977b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -31,6 +31,7 @@ "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseStorage-baee7d21e3743cf6.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseStorage-f483c715e48ec023.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseStorage-b9b969b0d1254065.zip", + "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseStorage-0435eeaa87324cd4.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Storage-6b3e77e1a7fdbc61.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Storage-4721c35d2b90a569.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Storage-821299369b9d0fb2.zip", From 533061f9484d2bf42878d6b79724dc10cd92e476 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 30 Sep 2024 17:20:22 -0400 Subject: [PATCH 093/258] [Vertex AI] Remove `NSObject` superclass from `VertexAI` (#13742) --- FirebaseVertexAI/Sources/VertexAI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 27abf206ad6..896f2982c61 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -22,7 +22,7 @@ import Foundation /// The Vertex AI for Firebase SDK provides access to Gemini models directly from your app. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public class VertexAI: NSObject { +public class VertexAI { // MARK: - Public APIs /// The default `VertexAI` instance. From 8213c97914925def6210f019afdafd0ec96b8e2f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 30 Sep 2024 16:40:53 -0700 Subject: [PATCH 094/258] Update versions for Release 11.4.0 (#13743) --- Firebase.podspec | 48 +++++++++---------- FirebaseABTesting.podspec | 2 +- FirebaseAnalytics.podspec | 6 +-- FirebaseAnalyticsOnDeviceConversion.podspec | 4 +- FirebaseAppCheck.podspec | 2 +- FirebaseAppCheckInterop.podspec | 2 +- FirebaseAppDistribution.podspec | 2 +- FirebaseAuth.podspec | 2 +- FirebaseAuthInterop.podspec | 2 +- FirebaseCore.podspec | 2 +- FirebaseCoreExtension.podspec | 2 +- FirebaseCoreInternal.podspec | 2 +- FirebaseCrashlytics.podspec | 2 +- FirebaseDatabase.podspec | 2 +- FirebaseDynamicLinks.podspec | 2 +- FirebaseFirestore.podspec | 4 +- FirebaseFirestoreInternal.podspec | 2 +- FirebaseFunctions.podspec | 2 +- FirebaseInAppMessaging.podspec | 2 +- FirebaseInstallations.podspec | 2 +- FirebaseMLModelDownloader.podspec | 2 +- FirebaseMessaging.podspec | 2 +- FirebaseMessagingInterop.podspec | 2 +- FirebasePerformance.podspec | 2 +- FirebaseRemoteConfig.podspec | 2 +- FirebaseRemoteConfigInterop.podspec | 2 +- FirebaseSessions.podspec | 2 +- FirebaseSharedSwift.podspec | 2 +- FirebaseStorage.podspec | 2 +- FirebaseVertexAI.podspec | 2 +- GoogleAppMeasurement.podspec | 4 +- ...leAppMeasurementOnDeviceConversion.podspec | 2 +- Package.swift | 2 +- .../FirebaseManifest/FirebaseManifest.swift | 5 +- 34 files changed, 63 insertions(+), 64 deletions(-) diff --git a/Firebase.podspec b/Firebase.podspec index bd9fb53a69d..17eedda5a20 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase' s.description = <<-DESC @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 11.3.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 11.3.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 11.3.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 11.4.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 11.4.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 11.4.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '11.3.0' + ss.dependency 'FirebaseCore', '11.4.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -79,13 +79,13 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.3.0' + ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.4.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 11.3.0' + ss.dependency 'FirebaseABTesting', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -95,13 +95,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'AppDistribution' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseAppDistribution', '~> 11.3.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 11.4.0-beta' ss.ios.deployment_target = '13.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 11.3.0' + ss.dependency 'FirebaseAppCheck', '~> 11.4.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -110,7 +110,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Auth' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAuth', '~> 11.3.0' + ss.dependency 'FirebaseAuth', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -120,7 +120,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Crashlytics' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseCrashlytics', '~> 11.3.0' + ss.dependency 'FirebaseCrashlytics', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' @@ -130,7 +130,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Database' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseDatabase', '~> 11.3.0' + ss.dependency 'FirebaseDatabase', '~> 11.4.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -140,13 +140,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'DynamicLinks' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.3.0' + ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.4.0' ss.ios.deployment_target = '13.0' end s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 11.3.0' + ss.dependency 'FirebaseFirestore', '~> 11.4.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -154,7 +154,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Functions' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFunctions', '~> 11.3.0' + ss.dependency 'FirebaseFunctions', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -164,20 +164,20 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'InAppMessaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.3.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.3.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.4.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.4.0-beta' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 11.3.0' + ss.dependency 'FirebaseInstallations', '~> 11.4.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 11.3.0' + ss.dependency 'FirebaseMessaging', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -187,7 +187,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'MLModelDownloader' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMLModelDownloader', '~> 11.3.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 11.4.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -197,15 +197,15 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Performance' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebasePerformance', '~> 11.3.0' - ss.tvos.dependency 'FirebasePerformance', '~> 11.3.0' + ss.ios.dependency 'FirebasePerformance', '~> 11.4.0' + ss.tvos.dependency 'FirebasePerformance', '~> 11.4.0' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 11.3.0' + ss.dependency 'FirebaseRemoteConfig', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -215,7 +215,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Storage' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseStorage', '~> 11.3.0' + ss.dependency 'FirebaseStorage', '~> 11.4.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index e1c6a25f29e..5c1bb26ea88 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 8e33b6ff5a5..e762baa4c5c 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -37,12 +37,12 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement', '11.3.0' + ss.dependency 'GoogleAppMeasurement', '11.4.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'WithoutAdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.3.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.4.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAnalyticsOnDeviceConversion.podspec b/FirebaseAnalyticsOnDeviceConversion.podspec index 72b1ef42be3..5eb6b5ef267 100644 --- a/FirebaseAnalyticsOnDeviceConversion.podspec +++ b/FirebaseAnalyticsOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalyticsOnDeviceConversion' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'On device conversion measurement plugin for FirebaseAnalytics. Not intended for direct use.' s.description = <<-DESC @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.cocoapods_version = '>= 1.12.0' - s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.3.0' + s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.4.0' s.static_framework = true diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 077d186b0ab..878e5eaf8e1 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 91590ec04a4..87dce8bb492 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Interfaces that allow other Firebase SDKs to use AppCheck functionality.' s.description = <<-DESC diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index a15f313b197..e7ad8c46c7c 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '11.3.0-beta' + s.version = '11.4.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index a78e59daac6..ef84243b5cd 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index 08120eaf25c..35664ff629a 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index a69f318c4ce..71913ba8939 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Core' s.description = <<-DESC diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index eb426d343ed..3cc916fcf71 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index 86b0a42bf9c..df449a9b8c6 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index b7b5ba079b9..c25cc9e79cd 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index 02cbb08406a..23cd7419910 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index 4129f573f63..b3335df303c 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Dynamic Links' s.description = <<-DESC diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 6da4590e11e..33232b14a75 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. @@ -37,7 +37,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.dependency 'FirebaseCore', '~> 11.0' s.dependency 'FirebaseCoreExtension', '~> 11.0' - s.dependency 'FirebaseFirestoreInternal', '11.3.0' + s.dependency 'FirebaseFirestoreInternal', '11.4.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' end diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 2f25d91e6fe..184af24dac8 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index dd31c1771c4..854828b01ae 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index 1822c89d08c..9a5380e2aab 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '11.3.0-beta' + s.version = '11.4.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 9ef28c929cf..9fb102de507 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Installations' s.description = <<-DESC diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index 008ff3b1918..5d74e6455a9 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '11.3.0-beta' + s.version = '11.4.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 5fe1da25749..ea99ba09367 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Messaging' s.description = <<-DESC diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 7ccc417d808..5037e809945 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Messaging functionality.' s.description = <<-DESC diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 0a559ea3e53..19ee4956ed6 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Performance' s.description = <<-DESC diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 50fc76bcfe3..10c05004b35 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index 2bb293c1d85..d0b2dce1f8a 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfigInterop' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' s.description = <<-DESC diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 51932c3826f..42282c5f361 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Sessions' s.description = <<-DESC diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index 94623570949..b3dc45ce87d 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index cddcb89b251..3b9e0ec23c9 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Firebase Storage' s.description = <<-DESC diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 184abbe8e45..8f433720a1c 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseVertexAI' - s.version = '11.3.0-beta' + s.version = '11.4.0-beta' s.summary = 'Vertex AI in Firebase - Public Preview' s.description = <<-DESC diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index f04df1e7e4e..1e6204dda17 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -37,7 +37,7 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.3.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.4.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index 8fa41d287e3..8ba795d836d 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/GoogleAppMeasurementOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurementOnDeviceConversion' - s.version = '11.3.0' + s.version = '11.4.0' s.summary = <<-SUMMARY On device conversion measurement plugin for Google App Measurement. Not intended for direct use. diff --git a/Package.swift b/Package.swift index e8a81b37020..8811b7faba3 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -let firebaseVersion = "11.3.0" +let firebaseVersion = "11.4.0" let package = Package( name: "Firebase", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 39dbc952214..e1c401b3f78 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -21,7 +21,7 @@ import Foundation /// The version and releasing fields of the non-Firebase pods should be reviewed every release. /// The array should be ordered so that any pod's dependencies precede it in the list. public let shared = Manifest( - version: "11.3.0", + version: "11.4.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), @@ -53,8 +53,7 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), - // TODO(andrewheard): Re-add FirebaseVertexAI when ready for release as zip. - // Pod("FirebaseVertexAI", isBeta: true, allowWarnings: true, zip: true), + Pod("FirebaseVertexAI", isBeta: true, zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), ] ) From 74e3d9ca176c39b665e580403c7686465821a0ae Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 30 Sep 2024 20:33:32 -0400 Subject: [PATCH 095/258] [Vertex AI] Remove `-Preview` suffix in SPM library name (#13744) --- .../Sample/VertexAISample.xcodeproj/project.pbxproj | 10 +++++----- Package.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseVertexAI/Sample/VertexAISample.xcodeproj/project.pbxproj b/FirebaseVertexAI/Sample/VertexAISample.xcodeproj/project.pbxproj index ceef95ccd61..54c2c620638 100644 --- a/FirebaseVertexAI/Sample/VertexAISample.xcodeproj/project.pbxproj +++ b/FirebaseVertexAI/Sample/VertexAISample.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 86D9CA8B2BED3EE1007D939E /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 86D9CA8A2BED3EE1007D939E /* FirebaseAppCheck */; }; 86D9CA8F2BED3EE1007D939E /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 86D9CA8E2BED3EE1007D939E /* FirebaseAuth */; }; 86D9CAB52BED3EE1007D939E /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 86D9CAB42BED3EE1007D939E /* FirebaseStorage */; }; - 86D9CAB92BED3EE1007D939E /* FirebaseVertexAI-Preview in Frameworks */ = {isa = PBXBuildFile; productRef = 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI-Preview */; }; + 86D9CAB92BED3EE1007D939E /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI */; }; 88263BF02B239C09008AB09B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88263BEE2B239BFE008AB09B /* ErrorView.swift */; }; 88263BF12B239C11008AB09B /* ErrorDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889873842B208563005B4896 /* ErrorDetailsView.swift */; }; 8848C8332B0D04BC007B434F /* VertexAISampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8848C8322B0D04BC007B434F /* VertexAISampleApp.swift */; }; @@ -72,7 +72,7 @@ 86D9CA8F2BED3EE1007D939E /* FirebaseAuth in Frameworks */, 86D9CA8B2BED3EE1007D939E /* FirebaseAppCheck in Frameworks */, 886F95E32B17D6630036F07A /* GenerativeAIUIComponents in Frameworks */, - 86D9CAB92BED3EE1007D939E /* FirebaseVertexAI-Preview in Frameworks */, + 86D9CAB92BED3EE1007D939E /* FirebaseVertexAI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -301,7 +301,7 @@ 86D9CA8A2BED3EE1007D939E /* FirebaseAppCheck */, 86D9CA8E2BED3EE1007D939E /* FirebaseAuth */, 86D9CAB42BED3EE1007D939E /* FirebaseStorage */, - 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI-Preview */, + 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI */, ); productName = GenerativeAISample; productReference = 8848C82F2B0D04BC007B434F /* VertexAISample.app */; @@ -625,9 +625,9 @@ isa = XCSwiftPackageProductDependency; productName = FirebaseStorage; }; - 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI-Preview */ = { + 86D9CAB82BED3EE1007D939E /* FirebaseVertexAI */ = { isa = XCSwiftPackageProductDependency; - productName = "FirebaseVertexAI-Preview"; + productName = FirebaseVertexAI; }; 886F95D72B17BA420036F07A /* MarkdownUI */ = { isa = XCSwiftPackageProductDependency; diff --git a/Package.swift b/Package.swift index 8811b7faba3..4bfb5a9b228 100644 --- a/Package.swift +++ b/Package.swift @@ -122,7 +122,7 @@ let package = Package( targets: ["FirebaseStorage"] ), .library( - name: "FirebaseVertexAI-Preview", + name: "FirebaseVertexAI", targets: ["FirebaseVertexAI"] ), ], From 9e7fbcecf61a71efab23c8822e7a3421871ce9a1 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 30 Sep 2024 20:43:30 -0700 Subject: [PATCH 096/258] Update some auth tests to Xcode 16 (#13745) --- .github/workflows/auth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index e9169e19701..1c096c2fb37 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -163,7 +163,7 @@ jobs: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Credentials.swift.gpg \ FirebaseAuth/Tests/SampleSwift/SwiftApiTests/Credentials.swift "$plist_secret" - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer - uses: nick-fields/retry@v3 with: timeout_minutes: 120 From 23b6a14dc5f0f02d583f53d7becd43d21ff1cf20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 07:29:54 -0700 Subject: [PATCH 097/258] NOTICES Change (#13755) Co-authored-by: Anka --- CoreOnly/NOTICES | 1 + 1 file changed, 1 insertion(+) diff --git a/CoreOnly/NOTICES b/CoreOnly/NOTICES index 6ee4a9aa089..84b2455db55 100644 --- a/CoreOnly/NOTICES +++ b/CoreOnly/NOTICES @@ -21,6 +21,7 @@ FirebaseRemoteConfig FirebaseRemoteConfigInterop FirebaseSessions FirebaseStorage +FirebaseVertexAI GTMSessionFetcher GoogleDataTransport PromisesObjC From 020d6afe94444ecc092948ad62cfac96194f546c Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:00:10 +0330 Subject: [PATCH 098/258] Enhance objc part of the FirebaseAuth tests (#13753) --- FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FacebookAuthTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FacebookAuthTests.m b/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FacebookAuthTests.m index d7e44b5e69e..5a85d1a95b5 100644 --- a/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FacebookAuthTests.m +++ b/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FacebookAuthTests.m @@ -39,7 +39,7 @@ @interface FacebookAuthTests : FIRAuthApiTestsBase @implementation FacebookAuthTests // TODO(#10752) - Update and fix the Facebook login Sample app and tests. -- (void)SKIPtestSignInWithFaceboook { +- (void)SKIPtestSignInWithFacebook { FIRAuth *auth = [FIRAuth auth]; if (!auth) { XCTFail(@"Could not obtain auth object."); From afd6e0b16e7838e2594a2258ae4267dee47629c8 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:16:45 +0330 Subject: [PATCH 099/258] Fix all FirebaseAppDistribution typos (#13750) --- FirebaseAppDistribution/Sources/FIRFADApiService.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAppDistribution/Sources/FIRFADApiService.m b/FirebaseAppDistribution/Sources/FIRFADApiService.m index 43d026f2752..5fa3de22182 100644 --- a/FirebaseAppDistribution/Sources/FIRFADApiService.m +++ b/FirebaseAppDistribution/Sources/FIRFADApiService.m @@ -107,7 +107,7 @@ + (NSArray *)handleReleaseResponse:(NSData *)data response:(NSURLResponse *)response error:(NSError **_Nullable)error { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - FIRFADInfoLog(@"HTTPResonse status code %ld response %@", (long)[httpResponse statusCode], + FIRFADInfoLog(@"HTTPResponse status code %ld response %@", (long)[httpResponse statusCode], httpResponse); if ([self handleHttpResponseError:httpResponse error:error]) { From e3bd19f5da00e0fad2b56f309aae0b0c07bd5134 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:17:18 +0330 Subject: [PATCH 100/258] Fix enum case name in the Authentication example (#13752) --- .../ViewControllers/SettingsViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift index 77f6a6f7d8b..33808fa2496 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/SettingsViewController.swift @@ -24,7 +24,7 @@ enum SettingsAction: String { case toggleAccessGroup = "Current Access Group" case toggleAPNSToken = "APNs Token" case toggleAppCredential = "App Credential" - case setAuthLanugage = "Auth Language" + case setAuthLanguage = "Auth Language" case useAppLanguage = "Use App Language" case togglePhoneAppVerification = "Disable App Verification (Phone)" } @@ -77,7 +77,7 @@ class SettingsViewController: UIViewController, DataSourceProviderDelegate { AppManager.shared.toggle() case .toggleAccessGroup: toggleAccessGroup() - case .setAuthLanugage: + case .setAuthLanguage: setAuthLanguage() case .useAppLanguage: auth.useAppLanguage() From 8fffe266236c0f5b05bccdd995923cd525a6e4ba Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:18:27 +0330 Subject: [PATCH 101/258] Fix crashlytics objc test files typos (#13749) --- Crashlytics/UnitTests/FIRCLSMetricKitManagerTests.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Crashlytics/UnitTests/FIRCLSMetricKitManagerTests.m b/Crashlytics/UnitTests/FIRCLSMetricKitManagerTests.m index af97cc04c20..d0b299e7701 100644 --- a/Crashlytics/UnitTests/FIRCLSMetricKitManagerTests.m +++ b/Crashlytics/UnitTests/FIRCLSMetricKitManagerTests.m @@ -191,7 +191,7 @@ - (FIRCLSMockMXCPUExceptionDiagnostic *)createCPUExceptionDiagnostic { applicationVersion:@"1"]; } -- (FIRCLSMockMXDiskWriteExceptionDiagnostic *)createDiskWriteExcptionDiagnostic { +- (FIRCLSMockMXDiskWriteExceptionDiagnostic *)createDiskWriteExceptionDiagnostic { return [[FIRCLSMockMXDiskWriteExceptionDiagnostic alloc] initWithCallStackTree:[self createMockCallStackTree] totalWritesCaused:[[NSMeasurement alloc] initWithDoubleValue:24.0 @@ -227,7 +227,7 @@ - (FIRCLSMockMXDiagnosticPayload *)createCPUExceptionDiagnosticPayload { - (FIRCLSMockMXDiagnosticPayload *)createDiskWriteExceptionDiagnosticPayload { NSDictionary *diagnostics = - @{@"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExcptionDiagnostic] ]}; + @{@"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExceptionDiagnostic] ]}; return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics timeStampBegin:self.beginTime timeStampEnd:self.endTime @@ -239,7 +239,7 @@ - (FIRCLSMockMXDiagnosticPayload *)createFullDiagnosticPayload { @"crashes" : @[ [self createCrashDiagnostic] ], @"hangs" : @[ [self createHangDiagnostic] ], @"cpuExceptionDiagnostics" : @[ [self createCPUExceptionDiagnostic] ], - @"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExcptionDiagnostic] ] + @"diskWriteExceptionDiagnostics" : @[ [self createDiskWriteExceptionDiagnostic] ] }; return [[FIRCLSMockMXDiagnosticPayload alloc] initWithDiagnostics:diagnostics timeStampBegin:self.beginTime From f2184b89604ac2f6383f33acf59a9a1e4ecd08dd Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:18:50 +0330 Subject: [PATCH 102/258] Fix crashlytics code docs (#13747) --- Crashlytics/Crashlytics/Components/FIRCLSApplication.h | 2 +- Crashlytics/Crashlytics/Handlers/FIRCLSException.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Crashlytics/Crashlytics/Components/FIRCLSApplication.h b/Crashlytics/Crashlytics/Components/FIRCLSApplication.h index 8e7510e7eac..a04c8669969 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSApplication.h +++ b/Crashlytics/Crashlytics/Components/FIRCLSApplication.h @@ -44,7 +44,7 @@ NSString* FIRCLSApplicationGetSDKBundleID(void); /** * Returns the platform identifier, either: ios, mac, or tvos. * Catalyst apps are treated as mac. - * This is a legacy function, for platform identificaiton please use + * This is a legacy function, for platform identification please use * FIRCLSApplicationGetFirebasePlatform. */ NSString* FIRCLSApplicationGetPlatform(void); diff --git a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm index 5a47d07fb94..e894f5fff71 100644 --- a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm +++ b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm @@ -110,7 +110,7 @@ void FIRCLSExceptionRecordNSException(NSException *exception) { NSString *reason = [exception reason] ?: @""; // It's tempting to try to make use of callStackSymbols here. But, the output - // of that function is not intended to be machine-readible. We could parse it, + // of that function is not intended to be machine-readable. We could parse it, // but that isn't really worthwhile, considering that address-based symbolication // needs to work anyways. From 998991ffdbe92b6886e6c45860a439af2ee2b71b Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 18:19:21 +0330 Subject: [PATCH 103/258] Fix crashlytics swift test typos (#13748) --- .../CrashlyticsRemoteConfigManagerTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift b/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift index 6c2e070e47f..3bf7c33aa5a 100644 --- a/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift +++ b/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift @@ -25,7 +25,7 @@ class RemoteConfigConfigMock: RemoteConfigInterop { for namespace: String) {} } -class PersistanceManagerMock: CrashlyticsPersistenceLog { +class PersistenceManagerMock: CrashlyticsPersistenceLog { func updateRolloutsStateToPersistence(rollouts: Data, reportID: String) {} func debugLog(message: String) {} } @@ -67,7 +67,7 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase { func testRemoteConfigManagerProperlyProcessRolloutsState() throws { let rcManager = CrashlyticsRemoteConfigManager( remoteConfig: rcInterop, - persistenceDelegate: PersistanceManagerMock() + persistenceDelegate: PersistenceManagerMock() ) rcManager.updateRolloutsState(rolloutsState: rollouts, reportID: "12R") XCTAssertEqual(rcManager.rolloutAssignment.count, 2) @@ -88,7 +88,7 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase { let rcManager = CrashlyticsRemoteConfigManager( remoteConfig: rcInterop, - persistenceDelegate: PersistanceManagerMock() + persistenceDelegate: PersistenceManagerMock() ) rcManager.updateRolloutsState(rolloutsState: singleRollout, reportID: "456") @@ -99,7 +99,7 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase { func testMultiThreadsUpdateRolloutAssignments() throws { let rcManager = CrashlyticsRemoteConfigManager( remoteConfig: rcInterop, - persistenceDelegate: PersistanceManagerMock() + persistenceDelegate: PersistenceManagerMock() ) DispatchQueue.main.async { [weak self] in if let singleRollout = self?.singleRollout { @@ -119,7 +119,7 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase { func testMultiThreadsReadAndWriteRolloutAssignments() throws { let rcManager = CrashlyticsRemoteConfigManager( remoteConfig: rcInterop, - persistenceDelegate: PersistanceManagerMock() + persistenceDelegate: PersistenceManagerMock() ) rcManager.updateRolloutsState(rolloutsState: singleRollout, reportID: "456") From bb71115c50fc0f638dd83f43db2205fc2a276233 Mon Sep 17 00:00:00 2001 From: "LamTrinh.Dev" Date: Tue, 1 Oct 2024 21:50:00 +0700 Subject: [PATCH 104/258] [docs] Update link in README.md for FirebaseAuth SampleSwift. (#13746) --- FirebaseAuth/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/README.md b/FirebaseAuth/README.md index 84818f88a36..1dbb6f9cbc3 100644 --- a/FirebaseAuth/README.md +++ b/FirebaseAuth/README.md @@ -13,5 +13,5 @@ Example/Auth contains a set of samples and tests that integrate with FirebaseAuth. The unit tests run without any additional configuration along with the rest of -Firebase. See [Tests/Sample/README.md](Tests/Sample/README.md) for +Firebase. See [Tests/SampleSwift/README.md](Tests/SampleSwift/README.md) for information about setting up, running, and testing the samples. From c304707251ccde6199f6538f50c8acc6acbb6047 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 20:36:02 +0330 Subject: [PATCH 105/258] Enhance FirebaseAuthCode quality (#13751) --- .../Tests/SampleSwift/ObjCApiTests/FIRAuthApiTestsBase.m | 2 +- FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift | 2 +- FirebaseAuth/Tests/Unit/RPCBaseTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FIRAuthApiTestsBase.m b/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FIRAuthApiTestsBase.m index 66330f70adf..9d94a3fc524 100644 --- a/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FIRAuthApiTestsBase.m +++ b/FirebaseAuth/Tests/SampleSwift/ObjCApiTests/FIRAuthApiTestsBase.m @@ -46,7 +46,7 @@ - (void)signInAnonymously { handler:^(NSError *error) { if (error != nil) { XCTFail(@"Failed to wait for expectations " - @"in anonymousy sign in. Error: %@", + @"in anonymously sign in. Error: %@", error.localizedDescription); } }]; diff --git a/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift b/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift index baa37f230e3..107a5c33b4f 100644 --- a/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift +++ b/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift @@ -48,7 +48,7 @@ class GetProjectConfigTests: RPCBaseTests { ) } - /** @fn testSuccessFulGetProjectConfigRequest + /** @fn testSuccessfulGetProjectConfigRequest @brief This test checks for a successful response */ func testSuccessfulGetProjectConfigRequest() async throws { diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index 7e7b865287c..cca4e1ee3cb 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -105,7 +105,7 @@ class RPCBaseTests: XCTestCase { } /** @fn checkBackendError - @brief This test checks error messagess from the backend map to the expected error codes + @brief This test checks error messages from the backend map to the expected error codes */ func checkBackendError(request: any AuthRPCRequest, message: String = "", From 2f32871a8a600c211fa659e5c2ec1256baef44fa Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 1 Oct 2024 20:37:42 +0330 Subject: [PATCH 106/258] Enhance FirebaseAuth file structure (#13754) --- ...onTests.swift => AuthBackendRPCImplementationTests.swift} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename FirebaseAuth/Tests/Unit/{AuthBackendRPCImplentationTests.swift => AuthBackendRPCImplementationTests.swift} (99%) diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift similarity index 99% rename from FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift rename to FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift index e3e6748c887..436d777ca10 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift @@ -524,9 +524,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let kTestValue = "TestValue" rpcIssuer.respondBlock = { // It doesn't matter what we respond with here, as long as it's not an error response. The - // fake - // response will deterministicly simulate a decoding error regardless of the response value it - // was given. + // fake response will deterministically simulate a decoding error regardless of the response + // value it was given. try self.rpcIssuer.respond(withJSON: [kTestKey: kTestValue]) } let rpcResponse = try await rpcImplementation.call(with: FakeRequest(withRequestBody: [:])) From 95596c2dd4d5667831c668a03a6ac21d2d389390 Mon Sep 17 00:00:00 2001 From: themiswang Date: Tue, 1 Oct 2024 13:17:30 -0400 Subject: [PATCH 107/258] Bump upload-symbols to 3.18 (#13760) --- Crashlytics/CHANGELOG.md | 3 +++ Crashlytics/upload-symbols | Bin 759536 -> 810016 bytes 2 files changed, 3 insertions(+) diff --git a/Crashlytics/CHANGELOG.md b/Crashlytics/CHANGELOG.md index be682cf2aed..a0b1f0fdd31 100644 --- a/Crashlytics/CHANGELOG.md +++ b/Crashlytics/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Updated `upload-symbols` to version 3.18 with support for upload mutiple DWARF contents in a dSYM bundle(#13543). + # 10.28.1 - [changed] Reverted "Add SIGTERM support (#12881)" (#13117) diff --git a/Crashlytics/upload-symbols b/Crashlytics/upload-symbols index 481428e6200d238fdd5e7950e98bb0caaf2d946a..f77bbe4304369166cfc1ab30e6bcf4e9a1b2f4ec 100755 GIT binary patch literal 810016 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è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

Idd$o`wiRsl*)?6Vc*p34NeFz%HRWa$<% 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

_6k;boO+ zW4Jq#xgwDtY7JXbjD(y!T(7ZEiI7iRFL_C-`$(!&`a4q~Uo-j2YNWz0mq%t}`#}xY z8L82uLi0Glua^4RyKLQU_1207I;gO56qwM z0)+t=a!K^d^p)*n!w0$n7_|QGPqOspk3Ue5U260i-uXaw;_Gq&CQuz1Wwgeq;Jv=- zg^;A1u4@gB7s5{>A@Pu~s=5?{tp z&1PYjW+fP(T^ga;rZLRkqq_WgBgF1U!`DTPF1sFy`N@bzys zX0}!9TVc5|+0@;=R3>EP*Zo?kx7``?0R>qObG z_U<@v3=EbU0rS)XzwGesZ?^+rs(0fk#&(o@iwYMR!b1AGux7DZ$^D&b z^=jMI;2L_boF9sMCoO2{>{7Q7*lxC8*d=aSQl}G|KPYG_96y@^_^F;g)Kf6P&^id|B_w!$f6yWIWsw~FT5bjFjFWME+!%A&_tGMV_Zwd|3Ax1o)a zu&L&a_za|>BX_l=H$9Zt79Pi*RYsJdEZ6!7IC@M9vVPVAS*q5JK@+g5E z1t|p{dy9fOQFTboaa&U-wJDS@E2Ca=NY%3!YES#!FN?fecro6*!EJ>WMB;NwA7^V- zbAm#wtQU66$Pe1O5=hjekXymp#ZG`6e8|gg+@kO%yXdR*Ma%;7ULGSbW5_iNzqXJV zd2jHNyg*H9Vg@a~;IlD%pOlW-dS6?XCV)VjgLef0j z^FHr=S>7d=2_?`_DY501LUqW`&AJ@69xA&`Xqv7oyvLS0tnK{1lWcmSH_3k!UGyP! zlaj=feJtHh%Vl8sbG<2PF^>9qR7YTbm{0?Gc46SA<635!9XVYDGqZHSXQok2HPhs* zSATz2Y6b;vD!9T%vLBX`t#i5D-gE2R)xRFs_d2fT7|L%4W8e*bUI4CHxfgmVhIi@7 z1%BnOQEv>VcX+ge=NRavF>*W91)VqmYl+^ZSbW{m@++ToEqh_M_j-#C`Ex&itf4d8 zT-YYKt5{u-As*+-S|WwX3mhuU$=a#LRVgn9})f?rihJOw(L0{Ki59+z)J#M(+_?yS;JqE@VGI8bZ(>k zCyu6+3Y_ZN-6DtQ(XQOJ;deLS0@s4yf*D%A2*2Kra0h;uyZMN*5dVX7PSBkjjQ^D< z|C{JWy5Z%nr~&_ticWKr5=yzdz%4-@c&3IDnta~#P-;8EHzHOyewDjUP&)9d({mwZ zlmpX={|W9CU)uj}nIeI7nO#Y#A}i-Ets1O9oSO&NTwUE`#{9DEs3;bli0dDPLz9DCf*<4-to*hwe6;p9_>zj5e@6B^2nsIITY#dfhiuEhOB3|I|i zM^3#k|K9BC>Ux=IU}khSl%15&Gv4o#^oJMn^zYFc$_}>_Nt~n%eR(JEm}ghl&ys6A zKF!;z>)|#?mQM|3Cs=CS4(^oIkg%!qKttJ4M&xTaNpcl)N+;Drlcq+iZ5NXIlEi5b zjG~!!Y*th@FOd8{Ny{_SnEjUs94$BrL0zM<^R zuntC{H#Nzdgi-T5;X|r*X*V1*jMBOAOq%`;Wy2*V-c`3E8l~Sgs>y#y?Wu?`R!dxgo*mIMva|N zKlbdi#(b>&N(f+P-K;CdomG9#m@CGP88!B-D>dd-7vPc)e2TbIHx`}cYAF7l?nCa= z?(6RR?iu%os5B~z&Wxr;v!ffMo1^zf{~E1}o{at&y&U~5j^bnEH^isKqvEsTv*U^J zlz3V^Qx!FisIaG+;u3$Ajvd1gJQ4p^;$Mt^s|b;-Sbi2MEFK7WGX5c4f4Jft!i|Vm zkAIE$U5_}8pf`fvh~M=mNB&zrE-xNWI(8vt9EJGTw6^xe+w1)TkU=gJ<5-JSW?_P-!>Bt&7*__R;G8m7(s-0Ra?Y_X9&?;?vyON1uoIjcf1)euMu**T zqKlWDVs^Yeu^vkB)Ui zwvTrMH=N^+*gVm>q35}1fGv9S5nsQqG|1}s2OlcyK|e{UA(cwMO!-D zfR0XAT-}9oyIsld#V#7M#1&O6aZ%$E=N?_+;^w8UZ1*kBO<3llnq@A2b(tGD;dbPA zJMz2TxplX@xZ+H#k>Z>lA{Z)7D)~~wa9ba<;ANx;N^y+^)*Zg%?^6)pn!#A8; zz7g_&!bMe2;5XpvC)~ib-*VCVZ@Hq~-*WM+ZzGRyyLi=i90cQvsy91V^zzE-L-8 z8#r>ii#vXbHu)Lq|8vN0hl@u(>)iflU2)AXom>A)7wrVxy3>_x+U25cyWD`%U!%-l zBmHl{!*ASx?%n8r&x6KdFJVr3$;He6 z;M}G^xS|n%bkW2=x}r6Jj{+XO5AxgRik1Ve0^E(cqx?^3^FJZ|pPXCqXW;(~`MvB) zR=k2Xe+4r93+nk-7w!2g^8X*`{NG&3n&K#)RuZ{&B~kIJ0g>A?05j&m=!l_*MA67Y zq5%_zL{aUKDBd_E8nE@yD6Tpz8c=n36tx~6l@uR`bjL-}j^m=@Jwu~-`SD19d=%{i ztT-`>cbphS`vFTaF|Rr~iq-*^mq+oMQ=;hMQ=)j+8!>N;h~m|!M#U9xisBV-j@*cm zQCxg_G;rM+Q9Ql^`By~6Lqa8WY9!W22~fY!p|Fi(K=#sA%K3 zDB3zMiieCxczhHs0o*=5if5k-m_+&W}nq zUl>JWE{fdI)1sm|)4=mI%wyA|sBwC9^t6kkl8Ki@$5hos(d?S2WXw#+V`fw``e?8{k>!YHH%~5e#OH?xZhN$eZ8zJk~$i3Pc6>V7vSuTu=J z$c?``W-xZZU3Ok|vy-{iPd!xff z-V+U)@V;o!#1BSM)d!=)kA47c`#?0PY;APN$d5&bR)0Jy+VJtHbnnNbLpS_eG-$;q zqLTfeiU!VlI4bG>Toji-5|wUv1nv8IwCxw7qDI&Xo4*hZ+W5t&bjuf`!>4^YDyiNO z4XXc2R9yP?C_efd(V&$ZqtbmFqeC8jJSwe+O|kL`@bpA9XvDXp*ukb~-W-+g|85lR z`X0*v0m^`VvGS=X>fRb1y6s0%ydL(&&}X9JZO=qSyMG*&ZrcvrPl5Y+R9v+K?Y<+5 z);t>(cmE=Cdw&td)jOloivNOs{x|aZRdmSyUq$iVyQ1Qb=TO(@pzFVhN=NR7K0XgS z=(o`!`+f^Od?9k%Ux2RfiK3Nz0Dl)1J@mUM+VDHH$KL3W`}anJ#_x-Y_U}VK`V;j2 zPf`5ppQ6K;?T?Dq?MM7qU|an)DysQwbjTXOjemtr^lDU8{XfxRrGJYKTlTl;&^3RL z4k>o=Ve1|22&|m<;P-?$K4eu4d!{%Z)Lnw`fH;15Kz!WR0r8*}gW_oYAlO2H`vzfg zI5>_v4vXVGuzyCB#qrTc#L@U8;`pH>;^K)%#YL@0#qp}6&BW$Kq<9OGparEk`u>IZ?$E|OUqZMz49rNb6XvxU9xcRg=+Hx9fq0{4{ zyH7{_GvcW1jCeqEMI5cFfPH)>!e_>1(?-Wp^Jv&+qvMiwBrpALuR>uQ2R>y;PUJwsha$!8U;-YxKriOmTNlT> z>*Au)`Zy}DkK+yX$n%Q0sQQX{@HW6bfXl9oi|)TN9$bAD>UmXMwC-*3fYz&FuU#Dv zUUf}8WdH2AXyUc8TXJ0-FM-{+xdFI2SOd&OcwSue@I1(He(Z)c#?eUFdo@jQGza$H zLkr?~PBUcG5=TQ@;^Lhxaa?^vT(SkW-m5nvO=}!4YlY3b5bd-u9$4Il_G^RO0AUBl zyW61)um`(4;*!U1ilfap#co<>T(YVwj@ER+j_i(0%5RUOWw)b@<#UjqFS=$Amh1o|b=FM)mu^h=;$0{s%`mq5P+`X$gWfqn_}OQ2r@{SxSxK)(d~ zCD1Q{ehKtT;5A5qZ}H?lKc2uiH=D5I@z{rP)iCpC@wb1wcX~Nvzxg|)XHxq>t^D`s zGqk*sj~DrJ_Wnp=+0zQg5Y`fQ5UwIzPq>wEFJaj>rYEc=>>yl4xSnt;;a`&G=4yQW9?$i-}EV|Zy^3oynB#( zmMv3g@o`%=^Jn$lewW7EMR>ywbd*eSZ zrzAe>)A4JS(-^|7gx0E-Pk!97ajlFI%YLZ&?0qRK_X(sY<>usU^~jG?N&K8#tC(N) z=*Y`;C1DajH_lr5`F5Z9$KKmL6hFy7!2iUt%8$(xN&Y##G5*I_X?*K{q5O>>qvPpg z=FjN)@`wMq_wmNsGar9ZApSI#_VApnydSjqIe9lT{`Pqq-^Pn?Sp3|0Vf^IvG|4|F zKhu|d{1E>m$dAcyO3uEq{POa+{pHstzrNz<`;XPn`j0iL$s-@%^3TUN{`2!*l7B#c z`S?lxx%l9#R(M;oSliR=KI7N)Z_YBM+q|34Z_LX&F4Pj1S(xL7xOaXmeP8W;!@%D2pGHPP1*)@Tln2EoO;82jGw%|TK+x};}ZD#Z7b4z z^72L;PhVEtJH4El{PCphI83wvpT6{5O}}?bk9eNGkMvOa4_6*I{TRHp7I}oycjJA{ zf#@q|W$C*+<*7g3+Y|05bX|& zJLwk(=-(oJY=HhU>6hikbK`IN*R}i;r4p$>qc?p+>3E`@`7`<|((yDr^Jnxmq>sx` z3-y1B^w9zO-}|7C-l+WLs+Y9SIMR;_7n}nob;S}B=UKd^bmi~k)BhZ zB>isE2P8GqA6CB?`zZe<(n|yB|I$bLS4j`)SF~C6KUDuB(nIw>q>uE6k{*)(QKaXV z3yJ)WBR!;F6{LscKbrK=`2N;D%0Hj3hk-moX5dU*Y56OQ%=^^>wNP4LLZKNL_ z(4X7-D1SNW#|P4XnDj#f^oK|f>ECBb56xdcCOy=Do+Ujre+>Mt>UYS#IG6Nc0sgNf z{k8!88Pef0Ci7?gdoSrN8ERpEy!v~}Uub@7B0Xrokp4m-|7G9T{GSfcYquypq~C8N z{ilKSXZ=9auMN<*k^Yka{oyAyeQ5st73m@S>)59>{q#Wob4U;MuX&`0+V57$zPhvtu4`^f(e(nI6tour5K=Ut?S?Ef#59Ml{h7*dsR^FY_)%ApNia{XWuZ?j*5H{vRPdwEp`P z=^_3eCOu>yZ6rO^KHnidG(Nr9NB%`SRsTZ#A5MCxeTR}B>Yrzl9%|q7NDr;gXOSKn zKi)z5;G|{rhqce=Ne}gpZ;>9V{|`xDm|GxP{^$R<$}iMEFCaane=|uxCBXkpq=)9` z_mCcv?}tebwa@4JDF26~ht@y)NDtNjFMZIL^4?iUzurfBNPZ8K9@_u=1?i#vlYOLz z#?S=le!Wb3c|gC0?$P{1^&duh zXnYz&dZ_)*C4Eew{2NGrQ-J}MUn}~ce}?pH1NvXOSIa*y zKwm?8i2qHbhwO(vq=)4HGU*}tPJ2oD3(Y@IbA1((&l08&^}kz452e4gkMzs?NPkBk z>F*>xG=4tZ2Yr1X`G0}*kbZxg^ici(PI{>Q2LD0ZC$xT=&7n_tiS*F;vx4-H z{r5G}L;ACo^ichOM0#lc`ZejH`C-T(RX(Bgvq%r|-$i;zKYmGiXnnJn^w9ptS$|Ug zLgQ;C=^^{9st@{wq=)q9GSWl#*)^nx_?t(1$bPu7kM#GEJ~1%<75!P|6Vks8q=)$X zIq4z&dY1H%e!N6_sDG8dto()MzvD;`>F0T*huUWv=^_2AAw4udyp8nG_})Z%sDHkl z^w9YAUeZJD|3T8{2io^9q+cDNU$I~1J0n1Ug!It<=o6%e`p%t#HhefhA69hW^Raf)9}J|w0bdG} z{J$5V-&CUX(D<^F^w9jVj`TML^8Y^RHwNe*8ldH$6rg{R^zH!tZr(o+wg2;^mj}`h zF4giw2OUST7SJldMN$CBQ$+z{r4@>Ck4viaFnJ$B|yLOXr-SKpx;6I zH39lBNDqy_w;ikbpC3s7b<#uid-ym_UlB+@Zm7~j^Yiye52b&Z^pO0{IbQP*jjvls z4~>uR1Wg~B|9(XJ_(1&zo~Y?VHn}( zG<`^ZZiLc9_U&NOL+jH+`=FPRE;3C2+5Ym8q=)!Fj`Yy^@@UdS$Nzn1h6 ze+_-mH2oG& zymE&3lRol%#n%$9B^+9%_-4XYggXhxOji1G!mWhmQxxApxRJ1Is^aGmt|u(Ni1`ri zB&?dI__c&?I@1$wBrLy}=?QldR$apMgsz6^2{#g!&tQ7OorG1FGCg6{2lTz#&=(Xg z|GUDSgf7zf8wr=k%$Itx=Mc5~rQ21p@AZRz3G_>#UjqFS=$Amh1o|b=FM)mu^h=;$ z0{s%`mq5P+`X$gWfqn_}OQ2r@{SxSxK)(d~CD1Q{ehKtTpkD(066lvezXbXv&@X|0 z3G_>#UjqFS=$Amh1o|b=FM)mu^h=;$0{s%`mq5P+`X$gWfqn_}OQ2r@{SxSxK)(d~ zCD1Q{ehK_vm%zSH@ty2L3WpF5B^*IGhHxU`G{SnqIfSi*O9)pGt|DAR_z>Zvgc}LB z5N;>jMYxynRl?FwE5An*mJ?PGP9Ur%tRxSa5A!qtT92-g!nM!1=98{tmE zJ%sxSi$9}$mJtpk97#Byu!^vTa28=BVF%$d!j**g6RstEm~aE(Cc>?RI|z3Z?jv;n z&h{r9N;raW4B{@#fbgkAK}^`awC^e3E({Y=T@o^TU!u_Z{qm zPxIf>cSah+{JtD){7^je7ZalYZ{y7$jg^z{*M|SU2F)Li@n!wO{Nflsf%KP?gmXVE zeGcthAL)9;e~pOH{Loi6=I6)ang1UPhoJfIF}nE$GCx7)H^}Ju_=e9z^CM(_g%bZk zjA(v#^7&cV{O*{4qI~}5kH_$qE?I>TGo{2iJ9CG*de_seAdmn?lAnqMTtn|~$qw`Bg9LiBuk^ZR7} zpe$Von!l(#J?|&V{70Ezs61chM=GDsYYkhyO%9>-`FdMC^Dkxklh>=OMyh|arG$48 zewgsHgijOxH{m|QzY`8PP4hXN@EF1>!b=FRCTt*FLih;bV}!%%kY~BO>Qj=TfG-Dd<&t; z!P@a2;vXb5d3=&#lgl?5HaR@Wa9&QQnD~4Yr{5#G=(ci>yJeB&#x_tvhK z&gy4!9mDh{_k2BV96q1%tUp}K@FGH!!@Ue!yBnXQD936-(-YI54=|mL_YX5{<620L zR%I6%$xBTlEwtj2vVdK&q(oO!$8P3b`$Hf1UFwcjT^Ixo=@wJcPtJ%)4 zGQII)s2kz0Yd8+CLc@xHPRm^{0ZUj2!~A8^347$XV~yo zzNI&N#pGu5lJR49x}`IF&-!UToz1s4f7>{_o_tt)eS_ie5I#xxOTyiRrcc&x#Z$C? z)}GcsA12=95t5hjVSF2ZR&SGw@tK#8*|}EFygbZ)GdtG!xNoZRZR2fz9JBfUt<@Uu zV!{Q4#_xL={u-glaT~+GBP_l^^EsYyGNFwN)}Bj<{{Z2Ggufv?>_W}Q?6YGSZYTT{ zq1DUmxo;BxcfwH@X+FnYqHrYPd4z8xoJ(kYEo1lrLYp^?f0N6nN&hP0W6nr^}V&{B;rjUYZ$(au$l0Eg#R@|%d>uH- z|28hzIBWBZ%|A9yRxrKwi?b|0!pVfzo|iJ*NNDZ1m|>eo?_v03gpU*MCbV{V!%VHm zXu?YfO>Va{Y;t=y!yh92H$tnwtz*AHyshKS99w_kwUgOxR$r5k z#sAjj%GZ;GPZR!(@I}HMv_t>O@R41j&X{gPiXbB_OE9;8+T2=PrOpowGx`$@l}RjCA9H)+*KOi^zl4~ZN9sVVY5f( zFl^BSovHaU!9_j42+w(&mS0I&M`-n!$8ZPXa>DxvKSB6qLbIp7&hR$EKN9{AVd>Rc z?vaEe3C|(Cgs_3|CPLefvHh2?5pQy~@zM0m^ynqVv-UK3oBe3*X!>G$V)M7j-Ru_A zAKO1Fx<>0`7LuhVo-aQr!$VdG;a!^Y<|4BPyB1H-owzKifd!p{;uPH63K`toDe<4Mv@F0U|L z)}Z|5_0HydTPK?Qj%R$EXHI2!3Sr4y&99x~pUKPgz}ov5#v4Is{lN6l_Ki)yzr}hO zU#6E<-*cFc&9gHZzKYP+_3vQ#mWj%zt;bFOZ2U02x|{JoL--X!({o$D+dhHWUDmF7 zx!Sm9aDD z_g>=7el~eU3zQ%0&o&NOyXEV5IO88fcnaYegf`#V{9yB=oyXWXc@E>-ykYwt*Ad@F zX!E>{mo{&he%{G=_Y>OsG2f0RUmKS!Uu(DhO#hMVRUXE_wS)2bCDKhkmY?k-TR$~B z+Rovu{Jg#N0`s%>ys}x#H@&m-o)+TYL1_8i%doA>EWb6xTfQS1HohNacq8Et3I9TP za*Og|a;jk1?BR16wsFDq^D5$RAbba*>DLDsew^^o8?>BRgf?&9$?$!IpC^2b@OOlh zZq)SqD90HL+kUgPZv*i+60RV8H{m+M?-1@Gw01wIP0Ohzw01IiTYH#1Zeu)?m&IF8 zyvg$`3|l*&(XQpx5L&+0j;2?iApP5fR^L|`KDtBm8AUjO(D?WR!^WqTWBi-`nfz>j z$=b*Gw{vBym$g%Lla_DgS^lpz-wX4a&^0o2otWGV@^w9L&+Rfq{A6Cy9Oqb`& z(&y`KdUFla-AHKrartsScaie-UyPTRr3f6BY!+$2UcD3`MUhVzI=^g+eiNa!zLfIr)e=)=RSneW*?M;by{*a^3q|hL5~m%e8U$ zL53e9{5;{4gwGM$y&1E&i{Tj76kd z``CHoCyBTAv~`T>kMU)CZ2aZ*-ujj0Z~fl%%JQ-F`SB%RA1lwsXX}?XzFWVw@%-+8 zQMrDI@H2#u5*oiI7n76mwd@_5eih+5LgUxQ?L41fWjxEz^0#vW8+UAcv+_>5Q_Hjb zZ)G^2|3u>7PiXmDyMB-OUlJN0R*vOw`C2<0-SB2F4#fpT`Q~dC=dTTq^L^6sPQRae zn)3Yw@gsWU{_&~eV!|rdspZKBp@VDTf*oET&>?@-AOt8gz-T3^(`j- zMbdvtdJ*W-@8o;kl8^EG3ga0+gE%i*Kb88W|4cr+bMmQudsben8U6qop|^2k_cm?U zblwO0ZT-j2$zNgm6_;zh?Oc1{Gn#K>o#Ia)tL<-kw2XMWA7}Vk^%{R1<6D1gCjPC& zAI1Ea5nm3bMbAnp_vwsxBIBjyF7d3~nT&Tokk&3ByO&unlTYm}S-I}ch0B&HUF0MA z+r5!`mM?kApM@ok{Mq-4HRN2VoleW;vzp~ux;zv25D|16MxBqJn4s=rSkt|j=u?{myw>Y|3uQ? z5TI9)J}N+$`AOQ*#)FB*2giZy7`A@?FAQ5het==?r(a>%`r%UyTR(e&Ve3bO$7{W< zpPa_9^@C{)o1S08u<7?r44Yox$FS-1ml?KkzXNrZzso6S8*le>oVD?`zKFcxM%k{Z2sFtyxqe;i{;I_M&nO9Tje;7`0ndCo)d5V zecD{b+dXC*f0~K6`^*-98S!@C*zk7~Z}*A~zlQkyxbYbA<*2jt`&#Pd5$7o1SCVh* z_cf%SLVCX6P9uFR={MxkSCc-4^t*F((R<;;>UA08S$*Eaue2di2mcG(3j!#;iE7Ud`P8+V!dgeAv4RTYn6j*SlUlO1!(lzIh5X+mFLP_cs;|R ze)_^cAkQJ-Bi~O~vcCC#+SsJ^vUetCFKr;+-jA4^%dXe>_TI$WYe(_J?x z-rhr4yA-!7-rkj1{G*9CJ-7Jf#BZnE4L^o>>j#FfB0k^lbBHg8jHEv`vfY2pd=L4l z>aX?d*?sieIi&xE>5t8&pGEp>?blY%K@(ITt$xsnNET4VETYFf&N|$JSd-rGYM-p%2vc;cB zyuE`md=2rZoU83_`OGFhug@LCn?85?RqIIqu7vmDdIozz#TnE$JNkLvF;jJKWP(71ZY&tJPf4?;Rr z=k;*&Qq>20FJ|NCHsbAFn6>YOWg6e!ZJ9o7x=r!-R%Z2K8}TMLi@%$Alds`RZ`XYC z`ZJXHa@0-Qa5>v|Ez8T>Im`OcpCzP+>>SY#DaY#hA1ue}VdIWyiNwcWbzU@%A$u(ud#v1Nv|X_^|hSW)C-0o_T$kvs~qG@Aga|T8X!Jc!u9~ zhsL+}eb$aE-=+BD-kQ~i)x?_|E&jvA=f|Zj#9KR9{GG(-^=&WldHpF~sr-~f7SgUO z+5RKQU*67L(TD!rO?pWGOn*!+qgamB+r|&8m+4nXf2`d%zBj8sTkpy0&xCt59MYd} z?0D_t$w0^}Kc3W6o_YN#eYeWl-Z`4S?YT?w_Kwopan0R|w|->!?e{3&F*4O_VMr8 z`P$n{&S&gBq{(gnYL$<@hcx`E2NZAbA`QQXc=Mz2De|%H!y4b-VH$q>aA{Tm6vY^ZU!ipHY0ie*1{e%VXWY zYkWIrwRW2Qu;T4}*3NkJ+$BZ5a~BFeQ*0FQjXQr_Cu|HcQBptzlvegqjIE`Kij7=`R~rj zfB6Tq?Z2C0k-yZ%-gDb{(E4SS^O1b7Yxtp$AGrPBPx&-|C|jQJ*xR3!FY5+-r)=fy zXZd!|#_YZgU(xbSUe=!OtBSY%C&QN#U(R|Mekk$zbI;b>$CinsU7mQOk5 zk=LV<#FwLPq9+ei52iEU{JFq-(k~_bQ0O+a4qw~l;RyYJ4m`{vbIJ*a&^!%`<1zrM+GOrDK>=+~jo zX@B3ze1wMs{Yg1uN7{SxH!%G}EXUrnTlt%wP`>OPyWw9Y{;3?l#hWz#4&tkr{}AGT zm%|SuKCdqo#Fv9t+3$Uf?fEyRzm)lkKFD0Nk@S+OIzH#ydlTtLke=VS+f4e&q`#Z_ z%UB?G!xqxV2I#_r@M&@~{V;i2*yJ^p`IwyQ7&iHIF>Lx(g}mj@_BE{Cr+ql9Py2J> zwI9jq*Qc1T&0n)A=g|E1pOiz@nru1BP0#;|e#yB{e*W6Ra_kZ?Zlh^DT}|G_*;0cXZUH~(fsq{+C#+e%;mp<_}}F48;Q^B>1N`~QD@PWt<;m> zGyUe2p31nijr2d0{%npedNdIKto*+*p7A-9eiBTMrdK9E8^=s;MmKp`*yLnk(=%y5 z`7=Ar+W(%a4s}H*wmh?yAU*6u8Jmt^s4}S31+4Yq9i?Mdo*v?JiyN8eF;@1)X z=^VbD_%G-1A0*!PgDro1FR?in|D%kb$J;$+v*RrvyC?m0E}tiuPrhE?BmNh;_&*B7 z{{`{3POx%+OZ;zg`Q+CX7XL-Y&&%KLhvxZ|_LO<)F9E(pUyJe2$~$D5j=xq;8N-(U zF$`ONZ(`WwX?z(U*6!BM)~?o$);m_l z${#vi+bPfQWa6LC#eWa+`FtKHK3~6G#7Dcc{rrs=Yq`hf@U6t3nZrLAz<)P@pTzX7 zgcjb!@Yv^-pEZQPB0TfAif<%*i0}`DGPcW~$$xFG9YXWxv|PVj{%_fFQ*$l+SK9Lk z{L7z%?YH@dHt=`5>q5Ycqo$~waI5C=GOYf#<$GuR0d+~RgPwgkQ-unqrRIh&` zhokY&{5e^@UM2tL-^uO)Z~3vx!Tdd0eaHMn@#go*@au>-|4yctyNEY`Plj*YuKAc> zC(CCi@#f#j@cW53zfBiWZmWN)`TT|QwQ>H@pDX?_`fsxM70)XE|FQQiU{O`w-*bkG zqoR(Aig_C^DR~_g6^)dehKfppidP0<6qL&ZgQB92W{E~QnI);|s8m=~6sUL&jS5W* z&5Fv5H#M=$s5H?mzqQX^Yna*02>pA%|MxuK!_m!|{ab6Vz4lsb@3YT2`O3!I;xzoO%3`ga@;K{>Qe-Gn&3&x-WVL_8e%Nxm6zdfyfCXCqGU%_4pa z;$pweLtFzsW_|NK_FEm+C$49&Kzba~#q-EZNKZlfLbQ`@zh}o|vWp4%sNK(Ep8Wed z=E<*=r*j5!*D1~@|KA!{n4Q>0*UzXwMZRX=pZxdOk1M&DXZ?wt|Iz!ws6U-Y zKhS&LI{w1@X;uY~7kZx;jW>$_aGc)PMeF`N5qF)$$E{u{$8wSL)BCl>ZdQOhsC=%Upb8R(0kITe*bH{etO>; z_1{v&>3wO0hyKj@zs7dZIF^k#UB9F8;o5c1Pw#6ZJnR>a-$XeiKN9iHh|@S0i#Wa4 zjmEJA#Km#Oh`0u9!RCsUI8L~P@i^U#cBOIR8q%91T^xtl@tw(`c61;)sOKom(|myW zfo(J&AU|dce$0K--mg`dcNPzOs_c);ycXwwnI5emiSb}U`-u0c4OQGP^d2EP4qp3} z5^eCLL!1%B1vyj^14 zc0^nQK4aG}6{Gn)>jah;uaj0`+HUgy zTy&K=E-Xir}sFLo*E_Rr}r?D{8+^4y^VyQN1Wc@NO*(`mqYJ|B)k-HZ)_Lg zT360b@1vxCT8uco53&RLDbkJe)B7>0U2%xh`QI$8*vTTnw^Ka zV!u0!<;CZWR7k(*=9yh%NIvsDQUCoh??9vbO)K8e+Y@>N1 z#dj6<15SnW8KC!`yo!x6%l_h;?D^Dp@`FS zNyHz4xJBTfg*d%8mH1;3e@Ea?K>Rr3#Gj7%*8;x@ak@`J{F#XVA@G|K7yIEh#5E8{ z?7oX9_7i8%b$B6Nn!@{$cz)Os>CT>4(;(g1^KLzm-WuhL>uS7_-qlI{eUV<*{UwU4 z5h#b^W-{g}E*4>){GN?@@@o<1$!}j^p8O(B<#wWR!yjzSwnf;lsDI_|6~>AE_W6(^ zo@f1w`H9|>OYdn=HR1h&-kVG3ou!EDBDo%P9+KXa^V9ol3C}{D-iNyt_00F+{Pf;l zs<%gTj?;U52`_HJacAS)I+UwAVBbz=8%LY7agO9TD&q)a_G8Bz`o0bI8$VC3?|;xv zgjXU?-+>@I1-Ie+^d4X0*S5WXyNj^hAqW3myGf3tHafysyBkBI_wLg9dlu?T@8|st z^|K&O?-{21Aw>`H_H~@eTB9QQ*xbbX_`GskBmejWAk z=)&=oY4&kk(T(HI;;a|@; zx0%jEviopcIh*TC_l1fFaGc)jO#Gn(Io?m;KRlS@vk)hK-ys~Q_d*kX3UP5>(PJ3r z*MKwF`KlI=%g)xj`5@hZ^*)Vur1`rq(w8G$JTLY``WmE*&sq5+{duHs739-P4Q$zd$2KqZI12-xdTq`ktTo3%&K|58T9Il1)zpS42km&vN)PL?`J@meKitkgSdEC%@{s}J%;W)iFp2m%maU7@b z6HvUKL!90pPk2Nq=co6_lblk-zm4YO67h#k;QaLddE$>n{14=(`??;JI6uAbp7hT~ zyz^Xp`Bf2|pUw}7KW-|=#p@0R#QUHe;!j6>s36~j_-KJY3-NFP--Nh$pJyB5^gRf& zPY&X91UdPL>jiy^5U1}J(0EadxB>ZTyeLIn952cc*FbEs^V3<_AD_nZVt>~m-Pv`9 zSfo39jv@}}&!hY&gz+K)>CWZ@2Bh!6^5S)PBhueMx}))goljAm&~*fggZHr<`S~*D z$-hlvc-)a+dtsjX-@}-vem5TT-=gp-& z?`*s&LpyWnb^m4K4I6vK>-c`tcz+VF2Noj!GPZ}Vn=2wYKYbU1;=KprZy-PQKOe;D zdnCl~hd6y_gzig)AWq*Mq5D!{i0>2RM?d(?~iL(vKQe&qkLeO`5n=UIPf25CEfC*y}|K2N%aexmQ4R3q*>oyR?W z?}W~~&d=cZCn%@cZ7#_f_b9Hq}WSr|+szy{2T2 z)A!;ZLHQXe9De}c4@&1LW+TVxJ0ujZzKc0d-)RX!Ip-0l@3G86{O%IY@9erl8QQB9 z?F=Tq?`8(7qx&W#=e~t)h|ctR4fVERz3)#J7wo)794EJB@OIPpXvlsQh>PQ=W;y4l z@30&|JrWRaGM^uJN)X?KIDL=iQ^bo9r|-d>N4x}aI__OV{1oCeKe~zdImGF^Hqsp) zr{@u;?+P?V{1W2yT^!=ShB$qnhxl(GPT$WV{=10NcX^24bp^MN25imF2h2F$&^*Kw z^`P^XO-S#6c55flvytA*34I&VeVouONFRmt_BQg_SjQ3@x5+|ec zWF_|peJ^MU;-=Lcch6mABVE1EMCU8CJ`bo)Vu76tr zzl*r|K5Ok3&fgXJ>G*aIacT#Z^Fcmm{!}&Q`jiWL+qRYc_CLql#oEyg5`9;S>>aU{ z>m@#ipxMT8`tDL&tnU)yA*dhC`$}Kt{Pdlrp`@RMeJV&7yu=n$Pjss3*&)4uYmlXfcR@D-v{|CKSX^HC;m$xar|q<>H5!&QjS*( zc=1V&cTVJb5Wn{+j*mf{=3%*~IZog0B7F)!;dm1AlRn;M9A7HnTEw42oZ>SCad8}q zKwKP$vJlrmpJ8K7t}qTQM|;sYl!x>@tk<^%Z#U8Nk$wQ_69hWz|7<-ewet+}QTuLV zp2jr|@Uo5Gk3-{u1^YXv!up;sdH?ss{^DO=Gaj(|==)|rqdwk$a=S+1_sVFTN=Mw; zdG~*?d_Kyj`kn3RI85JJv(|r?*Pn{@lfN~8aa{CY2;v&3h#dzC(awvpym(!N&Xef4 zKKY;$xUg&j}m`)?GVGBf<4@4-FXipLM( zj~RHL>LEPQ2_A~&7vp}LuqXYB#rq&2*wOrLL&rsn)&KQ|$+0bQA2iZFgQp!H5tZ@B-+kHwhh+zLg= zc}}M|(Fo7gI-8eV^t6xLa+E`I#XXA?_WlO?K4dkPFK^BLB_8KI+i;xhL)XX35f|6# z8QOAw@jX#J+H;(~Z%F;h8*%#1A<5SwPTxHo(}uUx7jgQICS9NQN1VQoNb*AvuN9w& zi}9I-xCU&*&a+A}ep)=v>z{~vu;UDiol{8ffbX`AYHt^ zR*v+MNEgS~^GFXxdY&Mk>B!D2$nG z;?eh89?ukyx(oK>m-lyd#5;5ZN8{pu+S1crQYnzQ0NH!L3q-C4Y` zcwlnGc--h+DHnNSzQO!8}=VhIhb2i^<_5incF1Abay?s3M+7t?Z#F_r= zIWYPTD$VP%y?MKLqMmJWo@joEjkA{!7vuLD;u^38^HU|pX9>y`pQjN3`Ne{E zC%@!hwD(Ur<|!^{o=EY*rSkVzFg@w}$n+i!qEnvaPHxEM$05f|eqSnD{B;s$b@zNbt6Pe7c$k4x_j zF(NL;MF!#;XdAQTHH?eisoXvR*iJg1t3tXT(kBRX78mSzO?r(-KGG){^VI(T>3dly z4w+xrM(+h8zgD6@$*-lC?ft9%f#?6H@lz$lPch2l+SLBb^km0B`X2FLSUwN!FUF5? zkmLBtLY(YJ^R!Kfi}90#xHt}(26Or37mAZLh>LNOg}4UV!20Eldc*;n?~wfew%^6^ zhy3Avh5MEKQH6O>Mc%wB|DE5@bg~~eiclWO7x(0M`W`hMe=I)S{-WR43~}uDZHSA0 z_ZZ6gMZZTNF8bYL80V+{N#lkZanbKyh-;u>%IbRUt{ZTKOcD98^+oW5U9@s*wJXlky%&FLy$xenfuz0r|);$}}Et zWcO~EC%gJ!p6oUa^K{(Qf(_V4&l6KzS$^XEgj3;p=4Z9+gQ``|g2je~+_iQX;TZJ&+Biw(yeZOP&WHi$E544A~ zel`^AsmAu2uiM*!)y=kC#96!8`$fd-etk#qc7KTV(RC6F;>{P?k2m>-w18u4tzY5Y@+;&R0Pq(WQ+ZI-XWU_3vB?J=YM zXx`|F^npkh-_PuY^hb~`USDDUW3s57VaP}An2UL`^9syUyfZ!7M%M$$zp7ujU#9RX{K;g|_(k)6@`LtQd;fX=W}k}S^ zdMUmL&vSx5gX5pbuX4hF#0h@d2~Nj{NQ@WSr(&M=8!%6MI{&lK@gmgzxZ$kdcR@Kt z7+=LV9LE{ih1t{5`)7<}d4J8qc&C1^8pm{X)n4*lC=f;*{bo9dWV$tU;XOnE1007yC~(;uPn^Z$Vt_KY57% zf_hSaFGO4%w+fPCcNM=*T)QuEPhzB-K_Q8=JM-XPp@UXT{KRR%vnNvi|g6m?pTiG zm{A|fi+gIXs7LJd`?r_ssY3a6)kA(0=kd&DH z(f2gTUPWjpvX}3lj_t<$&SH3129G})Kh;sZofe$8((&Dd_+r$X_|GH$wjjr%<8n#` zTpi8v9}%axKRlP?e~_P!@w|Tw-z%5f>zRpoOK5{PCfHdw3);2PN&Bsg_=DNQ(Yo+l ztY^2NS25z^dc(+AuCF+cKZUqBkH3U?t$Dnj%Ms`CS%{1A*>N7{7w7Rlh>t*f)A9Tg z;xh%@XFiwH80}5|UxRpS0WU_pqk!K zeG%@7&mVs{Yln6rx6l7={eKtq_m}MLq{Do=fY+secEbOT{>`ypQT!{E_WElu? zKiSt=d~L$G4ZwIpw<)Fw@nsU?%h_?JE9y&f6fXAqT5Lr414UT8oz;?C?bMzn`eUd%Fku(4je&gzxK+bteny^=XD+9?ik z4a%E^$AuXy`1rII{Y%Fc9n#~GF5WMQMY;j$;`L3kV@IgZcBA$iTP-cG)lcXoVwMXGwysl9%-Jhs1`>3j5l zsBZdW02c&M@~EGmSUv1M%5W2Jr?uaqeZ_S<>czZ%8kcB2g(98f;=0sO#Km$I02XXPbR373Q(3SOHBlh3PSl-$CP?A3r`RIBR^+#GCN_NvUv$tEBkgvu(`DeS} zpM+)H4$k6fE&3<2xqUs*B!?TThs`ryMg1ubiqIZui0?tUrX{@oj}a$-UPAl=;^en& zsP9$8>Ardn;-ddMW;pi$VZ=rMmmn_Advlg^IbvMgKwJZDX2*kc^t1aa9-oJ?oiy*w zKzd80i|brWNblmL{2HVWa#Ehf35z$fTL|(|KWAfx?MD7B7W`Y(!rtHhYM!U#YJIQ& ze}V0%IxQ%N^5UN2RJ;#SjdrGXh~t&Do|atCy6X9-?YJzoBM$2k^{2eEb|_ZxIC9nw zs;3m?*HzCyZO3h)9okm*ekT2C&)UJ}&vg8y{;a}wh<*w~Tmxm8ZkZUblbpmW*+C7w zwj1ebM*T?7QYZR4vxBqaPOAs)kCVQw>$U^y!?Up+G)`qYv4d(Q_p^9h$V6NNvY5_U zXdeUh)6aQ18ZVrU|D=C5>P`ArI%&s0wU4)8pGeR8*=LzxpH0|aF-|;9j_osR701aw zX0#98Cl-&J&g?__7opyyzp9PncKlQOj1=sX?nExQSl(DaVm=k^6Nl{;?Nf=k7(d!4 z?CrDZe`p`lzZ~@@{k_}X-#+wQ2yYcUe+GtQ#}x6nGDWaYwiCG|huMdX0k`nJ*E}qr zkL?wY>zcLP4&uBnD~sdeak}_fj(5d*In5_Z5PuubFKAwO3h{jceh%?%0)8IxodSLd z@z)Wjdaog#h5d!EiV}3)0Uzq30ldVjv%f#qzmGzv!fV9@2kyqF+AJe|J*85b5`v(2J1X zdNt~Y@krzHVWf9Py7+##Vx;#+`fi~-i*;saipPhMkK%9~<|+PSF;8)~7V{KuJ1|di zb_nwnUnek6adn0GvHi`S;O(S18i;v{pJ|w<@m2@Avn>YOMg6@R`#JS@lb8K?QrVv8 zS$}8O(>_4CbUd|~c|2bf@W|&len-I78#&%=4cDL67nCACgmAo{eeQYAPvd=k$1_Dw zF2C{tu76wy`*tx~*lw(S?0&Xc(AR?fOuRl6g6)n$eaRk?h(9Ud2E;7_o{6|PUiNr_ zw_6-9RWEW}94{jf-;eTX{D?$+t}w17AYLl)n-G6#JhvaUYZKx*Xdm*M=Vq?YLBy%v zYQ)8HOOeg_HDGJz(-Q1=pP(LMzhwQ6ou`rfuaS@1c^C84KFU+Onmx(eLH&T{t#n>a zaZ-x$L2=^S$v$3mn0FS3&d$eMU>rgdVw_KR4=HJ}q~|0(p#cCxQ^o5Z8dJ%)Vvlho7)~e~d?(SCAh_esvvwAp7`twYPh@kXLl$ zbfyE_9NnL1@!Rex9`_VqGBf)%>%=u|NApW7qFX91+x9P>p5&tA7@|its^`h~Z z^r1ZILD##ee#%q5daQ@)qw>wKaKF-?^rk)OOLVfM|3rJcI~z|<^|WtqZg=}~WCz-_ zIA+hsAHZ=d677|M@g$B989TWCqW?TC94C8`UppdBcB6LoK%DGHcwfZHj${uX#L1o{ z-w$!IKLjJLfwnQfp2Pm|8u~>%4=6|aekb(vNH0aYc)Y1V`XwjyOGvLm`kQEH>W`I3 zzvrZW7N6{TH`%@ITJC4Ew=d?&&NDGj_Faj2vg`AhCwpGTJdIEP_IY1l(3NeidT~7| zE>%5vJW^a%VxDuubBhoD-x)ueXL3C#&Zxd_PH-P5_!uX6ea8>dgT@Q0pYl{M<*7c( z)3`u;YLBx$n**`Ug5yVh&qIDhzwy%W-jQB)k0UHTKg4l*j5oKp3H#wIh#&Zfj}tw1 z^1V1OG$2lKPW`+Taf)}s%Md5Kk{!wsC;Jj!fjHTj@JhtV-h@{nE{-qNh-;wDY<#(f z{ny#}QiXJ9PaKY5lVoG%XLrUbz!K)iQfNht2>nALW_r zXoqGsm#6B-^)d}|tY7*X`+9So@Wr9LNR;PHztSh|&@U|0UY;}k)K77~ z>N@0Up01y~Y^*n=|NZT!eWrf$;;`QAI^-EP)K6YI)>~PJJk4|UlUF3v>&$O)C{OJq zj;UP8MqY1b9em}R?DeDa;=UT?IqN^fui0Ebd6pMApSRX=zaV+pFV#~X)|+03yy~s> zlc(6i`AX}Mr`ujXdAea9dR zm6P#|{Ok2r{pt}ayQF^dit&4$rFF>j z7QT-p+Si)L?~{2Ay?=X6_599{0qc#cLtfY?^=q#R`HJe0r#n+WdD-=>w;JVD)KRbh zSzd4Cu>0H3T*r4U$RC*~FSibPapm=EZ{#`7S6zp^+^^~2ULN7%zT!sx_=-@TxsG}@ zx9cY_7x^mckf*-G;oXD-f=OXg!;L{?XuM;^`uL7TAaTZU+SLWn-bfVKf|8D*K>m@wb>C8UfPM%k! z`e?6qzrP=dFASfH%B;gKCgFJ=DlhJJ!t+tiAHXvU* z@_9SqS2q{rIN>Wtd8Kv8ixk(lJCTDUft~Vb0)7E>&--Yq_?<_>}fAgq*K1QJLfB}Ltarm?`x>={s^sk zVtba@H*MHGAhsSr1IZhb781LU>klaeQW&I2NIFPykTj4OKP$&%u%v-Ra#)_^5sxju zBaD?H+*S_z?F~r_$yP4Id=R5@B!ksOze$z`Fjj`)EU~o@qE4hU<74&tLSnp*WQZ8! zcf@-?InK&3IkvLIyaws_)7Q2P{kE0KWO$<-u`Q1H9s=IpkVKtrc^&;`7$w_!2(#VW z(!H@R<^z97A&|l#MM9!7jGrZzcU0CH4(tl`d48W-i~BI#GaB1MaQzz3!Sm4>^_zd` zuzR|s@bpD2`X+G#I` z__CegB)`%LoyspK{dYN*@8tw1`QCWFp5%#p^>uEaA}81X%begOzsd=n>gjcZ%P)0u zJ+Z_q+NWKEEhY(#Q z>4$k%@Kf4b8q20Lt#Y&v#;og~C!byI09sCBAZ@}v*nZo;f za)rDozrqQf_O@r6psu2&v>j)OyoBerwSKwZY=c^{%m*HL$$oc3ztQhlZJpa+*J|Fku z`vEC#8ukqqXs|$o1sW{SV1Wh;G+3a)0u2^uut0+a8Z6LYfd&gSSfIfI4HjsyK!XJu zEYM(q1`9M;puqwS7HF_Qg9REa&|rZE3p7}u!2%5yXs|$o1sW{SV1Wh;G+3a)0u2^u zut0+a8Z6LYf&U*Y(6U>l6dq(!dg(d_h6mODXYewp!hvG&nrurCFsS8HtboBQJOEe% zCI`YV8xi(7yW&BR3y27uZ2cDj!dZ@0L;t1*nzp(?^}3|)3f-iD$pN~atQJsK6<|_o z#)ZRQ)#QN5!LS==fM1Y8;IEfSsSYtIy#ozeXjh;~>HjEjXtO1tLj^z`%P6WKXjyk^7Ed_xx##9f|O=N7&{sn>`ra%F&AYEtT35*08U}tSqjXWm4M=b_j zzuHg-4h2p{A-b+2(1rnl-~zc4)}YZ~R`!pXHi*{)bTEP{B0z5VXx28l7!wOzV3N@~ ztO|rNjR6G{jn>KHk$Z*>F1Igm4o9#;UahmgU`$;{2tCdW&ck0~;hCiQU{Ga-toJOL znKdUoSm%e@=-SFOU1##=Xl@+Y7QoAlJe2dwZAI-Fj3Ur(j2Gg}o?R}2&C|7^C}APf zf$D?Ibon>@wJ{&~fpsUbfm2zu2G{r)bz*Lj4L!a_Rhgt~#cj*<)!Nd?=(=94a;CO5 zZlFOB%FK7NVnCCFjzhDc_dzdE>E>~1JZ4yT7(HFqnGFRsPJviq`da-DQNco6#k+wu zdSzXim#ic&8R7xQ1w!`(gG>%$9hLbAB7rp<`z#MrIr;);aRP@x5c(fOxdxaMVDN>8 zV`o4ecqn7|!9al(Vo!uFWZN~tN36Xz!50Vyvimm;0ud}=6GLb?W(n3NYBD%H*gj{_ zFd7)2{R^5Br1N4Sf_?&CgICm;sbd(G860>6rUyhYmDJOjWce5L0~oVvD8{$#Q@LhCUbs!CkCQKJWz22#Tmt4-0bF!8jKfDO0^*xVH|45G&N*Y1EVRHRBjW zPk_mV3t=th_0{~E3K~TOag+trlsbOfM>TH}`x}TIJIdN&>_U&}9|8Jfx3L+eWqqxA z7nQ9u17XNR16jv8UYVyH7PXi$_BF)_VSB3x$7F}BcC|4MRu;M{>|uT&??EhUAS%Lt+P0jFAnEt3 z+txCi4}@wf-Q+GD%ntJ^_Sa1Xu?P__{0ScoGY#%e_LDPD9tV5o0G4V*$iEqvoIrP& zr-P=;$;CcnJEM%15#3=muRD+K&UB?f|}=cgp6CB;TWr!EYNPKwbdB3NSKs?gLt_p zJ@Y0nZ7pn1#6l5M#ZofCHiqIMMB6ecQOR=`#6%gOP02|R8^$G3i?m!!)co}HS_Du# zc1c1k_%dy7)ZBO$Y1rVXpfM2Y&<~P~+>b#C(W$AE_4D*8dT;=X)9eR|VOB5ep0qd- zY>-;*D(7Mo!WKc^q=uZVWl(59c(@$sTowedDPLJ+F5Gv}Vv>sX9(;En?DxI%u6ge} z`}Q6zeAoPrxp04>C0i<1`W7DEf580i{(_=|`wtW=!}iw}$|80`bW-$uroGAh6Lp_` zdoAgfBB{tyD!p5<*HZ3cvAEPOJt55)JvTug93R6vU~~#}uQsO2-$=+IOOdkBQq-te zsk2l_`z@tPOPSKC6uHZR5}DVi{Na6|!cmJ^f)*YugfU>>-UF60X)k2=9ywH7kgXo3 z%wI{&eoHYJ>JT)=QVCWGu~fsr(#VqkEE^;QBa;&53xD(C5?B(N{|+<-+F+@0g~}}^ z7fXJngO>1a!CaL63+h^0!o&tg$pvO!2yquN!5r6kAbQ&Yz#%}a*XTdtGZmNGXm z`e931qocJ8O-a?$pu&2ksq|-Sv7%PB+1Z!@!pmy7ilHqo`j|w)W@~$E zGs39E+U8}cP`+!)R35P8O0aXJ?Ih^7vX+mf>C-?iIN%Rb6%HT!3c+!;QT4#$NgI64Iw3<>dYbdwLgtxOg7i46!pdDv51DqSqujcbb}PsoQo*2rS3 zG;rU3=z~Sf6($GK1m+Bf?gLuDpgS*qei{!1X4^8WklHoY6mw*PiD`**xwX9xSh8K& zh*9Cdqm$qup2h}wI4rYB&9??!?FuKRB_u!>bl{Q6`j|90kS@~0qy(l!?Mx23BRhOU zX_*bCLN7q@qIekz)4{_{%cZe+4Xm zfT0Ce%$7r@TPakImy(MdF~g)AeianT*@9xF#(V?>Vy!hrVVHvqG>1=`^oa(TF2zL0 z#DQTY!s*O0z?W>B_t z^g!WJ_y?iHcq^p?jG65nR6)lB=BMhjq>rVv3GoU>uHiS8-Lz$=^BQ@AD$X%ea#@&0 z&5-ACwWn~3R7G2VZXr1DfjMzN8k`I06X4u|&B2XnDM?eK6X1AdR-~$kGrERHU{Mnb zieSaO9)S9%BrS&dXVheUYFeT!OarJsJ>JMTycvhhS=l@|$zZsaq}fy$mYkdrmTX{T zUm_=_&JWio#WKvFFy0ylg%A|YPIVX(Mu>$&oA0_c%M7wb(+jbr6?M6YEs z!5nez*ePBF#9nd=Og)3+Q=p%~$q*A_AR(YR#3h`QFf^Uex%&BVf?~x@64=Ok@&ae6 zvJo>SVozO?6l1lgnK2vS^vpPq$;`HL)zX)dEmkrY!bTv5=Sr>pqozbeEHUWWVGBl! z)QDt8$hQ_i!-uCqYz|_iA}cACg%KQfl9T2$LU9d2Z=`WJDhX!f#^l8Kn5dX!gKU-3 z8a^gqnm&3VD_&-WZCt_#<*cbu^65B>Vlc04rDPbYfm$K8^pBdqG$sM_l{^nefh0z* z;@QP;rYf^n*A@n6Me*}xW|NX^osb+I%M=4=Lx<7JahGYO!Z|3kdI@Q6R@yoa=2U6= zD7eJHW*eYEwzU{{4Zp4c1dElxEJMO_t!R8wyphm+Rz+%pUN85GBBdO3p~>9tJQtFj z2&X_c(O1gxa6Oy0g+&|V;AlV$@p_o)^V959rev8ce@SYLQSO!HEIVIsG{9*oP%D&e z{NZ$RG9NP-QmKT%359DZh9!7W!Db!?2UDmD{xZ%gUX~r;Ws^*cH^xECz|4(Nt64?! zYU0@B0(q-1Gg&S%F`t|&QZnK+9b2OBLIn(HbwoPqhS zaujkK$d+Ok7#O9934!KJ2p+<+#jez?K)J+t7!SBLt+1Xho<%_yiA&)PE5kY|aCkj| zO9EnJA$Y>{DKYvaanp^CidH z!o>`ZVUCf<-w84Un;f&6OBoS|R1hVbj^SmjzC?W@R#)zzDIgH0*-^=hlejS|m?%R^ zvN1U(*-o!Yl$DsgNcMUa%NxMUa9Y6HARoS}83^4TEC_nAXpMrIcuZ81++348Gf-4a zdUTXEIx=O<`9-}Tc2N(SVy-DG_cn+4O6Cl;~%T(~}Mgv&;9fEF`EXe=8k z6QbwpnUV^HrOPR$MeTK0+NAXME|${IONGip=C9?oo`gG4D+p!yt1WWwWnv1 zGSih6b+F}1h0+b>Hup=e5o!;Or&_JiWlJ78E^5#7Qbf5jPgy3FDdCodQtbh>*fLhR zRI19BF1dJ?DKmUDo~)3ku3GZArnE>}?`l_1wWmfM20)I>IViZM5##|2E0j!bVIHb{ z>9DI>LUN=$Rk<4IuxpGh_Uov`Txn(KKr5+a7QspV7NvT@{ zrdlI~o>RuEJV5Cx>8`TcRb8m8lGMJ1N{iA%ohO;3YBw+~*btIu+#01y#XKKjlD0{~ zIxqO=8L5u+&6XIGf2c>EvQ&-&)@CME<&jVfmhsF{f)`!xs`8{+*Q8mV*($YJsa6@x zN(0>7@;uk5q>)AH*wGAzOi&dm^QDf35JcxdLC;{{9B8{bAM*0AO%M?|QdrnIDN6~L z$t6FZN@d1*D55n>o+XlBr24ROmP!S!E0eCdWx1+NE=CZO>yjmvH>n2wxmrxWg!IrJ zYRocSBfKt2H>5Snj_2Gohh2QNB`*HwpjfH2t;IHFLPzG{O)eH?g25{v90>;Sk3A=y zQdTI}DE)%{L%n-2A$&WmEONi*mLr+nAY4_!adFqA3RjQwQl+xOmHqVrD-CSc`n&ftXb(BTkYa;S9w=5 zD^IypDorj~U|5xxhp(zwii6FkZnGr z44b4&cDb-zNmVU*d$^XmZjvmG;`}?R{KFDVZr-eTm}giIpU5zyPl;r-DC6=Zt;Z>4 zWJb9ZaTwyx*rU*m_h&zqI@7fpx=pck4Z_j4N&?)&xXDGE5Eo{0t5*8@R!gy&Qob@v z>8i3w=|HWPBC@5p9LTG*rIMFQeI5#VWGXL7UWSk&_FESc$^?0k=R*8_jUKM$O7C=4 zkzB7f%N=e)xRgM2UvgE+|NME0o|i19*__)vinqI4%MPxJ^oRzH*bh z%Fi&XL^9;T*cGCIG0Ny=me~J%2s=O6d|)dD;TAAO@FgkIqzubf#%@#k_>7rGoTO?J56b5RBfZI)y2e2slA7hpKwI+qGatknu)=0UM z+RMue?hV1eEM*y3A=K5=)6)drQh9{9daBG)EW}ZS%A@87w2YVc%aKAO`A@Av(L=3J z_`$dyI6B&mU1;%ZqEJW*1^ngPM~&?Bi<`@NnU`^xgnT7@d>+bM>6rq3J(p*t|1R)% z;PqSS7X<#>LVk5qxjq+ESJ8yit@$^(JV5!@i`Qq(2MGDiN?D$je%RGMKUm;@gtyPi zUnb~3S>TU^4}93#=OIDAje@)ufF)~Vn{hhSiuMrlgTZpP z`i^R%sV4s@*Wc>@P;L)v{Qo3w(1|x z>$m#P#Pe498-;uUk6$Z2nai{0zY_dEoR2S7`UD{#E#$ih@-7SXRh(|s*NeB$D*s^~ z&(^#eT51~~#Xzym-xkVi1b+Cuj~%~S(C4TSueSyIbwS?4LOxK?H;>1kRsV4UeVkDK z6~RA81^<)@`Nsr#VFJA?Zx5J=Z}3?%)_!XqK6hZ3kL2Y6xo<*e}lpFOb4?-%H0LjFe~pT_-dm3KvuH$lj+75E!*V_C~T$>YtM z|3fGrCFHvb<l2V`ge zmxcBh3I09D`=7P^Ji))oLVsEzRs;`VA1qiz-2% zYN7lHXc?H?Du2DuJ~v@JPZat~YdGJqEuReW%D$a84N@4S&X8I{ng^*BBv42393%sz zPLSF`>Hw)Dq&AS)c>Xw~*^r_k&4i?bG#AoikP;y!K>7$$9;BBcq?3|}z|2!nUwF?~Eb#LD zsyqI!SztozJscC*f{a=exZ7b}w!(>y)_HIWEgj)GTD3wsc7Gq`^ZWAwHHz7-juEX% z>%eMO9g7^3_?jsW3oCt)1WQL?;RW2DuzMcC!FncJb!{}XvxCEBD56rRHeY|ni4GrQ zuzgg*_JIhli0v&Zj?^r1fi{IYd9DFS4jzUORkT?^U2`KwUJv8M3g|zZwJn^H1MIFG zEtL_gvNc1^VmDksEi&7!Ef$IL+GY`~y?j~ZfO~V+W!ECjZgsUtoJ4EI9+TG^i)6O? zLd0rTJl9_H13lN~aTBp51`S&w$SrHXDhRD@vm6K!=j(uMgM>YBQu|>RQ3t-*Sy%{! zYj*^c-MwSWfvoF-;D>yhANG7D5ZBLkp%F)HSN3qE)=D63nsw=tsHfF2Xn&#OliL(E zp>4Nfh>Pb#5nn3A5$hTup0zFx601j7f&XcFH<;S`M38Lu`3hW01UdUvLjcshTu7|Z zezmtls?E}IkuJxS@c2Wx{o-(&rAP47(MqCvEJYIQb+CM#Ry;*HdS(i4U(2hbL^<-J za$B|dQP#;i75MAJzFh>%d7a3D_R)ES6PY8rV)X!#D4eN9u%N3MC2$fo+5-xHV zD_{lrg8Et|YDo5L?Q41zUj|i^XZP>zRyx&??_ zgtcmj^5t<(R?^w8x`WO}>%wdnsDWOaiw2oZ&mcH(;v5;4-6eV}rJ}U!S!P{`%$Y4}F{M?y$n5f2-G_ zXU=qP@9|!ExbD}OZh3?LbGT)}?gx_wbR1mz!NE=A#`J3!+9qOPa=e>)M(P)7QCl}Z z{_Q;f@U>m$9o=#$Qb8P&~$*+xGb@H`e=Jh-C zb!GQ2T>Sr3&#R6*d2!*++44xJ2NLelsT$;+30Cc z8uD81*t>YeqE#xlkM{q1chu$?-#)qjV)P-^n{gF^pBdB4LnbXPIP_{nm%U3PgC4*4 zY>Vh4yAvmVX3j71-TQGPP2S37{qFs|cKY49pS1k``s!y#&i<+0xv?P`O&^%E>EyKB ziM^-YIo~g%+vpKq1s`tOy?;{V@{~{gc4nJCd%FLLZyr)yTNbzIt-x-*l0Q&gZ=VyC zaqD>K-A7tfnzH?T2AccL96spu$Q7ZbK0mkb)2ZiQ{qN+34ON&;XZL@xy6nZl?_Uc! z`@*t?<9<)S)YN;~mg!la`c41yX2FoPJ-UrA=z8Rfz|PIy+-Ls%!lKV#_(U3d@zp!- z)029h-I(#!8*Xp+b6fvM$&NekY}Mae)B2~lzk7J}+Hj|7zuA>(c~d+1H|@4y*;V(2 zZ}|)B2pb z9~rM5i+-`{^)KHTzdgBS>5bbybKkgpuE~zMnGYUmI_XN_+04A6&*OV~<QcsDH0x z8^4I38uRtd7arYmHRXxG1!F$EJaYLjBNvv9zplN0ZgBRe6ZVwk4S#!(ru{cL``WLXi)Q*FTCHI=V}?G>K^>kfR5k1wS8`4!JyYPZ6E!7$c~7Up1VJdxp@2L zn(rQ5GP1H&!WTEr>}rv7;-x>diRA>!i>%SX1Juy6W3L(qr1-*2u8R(({kEA)+bpNb6mq;bv% z8;#q>Kia?=whe|RVS z#Lu%zjvYSZIqtgusM&hY1qWXLrgg?gw_iEd>~{IbtKS=ZvghM{JdJ-JUEwjU>4~R* z_`BmX$uIqI^8?lNAHt2gn3qqii0scn_ zO1A?GukCMYOjt|?p{HMeh?#y!CG01cFbFI2HAA7-bZ_1#G=tJFJx$)wm zSLZHV_u{zT{#|+;&q{3e_=rUp+Yjmd>B4KK$+V z>xZ>@sN~7V!uJ<>Bz+Nb_vE&aDTYK9{h7EAPvHh<@J+q=m zS~@TES-)KKQNJgK#_w7%JEZ4VQ*wV@Jt2M3=P}O@%-h&|{YNb`Iv;Ht-0#75pTDau zURGi>#9e#nSnk-iGY9n8-sFU9@vPqizWeCZy}*{A9iRSnyN@5XM4U~@GC#DTspq;G zLw&zIxoO86DZM&8vg!O;MR>;6%BN5L{GE2gg^XJ}0#{6M>Fzf3qTdFe&;K~t=_hUD zQ89`p1%B&po%R{LceMYgaeMMSCbwAHH(2w-+Skv;bq;ucQ0e&zzx_VoZ2D`ix~$0A zciu>RXuw-p)%!C)>eFk-FEhHlwC!}y-Nzf>^X~L%@FU(I1ijYbm#hP~*FKWAVprjy z!PyhLomlizK%Zyd{Ncos?Bofb9{Jm?>6OYSb3bUj?C!TaZm;V7`>-RAGqCl%6-|{RcU7qM%<_>_ixeue;>OdvfY%IvSzrcm)^SF{{2_3%!&`XF?`f4)n60GB(?Tk z+vfcr#)g;vWoUDv(W2$q#-4^}pZDK2;_Z*BraUuce&F(#Pk;RFoY9K4i_7DR=k{t} z-9;7pSMw$hY|GVz9_TV9b@2O9i>Ck8?AymjM65qOD!cE&2M11E@ndQ)OU#11kF0H7 zwR_8VkG)#)*ow7V-hKJ&i`yDYaf5&VCg7&#-N2`>{aXB>_m)Aww+Zbtq59F)M?19{ zzF_9+jlO#xJT`1!(#!FU`=~PF4F&sVKN!|z9LcW`~EVp+5VX=*KX?nVfeVSN7uZ%cTP#n>FtjXxp8FN zBEQ^WXWe@i^gLT}IDcK^rzdosGH}k>d+T3)qxu=INtrp*Cj653)3DQt13bT)JNWd; zl`Tv!`}Frat-kz8w?opNey6*)d`{PKx<|pm_P$Y%JT-Dxr;g^YcD*!ybmzbgb3#6t zarm*^m-lQ|#jKl|=&^6l>t{b`y`fcI`u|&ciw9p>Yd&@F$Hx!0p3>2>y;8U2<-ePL zIjyr|_9JtTw0+)it;L)*rib_WfAaod&2QsRrKS9GW`g(70a5B(hSq;}?HRf4jWJSs zPIaFJDUIVJTs~X%``?#OubBJ#=Uon@d-QV&dFtTuw@2h=H&aD!9QyeWAHKV_Tg#)_ zyT&)WdMsnvn(`l3ecwI**o-O9+**F&p~-jqeb{p9w=b=0(|^oYn~wZ^YGJ^Ig>zp$ z_R6T`Q_V%Ur}RrISyqwMcA3ZWCwJa1-5I<5c2e8uunX7XehGDHp6{I$KV5NL3U}$% zIQ55Bzf8&7>i^61g##v@iCwy7SkQ+Bex`AcE_rxp!P_@ayf^aeo39-0^wZnRQ*Y_7 zYoj)0E;?P-D)Xx!AGZvRbWx4`{nhRZn~WUr*F&w|Kj5=!RODxKyPB>_-4^9`@QV0u z?e3`|Jum$IO~#Nm&t5J#boa{vKPT_^%SrgU@iF(mwofU1F{R?cf?sxi=#%CZ(e`S` zBc{kkM_a9&`qt%lNB^X2G;f^zk56G^=6(+2hQ?Z z{MPwr7fkuj(q9^f`{nd{WlYak`tJH*r+WK^^XsPX>fP?logaSga_Pms!EYZan%-MC z>g2a!Apz(9Sl~8uUE-}vACLXx&Q}S>H!dFe=FHcfrZt;$eeN&+`RI!N=2uU3y*qG3 z+b7;ESDn1$wKZ)#!Geb76R-+QONTBO@MXu;?W zPcO>)?bo&oH(h@I;E#XLIlS}c!99((X009EbMlr(?bpPIHy^S&C^zi#XKy{4x?shZ zcROXATNODmto0X)Vr|H2IOSj`8l{lfo}HR_0tP$z&N{=`opE;h(W}0v4_Jn+q+n&) zoqZ;!N$;mZp1Kti>#k%d--ym~R=UL2An=W?E?`@e)HL}rM*m4{T?5LqIZaXAxzH5q z=-yLna&Iix(@bilw3y&^r;;?Vg|?Y{W8G6BlHSEtDY+?ZnV^(qb@P?l)vm7E)yiuY z7p0_xM;Vsx$vysJR}WSY#Ys(};M{>twT<0frntJdR!#{Y*jn3?Wty~Z!mdi`HDi-v z`VZ`>?ZLpNtvhir2riJpbBz48DH^z+7?%tWNG}=KR_n?5Tv|8de3O%tjhdhUZRd8) z1`g5=(heLnXyBlMLuRyVHfWelR=Z?iKW!hmq(_fn{USZP)|H|e8?13w5h>(RheFgE z58Kw*wpY7a>Sk*ZFr~|Csf7aYCd$>4q*%9TlKadp0oPyY-bcAR>dVwo#Y6I+Uejch zdykc`y)f#hNzI>syRdm#>B!58UIkq$*FE~)myK1I?oG{psLhp4FCQ4PyUFRo;{SZF zyK(Z`L+PXA<8=$8)^EJKz55R%j&~~TQG8KNxJ9irgedx zwhIHgyLxHcnmqfI#Xq`fQRBaFEPSTF|Esqiov>0hN86tPifWe)*hyGcZEv~wyuQW23Jao zz~RP)JI{ZZvtd~8tb$HWf)BsYcje*fMS0PGHZpG)OmNhRd&i zJ3gUf_s9E1o%!JTqt~~tH|STt-1g!F>UQh;<(~e_BRz7ghxf|C*A+`U6fVl!xA)Mb z?{Yi5T-tu~xTaI*4>&$@UqQ(eBdQiB8cX_jf9(2}S6q^HH(m*O&HebH8Q*v5GG_FO zOPg=@Qt#JvgIB+O{b5+z2ai0sVfJpt)g42#LWXZBc=$)Nss9UGbT`sJxzW5y_{d#% zCY5)7Yt0MK3_3h@!n&*JUKM#;0uL@7(_zi7b)R@vW}bJ(oLUr}@OV90T+~v7%IzJlrliSa8maOUb)6iYhLZ@7Ny8XHfx5p>k zZ1hGqt+LW+K?DK}>~|9oZC*54j*Kt>nqq?Aso2s7( zSUY(As&}-J*Jij5%8D!4(CLCc_1^qB`om+(7ws7Aso%MH(}{k(r>~Zl!q~S+=xXk@ zJ9o`s;IM1g+4omV>_F2=L5*;zk`#DkiivfTw5xv8_I6kGahKfO8Y!i=o!Q8c z3PZ!<#fxo*26>Q(i5{R`^}dx^sa>`2fmH?Yx`}x9sz^<8lExSppQ?$m9<}tExl1(B zNlP?_Xt>j(f%^sUl9yCXbPRj2o0pjfW%Mj1X;>X_tb+%MHSBeWvG6`n4ZAS~1(ISl zG091>@$9uQshZ?@nzU4XU%QG7$xxP62AtOVfm>r>@0p7S_1G&Z^oj6hfYkm%*OZ5^ zSwmk6?U%m)=d7*ge_y=yw?$_cYbPD+^5hdwc6jNLM~D5kXRH6Seb2pPZdHEtM(Wn< zXJcD@ykwG3`L2<-)9)=TZFFt<@F$)Q*L>z%^3Ho(CN)`jug~4-uemkOXy-q*WoP4O z`=9Rq@T0pwPww&kPiOWUzH2f1KfO-=nwb9cmA&oS<(^;BZOF+7KmV%57Yonr>e0RB zt(|!rQdj9d>@c(OfQ|1~2ZvPYe7Co``AEQ7&{=-T}2gQ@K&?fq)#%42J%eD&H(2L=WHS^oLs+gI$IyuZVd=L37R>itoR zPWvWon-fzy-qbGa_4S{Bed%sl<3_{s)6%+XSGyI$5Vj4cF-^y9+4t?~{qNeEicMqw zA5I})b~A|0ZhQv~8a!x7?b*$$4Fez0dbu}gys_uH=YQ)PD@h(rY9@LEM`(SaBe=SF z478j2uo)E2utsPeO-`N<;TIYjr17pP`Vh_5n%W7hne@~Yvzz2WSG)hDr}R?GuU9F~ zd~(@y?}^=iB#qkVm3n2zuw{d?jtuQySn~0YXJ7s`zR8a3ja`>F+xym+F3}&xd%X13 z((#Wx_x9#vKYH&xH~nqDHJ84RdDT?%#PdhmKIye~mPh|lBWIo7@cz-C+l@UpGUxlH zilIL!Zr=Pq?Ob^{R9ypa#*nc!B1sBEwlH@X%ZRay>{%*8VywwBwp2nzvecVOp~+H` zA~A(QQj`fvmde(=krEn>{*J+d+Zjo|E;xf*G4o`vYbb z!&>=1vPIf0z2^tLG(56-hje)&_jl4S&kPEgaA*$XX9DRx?mkURmC(R2f zFN;U-T6>>k53ev{y;@!PzA$W{xdV-jWieP^RT9gZ)OLksr`o=mZhnqWh;0cziSFcOZ%0J2N$`@hgS5+=3S9)^vC34pqZPfa7l zd!I14qbg(LP<1p`4nPZon=|_`b~sW;TU+VNs-QIpY-%F9W;%?m#)ZKlg%lucZmn%> zs3VM)#b6kt3=xCTwbB(fH#W4i5(dwK!RVQyh0#A{74Se32ArAyzC=bAY#`?n;DQD( z^X=@*ZU?M&0arK^*pO;o!~+Cp7k6-qmPk^=FiK!0xVMWF;|o&F1>6vm@x@HZ5Fe;z zZtCZ26^M0FR`hTQ4cctu;)+%DQD+>7nLkhsGv5}hnY)4-X72g~wjZPc2-J#z`i-z5 z*Ama-*G)kNL%vB_1U5p#!)ItZ>5a$9TLk1BX>$}M$|U)@-H z&QQJHBA*h74E!2-H{2}15a+CM1klSlAkw^dv*EFjBvx|x4gqR#aY>p;_uc1w^UJ-u>l(h?PYdU83 zzNOj#En=v^s+M3%QA&$~n;~z6!AzFjaD2Z@LTj7GE(<4#OfJjsw z3;TTZgG9l(u#qUx7@~^4zsX>{6p6|PtFFLdU}pehA?D3oOmG2^^$sp+2}7jqLwEx2 zV9#b@mS#dQvjA8I=1aO3LRfJt_V}Jxo;tary)Wu2t<%dtJ{mczbONWM({sB2jgAwX z)yEHe`}PK2Eq;)9m-j8u+r!|<72^jthw=y!&C(({!JB7>gC_RFyb6?Ed7I~sd=Ka_fz`0jp*lcCg8dSN3)B>RHUwtz0DAV;UrBH zh1jWN-r)@yYNnTJ*Xv}DkHr6>#3f(WZO!$podX}Wg?>}xDf#yLq*xi6=5(*h_7Gvm zaOu4?hfzx9HZrN&IdOapo63y1xFdfGIQ6K~Ng}snDNh>@Wl8N8`(sazoYD-jBZ?cjM~S;HleXdY;Zo zq|cw^2Twa>x?Wm;#u9f)vyA)tsXkVRiJYDw&KEPbAvAfZ6ES(s`YWQJ(kxUOZh<~{ zC1^>8=2`Ht@eb$9qr*i%vfy7A>Ei%JMzD{=eT(!Li@%tO|IFYv1@os*TtvC;elABY zJ|*f#nAU!SJj`~on17n2i7BsoXkC6z8$iHHO~y z!cF+ENa~pC=+kW;F3cD8>1~QtME;GB_%c^JQRK#lV>shF)IYN{_(W|oMm|_y!xeHO zz7kzb72!#F^6@0O^Pt^qW?y4wMA*!1Z=7Mz7Pcu=mcK%&Ow~49e>S{%K&RyX1CC+W zvUwY93T^gX?HjJjoBAM}!2eh>;0>!ujJWmzhtGSu^>(KJVPWWSW}8XLP&4UH8{%O1 z_RF=I>+ElOUD!u{GY}xFOQ1UuR_cY}0>v+ArUa5}fI*{*;8QG^nhK`1*JvvtvBo>p%5g*|0rf0scghNm4W>`j>c8|(JRtZN z6?eVI<*Z4|+wGRkaWiEjv9Nm;ppEOxYl1ah>(nL%9=R>=& zF)jIs33uY}9^SnVGFm z^s!ENKk;EZfi~9M`$gkQk9hbURj<~EZ>QuVYUDEveotV@T-Q@ryT#}=y^gEk)`iBS zP^$lJYVpr{I+W@&=I@|XA4>KAqf&irBwPbjGF#@-p%cGWEx=j7+sQ@8n-&oV2z+9`V z?L8yaPOQwnf74MAWl`@5CmsFFoY6UMQ0rqV+}11b!g3d1HBskG@7K#x!=*A;+}{eQ zd0ke%VOHn1=i0`*NPxFVw3k}?NUY^G>}mJre5$3*lSvgbe7jJ{IFd(qwY_eJ(^SE& zfy6GG(zEqXu5Xs{*m9)nH`GK3e*d|KiQFuFrt(_$6&7DVwbiY}zc_Y&mNoZmMf9aV zu1jsJJZ@Va_l3yD`_4ptkJQ7N$_m!2Nqz;@l5?wOpnnho5CRYa5CRYa5CRYa5CRYa z5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa5CRYa|1Svq E4fcriHvj+t literal 759536 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

QiGj*Q(c9877aeRWId3*s33sgXUt2kB@++Y;3Zn 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
NF^XEoiLXf5@w^Y&RKV%79ruO!hKKhV{ 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�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

7MRFLs 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

  • a6q28H0^>`w&d%EKVS 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 Date: Tue, 1 Oct 2024 12:23:38 -0700 Subject: [PATCH 108/258] Docs for clang-format 19 (#13762) --- CONTRIBUTING.md | 2 +- README.md | 2 +- scripts/style.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f549dfa9d2..5722110bb01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -132,7 +132,7 @@ To develop Firebase software, **install**: To install [clang-format] and [mint] using [Homebrew]: ```console - brew install clang-format@18 + brew install clang-format@19 brew install mint ``` diff --git a/README.md b/README.md index 5da753b2321..f5a0c418e97 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ GitHub Actions will verify that any code changes are done in a style-compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@18 +brew install clang-format@19 brew install mint ``` diff --git a/scripts/style.sh b/scripts/style.sh index 5caf8e54296..d0bfaf8b2c1 100755 --- a/scripts/style.sh +++ b/scripts/style.sh @@ -65,7 +65,7 @@ case "$version" in exit 1 ;; *) - echo "Please upgrade to clang-format version 18." + echo "Please upgrade to clang-format version 19." echo "If it's installed via homebrew you can run:" echo "brew upgrade clang-format" exit 1 From b2f1e2629e89589cb63bab574700ab556900108e Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 1 Oct 2024 15:05:56 -0700 Subject: [PATCH 109/258] Fix zip auth test (#13758) --- .github/workflows/zip.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index 0059e7b5c70..2dd23dcc7fd 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -173,11 +173,9 @@ jobs: SDK: "Authentication" strategy: matrix: - os: [macos-13, macos-14] + os: [macos-14] artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] include: - - os: macos-13 - xcode: Xcode_15.2 - os: macos-14 xcode: Xcode_16 runs-on: ${{ matrix.os }} From 8f44bbcbcf95f17f1e7fc34023360d151609ae9b Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 2 Oct 2024 07:49:28 -0700 Subject: [PATCH 110/258] Fix FIROptions module organization (#13765) --- .github/workflows/installations.yml | 2 +- ...ABTConditionalUserPropertyControllerTest.m | 8 ++--- .../Unit/Core/FIRAppCheckValidatorTests.m | 24 +++++++------- .../Sources/FIRFADLogger.m | 1 + FirebaseAuth.podspec | 4 +-- FirebaseCore/CHANGELOG.md | 3 ++ FirebaseCore/Extension/FIRAppInternal.h | 21 ++++++++++++ FirebaseCore/Extension/FIRLogger.h | 4 +-- FirebaseCore/Extension/FirebaseCoreInternal.h | 1 - FirebaseCore/Sources/FIRApp.m | 3 +- FirebaseCore/Sources/FIRComponentContainer.m | 3 +- FirebaseCore/Sources/FIROptions.m | 2 +- .../FIROptionsInternal.h | 23 +------------ FirebaseCore/Tests/Unit/FIRAppTest.m | 2 +- FirebaseCore/Tests/Unit/FIRLoggerTest.m | 1 + FirebaseCore/Tests/Unit/FIROptionsTest.m | 2 +- FirebaseCrashlytics.podspec | 2 +- .../Sources/FIRDynamicLinks.m | 33 +++++-------------- FirebaseFirestore.podspec | 4 +-- FirebaseFunctions.podspec | 4 +-- .../Unit/FIRInstallationsIDControllerTests.m | 10 +++--- FirebaseMLModelDownloader.podspec | 4 +-- .../UnitTests/FIRMessagingTokenInfoTest.m | 4 +++ FirebaseSessions.podspec | 4 +-- FirebaseStorage.podspec | 4 +-- FirebaseVertexAI.podspec | 4 +-- .../Integration/API/FIRValidationTests.mm | 2 +- .../Tests/Util/FSTIntegrationTestCase.mm | 3 +- Firestore/Source/API/FSTFirestoreComponent.mm | 2 +- .../firebase_metadata_provider_apple.mm | 2 +- Firestore/core/src/util/log_apple.mm | 1 + .../core/test/unit/testutil/app_testing.mm | 2 +- SharedTestUtilities/FIROptionsMock.m | 20 ++++++++++- 33 files changed, 109 insertions(+), 100 deletions(-) rename FirebaseCore/{Extension => Sources}/FIROptionsInternal.h (81%) diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index 5fe5174fa9a..883fa696ae9 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -55,7 +55,7 @@ jobs: run: | export FIS_INTEGRATION_TESTS_REQUIRED=${{ steps.secrets.outputs.val }} scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseInstallations.podspec \ - --platforms=${{ matrix.target }} --test-specs=--platforms=${{ matrix.test-specs }} + --platforms=${{ matrix.target }} --test-specs=${{ matrix.test-specs }} spm-package-resolved: env: diff --git a/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m b/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m index 0c16a290f15..01673efb4d7 100644 --- a/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m +++ b/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m @@ -65,11 +65,9 @@ - (void)setUp { // Must initialize FIRApp before calling set experiment as Firebase Analytics internal event // logging requires it. - NSDictionary *optionsDictionary = @{ - kFIRGoogleAppID : @"1:123456789012:ios:1234567890123456", - @"GCM_SENDER_ID" : @"123456789012" - }; - FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; + FIROptions *options = + [[FIROptions alloc] initWithGoogleAppID:@"1:123456789012:ios:1234567890123456" + GCMSenderID:@"123456789012"]; [FIRApp configureWithOptions:options]; } diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckValidatorTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckValidatorTests.m index da22c5c2747..97d41d7141e 100644 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckValidatorTests.m +++ b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckValidatorTests.m @@ -17,7 +17,7 @@ #import #import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" @interface FIRAppCheckValidatorTests : XCTestCase @end @@ -25,11 +25,10 @@ @interface FIRAppCheckValidatorTests : XCTestCase @implementation FIRAppCheckValidatorTests - (void)test_tokenExchangeMissingFieldsInOptions_noMissingFields { - FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:@{ - kFIRGoogleAppID : @"TEST_GoogleAppID", - kFIRAPIKey : @"TEST_APIKey", - kFIRProjectID : @"TEST_ProjectID" - }]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"TEST_GoogleAppID" + GCMSenderID:@"TEST_GCMSenderID"]; + options.APIKey = @"TEST_APIKey"; + options.projectID = @"TEST_ProjectID"; NSArray *missingFields = [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:options]; @@ -38,11 +37,10 @@ - (void)test_tokenExchangeMissingFieldsInOptions_noMissingFields { - (void)test_tokenExchangeMissingFieldsInOptions_singleMissingField { // Google App ID is empty: - FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:@{ - kFIRGoogleAppID : @"", - kFIRAPIKey : @"TEST_APIKey", - kFIRProjectID : @"TEST_ProjectID" - }]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"" + GCMSenderID:@"TEST_GCMSenderID"]; + options.APIKey = @"TEST_APIKey"; + options.projectID = @"TEST_ProjectID"; NSArray *missingFields = [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:options]; @@ -51,8 +49,8 @@ - (void)test_tokenExchangeMissingFieldsInOptions_singleMissingField { - (void)test_tokenExchangeMissingFieldsInOptions_multipleMissingFields { // Google App ID is empty, and API Key and Project ID are not set: - FIROptions *options = - [[FIROptions alloc] initInternalWithOptionsDictionary:@{kFIRGoogleAppID : @""}]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"" + GCMSenderID:@"TEST_GCMSenderID"]; NSArray *missingFields = [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:options]; diff --git a/FirebaseAppDistribution/Sources/FIRFADLogger.m b/FirebaseAppDistribution/Sources/FIRFADLogger.m index 22a37ed603a..5eb8b92acd1 100644 --- a/FirebaseAppDistribution/Sources/FIRFADLogger.m +++ b/FirebaseAppDistribution/Sources/FIRFADLogger.m @@ -14,6 +14,7 @@ #import "FirebaseAppDistribution/Sources/FIRFADLogger.h" #import "FirebaseCore/Extension/FIRLogger.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" FIRLoggerService kFIRLoggerAppDistribution = @"[FirebaseAppDistribution]"; diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index ef84243b5cd..91093bc7d99 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -58,8 +58,8 @@ supports email and password accounts, as well as several 3rd party authenticatio s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' diff --git a/FirebaseCore/CHANGELOG.md b/FirebaseCore/CHANGELOG.md index 55ad25bbeb7..16a6b9d1290 100644 --- a/FirebaseCore/CHANGELOG.md +++ b/FirebaseCore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Firebase 11.4.0 +- [fixed] Fixed issue building documentation with some Firebase products. (#13756) + # Firebase 11.0.0 - [changed] **Breaking change**: Firebase's minimum supported versions have updated for the following platforms: diff --git a/FirebaseCore/Extension/FIRAppInternal.h b/FirebaseCore/Extension/FIRAppInternal.h index b0b15117227..a5669608449 100644 --- a/FirebaseCore/Extension/FIRAppInternal.h +++ b/FirebaseCore/Extension/FIRAppInternal.h @@ -40,6 +40,27 @@ extern NSString *const kFIRAppNameKey; extern NSString *const kFIRGoogleAppIDKey; extern NSString *const kFirebaseCoreErrorDomain; +/** + * Keys for the strings in the plist file. + */ +extern NSString *const kFIRAPIKey; +extern NSString *const kFIRTrackingID; +extern NSString *const kFIRGoogleAppID; +extern NSString *const kFIRClientID; +extern NSString *const kFIRGCMSenderID; +extern NSString *const kFIRAndroidClientID; +extern NSString *const kFIRDatabaseURL; +extern NSString *const kFIRStorageBucket; +extern NSString *const kFIRBundleID; +extern NSString *const kFIRProjectID; + +/** + * Keys for the plist file name + */ +extern NSString *const kServiceInfoFileName; + +extern NSString *const kServiceInfoFileType; + /** * The format string for the `UserDefaults` key used for storing the data collection enabled flag. * This includes formatting to append the `FirebaseApp`'s name. diff --git a/FirebaseCore/Extension/FIRLogger.h b/FirebaseCore/Extension/FIRLogger.h index 52ed75d7d6e..95adea51e73 100644 --- a/FirebaseCore/Extension/FIRLogger.h +++ b/FirebaseCore/Extension/FIRLogger.h @@ -16,7 +16,7 @@ #import -#import +typedef NS_ENUM(NSInteger, FIRLoggerLevel); NS_ASSUME_NONNULL_BEGIN @@ -132,7 +132,7 @@ NS_SWIFT_NAME(FirebaseLogger) /// /// - Parameters: /// - level: The log level to use (defined by `FirebaseLoggerLevel` enum values). -/// - service: The service name of type `FirebaseLoggerService`. +/// - category: The service name of type `FirebaseLoggerService`. /// - code: The message code. Starting with "I-" which means iOS, followed by a capitalized /// three-character service identifier and a six digit integer message ID that is unique within /// the service. An example of the message code is @"I-COR000001". diff --git a/FirebaseCore/Extension/FirebaseCoreInternal.h b/FirebaseCore/Extension/FirebaseCoreInternal.h index 0cb388be4fc..89a20493e08 100644 --- a/FirebaseCore/Extension/FirebaseCoreInternal.h +++ b/FirebaseCore/Extension/FirebaseCoreInternal.h @@ -21,4 +21,3 @@ #import "FIRHeartbeatLogger.h" #import "FIRLibrary.h" #import "FIRLogger.h" -#import "FIROptionsInternal.h" diff --git a/FirebaseCore/Sources/FIRApp.m b/FirebaseCore/Sources/FIRApp.m index 0fc8691b3e2..702799c8114 100644 --- a/FirebaseCore/Sources/FIRApp.m +++ b/FirebaseCore/Sources/FIRApp.m @@ -38,7 +38,8 @@ #import "FirebaseCore/Extension/FIRHeartbeatLogger.h" #import "FirebaseCore/Extension/FIRLibrary.h" #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" #import diff --git a/FirebaseCore/Sources/FIRComponentContainer.m b/FirebaseCore/Sources/FIRComponentContainer.m index b70881cc2e3..d4e4c7c5ad6 100644 --- a/FirebaseCore/Sources/FIRComponentContainer.m +++ b/FirebaseCore/Sources/FIRComponentContainer.m @@ -20,7 +20,8 @@ #import "FirebaseCore/Extension/FIRComponent.h" #import "FirebaseCore/Extension/FIRLibrary.h" #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" NS_ASSUME_NONNULL_BEGIN diff --git a/FirebaseCore/Sources/FIROptions.m b/FirebaseCore/Sources/FIROptions.m index 4676a5737f1..bfaeff1620e 100644 --- a/FirebaseCore/Sources/FIROptions.m +++ b/FirebaseCore/Sources/FIROptions.m @@ -14,8 +14,8 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" #import "FirebaseCore/Sources/FIRBundleUtil.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" // Keys for the strings in the plist file. diff --git a/FirebaseCore/Extension/FIROptionsInternal.h b/FirebaseCore/Sources/FIROptionsInternal.h similarity index 81% rename from FirebaseCore/Extension/FIROptionsInternal.h rename to FirebaseCore/Sources/FIROptionsInternal.h index 93a03d6894c..bdd0267ec92 100644 --- a/FirebaseCore/Extension/FIROptionsInternal.h +++ b/FirebaseCore/Sources/FIROptionsInternal.h @@ -14,28 +14,7 @@ * limitations under the License. */ -#import - -/** - * Keys for the strings in the plist file. - */ -extern NSString *const kFIRAPIKey; -extern NSString *const kFIRTrackingID; -extern NSString *const kFIRGoogleAppID; -extern NSString *const kFIRClientID; -extern NSString *const kFIRGCMSenderID; -extern NSString *const kFIRAndroidClientID; -extern NSString *const kFIRDatabaseURL; -extern NSString *const kFIRStorageBucket; -extern NSString *const kFIRBundleID; -extern NSString *const kFIRProjectID; - -/** - * Keys for the plist file name - */ -extern NSString *const kServiceInfoFileName; - -extern NSString *const kServiceInfoFileType; +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" /** * This header file exposes the initialization of FirebaseOptions to internal use. diff --git a/FirebaseCore/Tests/Unit/FIRAppTest.m b/FirebaseCore/Tests/Unit/FIRAppTest.m index b231e608b92..4bfbded209a 100644 --- a/FirebaseCore/Tests/Unit/FIRAppTest.m +++ b/FirebaseCore/Tests/Unit/FIRAppTest.m @@ -31,8 +31,8 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRComponentType.h" #import "FirebaseCore/Extension/FIRHeartbeatLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" #import "SharedTestUtilities/FIROptionsMock.h" NSString *const kFIRTestAppName1 = @"test_app_name_1"; diff --git a/FirebaseCore/Tests/Unit/FIRLoggerTest.m b/FirebaseCore/Tests/Unit/FIRLoggerTest.m index 1c5bda1f9d3..e99a54a88c9 100644 --- a/FirebaseCore/Tests/Unit/FIRLoggerTest.m +++ b/FirebaseCore/Tests/Unit/FIRLoggerTest.m @@ -21,6 +21,7 @@ // No test should include both includes. #import #import "FirebaseCore/Extension/FIRLogger.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" #import diff --git a/FirebaseCore/Tests/Unit/FIROptionsTest.m b/FirebaseCore/Tests/Unit/FIROptionsTest.m index fd5c901598f..3f228c83fad 100644 --- a/FirebaseCore/Tests/Unit/FIROptionsTest.m +++ b/FirebaseCore/Tests/Unit/FIROptionsTest.m @@ -15,8 +15,8 @@ #import "FirebaseCore/Tests/Unit/FIRTestCase.h" #import "FirebaseCore/Extension/FIRAppInternal.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" #import "FirebaseCore/Sources/FIRBundleUtil.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h" #import "SharedTestUtilities/FIROptionsMock.h" diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index c25cc9e79cd..49d7b6d9f7a 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -59,7 +59,7 @@ Pod::Spec.new do |s| cp -f ./Crashlytics/CrashlyticsInputFiles.xcfilelist ./CrashlyticsInputFiles.xcfilelist PREPARE_COMMAND_END - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseSessions', '~> 11.0' s.dependency 'FirebaseRemoteConfigInterop', '~> 11.0' diff --git a/FirebaseDynamicLinks/Sources/FIRDynamicLinks.m b/FirebaseDynamicLinks/Sources/FIRDynamicLinks.m index 7b340e6a9d6..f7b0b0f5e3c 100644 --- a/FirebaseDynamicLinks/Sources/FIRDynamicLinks.m +++ b/FirebaseDynamicLinks/Sources/FIRDynamicLinks.m @@ -166,30 +166,15 @@ - (void)configureDynamicLinks:(FIRApp *)app { userInfo:errorDict]; } if (error) { - NSString *message = nil; - if (options.usingOptionsFromDefaultPlist) { - // Configured using plist file - message = [NSString - stringWithFormat: - @"Firebase Dynamic Links has stopped your project " - @"because there are missing or incorrect values provided in %@.%@ that may " - @"prevent your app from behaving as expected:\n\n" - @"Error: %@\n\n" - @"Please fix these issues to ensure that Firebase is correctly configured in " - @"your project.", - kServiceInfoFileName, kServiceInfoFileType, error.localizedFailureReason]; - } else { - // Configured manually - message = [NSString - stringWithFormat: - @"Firebase Dynamic Links has stopped your project " - @"because there are incorrect values provided in Firebase's configuration " - @"options that may prevent your app from behaving as expected:\n\n" - @"Error: %@\n\n" - @"Please fix these issues to ensure that Firebase is correctly configured in " - @"your project.", - error.localizedFailureReason]; - } + NSString *message = + [NSString stringWithFormat: + @"Firebase Dynamic Links has stopped your project " + @"because there are incorrect values provided in Firebase's configuration " + @"options that may prevent your app from behaving as expected:\n\n" + @"Error: %@\n\n" + @"Please fix these issues to ensure that Firebase is correctly configured in " + @"your project.", + error.localizedFailureReason]; [NSException raise:kFirebaseDurableDeepLinkErrorDomain format:@"%@", message]; } [self checkForCustomDomainEntriesInInfoPlist]; diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 33232b14a75..3cb7f40c64b 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -35,8 +35,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, "#{s.module_name}_Privacy" => 'Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'FirebaseFirestoreInternal', '11.4.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index 854828b01ae..fefc00769ee 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -35,8 +35,8 @@ Cloud Functions for Firebase. 'FirebaseFunctions/Sources/**/*.swift', ] - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseMessagingInterop', '~> 11.0' diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m index 6b96860da65..cfa8ce9da4f 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m @@ -97,12 +97,10 @@ - (void)tearDown { #pragma mark - Initialization - (void)testInitWhenProjectIDSetThenItIsPassedToAPIService { - FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:@{ - kFIRAPIKey : @"api-key", - kFIRProjectID : @"project-id", - kFIRGoogleAppID : @"app-id", - kFIRGCMSenderID : @"sender-id" - }]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app-id" GCMSenderID:@"sender-id"]; + options.projectID = @"project-id"; + options.APIKey = @"api-key"; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"app-name" options:options]; OCMExpect([self.mockAPIService alloc]).andReturn(self.mockAPIService); diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index 5d74e6455a9..443d4c41347 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -36,8 +36,8 @@ Pod::Spec.new do |s| ] s.framework = 'Foundation' - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenInfoTest.m b/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenInfoTest.m index 76dceb1c23d..13d353b1d02 100644 --- a/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenInfoTest.m +++ b/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenInfoTest.m @@ -32,6 +32,10 @@ static NSString *const kIID = @"eMP633ZkDYA"; static BOOL const kAPNSSandbox = NO; +@interface FIROptions () ++ (NSDictionary *)defaultOptionsDictionary; +@end + @interface FIRMessagingTokenInfoTest : XCTestCase @property(nonatomic, strong) NSData *APNSDeviceToken; diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 42282c5f361..5103d4cac35 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -39,8 +39,8 @@ Pod::Spec.new do |s| base_dir + 'SourcesObjC/**/*.{c,h,m,mm}', ] - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 3b9e0ec23c9..57e25e7f5a1 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -39,8 +39,8 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' - s.dependency 'FirebaseCoreExtension', '~> 11.0' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 8f433720a1c..173916a0f7e 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -46,8 +46,8 @@ the Vertex AI in Firebase SDK. s.dependency 'FirebaseAppCheckInterop', '~> 11.2' s.dependency 'FirebaseAuthInterop', '~> 11.2' - s.dependency 'FirebaseCore', '~> 11.2' - s.dependency 'FirebaseCoreExtension', '~> 11.2' + s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCoreExtension', '~> 11.4' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' diff --git a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm index acc32c66be2..9bd751377f6 100644 --- a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm @@ -20,7 +20,7 @@ #include #import "FirebaseCore/Extension/FIRAppInternal.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm index f42d10bcb01..4b7c7b9f034 100644 --- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm +++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm @@ -34,7 +34,8 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "Firestore/Example/Tests/Util/FIRFirestore+Testing.h" #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" #import "Firestore/Source/API/FIRAggregateQuery+Internal.h" diff --git a/Firestore/Source/API/FSTFirestoreComponent.mm b/Firestore/Source/API/FSTFirestoreComponent.mm index 64c7c882428..ff6e89eadb0 100644 --- a/Firestore/Source/API/FSTFirestoreComponent.mm +++ b/Firestore/Source/API/FSTFirestoreComponent.mm @@ -28,7 +28,7 @@ #import "FirebaseCore/Extension/FIRComponentContainer.h" #import "FirebaseCore/Extension/FIRComponentType.h" #import "FirebaseCore/Extension/FIRLibrary.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #include "Firestore/core/include/firebase/firestore/firestore_version.h" diff --git a/Firestore/core/src/remote/firebase_metadata_provider_apple.mm b/Firestore/core/src/remote/firebase_metadata_provider_apple.mm index 998961ca977..ea2a4c982b3 100644 --- a/Firestore/core/src/remote/firebase_metadata_provider_apple.mm +++ b/Firestore/core/src/remote/firebase_metadata_provider_apple.mm @@ -18,7 +18,7 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRHeartbeatLogger.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #include "Firestore/core/src/util/string_apple.h" diff --git a/Firestore/core/src/util/log_apple.mm b/Firestore/core/src/util/log_apple.mm index 7a35adf736c..6d1e7d7ab6c 100644 --- a/Firestore/core/src/util/log_apple.mm +++ b/Firestore/core/src/util/log_apple.mm @@ -24,6 +24,7 @@ #include #import "FirebaseCore/Extension/FIRLogger.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" #include "Firestore/core/src/util/string_apple.h" diff --git a/Firestore/core/test/unit/testutil/app_testing.mm b/Firestore/core/test/unit/testutil/app_testing.mm index a51732375a6..643067bb4d3 100644 --- a/Firestore/core/test/unit/testutil/app_testing.mm +++ b/Firestore/core/test/unit/testutil/app_testing.mm @@ -15,7 +15,7 @@ */ #import "FirebaseCore/Extension/FIRAppInternal.h" -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #include "Firestore/core/src/util/string_apple.h" #include "Firestore/core/test/unit/testutil/app_testing.h" diff --git a/SharedTestUtilities/FIROptionsMock.m b/SharedTestUtilities/FIROptionsMock.m index 8002fa867db..d9dc4c246ba 100644 --- a/SharedTestUtilities/FIROptionsMock.m +++ b/SharedTestUtilities/FIROptionsMock.m @@ -14,7 +14,7 @@ #import -#import "FirebaseCore/Extension/FIROptionsInternal.h" +#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" #import "SharedTestUtilities/FIROptionsMock.h" NSString *const kAPIKey = @"correct_api_key"; @@ -31,6 +31,24 @@ NSString *const kBundleID = @"com.google.FirebaseSDKTests"; NSString *const kProjectID = @"abc-xyz-123"; +/** + * Keys for the strings in the plist file. + */ +extern NSString *const kFIRAPIKey; +extern NSString *const kFIRTrackingID; +extern NSString *const kFIRGoogleAppID; +extern NSString *const kFIRClientID; +extern NSString *const kFIRGCMSenderID; +extern NSString *const kFIRAndroidClientID; +extern NSString *const kFIRDatabaseURL; +extern NSString *const kFIRStorageBucket; +extern NSString *const kFIRBundleID; +extern NSString *const kFIRProjectID; + +@interface FIROptions () ++ (NSDictionary *)defaultOptionsDictionary; +@end + @interface FIROptionsMock () @end From c421661fa2034c66fb791e02a9d065ca2f01677e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:40:12 -0400 Subject: [PATCH 111/258] [Infra] Release testing should grab latest tag on any branch (#13769) --- scripts/release_testing_setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/release_testing_setup.sh b/scripts/release_testing_setup.sh index 010d558ca37..99206e37485 100755 --- a/scripts/release_testing_setup.sh +++ b/scripts/release_testing_setup.sh @@ -25,7 +25,10 @@ git fetch --tags --quiet origin main git checkout main # The chunk below is to determine the latest version by searching -# Get the latest released tag Cocoapods-X.Y.Z for release and prerelease testing, beta version will be excluded. +# Get the latest released tag Cocoapods-X.Y.Z for release and prerelease +# testing, beta version will be excluded. +# Note: If the nightly tag was not updated, check that the next release's tag +# is on the main branch. test_version=$(git tag -l --sort=-version:refname --merged main 'CocoaPods-*[0-9]' | head -n 1) if [ -z "$test_version" ]; then echo "Latest tag could not be found. Exiting." >&2 From 82febdd0616df8ec9e0923ef15adccfe128c0e38 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 2 Oct 2024 17:34:16 -0400 Subject: [PATCH 112/258] [Vertex AI] Removed `-beta` from pod version (#13759) --- FirebaseVertexAI.podspec | 14 +++++++------- .../FirebaseManifest/FirebaseManifest.swift | 2 +- ReleaseTooling/Sources/ZipBuilder/Platform.swift | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 173916a0f7e..a9ee6fb77eb 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = 'FirebaseVertexAI' - s.version = '11.4.0-beta' - s.summary = 'Vertex AI in Firebase - Public Preview' + s.version = '11.4.0' + s.summary = 'Vertex AI in Firebase SDK' s.description = <<-DESC -[Public Preview] Build AI-powered apps and features with the Gemini API using -the Vertex AI in Firebase SDK. +Build AI-powered apps and features with the Gemini API using the Vertex AI in +Firebase SDK. DESC s.homepage = 'https://firebase.google.com' @@ -20,7 +20,7 @@ the Vertex AI in Firebase SDK. s.social_media_url = 'https://twitter.com/Firebase' ios_deployment_target = '15.0' - osx_deployment_target = '11.0' + osx_deployment_target = '12.0' tvos_deployment_target = '15.0' watchos_deployment_target = '8.0' @@ -44,8 +44,8 @@ the Vertex AI in Firebase SDK. s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAppCheckInterop', '~> 11.2' - s.dependency 'FirebaseAuthInterop', '~> 11.2' + s.dependency 'FirebaseAppCheckInterop', '~> 11.4' + s.dependency 'FirebaseAuthInterop', '~> 11.4' s.dependency 'FirebaseCore', '~> 11.4' s.dependency 'FirebaseCoreExtension', '~> 11.4' diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index e1c401b3f78..cd38e8f6238 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -53,7 +53,7 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), - Pod("FirebaseVertexAI", isBeta: true, zip: true), + Pod("FirebaseVertexAI", zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), ] ) diff --git a/ReleaseTooling/Sources/ZipBuilder/Platform.swift b/ReleaseTooling/Sources/ZipBuilder/Platform.swift index d9281b11331..cab980cec85 100644 --- a/ReleaseTooling/Sources/ZipBuilder/Platform.swift +++ b/ReleaseTooling/Sources/ZipBuilder/Platform.swift @@ -69,7 +69,7 @@ enum PlatformMinimum { /// for the minimum version specified in the podspec. static func useRecentVersions() { minimumIOSVersion = "15.0" - minimumMacOSVersion = "11.0" + minimumMacOSVersion = "12.0" minimumTVOSVersion = "15.0" minimumWatchOSVersion = "8.0" } From 0a36c2a67fc0f35bb3d7d7f40542587826ac09b2 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 2 Oct 2024 19:35:14 -0400 Subject: [PATCH 113/258] [Infra] Switch to the iPhone 16 sim on Xcode 16 (#13766) --- scripts/build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 496f1e2de3d..1fd68410596 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -143,11 +143,16 @@ if [[ "$xcode_major" -lt 15 ]]; then -sdk 'iphonesimulator' -destination 'platform=iOS Simulator,name=iPhone 14' ) -else +elif [[ "$xcode_major" -lt 16 ]]; then ios_flags=( -sdk 'iphonesimulator' -destination 'platform=iOS Simulator,name=iPhone 15' ) +else + ios_flags=( + -sdk 'iphonesimulator' + -destination 'platform=iOS Simulator,name=iPhone 16' + ) fi ios_device_flags=( From 2a5637541cb04448939a04ba1a27f2ffe7193020 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 17:36:44 +0330 Subject: [PATCH 114/258] Fix FirebaseCore tests comments (#13777) --- FirebaseCore/Tests/Unit/FIRAppTest.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseCore/Tests/Unit/FIRAppTest.m b/FirebaseCore/Tests/Unit/FIRAppTest.m index 4bfbded209a..0bba5dceba1 100644 --- a/FirebaseCore/Tests/Unit/FIRAppTest.m +++ b/FirebaseCore/Tests/Unit/FIRAppTest.m @@ -115,7 +115,7 @@ + (void)tearDown { // We stop mocking `FIRHeartbeatLogger` in the class `tearDown` method to // prevent interfering with other tests that use the real `FIRHeartbeatLogger`. // Doing this in the instance `tearDown` causes test failures due to a race - // condition between `NSNoticationCenter` and `OCMVerifyAllWithDelay`. + // condition between `NSNotificationCenter` and `OCMVerifyAllWithDelay`. // Affected tests: // - testHeartbeatLogIsAttemptedWhenAppDidBecomeActive [OCMClassMock([FIRHeartbeatLogger class]) stopMocking]; @@ -301,7 +301,7 @@ - (void)testConfigureDefaultAppInExtension { XCTAssertThrows([FIRApp configureWithOptions:differentOptions]); XCTAssertEqual([FIRApp allApps].count, 1); - // Explicily stop the environmentMock. + // Explicitly stop the environmentMock. [environmentMock stopMocking]; environmentMock = nil; } @@ -330,7 +330,7 @@ - (void)testConfigureCustomAppInExtension { XCTAssertThrows([FIRApp configureWithName:kFIRTestAppName1 options:differentOptions]); XCTAssertEqual([FIRApp allApps].count, 1); - // Explicily stop the environmentMock. + // Explicitly stop the environmentMock. [environmentMock stopMocking]; environmentMock = nil; } @@ -601,7 +601,7 @@ - (void)testAppIDContainsInvalidBundleIDHash { // Uncomment if you need to measure performance of [FIRApp validateAppID:]. // It is commented because measures are heavily dependent on a build agent configuration, // so it cannot produce reliable resault on CI -//- (void)testAppIDValidationPerfomance { +//- (void)testAppIDValidationPerformance { // [self measureBlock:^{ // for (NSInteger i = 0; i < 100; ++i) { // [self testAppIDPrefix]; From c216e1fa71b8fc7750807ec5417236dfb51f82ec Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 17:42:53 +0330 Subject: [PATCH 115/258] Docs: improve comments of the FirebaseDatabase (#13785) --- FirebaseDatabase/Tests/Unit/FTreeSortedDictionaryTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseDatabase/Tests/Unit/FTreeSortedDictionaryTests.m b/FirebaseDatabase/Tests/Unit/FTreeSortedDictionaryTests.m index fe3b0babded..138ac3a1fb6 100644 --- a/FirebaseDatabase/Tests/Unit/FTreeSortedDictionaryTests.m +++ b/FirebaseDatabase/Tests/Unit/FTreeSortedDictionaryTests.m @@ -173,7 +173,7 @@ - (void)testIncreasing { XCTAssertTrue([map count] == 0, @"Check if all 100 objects were removed"); // We can't check the depth here because the map no longer contains values, so we check that it - // doesn't responsd to this check + // doesn't responds to this check XCTAssertTrue([map.root isMemberOfClass:[FLLRBEmptyNode class]], @"Root is an empty node"); XCTAssertFalse([map respondsToSelector:@selector(checkMaxDepth)], @"The empty node doesn't respond to this selector."); From 6d297a763dd686f55aa534dc65c4273e9d182461 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:02:00 +0330 Subject: [PATCH 116/258] Fix FirebaseDatabase sync point test file content (#13784) --- FirebaseDatabase/Tests/Unit/FSyncPointTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseDatabase/Tests/Unit/FSyncPointTests.m b/FirebaseDatabase/Tests/Unit/FSyncPointTests.m index 1b2b6e17b4f..bf37b39ccfb 100644 --- a/FirebaseDatabase/Tests/Unit/FSyncPointTests.m +++ b/FirebaseDatabase/Tests/Unit/FSyncPointTests.m @@ -560,7 +560,7 @@ - (void)testUpdateDescendantOfDefaultListenerWithFullCache { [self runTestForName:@"Update descendant of default listener with full cache"]; } -- (void)testDescendantSetBelowAnEmptyDefaultLIstenerIsIgnored { +- (void)testDescendantSetBelowAnEmptyDefaultListenerIsIgnored { [self runTestForName:@"Descendant set below an empty default listener is ignored"]; } From 4371cb5b11a0e8667e3e4dd4ec37c637263a0370 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:02:40 +0330 Subject: [PATCH 117/258] Fix a little typo in a comment of a json resource (#13782) --- FirebaseDatabase/Tests/Resources/syncPointSpec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseDatabase/Tests/Resources/syncPointSpec.json b/FirebaseDatabase/Tests/Resources/syncPointSpec.json index 60e7cd2eaa2..b6773f979b1 100644 --- a/FirebaseDatabase/Tests/Resources/syncPointSpec.json +++ b/FirebaseDatabase/Tests/Resources/syncPointSpec.json @@ -6957,7 +6957,7 @@ ] }, { - ".comment": "this caused vomitting in the past...", + ".comment": "this caused vomiting in the past...", "type": "set", "path": "a/foo/bar", "data": null, From 3857eac90bc7c8a8d6436e42e84b7f9cfc2df5f3 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:03:08 +0330 Subject: [PATCH 118/258] Fix FirebaseCore swift unit tests function names (#13779) --- FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift b/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift index 61922fbe740..c6f82725675 100644 --- a/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift +++ b/FirebaseCore/Tests/SwiftUnit/FirebaseAppTests.swift @@ -183,7 +183,7 @@ class FirebaseAppTests: XCTestCase { waitForExpectations(timeout: 1) } - func testGetUnitializedDefaultApp() { + func testGetUninitializedDefaultApp() { let app = FirebaseApp.app() XCTAssertNil(app) } From cf2348de402725741ad89e000cc2f013f3331bab Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:03:41 +0330 Subject: [PATCH 119/258] Fix FirebaseDatabase integration test files content (#13783) --- FirebaseDatabase/Tests/Unit/FRangeMergeTest.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseDatabase/Tests/Unit/FRangeMergeTest.m b/FirebaseDatabase/Tests/Unit/FRangeMergeTest.m index 8c1837c9e7c..abfd7432998 100644 --- a/FirebaseDatabase/Tests/Unit/FRangeMergeTest.m +++ b/FirebaseDatabase/Tests/Unit/FRangeMergeTest.m @@ -153,7 +153,7 @@ - (void)testCanReplaceLeafNodeWithLeafNode { XCTAssertEqualObjects(actual, expected); } -- (void)testLeafsAreUpdatedWhenRangesIncludeDeeperPath { +- (void)testLeavesAreUpdatedWhenRangesIncludeDeeperPath { id node = NODE((@{@"foo" : @{@"bar" : @"bar-value"}})); id updates = NODE((@{@"foo" : @{@"bar" : @"new-bar-value"}})); @@ -167,7 +167,7 @@ - (void)testLeafsAreUpdatedWhenRangesIncludeDeeperPath { XCTAssertEqualObjects(actual, expected); } -- (void)testLeafsAreNotUpdatedWhenRangesIncludeDeeperPaths { +- (void)testLeavesAreNotUpdatedWhenRangesIncludeDeeperPaths { id node = NODE((@{@"foo" : @{@"bar" : @"bar-value"}})); id updates = NODE((@{@"foo" : @{@"bar" : @"new-bar-value"}})); From 7c4c1340629ed795a1f6d2f91c81bf8da1c0a53f Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:04:11 +0330 Subject: [PATCH 120/258] Fix FirebaseDatabase integration test files content (#13781) --- FirebaseDatabase/Tests/Integration/FData.m | 4 ++-- FirebaseDatabase/Tests/Integration/FTransactionTest.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseDatabase/Tests/Integration/FData.m b/FirebaseDatabase/Tests/Integration/FData.m index 9570cc1c5aa..eb7aa1787bc 100644 --- a/FirebaseDatabase/Tests/Integration/FData.m +++ b/FirebaseDatabase/Tests/Integration/FData.m @@ -1190,10 +1190,10 @@ - (void)testEffectsOfSetPriorityIsImmediatelyEvident { }]; NSArray *expectedValues = @[ @5, @5 ]; - NSArray *expectedPriorites = @[ [NSNull null], @10 ]; + NSArray *expectedPriorities = @[ [NSNull null], @10 ]; XCTAssertTrue([values isEqualToArray:expectedValues], @"Expected both listeners to get 5, got %@ instead", values); - XCTAssertTrue([priorities isEqualToArray:expectedPriorites], + XCTAssertTrue([priorities isEqualToArray:expectedPriorities], @"The first listener should have missed the priority, got %@ instead", priorities); } diff --git a/FirebaseDatabase/Tests/Integration/FTransactionTest.m b/FirebaseDatabase/Tests/Integration/FTransactionTest.m index 531e4a6ec95..935bc4a5561 100644 --- a/FirebaseDatabase/Tests/Integration/FTransactionTest.m +++ b/FirebaseDatabase/Tests/Integration/FTransactionTest.m @@ -720,14 +720,14 @@ - (void)testUpdateShouldNotCancelUnrelatedTransactions { barTransactionDone = YES; }]; - NSDictionary *udpateData = @{ + NSDictionary *updateData = @{ @"foo" : @"newValue", @"boo" : @"newValue", @"doo/foo" : @"newValue", @"loo" : @{@"doo" : @{@"boo" : @"newValue"}} }; - [self waitForCompletionOf:ref updateChildValues:udpateData]; + [self waitForCompletionOf:ref updateChildValues:updateData]; XCTAssertTrue(fooTransactionDone, "Should have gotten cancelled before the update"); XCTAssertFalse(barTransactionDone, "Should run after the update"); [ref.repo setHijackHash:NO]; From d4e42eef8c6d40abbe0dede3bd96c304dab4f0e8 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 18:04:36 +0330 Subject: [PATCH 121/258] Enhance FirebaseDatabase function names (#13778) --- FirebaseDatabase/Sources/Persistence/FPruneForest.h | 2 +- FirebaseDatabase/Sources/Persistence/FPruneForest.m | 2 +- FirebaseDatabase/Tests/Helpers/FMockStorageEngine.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseDatabase/Sources/Persistence/FPruneForest.h b/FirebaseDatabase/Sources/Persistence/FPruneForest.h index 9e77217a5f6..91c4cc3ea6c 100644 --- a/FirebaseDatabase/Sources/Persistence/FPruneForest.h +++ b/FirebaseDatabase/Sources/Persistence/FPruneForest.h @@ -33,6 +33,6 @@ - (FPruneForest *)keepAll:(NSSet *)children atPath:(FPath *)path; - (FPruneForest *)pruneAll:(NSSet *)children atPath:(FPath *)path; -- (void)enumarateKeptNodesUsingBlock:(void (^)(FPath *path))block; +- (void)enumerateKeptNodesUsingBlock:(void (^)(FPath *path))block; @end diff --git a/FirebaseDatabase/Sources/Persistence/FPruneForest.m b/FirebaseDatabase/Sources/Persistence/FPruneForest.m index b777ba9b88c..7d5cdc29b4a 100644 --- a/FirebaseDatabase/Sources/Persistence/FPruneForest.m +++ b/FirebaseDatabase/Sources/Persistence/FPruneForest.m @@ -183,7 +183,7 @@ - (FPruneForest *)setPruneValue:(FImmutableTree *)pruneValue initWithForest:[self.pruneForest setTree:newSubtree atPath:path]]; } -- (void)enumarateKeptNodesUsingBlock:(void (^)(FPath *))block { +- (void)enumerateKeptNodesUsingBlock:(void (^)(FPath *))block { [self.pruneForest forEach:^(FPath *path, id value) { if (value != nil && ![value boolValue]) { block(path); diff --git a/FirebaseDatabase/Tests/Helpers/FMockStorageEngine.m b/FirebaseDatabase/Tests/Helpers/FMockStorageEngine.m index e88c3a85d1c..dafc1a9bc30 100644 --- a/FirebaseDatabase/Tests/Helpers/FMockStorageEngine.m +++ b/FirebaseDatabase/Tests/Helpers/FMockStorageEngine.m @@ -125,7 +125,7 @@ - (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)prunePath { FPath *relativePath = [FPath relativePathFrom:prunePath to:absolutePath]; if ([pruneForest shouldPruneUnkeptDescendantsAtPath:relativePath]) { __block FCompoundWrite *newCache = [FCompoundWrite emptyWrite]; - [[pruneForest childAtPath:relativePath] enumarateKeptNodesUsingBlock:^(FPath *keepPath) { + [[pruneForest childAtPath:relativePath] enumerateKeptNodesUsingBlock:^(FPath *keepPath) { newCache = [newCache addWrite:[node getChild:keepPath] atPath:keepPath]; }]; self.serverCache = From 5df5663cbc52be43d92fa3c482669149a559bb89 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 19:36:37 +0330 Subject: [PATCH 122/258] Docs: enhance FirebaseDynamicLinks comments (#13786) --- .../Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h b/FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h index a23dea44780..c07a88fa2bf 100644 --- a/FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h +++ b/FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h @@ -130,7 +130,7 @@ NS_SWIFT_NAME(DynamicLinks) /** * @method performDiagnosticsWithCompletion: * @abstract Performs basic FDL self diagnostic. Method effect on startup latency is quite small - * and no user-visble UI is presented. This method should be used for debugging purposes. + * and no user-visible UI is presented. This method should be used for debugging purposes. * App developers are encouraged to include output, generated by this method, to the support * requests sent to Firebase support. * @param completionHandler Handler that will be called when diagnostic completes. From 90eb6d56d614fd95e989aa37b8be7fc807fcb5ea Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Thu, 3 Oct 2024 19:41:08 +0330 Subject: [PATCH 123/258] Docs: enhance firebase in app messaging comments (#13790) --- .../Sources/Analytics/FIRIAMClearcutUploader.m | 2 +- .../DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m | 2 +- .../Sources/DefaultUI/Modal/FIRIAMModalViewController.m | 4 ++-- FirebaseInAppMessaging/Sources/DefaultUI/README.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m b/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m index 841cdeee1b3..a1a0646f5f3 100644 --- a/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m +++ b/FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutUploader.m @@ -177,7 +177,7 @@ - (void)attemptUploading { * Note that there is a chance that the app crashes before we can * call pushRecords: on the logStorage below which means we lost * these log records permanently. This is a trade-off between handling - * duplicate records on server side vs taking the risk of lossing + * duplicate records on server side vs taking the risk of losing * data. This implementation picks the latter. */ FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260007", diff --git a/FirebaseInAppMessaging/Sources/DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m b/FirebaseInAppMessaging/Sources/DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m index 4fd7bf8a133..990aea253bb 100644 --- a/FirebaseInAppMessaging/Sources/DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m +++ b/FirebaseInAppMessaging/Sources/DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m @@ -117,7 +117,7 @@ - (void)viewDidLayoutSubviews { // Calculate the size of the image view under the constraints: // 1 Retain the image ratio - // 2 Have at least 30 point of margines around four sides of the image view + // 2 Have at least 30 point of margins around four sides of the image view CGFloat minimalMargine = 30; // 30 points CGFloat maxImageViewWidth = self.view.window.frame.size.width - minimalMargine * 2; diff --git a/FirebaseInAppMessaging/Sources/DefaultUI/Modal/FIRIAMModalViewController.m b/FirebaseInAppMessaging/Sources/DefaultUI/Modal/FIRIAMModalViewController.m index ada6dd089e1..3009e38fc42 100644 --- a/FirebaseInAppMessaging/Sources/DefaultUI/Modal/FIRIAMModalViewController.m +++ b/FirebaseInAppMessaging/Sources/DefaultUI/Modal/FIRIAMModalViewController.m @@ -296,7 +296,7 @@ - (void)layoutFineTuneInPortraitMode { "with frame height as %lf", heightCalcReference, self.view.window.frame.size.height); - // this makes sure titleLable gets correct width to be ready for later's height estimate for the + // this makes sure titleLabel gets correct width to be ready for later's height estimate for the // text & button column [self.messageCardView layoutIfNeeded]; @@ -403,7 +403,7 @@ - (void)layoutFineTuneInLandscapeMode { self.cardLeadingMarginInLandscapeMode.constant = self.view.window.frame.size.width / 5; } - // this makes sure titleLable gets correct width to be ready for later's height estimate for the + // this makes sure titleLabel gets correct width to be ready for later's height estimate for the // text & button column [self.messageCardView layoutIfNeeded]; diff --git a/FirebaseInAppMessaging/Sources/DefaultUI/README.md b/FirebaseInAppMessaging/Sources/DefaultUI/README.md index 872eff63623..c5200f50913 100644 --- a/FirebaseInAppMessaging/Sources/DefaultUI/README.md +++ b/FirebaseInAppMessaging/Sources/DefaultUI/README.md @@ -1,5 +1,5 @@ FirebaseInAppMessagingDisplay is the default UI implementation from Firebase for -rendering In-App Messaging messges to end users. +rendering In-App Messaging messages to end users. Apps can also provide custom UI implementation to replace it. Check out our guides for details. From 8738b84dff531e18c68ad403187314b8559ff584 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Thu, 3 Oct 2024 13:51:28 -0400 Subject: [PATCH 124/258] Reapply "SessionToken persistence implementation (#13684)" (#13719) (#13794) --- Firestore/CHANGELOG.md | 3 + .../Firestore.xcodeproj/project.pbxproj | 44 ++++++++++++ Firestore/core/src/local/globals_cache.h | 57 +++++++++++++++ .../core/src/local/leveldb_globals_cache.cc | 57 +++++++++++++++ .../core/src/local/leveldb_globals_cache.h | 52 ++++++++++++++ Firestore/core/src/local/leveldb_key.cc | 36 ++++++++++ Firestore/core/src/local/leveldb_key.h | 35 +++++++++ .../core/src/local/leveldb_persistence.cc | 5 ++ .../core/src/local/leveldb_persistence.h | 4 ++ .../core/src/local/memory_globals_cache.cc | 33 +++++++++ .../core/src/local/memory_globals_cache.h | 49 +++++++++++++ .../core/src/local/memory_persistence.cc | 4 ++ Firestore/core/src/local/memory_persistence.h | 5 ++ Firestore/core/src/local/persistence.h | 6 ++ .../test/unit/local/globals_cache_test.cc | 71 +++++++++++++++++++ .../core/test/unit/local/globals_cache_test.h | 59 +++++++++++++++ .../unit/local/leveldb_globals_cache_test.cc | 38 ++++++++++ .../unit/local/memory_globals_cache_test.cc | 38 ++++++++++ 18 files changed, 596 insertions(+) create mode 100644 Firestore/core/src/local/globals_cache.h create mode 100644 Firestore/core/src/local/leveldb_globals_cache.cc create mode 100644 Firestore/core/src/local/leveldb_globals_cache.h create mode 100644 Firestore/core/src/local/memory_globals_cache.cc create mode 100644 Firestore/core/src/local/memory_globals_cache.h create mode 100644 Firestore/core/test/unit/local/globals_cache_test.cc create mode 100644 Firestore/core/test/unit/local/globals_cache_test.h create mode 100644 Firestore/core/test/unit/local/leveldb_globals_cache_test.cc create mode 100644 Firestore/core/test/unit/local/memory_globals_cache_test.cc diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 092e66027f8..0635a24af70 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [changed] Prepare Firestore cache to support session token. + # 11.3.0 - [changed] Improve efficiency of memory persistence when processing a large number of writes. (#13572) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index d90023b76d6..7b6e8450bf1 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 00A5761CD97E26A0EF4D47ED /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8582DFD74E8060C7072104B /* Validation_BloomFilterTest_MD5_5000_0001_membership_test_result.json */; }; 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; 00F1CB487E8E0DA48F2E8FEC /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; + 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 0131DEDEF2C3CCAB2AB918A5 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 01C66732ECCB83AB1D896026 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; @@ -50,6 +51,7 @@ 06E0914D76667F1345EC17F5 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C939D1789E38C09F9A0C1157 /* Validation_BloomFilterTest_MD5_1_0001_membership_test_result.json */; }; 070B9CCDD759E66E6E10CC68 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = A5D9044B72061CAF284BC9E4 /* Validation_BloomFilterTest_MD5_50000_0001_bloom_filter_proto.json */; }; 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 076465DFEEEAA4CAF5A0595A /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; }; 077292C9797D97D3851F15CE /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; 0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; @@ -110,7 +112,9 @@ 0F99BB63CE5B3CFE35F9027E /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; + 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; + 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */; }; 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; @@ -148,6 +152,7 @@ 15576E9A23A1C6678D5D7DE1 /* bloom_filter.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C7C0DCD2790019E66D8CC /* bloom_filter.pb.cc */; }; 155B7B54FFC72C14530BC4D4 /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; 156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = D872D754B8AD88E28AF28B28 /* aggregation_result.pb.cc */; }; + 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 15A5DEC8430E71D64424CBFD /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; 15A5F95DA733FD89A1E4147D /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; 15BF63DFF3A7E9A5376C4233 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; @@ -274,6 +279,7 @@ 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; 2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; + 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 284A5280F868B2B4B5A1C848 /* leveldb_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */; }; 28691225046DF9DF181B3350 /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; 28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -371,6 +377,7 @@ 392966346DA5EB3165E16A22 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 392F527F144BADDAC69C5485 /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; 394259BB091E1DB5994B91A2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; + 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */; }; 39CDC9EC5FD2E891D6D49151 /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; }; 3A307F319553A977258BB3D6 /* view_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */; }; @@ -389,6 +396,8 @@ 3BA4EEA6153B3833F86B8104 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; 3BAFCABA851AE1865D904323 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 3C5D441E7D5C140F0FB14D91 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; + 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; @@ -679,6 +688,7 @@ 5DA343D28AE05B0B2FE9FFB3 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 5DA741B0B90DB8DAB0AAE53C /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 5DDEC1A08F13226271FE636E /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; + 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; 5E53122E4214FC4EA3B3DC1E /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; }; 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; @@ -688,6 +698,7 @@ 5ECE040F87E9FCD0A5D215DB /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; 5EDF0D63EAD6A65D4F8CDF45 /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; 5EE21E86159A1911E9503BC1 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; + 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 5EFBAD082CB0F86CD0711979 /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 5F05A801B1EA44BC1264E55A /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; 5F096E8A16A3FAC824E194D1 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -798,6 +809,7 @@ 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 6E59498D20F55BA800ECD9A5 /* FuzzingResources in Resources */ = {isa = PBXBuildFile; fileRef = 6ED6DEA120F5502700FC6076 /* FuzzingResources */; }; + 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; 6E7603BC1D8011A5D6F62072 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; }; 6E8302E021022309003E1EA3 /* FSTFuzzTestFieldPath.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */; }; 6E8CD8F545C8EDA84918977C /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -956,6 +968,7 @@ 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; + 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */; }; 87B5972F1C67CB8D53ADA024 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; 87B5AC3EBF0E83166B142FA4 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 881E55152AB34465412F8542 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; @@ -1234,9 +1247,11 @@ B9706A5CD29195A613CF4147 /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; B99452AB7E16B72D1C01FBBC /* datastore_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3167BD972EFF8EC636530E59 /* datastore_test.cc */; }; B998971CE6D0D1DD2AD9250A /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */; }; + B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; BA0BB02821F1949783C8AA50 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; BA3C0BA8082A6FB2546E47AC /* CodableTimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B65C996438B84DBC7616640 /* CodableTimestampTests.swift */; }; + BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; BACBBF4AF2F5455673AEAB35 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; @@ -1298,6 +1313,7 @@ C4548D8C790387C8E64F0FC4 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; C482E724F4B10968417C3F78 /* Pods_Firestore_FuzzTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */; }; C4C7A8D11DC394EF81B7B1FA /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; }; + C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* objc_type_traits_apple_test.mm */; }; C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; C57B15CADD8C3E806B154C19 /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; @@ -1336,6 +1352,7 @@ CCE596E8654A4D2EEA75C219 /* index_backfiller_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */; }; CD1E2F356FC71D7E74FCD26C /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; CD226D868CEFA9D557EF33A1 /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; + CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; CD78EEAA1CD36BE691CA3427 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; CDB5816537AB1B209C2B72A4 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; CE2962775B42BDEEE8108567 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; @@ -1604,6 +1621,7 @@ FB3D9E01547436163C456A3C /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; FBBB13329D3B5827C21AE7AB /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; FC1D22B6EC4E5F089AE39B8C /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; }; + FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; FCF8E7F5268F6842C07B69CF /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; FD365D6DFE9511D3BA2C74DF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -1746,6 +1764,7 @@ 4334F87873015E3763954578 /* status_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = status_testing.h; sourceTree = ""; }; 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json; sourceTree = ""; }; 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = ""; }; + 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = globals_cache_test.cc; sourceTree = ""; }; 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json; sourceTree = ""; }; 48D0915834C3D234E5A875A9 /* grpc_stream_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = grpc_stream_tester.h; sourceTree = ""; }; 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json; sourceTree = ""; }; @@ -1863,6 +1882,7 @@ 5B5414D28802BC76FDADABD6 /* stream_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = stream_test.cc; sourceTree = ""; }; 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json; sourceTree = ""; }; 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_1_01_membership_test_result.json; sourceTree = ""; }; + 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = memory_globals_cache_test.cc; sourceTree = ""; }; 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_mutation_queue_test.cc; sourceTree = ""; }; 5CAE131920FFFED600BE9A4A /* Firestore_Benchmarks_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_Benchmarks_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5CAE131D20FFFED600BE9A4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1974,6 +1994,7 @@ 9B0B005A79E765AF02793DCE /* schedule_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = schedule_test.cc; sourceTree = ""; }; 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; path = recovery_spec_test.json; sourceTree = ""; }; 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_format_apple_test.mm; sourceTree = ""; }; + 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = globals_cache_test.h; sourceTree = ""; }; A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = testing_hooks_test.cc; sourceTree = ""; }; A082AFDD981B07B5AD78FDE8 /* token_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = token_test.cc; path = credentials/token_test.cc; sourceTree = ""; }; A20BAA3D2F994384279727EC /* md5_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = md5_testing.h; sourceTree = ""; }; @@ -2105,6 +2126,7 @@ F848C41C03A25C42AD5A4BC2 /* target_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = target_cache_test.h; sourceTree = ""; }; F869D85E900E5AF6CD02E2FC /* firebase_auth_credentials_provider_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; name = firebase_auth_credentials_provider_test.mm; path = credentials/firebase_auth_credentials_provider_test.mm; sourceTree = ""; }; FA2E9952BA2B299C1156C43C /* Pods-Firestore_Benchmarks_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; sourceTree = ""; }; + FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = leveldb_globals_cache_test.cc; sourceTree = ""; }; FC738525340E594EBFAB121E /* Pods-Firestore_Example_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.release.xcconfig"; sourceTree = ""; }; FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRQueryUnitTests.mm; sourceTree = ""; }; FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = document_overlay_cache_test.cc; sourceTree = ""; }; @@ -2448,11 +2470,14 @@ 75E24C5CD7BC423D48713100 /* counting_query_engine.h */, FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */, DF445D5201750281F1817387 /* document_overlay_cache_test.h */, + 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */, + 9E60C06991E3D28A0F70DD8D /* globals_cache_test.h */, 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */, AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */, 73F1F73A2210F3D800E1F692 /* index_manager_test.h */, 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */, AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */, + FC44D934D4A52C790659C8D6 /* leveldb_globals_cache_test.cc */, 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */, 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */, 5FF903AEFA7A3284660FA4C5 /* leveldb_local_store_test.cc */, @@ -2474,6 +2499,7 @@ CB7B2D4691C380DE3EB59038 /* lru_garbage_collector_test.h */, AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */, 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */, + 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */, DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */, F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */, 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */, @@ -4197,6 +4223,7 @@ 9B9BFC16E26BDE4AE0CDFF4B /* firebase_auth_credentials_provider_test.mm in Sources */, C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */, B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */, + B9D4DA59E3ADFA44669E4514 /* globals_cache_test.cc in Sources */, 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */, 4D98894EB5B3D778F5628456 /* grpc_stream_test.cc in Sources */, 0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */, @@ -4213,6 +4240,7 @@ 49C04B97AB282FFA82FD98CD /* latlng.pb.cc in Sources */, 292BCC76AF1B916752764A8F /* leveldb_bundle_cache_test.cc in Sources */, 095A878BB33211AB52BFAD9F /* leveldb_document_overlay_cache_test.cc in Sources */, + 15A0A6FD290362B42B8DC93B /* leveldb_globals_cache_test.cc in Sources */, 8B3EB33933D11CF897EAF4C3 /* leveldb_index_manager_test.cc in Sources */, 568EC1C0F68A7B95E57C8C6C /* leveldb_key_test.cc in Sources */, 843EE932AA9A8F43721F189E /* leveldb_local_store_test.cc in Sources */, @@ -4238,6 +4266,7 @@ FE20E696E014CDCE918E91D6 /* md5_testing.cc in Sources */, FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */, 8F2055702DB5EE8DA4BACD7C /* memory_document_overlay_cache_test.cc in Sources */, + 0761CA9FBEDE1DF43D959252 /* memory_globals_cache_test.cc in Sources */, CFF1EBC60A00BA5109893C6E /* memory_index_manager_test.cc in Sources */, 49774EBBC8496FE1E43AEE29 /* memory_local_store_test.cc in Sources */, 66D9F8E8A65F97F436B1EE5E /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4415,6 +4444,7 @@ 0E17927CE45F5E3FC6691E24 /* firebase_auth_credentials_provider_test.mm in Sources */, 8683BBC3AC7B01937606A83B /* firestore.pb.cc in Sources */, F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */, + 101393F60336924F64966C74 /* globals_cache_test.cc in Sources */, BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */, D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */, 336E415DD06E719F9C9E2A14 /* grpc_stream_tester.cc in Sources */, @@ -4431,6 +4461,7 @@ 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */, 513D34C9964E8C60C5C2EE1C /* leveldb_bundle_cache_test.cc in Sources */, A6BDA28DBC85BC1BAB7061F4 /* leveldb_document_overlay_cache_test.cc in Sources */, + 3CCABD7BB5ED39DF1140B5F0 /* leveldb_globals_cache_test.cc in Sources */, A215078DBFBB5A4F4DADE8A9 /* leveldb_index_manager_test.cc in Sources */, B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */, E63342115B1DA65DB6F2C59A /* leveldb_local_store_test.cc in Sources */, @@ -4456,6 +4487,7 @@ 169EDCF15637580BA79B61AD /* md5_testing.cc in Sources */, 9611A0FAA2E10A6B1C1AC2EA /* memory_bundle_cache_test.cc in Sources */, 75C6CECF607CA94F56260BAB /* memory_document_overlay_cache_test.cc in Sources */, + 3C9DEC46FE7B3995A4EA629C /* memory_globals_cache_test.cc in Sources */, 3987A3E8534BAA496D966735 /* memory_index_manager_test.cc in Sources */, B15D17049414E2F5AE72C9C6 /* memory_local_store_test.cc in Sources */, D4D8BA32ACC5C2B1B29711C0 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4657,6 +4689,7 @@ F7EE3CCC821975B71E834453 /* firebase_auth_credentials_provider_test.mm in Sources */, 8C602DAD4E8296AB5EFB962A /* firestore.pb.cc in Sources */, 6ABB82D43C0728EB095947AF /* geo_point_test.cc in Sources */, + 5DE8F28A95F7CBD2B699D470 /* globals_cache_test.cc in Sources */, D9DA467E7903412DC6AECDE4 /* grpc_connection_test.cc in Sources */, B7DD5FC63A78FF00E80332C0 /* grpc_stream_test.cc in Sources */, 10120B9B650091B49D3CF57B /* grpc_stream_tester.cc in Sources */, @@ -4673,6 +4706,7 @@ CBC891BEEC525F4D8F40A319 /* latlng.pb.cc in Sources */, 2E76BC76BBCE5FCDDCF5EEBE /* leveldb_bundle_cache_test.cc in Sources */, 6711E75A10EBA662341F5C9D /* leveldb_document_overlay_cache_test.cc in Sources */, + 2839CB9BF3250576F5044461 /* leveldb_globals_cache_test.cc in Sources */, A602E6C7C8B243BB767D251C /* leveldb_index_manager_test.cc in Sources */, 8AA7A1FCEE6EC309399978AD /* leveldb_key_test.cc in Sources */, 55E84644D385A70E607A0F91 /* leveldb_local_store_test.cc in Sources */, @@ -4698,6 +4732,7 @@ E2AC3BDAAFFF9A45C916708B /* md5_testing.cc in Sources */, FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */, 5F6FD840AC2D729B50991CCB /* memory_document_overlay_cache_test.cc in Sources */, + 39790AC7E71BC06D48144BED /* memory_globals_cache_test.cc in Sources */, E6B825EE85BF20B88AF3E3CD /* memory_index_manager_test.cc in Sources */, 7ACA8D967438B5CD9DA4C884 /* memory_local_store_test.cc in Sources */, 444298A613D027AC67F7E977 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -4899,6 +4934,7 @@ B6BEB7AF975FA31E169B7DD2 /* firebase_auth_credentials_provider_test.mm in Sources */, D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */, 8B31F63673F3B5238DE95AFB /* geo_point_test.cc in Sources */, + FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */, 5958E3E3A0446A88B815CB70 /* grpc_connection_test.cc in Sources */, 0C18678CE7E355B17C34F2EE /* grpc_stream_test.cc in Sources */, B83A1416C3922E2F3EBA77FE /* grpc_stream_tester.cc in Sources */, @@ -4915,6 +4951,7 @@ 4173B61CB74EB4CD1D89EE68 /* latlng.pb.cc in Sources */, 1E8F5F37052AB0C087D69DF9 /* leveldb_bundle_cache_test.cc in Sources */, 10B69419AC04F157D855FED7 /* leveldb_document_overlay_cache_test.cc in Sources */, + 5EE3552E9EFB45791F83CBED /* leveldb_globals_cache_test.cc in Sources */, 839D8B502026706419FE09D6 /* leveldb_index_manager_test.cc in Sources */, A4AD189BDEF7A609953457A6 /* leveldb_key_test.cc in Sources */, 1029F0461945A444FCB523B3 /* leveldb_local_store_test.cc in Sources */, @@ -4940,6 +4977,7 @@ E72A77095FF6814267DF0F6D /* md5_testing.cc in Sources */, 94854FAEAEA75A1AC77A0515 /* memory_bundle_cache_test.cc in Sources */, 053C11420E49AE1A77E21C20 /* memory_document_overlay_cache_test.cc in Sources */, + BA630BD416C72344416BF7D9 /* memory_globals_cache_test.cc in Sources */, 4D8367018652104A8803E8DB /* memory_index_manager_test.cc in Sources */, 91AEFFEE35FBE15FEC42A1F4 /* memory_local_store_test.cc in Sources */, 3B23E21D5D7ACF54EBD8CF67 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5127,6 +5165,7 @@ C09BDBA73261578F9DA74CEE /* firebase_auth_credentials_provider_test.mm in Sources */, 544129DB21C2DDC800EFB9CC /* firestore.pb.cc in Sources */, AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */, + 00F49125748D47336BCDFB69 /* globals_cache_test.cc in Sources */, B6D9649121544D4F00EB9CFB /* grpc_connection_test.cc in Sources */, B6BBE43121262CF400C6A53E /* grpc_stream_test.cc in Sources */, 34202A37E0B762386967AF3D /* grpc_stream_tester.cc in Sources */, @@ -5143,6 +5182,7 @@ 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */, 0EDFC8A6593477E1D17CDD8F /* leveldb_bundle_cache_test.cc in Sources */, E962CA641FB1312638593131 /* leveldb_document_overlay_cache_test.cc in Sources */, + 8778C1711059598070F86D3C /* leveldb_globals_cache_test.cc in Sources */, B743F4E121E879EF34536A51 /* leveldb_index_manager_test.cc in Sources */, 54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */, 04887E378B39FB86A8A5B52B /* leveldb_local_store_test.cc in Sources */, @@ -5168,6 +5208,7 @@ 723BBD713478BB26CEFA5A7D /* md5_testing.cc in Sources */, A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */, E56EEC9DAC455E2BE77D110A /* memory_document_overlay_cache_test.cc in Sources */, + 6E6B8B8D61426E20495D9DF5 /* memory_globals_cache_test.cc in Sources */, 3B47CC43DBA24434E215B8ED /* memory_index_manager_test.cc in Sources */, C6BF529243414C53DF5F1012 /* memory_local_store_test.cc in Sources */, 72B25B2D698E4746143D5B74 /* memory_lru_garbage_collector_test.cc in Sources */, @@ -5388,6 +5429,7 @@ 58693C153EC597BC25EE9648 /* firebase_auth_credentials_provider_test.mm in Sources */, 920B6ABF76FDB3547F1CCD84 /* firestore.pb.cc in Sources */, 5FE84472E5369DA866193C45 /* geo_point_test.cc in Sources */, + C4D430E12F46F05416A66E0A /* globals_cache_test.cc in Sources */, 0DDEE9FE08845BB7CA4607DE /* grpc_connection_test.cc in Sources */, 549CEDA0519BA5F2508794E1 /* grpc_stream_test.cc in Sources */, DE50F1D39D34F867BC750957 /* grpc_stream_tester.cc in Sources */, @@ -5404,6 +5446,7 @@ 23C04A637090E438461E4E70 /* latlng.pb.cc in Sources */, 77C459976DCF7503AEE18F7F /* leveldb_bundle_cache_test.cc in Sources */, 01CF72FBF97CEB0AEFD9FAFE /* leveldb_document_overlay_cache_test.cc in Sources */, + 0FC27212D6211ECC3D1DD2A1 /* leveldb_globals_cache_test.cc in Sources */, 2C5C612B26168BA9286290AE /* leveldb_index_manager_test.cc in Sources */, 7731E564468645A4A62E2A3C /* leveldb_key_test.cc in Sources */, 380A137B785A5A6991BEDF4B /* leveldb_local_store_test.cc in Sources */, @@ -5429,6 +5472,7 @@ 1DCDED1F94EBC7F72FDBFC98 /* md5_testing.cc in Sources */, 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */, 5CEB0E83DA68652927D2CF07 /* memory_document_overlay_cache_test.cc in Sources */, + CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */, 90FE088B8FD9EC06EEED1F39 /* memory_index_manager_test.cc in Sources */, 1CC56DCA513B98CE39A6ED45 /* memory_local_store_test.cc in Sources */, 264AAB492E24318C5EEB0649 /* memory_lru_garbage_collector_test.cc in Sources */, diff --git a/Firestore/core/src/local/globals_cache.h b/Firestore/core/src/local/globals_cache.h new file mode 100644 index 00000000000..78800470292 --- /dev/null +++ b/Firestore/core/src/local/globals_cache.h @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ +#define FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ + +#include "Firestore/core/src/nanopb/byte_string.h" + +using firebase::firestore::nanopb::ByteString; + +namespace firebase { +namespace firestore { +namespace local { + +/** + * General purpose cache for global values. + * + * Global state that cuts across components should be saved here. Following are + * contained herein: + * + * `sessionToken` tracks server interaction across Listen and Write streams. + * This facilitates cache synchronization and invalidation. + */ +class GlobalsCache { + public: + virtual ~GlobalsCache() = default; + + /** + * Gets session token. + */ + virtual ByteString GetSessionToken() const = 0; + + /** + * Sets session token. + */ + virtual void SetSessionToken(const ByteString& session_token) = 0; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_globals_cache.cc b/Firestore/core/src/local/leveldb_globals_cache.cc new file mode 100644 index 00000000000..366d0c811ea --- /dev/null +++ b/Firestore/core/src/local/leveldb_globals_cache.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "Firestore/core/src/local/leveldb_globals_cache.h" +#include "Firestore/core/src/local/leveldb_key.h" +#include "Firestore/core/src/local/leveldb_persistence.h" + +namespace firebase { +namespace firestore { +namespace local { + +namespace { + +const char* kSessionToken = "session_token"; + +} + +LevelDbGlobalsCache::LevelDbGlobalsCache(LevelDbPersistence* db) + : db_(NOT_NULL(db)) { +} + +ByteString LevelDbGlobalsCache::GetSessionToken() const { + auto key = LevelDbGlobalKey::Key(kSessionToken); + + std::string encoded; + auto done = db_->current_transaction()->Get(key, &encoded); + + if (!done.ok()) { + return ByteString(); + } + + return ByteString(encoded); +} + +void LevelDbGlobalsCache::SetSessionToken(const ByteString& session_token) { + auto key = LevelDbGlobalKey::Key(kSessionToken); + db_->current_transaction()->Put(key, session_token.ToString()); +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/local/leveldb_globals_cache.h b/Firestore/core/src/local/leveldb_globals_cache.h new file mode 100644 index 00000000000..4b41df5705e --- /dev/null +++ b/Firestore/core/src/local/leveldb_globals_cache.h @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ +#define FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ + +#include "Firestore/core/src/local/globals_cache.h" + +namespace firebase { +namespace firestore { +namespace local { + +class LevelDbPersistence; + +class LevelDbGlobalsCache : public GlobalsCache { + public: + /** Creates a new bundle cache in the given LevelDB. */ + explicit LevelDbGlobalsCache(LevelDbPersistence* db); + + /** + * Gets session token. + */ + ByteString GetSessionToken() const override; + + /** + * Sets session token. + */ + void SetSessionToken(const ByteString& session_token) override; + + private: + // The LevelDbGlobalsCache is owned by LevelDbPersistence. + LevelDbPersistence* db_ = nullptr; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_LEVELDB_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/leveldb_key.cc b/Firestore/core/src/local/leveldb_key.cc index e8819650df4..fd0fa8dacd1 100644 --- a/Firestore/core/src/local/leveldb_key.cc +++ b/Firestore/core/src/local/leveldb_key.cc @@ -39,6 +39,7 @@ namespace local { namespace { const char* kVersionGlobalTable = "version"; +const char* kGlobalsTable = "globals"; const char* kMutationsTable = "mutation"; const char* kDocumentMutationsTable = "document_mutation"; const char* kMutationQueuesTable = "mutation_queue"; @@ -159,6 +160,11 @@ enum ComponentLabel { */ DataMigrationName = 25, + /** + * The name of a global. + */ + GlobalName = 26, + /** * A path segment describes just a single segment in a resource path. Path * segments that occur sequentially in a key represent successive segments in @@ -245,6 +251,10 @@ class Reader { return ReadLabeledString(ComponentLabel::BundleId); } + std::string ReadGlobalName() { + return ReadLabeledString(ComponentLabel::GlobalName); + } + std::string ReadQueryName() { return ReadLabeledString(ComponentLabel::QueryName); } @@ -718,6 +728,10 @@ class Writer { WriteLabeledString(ComponentLabel::TableName, table_name); } + void WriteGlobalName(absl::string_view global_name) { + WriteLabeledString(ComponentLabel::GlobalName, global_name); + } + void WriteBatchId(model::BatchId batch_id) { WriteLabeledInt32(ComponentLabel::BatchId, batch_id); } @@ -1206,6 +1220,28 @@ bool LevelDbRemoteDocumentReadTimeKey::Decode(absl::string_view key) { return reader.ok(); } +std::string LevelDbGlobalKey::KeyPrefix() { + Writer writer; + writer.WriteTableName(kGlobalsTable); + return writer.result(); +} + +std::string LevelDbGlobalKey::Key(absl::string_view global_name) { + Writer writer; + writer.WriteTableName(kGlobalsTable); + writer.WriteGlobalName(global_name); + writer.WriteTerminator(); + return writer.result(); +} + +bool LevelDbGlobalKey::Decode(absl::string_view key) { + Reader reader{key}; + reader.ReadTableNameMatching(kGlobalsTable); + global_name_ = reader.ReadGlobalName(); + reader.ReadTerminator(); + return reader.ok(); +} + std::string LevelDbBundleKey::KeyPrefix() { Writer writer; writer.WriteTableName(kBundlesTable); diff --git a/Firestore/core/src/local/leveldb_key.h b/Firestore/core/src/local/leveldb_key.h index 51505b9c5c6..14ecd809ea2 100644 --- a/Firestore/core/src/local/leveldb_key.h +++ b/Firestore/core/src/local/leveldb_key.h @@ -768,6 +768,41 @@ class LevelDbNamedQueryKey { std::string name_; }; +/** + * A key in the globals table, storing the name of the global value. + */ +class LevelDbGlobalKey { + public: + /** + * Creates a key prefix that points just before the first key of the table. + */ + static std::string KeyPrefix(); + + /** + * Creates a key that points to the key for the given name of global value. + */ + static std::string Key(absl::string_view global_name); + + /** + * Decodes the given complete key, storing the decoded values in this + * instance. + * + * @return true if the key successfully decoded, false otherwise. If false is + * returned, this instance is in an undefined state until the next call to + * `Decode()`. + */ + ABSL_MUST_USE_RESULT + bool Decode(absl::string_view key); + + /** The name that serves as identifier for global value for this entry. */ + const std::string& global_name() const { + return global_name_; + } + + private: + std::string global_name_; +}; + /** * A key in the index_configuration table, storing the index definition proto, * and the collection (group) it applies to. diff --git a/Firestore/core/src/local/leveldb_persistence.cc b/Firestore/core/src/local/leveldb_persistence.cc index c5ce4c60c2d..9725e267356 100644 --- a/Firestore/core/src/local/leveldb_persistence.cc +++ b/Firestore/core/src/local/leveldb_persistence.cc @@ -126,6 +126,7 @@ LevelDbPersistence::LevelDbPersistence(std::unique_ptr db, reference_delegate_ = absl::make_unique(this, lru_params); bundle_cache_ = absl::make_unique(this, &serializer_); + globals_cache_ = absl::make_unique(this); // TODO(gsoltis): set up a leveldb transaction for these operations. target_cache_->Start(); @@ -250,6 +251,10 @@ LevelDbTargetCache* LevelDbPersistence::target_cache() { return target_cache_.get(); } +LevelDbGlobalsCache* LevelDbPersistence::globals_cache() { + return globals_cache_.get(); +} + LevelDbRemoteDocumentCache* LevelDbPersistence::remote_document_cache() { return document_cache_.get(); } diff --git a/Firestore/core/src/local/leveldb_persistence.h b/Firestore/core/src/local/leveldb_persistence.h index 1374d91a08e..5ca6491bccd 100644 --- a/Firestore/core/src/local/leveldb_persistence.h +++ b/Firestore/core/src/local/leveldb_persistence.h @@ -25,6 +25,7 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/leveldb_bundle_cache.h" #include "Firestore/core/src/local/leveldb_document_overlay_cache.h" +#include "Firestore/core/src/local/leveldb_globals_cache.h" #include "Firestore/core/src/local/leveldb_index_manager.h" #include "Firestore/core/src/local/leveldb_lru_reference_delegate.h" #include "Firestore/core/src/local/leveldb_migrations.h" @@ -84,6 +85,8 @@ class LevelDbPersistence : public Persistence { LevelDbBundleCache* bundle_cache() override; + LevelDbGlobalsCache* globals_cache() override; + LevelDbDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; LevelDbOverlayMigrationManager* GetOverlayMigrationManager( @@ -154,6 +157,7 @@ class LevelDbPersistence : public Persistence { bool started_ = false; std::unique_ptr bundle_cache_; + std::unique_ptr globals_cache_; std::unordered_map> document_overlay_caches_; std::unordered_map + +#include "Firestore/core/src/local/globals_cache.h" + +namespace firebase { +namespace firestore { +namespace local { + +class MemoryGlobalsCache : public GlobalsCache { + public: + /** + * Gets session token. + */ + ByteString GetSessionToken() const override; + + /** + * Sets session token. + */ + void SetSessionToken(const ByteString& session_token) override; + + private: + ByteString session_token_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_LOCAL_MEMORY_GLOBALS_CACHE_H_ diff --git a/Firestore/core/src/local/memory_persistence.cc b/Firestore/core/src/local/memory_persistence.cc index 009036e1d8b..f1946661ba5 100644 --- a/Firestore/core/src/local/memory_persistence.cc +++ b/Firestore/core/src/local/memory_persistence.cc @@ -102,6 +102,10 @@ MemoryBundleCache* MemoryPersistence::bundle_cache() { return &bundle_cache_; } +MemoryGlobalsCache* MemoryPersistence::globals_cache() { + return &globals_cache_; +} + MemoryDocumentOverlayCache* MemoryPersistence::GetDocumentOverlayCache( const User& user) { auto iter = document_overlay_caches_.find(user); diff --git a/Firestore/core/src/local/memory_persistence.h b/Firestore/core/src/local/memory_persistence.h index 674577bbac2..bebadb86790 100644 --- a/Firestore/core/src/local/memory_persistence.h +++ b/Firestore/core/src/local/memory_persistence.h @@ -27,6 +27,7 @@ #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/memory_bundle_cache.h" #include "Firestore/core/src/local/memory_document_overlay_cache.h" +#include "Firestore/core/src/local/memory_globals_cache.h" #include "Firestore/core/src/local/memory_index_manager.h" #include "Firestore/core/src/local/memory_mutation_queue.h" #include "Firestore/core/src/local/memory_remote_document_cache.h" @@ -90,6 +91,8 @@ class MemoryPersistence : public Persistence { MemoryBundleCache* bundle_cache() override; + MemoryGlobalsCache* globals_cache() override; + MemoryDocumentOverlayCache* GetDocumentOverlayCache( const credentials::User& user) override; @@ -138,6 +141,8 @@ class MemoryPersistence : public Persistence { MemoryBundleCache bundle_cache_; + MemoryGlobalsCache globals_cache_; + DocumentOverlayCaches document_overlay_caches_; MemoryOverlayMigrationManager overlay_migration_manager_; diff --git a/Firestore/core/src/local/persistence.h b/Firestore/core/src/local/persistence.h index b1f7ebde74f..3e184e4bac8 100644 --- a/Firestore/core/src/local/persistence.h +++ b/Firestore/core/src/local/persistence.h @@ -36,6 +36,7 @@ namespace local { class BundleCache; class DocumentOverlayCache; +class GlobalsCache; class IndexManager; class MutationQueue; class OverlayMigrationManager; @@ -86,6 +87,11 @@ class Persistence { /** Releases any resources held during eager shutdown. */ virtual void Shutdown() = 0; + /** + * Returns GlobalCache representing a general purpose cache for global values. + */ + virtual GlobalsCache* globals_cache() = 0; + /** * Returns a MutationQueue representing the persisted mutations for the given * user. diff --git a/Firestore/core/test/unit/local/globals_cache_test.cc b/Firestore/core/test/unit/local/globals_cache_test.cc new file mode 100644 index 00000000000..0945a3a7b35 --- /dev/null +++ b/Firestore/core/test/unit/local/globals_cache_test.cc @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/unit/local/globals_cache_test.h" + +#include "Firestore/core/src/local/globals_cache.h" +#include "Firestore/core/src/local/persistence.h" +#include "Firestore/core/test/unit/testutil/testutil.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { + +using util::ComparisonResult; + +namespace local { + +GlobalsCacheTest::GlobalsCacheTest(std::unique_ptr persistence) + : persistence_(std::move(NOT_NULL(persistence))), + cache_(persistence_->globals_cache()) { +} + +GlobalsCacheTest::GlobalsCacheTest() : GlobalsCacheTest(GetParam()()) { +} + +namespace { + +TEST_P(GlobalsCacheTest, ReturnsEmptyBytestringWhenSessionTokenNotFound) { + persistence_->Run( + "test_returns_empty_bytestring_when_session_token_not_found", [&] { + auto expected = ByteString(); + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + }); +} + +TEST_P(GlobalsCacheTest, ReturnsSavedSessionToken) { + persistence_->Run("test_returns_saved_session_token", [&] { + auto expected = ByteString("magic"); + cache_->SetSessionToken(expected); + + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + + // Overwrite + expected = ByteString("science"); + cache_->SetSessionToken(expected); + + EXPECT_EQ(cache_->GetSessionToken().CompareTo(expected), + ComparisonResult::Same); + }); +} + +} // namespace + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/local/globals_cache_test.h b/Firestore/core/test/unit/local/globals_cache_test.h new file mode 100644 index 00000000000..aad64844339 --- /dev/null +++ b/Firestore/core/test/unit/local/globals_cache_test.h @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ +#define FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ + +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace local { + +class Persistence; +class GlobalsCache; + +using FactoryFunc = std::unique_ptr (*)(); + +/** + * These are tests for any implementation of the GlobalsCache interface. + * + * To test a specific implementation of GlobalsCache: + * + * - Write a persistence factory function + * - Call INSTANTIATE_TEST_SUITE_P(MyNewGlobalsCacheTest, + * GlobalsCacheTest, + * testing::Values(PersistenceFactory)); + */ +class GlobalsCacheTest : public testing::Test, + public testing::WithParamInterface { + public: + GlobalsCacheTest(); + explicit GlobalsCacheTest(std::unique_ptr persistence); + ~GlobalsCacheTest() = default; + + protected: + std::unique_ptr persistence_; + GlobalsCache* cache_ = nullptr; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_LOCAL_GLOBALS_CACHE_TEST_H_ diff --git a/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc b/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc new file mode 100644 index 00000000000..4b6f11e5a48 --- /dev/null +++ b/Firestore/core/test/unit/local/leveldb_globals_cache_test.cc @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/local/leveldb_persistence.h" +#include "Firestore/core/test/unit/local/globals_cache_test.h" +#include "Firestore/core/test/unit/local/persistence_testing.h" + +namespace firebase { +namespace firestore { +namespace local { +namespace { + +std::unique_ptr PersistenceFactory() { + return LevelDbPersistenceForTesting(); +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(LevelDbGlobalsCacheTest, + GlobalsCacheTest, + testing::Values(PersistenceFactory)); + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/local/memory_globals_cache_test.cc b/Firestore/core/test/unit/local/memory_globals_cache_test.cc new file mode 100644 index 00000000000..e3bcde58d96 --- /dev/null +++ b/Firestore/core/test/unit/local/memory_globals_cache_test.cc @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/local/memory_persistence.h" +#include "Firestore/core/test/unit/local/globals_cache_test.h" +#include "Firestore/core/test/unit/local/persistence_testing.h" + +namespace firebase { +namespace firestore { +namespace local { +namespace { + +std::unique_ptr PersistenceFactory() { + return MemoryPersistenceWithEagerGcForTesting(); +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(MemoryGloablsCacheTest, + GlobalsCacheTest, + testing::Values(PersistenceFactory)); + +} // namespace local +} // namespace firestore +} // namespace firebase From ba8623fb293749b6ec6f53664a8ffc55ae3c94fd Mon Sep 17 00:00:00 2001 From: Sam Edson Date: Thu, 3 Oct 2024 15:42:17 -0400 Subject: [PATCH 125/258] Fix Performance Unit Test Warnings around UserDefaults (#13787) --- .../Configurations/FPRConfigurationsTest.m | 42 +++++++++++-------- .../Tests/Unit/FPRNetworkTraceTest.m | 4 +- .../Tests/Unit/Gauges/FPRGaugeManagerTests.m | 4 +- .../Unit/Instruments/FIRHTTPMetricTests.m | 4 +- .../Tests/Unit/Timer/FIRTraceTest.m | 4 +- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/FirebasePerformance/Tests/Unit/Configurations/FPRConfigurationsTest.m b/FirebasePerformance/Tests/Unit/Configurations/FPRConfigurationsTest.m index 8489223e1e4..4d87cbdb29b 100644 --- a/FirebasePerformance/Tests/Unit/Configurations/FPRConfigurationsTest.m +++ b/FirebasePerformance/Tests/Unit/Configurations/FPRConfigurationsTest.m @@ -14,6 +14,8 @@ #import +#import + #import "FirebasePerformance/Sources/Common/FPRConstants.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h" @@ -30,6 +32,10 @@ @interface FPRConfigurationsTest : XCTestCase @implementation FPRConfigurationsTest +- (GULUserDefaults *_Nonnull)makeEmptyUserDefaults { + return [[GULUserDefaults alloc] init]; +} + /** Validates if instance creation works. */ - (void)testInstanceCreation { XCTAssertNotNil([[FPRConfigurations alloc] initWithSources:FPRConfigurationSourceNone]); @@ -78,7 +84,7 @@ - (void)testOverridesForDiagnosticsEnabled { FPRConfigurations *configurations = [[FPRConfigurations alloc] initWithSources:FPRConfigurationSourceRemoteConfig]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configurations.userDefaults = userDefaults; XCTAssertFalse(configurations.diagnosticsEnabled); @@ -101,7 +107,7 @@ - (void)testTraceSamplingRateRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; [configFlags resetCache]; @@ -126,7 +132,7 @@ - (void)testNetworkRequestSamplingRateRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; [configFlags resetCache]; @@ -151,7 +157,7 @@ - (void)testSessionSamplingRateRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; [configFlags resetCache]; @@ -176,7 +182,7 @@ - (void)testSessionSamplingRatePlistOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; [configFlags resetCache]; @@ -205,7 +211,7 @@ - (void)testLogSourceRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; [configFlags resetCache]; @@ -239,7 +245,7 @@ - (void)testDisabledSDKVersionsConfigResolveSuccessful { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSSet *versionSet = @@ -269,7 +275,7 @@ - (void)testDisabledSDKVersionsDisablesSDK { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; @@ -299,7 +305,7 @@ - (void)testSDKEnabledFlag { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; @@ -325,7 +331,7 @@ - (void)testPlistOverridesSDKEnabledFlag { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; @@ -351,7 +357,7 @@ - (void)testForegroundRateLimitingTraceCountRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = @@ -375,7 +381,7 @@ - (void)testBackgroundRateLimitingTraceCountRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = @@ -399,7 +405,7 @@ - (void)testForegroundRateLimitingNetworkCountRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString @@ -423,7 +429,7 @@ - (void)testBackgroundRateLimitingNetworkCountRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString @@ -447,7 +453,7 @@ - (void)testRateLimitingDurationRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = @@ -477,7 +483,7 @@ - (void)testGaugeCollectionFrequencyRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKeyCPUFg = @@ -526,7 +532,7 @@ - (void)testSessionMaxLengthDurationRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = @@ -549,7 +555,7 @@ - (void)testPrewarmDetectionRemoteConfigOverrides { configurations.remoteConfigFlags = configFlags; configFlags.lastFetchedTime = [NSDate date]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [self makeEmptyUserDefaults]; configFlags.userDefaults = userDefaults; NSString *configKey = diff --git a/FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m b/FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m index c913dac3aaf..9956735c75d 100644 --- a/FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m +++ b/FirebasePerformance/Tests/Unit/FPRNetworkTraceTest.m @@ -14,6 +14,8 @@ #import +#import + #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h" #import "FirebasePerformance/Sources/Common/FPRConstants.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h" @@ -141,7 +143,7 @@ - (void)testTraceCreationWhenSDKFlagEnabled { [[FPRRemoteConfigFlags alloc] initWithRemoteConfig:(FIRRemoteConfig *)remoteConfig]; configurations.remoteConfigFlags = configFlags; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *userDefaults = [[GULUserDefaults alloc] init]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; diff --git a/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m b/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m index c31ced38799..214b1b38daf 100644 --- a/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m +++ b/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m @@ -14,6 +14,8 @@ #import +#import + #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h" #import "FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags+Private.h" @@ -91,7 +93,7 @@ - (void)testGaugeCollectionDisabledWhenSDKFlagDisabled { [[FPRRemoteConfigFlags alloc] initWithRemoteConfig:(FIRRemoteConfig *)remoteConfig]; configurations.remoteConfigFlags = configFlags; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *_Nonnull userDefaults = [[GULUserDefaults alloc] init]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; diff --git a/FirebasePerformance/Tests/Unit/Instruments/FIRHTTPMetricTests.m b/FirebasePerformance/Tests/Unit/Instruments/FIRHTTPMetricTests.m index 7139976b4c6..c837c477a44 100644 --- a/FirebasePerformance/Tests/Unit/Instruments/FIRHTTPMetricTests.m +++ b/FirebasePerformance/Tests/Unit/Instruments/FIRHTTPMetricTests.m @@ -14,6 +14,8 @@ #import +#import + #import "FirebasePerformance/Sources/Common/FPRConstants.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations+Private.h" #import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h" @@ -118,7 +120,7 @@ - (void)testMetricCreationWhenSDKFlagEnabled { [[FPRRemoteConfigFlags alloc] initWithRemoteConfig:(FIRRemoteConfig *)remoteConfig]; configurations.remoteConfigFlags = configFlags; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *userDefaults = [[GULUserDefaults alloc] init]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; diff --git a/FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m b/FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m index fd57a2bf503..bd187089d16 100644 --- a/FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m +++ b/FirebasePerformance/Tests/Unit/Timer/FIRTraceTest.m @@ -14,6 +14,8 @@ #import +#import + #import "FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.h" #import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h" #import "FirebasePerformance/Sources/Common/FPRConstants.h" @@ -99,7 +101,7 @@ - (void)testTraceCreationWhenSDKFlagEnabled { [[FPRRemoteConfigFlags alloc] initWithRemoteConfig:(FIRRemoteConfig *)remoteConfig]; configurations.remoteConfigFlags = configFlags; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + GULUserDefaults *userDefaults = [[GULUserDefaults alloc] init]; configFlags.userDefaults = userDefaults; NSString *configKey = [NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_enabled"]; From 64aedfc8eff231645d1cdafae00514892256db65 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:03:48 -0400 Subject: [PATCH 126/258] [Infra] Sync messaging with recent 1P infra changes (#13793) --- FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m | 2 ++ FirebaseMessaging/Sources/FIRMessaging.m | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m b/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m index 1902d067062..0ff8d3dea04 100644 --- a/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m +++ b/FirebaseMessaging/Sources/FIRMessaging+ExtensionHelper.m @@ -30,8 +30,10 @@ + (FIRMessagingExtensionHelper *)extensionHelper { return extensionHelper; } +#if SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE /// Stub used to force the linker to include the categories in this file. void FIRInclude_FIRMessaging_ExtensionHelper_Category(void) { } +#endif // SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE @end diff --git a/FirebaseMessaging/Sources/FIRMessaging.m b/FirebaseMessaging/Sources/FIRMessaging.m index 590fabd042c..e2bb09b098c 100644 --- a/FirebaseMessaging/Sources/FIRMessaging.m +++ b/FirebaseMessaging/Sources/FIRMessaging.m @@ -1020,8 +1020,9 @@ + (NSString *)FIRMessagingSDKCurrentLocale { } #pragma mark - Force Category Linking - +#if SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE extern void FIRInclude_FIRMessaging_ExtensionHelper_Category(void); +#endif // SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE extern void FIRInclude_NSDictionary_FIRMessaging_Category(void); extern void FIRInclude_NSError_FIRMessaging_Category(void); @@ -1030,7 +1031,9 @@ + (NSString *)FIRMessagingSDKCurrentLocale { /// This method forces the linker to include categories even if /// users do not include the '-ObjC' linker flag in their project. + (void)noop { +#if SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE FIRInclude_FIRMessaging_ExtensionHelper_Category(); +#endif // SWIFT_PACKAGE || COCOAPODS || FIREBASE_BUILD_CARTHAGE || FIREBASE_BUILD_ZIP_FILE FIRInclude_NSDictionary_FIRMessaging_Category(); FIRInclude_NSError_FIRMessaging_Category(); } From 995c9339f9b3c83630abca892fcf7d2be882294b Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Thu, 3 Oct 2024 22:07:46 +0200 Subject: [PATCH 127/258] Refactored Duplicate Methods in `Functions` (#13771) --- FirebaseFunctions/Sources/Functions.swift | 37 ++++--------------- FirebaseFunctions/Sources/HTTPSCallable.swift | 36 +++++------------- .../CombineUnit/HTTPSCallableTests.swift | 26 ++++++++----- .../Tests/Unit/FunctionsTests.swift | 34 ++++++++++------- 4 files changed, 54 insertions(+), 79 deletions(-) diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index 60bb9032f4b..75091b26815 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -135,7 +135,7 @@ enum FunctionsConstants { /// - Parameter name: The name of the Callable HTTPS trigger. /// - Returns: A reference to a Callable HTTPS trigger. @objc(HTTPSCallableWithName:) open func httpsCallable(_ name: String) -> HTTPSCallable { - return HTTPSCallable(functions: self, name: name) + HTTPSCallable(functions: self, url: functionURL(for: name)!) } /// Creates a reference to the Callable HTTPS trigger with the given name and configuration @@ -147,7 +147,7 @@ enum FunctionsConstants { @objc(HTTPSCallableWithName:options:) public func httpsCallable(_ name: String, options: HTTPSCallableOptions) -> HTTPSCallable { - return HTTPSCallable(functions: self, name: name, options: options) + HTTPSCallable(functions: self, url: functionURL(for: name)!, options: options) } /// Creates a reference to the Callable HTTPS trigger with the given name. @@ -369,46 +369,23 @@ enum FunctionsConstants { appCheck: appCheck) } - func urlWithName(_ name: String) -> String { + func functionURL(for name: String) -> URL? { assert(!name.isEmpty, "Name cannot be empty") // Check if we're using the emulator if let emulatorOrigin { - return "\(emulatorOrigin)/\(projectID)/\(region)/\(name)" + return URL(string: "\(emulatorOrigin)/\(projectID)/\(region)/\(name)") } // Check the custom domain. if let customDomain { - return "\(customDomain)/\(name)" + return URL(string: "\(customDomain)/\(name)") } - return "https://\(region)-\(projectID).cloudfunctions.net/\(name)" + return URL(string: "https://\(region)-\(projectID).cloudfunctions.net/\(name)") } - func callFunction(name: String, - withObject data: Any?, - options: HTTPSCallableOptions?, - timeout: TimeInterval, - completion: @escaping ((Result) -> Void)) { - // Get context first. - contextProvider.getContext(options: options) { context, error in - // Note: context is always non-nil since some checks could succeed, we're only failing if - // there's an error. - if let error { - completion(.failure(error)) - } else { - let url = self.urlWithName(name) - self.callFunction(url: URL(string: url)!, - withObject: data, - options: options, - timeout: timeout, - context: context, - completion: completion) - } - } - } - - func callFunction(url: URL, + func callFunction(at url: URL, withObject data: Any?, options: HTTPSCallableOptions?, timeout: TimeInterval, diff --git a/FirebaseFunctions/Sources/HTTPSCallable.swift b/FirebaseFunctions/Sources/HTTPSCallable.swift index 629f3949527..4a196134e3e 100644 --- a/FirebaseFunctions/Sources/HTTPSCallable.swift +++ b/FirebaseFunctions/Sources/HTTPSCallable.swift @@ -39,12 +39,7 @@ open class HTTPSCallable: NSObject { // The functions client to use for making calls. private let functions: Functions - private enum EndpointType { - case name(String) - case url(URL) - } - - private let endpoint: EndpointType + private let url: URL private let options: HTTPSCallableOptions? @@ -53,16 +48,10 @@ open class HTTPSCallable: NSObject { /// The timeout to use when calling the function. Defaults to 70 seconds. @objc open var timeoutInterval: TimeInterval = 70 - init(functions: Functions, name: String, options: HTTPSCallableOptions? = nil) { - self.functions = functions - self.options = options - endpoint = .name(name) - } - init(functions: Functions, url: URL, options: HTTPSCallableOptions? = nil) { self.functions = functions + self.url = url self.options = options - endpoint = .url(url) } /// Executes this Callable HTTPS trigger asynchronously. @@ -98,20 +87,13 @@ open class HTTPSCallable: NSObject { } } - switch endpoint { - case let .name(name): - functions.callFunction(name: name, - withObject: data, - options: options, - timeout: timeoutInterval, - completion: callback) - case let .url(url): - functions.callFunction(url: url, - withObject: data, - options: options, - timeout: timeoutInterval, - completion: callback) - } + functions.callFunction( + at: url, + withObject: data, + options: options, + timeout: timeoutInterval, + completion: callback + ) } /// Executes this Callable HTTPS trigger asynchronously. This API should only be used from diff --git a/FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift b/FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift index 7485ff0e236..8ed63c344b7 100644 --- a/FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift +++ b/FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift @@ -30,14 +30,16 @@ private let expectationTimeout: TimeInterval = 2 class MockFunctions: Functions { let mockCallFunction: () throws -> HTTPSCallableResult - var verifyParameters: ((_ name: String, _ data: Any?, _ timeout: TimeInterval) throws -> Void)? - override func callFunction(name: String, + var verifyParameters: ((_ url: URL, _ data: Any?, _ timeout: TimeInterval) throws -> Void)? + override func callFunction(at url: URL, withObject data: Any?, options: HTTPSCallableOptions?, timeout: TimeInterval, - completion: @escaping ((Result) -> Void)) { + completion: @escaping ( + (Result) -> Void + )) { do { - try verifyParameters?(name, data, timeout) + try verifyParameters?(url, data, timeout) let result = try mockCallFunction() completion(.success(result)) } catch { @@ -49,7 +51,7 @@ class MockFunctions: Functions { self.mockCallFunction = mockCallFunction super.init( projectID: "dummy-project", - region: "", + region: "test-region", customDomain: nil, auth: nil, messaging: nil, @@ -122,8 +124,11 @@ class HTTPSCallableTests: XCTestCase { httpsFunctionWasCalledExpectation.fulfill() return HTTPSCallableResultFake(data: expectedResult) } - functions.verifyParameters = { name, data, timeout in - XCTAssertEqual(name as String, "dummyFunction") + functions.verifyParameters = { url, data, timeout in + XCTAssertEqual( + url.absoluteString, + "https://test-region-dummy-project.cloudfunctions.net/dummyFunction" + ) XCTAssertEqual(data as? String, inputParameter) XCTAssertEqual(timeout as TimeInterval, timeoutInterval) } @@ -169,8 +174,11 @@ class HTTPSCallableTests: XCTestCase { code: FunctionsErrorCode.internal.rawValue, userInfo: [NSLocalizedDescriptionKey: "Response is missing data field."]) } - functions.verifyParameters = { name, data, timeout in - XCTAssertEqual(name as String, "dummyFunction") + functions.verifyParameters = { url, data, timeout in + XCTAssertEqual( + url.absoluteString, + "https://test-region-dummy-project.cloudfunctions.net/dummyFunction" + ) XCTAssertEqual(data as? String, inputParameter) XCTAssertEqual(timeout as TimeInterval, timeoutInterval) } diff --git a/FirebaseFunctions/Tests/Unit/FunctionsTests.swift b/FirebaseFunctions/Tests/Unit/FunctionsTests.swift index 7501d613920..89cf70fa6a0 100644 --- a/FirebaseFunctions/Tests/Unit/FunctionsTests.swift +++ b/FirebaseFunctions/Tests/Unit/FunctionsTests.swift @@ -79,26 +79,34 @@ class FunctionsTests: XCTestCase { XCTAssertEqual(functions1, functions2) } - func testURLWithName() throws { - let url = try XCTUnwrap(functions?.urlWithName("my-endpoint")) - XCTAssertEqual(url, "https://my-region-my-project.cloudfunctions.net/my-endpoint") + func testFunctionURLForName() throws { + XCTAssertEqual( + functions?.functionURL(for: "my-endpoint")?.absoluteString, + "https://my-region-my-project.cloudfunctions.net/my-endpoint" + ) } - func testRegionWithEmulator() throws { + func testFunctionURLForNameEmulator() throws { functionsCustomDomain?.useEmulator(withHost: "localhost", port: 5005) - let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint")) - XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint") + XCTAssertEqual( + functionsCustomDomain?.functionURL(for: "my-endpoint")?.absoluteString, + "http://localhost:5005/my-project/my-region/my-endpoint" + ) } - func testRegionWithEmulatorWithScheme() throws { + func testFunctionURLForNameRegionWithEmulatorWithScheme() throws { functionsCustomDomain?.useEmulator(withHost: "http://localhost", port: 5005) - let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint")) - XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint") + XCTAssertEqual( + functionsCustomDomain?.functionURL(for: "my-endpoint")?.absoluteString, + "http://localhost:5005/my-project/my-region/my-endpoint" + ) } - func testCustomDomain() throws { - let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint")) - XCTAssertEqual(url, "https://mydomain.com/my-endpoint") + func testFunctionURLForNameCustomDomain() throws { + XCTAssertEqual( + functionsCustomDomain?.functionURL(for: "my-endpoint")?.absoluteString, + "https://mydomain.com/my-endpoint" + ) } func testSetEmulatorSettings() throws { @@ -303,7 +311,7 @@ class FunctionsTests: XCTestCase { let completionExpectation = expectation(description: "completionExpectation") functionsCustomDomain?.callFunction( - name: "fake_func", + at: URL(string: "https://example.com/fake_func")!, withObject: nil, options: nil, timeout: 10 From 143c16921c7db4935ccb43e090db69e057e1bd95 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 3 Oct 2024 17:55:20 -0700 Subject: [PATCH 128/258] [Auth] Address possible race condition (#13772) --- .../Sources/Swift/Backend/AuthBackend.swift | 20 ++++++------ .../Tests/Unit/FIROAuthProviderTests.m | 31 ------------------- FirebaseAuth/Tests/Unit/RPCBaseTests.swift | 4 +-- 3 files changed, 11 insertions(+), 44 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 444a97c1c07..84bf5ec55c9 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -76,21 +76,19 @@ class AuthBackend { return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))" } - private static var gBackendImplementation: AuthBackendImplementation? + private static var realRPCBackend = AuthBackendRPCImplementation() + private static var gBackendImplementation = realRPCBackend - class func setDefaultBackendImplementationWithRPCIssuer(issuer: AuthBackendRPCIssuer?) { - let defaultImplementation = AuthBackendRPCImplementation() - if let issuer = issuer { - defaultImplementation.rpcIssuer = issuer - } - gBackendImplementation = defaultImplementation + class func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { + gBackendImplementation.rpcIssuer = issuer + } + + class func resetRPCIssuer() { + gBackendImplementation.rpcIssuer = realRPCBackend.rpcIssuer } class func implementation() -> AuthBackendImplementation { - if gBackendImplementation == nil { - gBackendImplementation = AuthBackendRPCImplementation() - } - return gBackendImplementation! + return gBackendImplementation } class func call(with request: T) async throws -> T.Response { diff --git a/FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m b/FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m index b5921312caa..0a8e7d30b05 100644 --- a/FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m +++ b/FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m @@ -22,11 +22,6 @@ @import FirebaseAuth; @import FirebaseCore; -/** @var kExpectationTimeout - @brief The maximum time waiting for expectations to fulfill. - */ -static const NSTimeInterval kExpectationTimeout = 1; - /** @var kFakeAuthorizedDomain @brief A fake authorized domain for the app. */ @@ -162,32 +157,6 @@ - (void)testObtainingOAuthCredentialWithIDToken { XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken); } -/** @fn testGetCredentialWithUIDelegateWithClientIDOnMainThread - @brief Verifies @c getCredentialWithUIDelegate:completion: calls its completion handler on the - main thread. Regression test for firebase/FirebaseUI-iOS#1199. - */ -- (void)testGetCredentialWithUIDelegateWithClientIDOnMainThread { - XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - - FIROptions *options = - [[FIROptions alloc] initWithGoogleAppID:@"0:0000000000000:ios:0000000000000000" - GCMSenderID:@"00000000000000000-00000000000-000000000"]; - options.APIKey = kFakeAPIKey; - options.projectID = @"myProjectID"; - options.clientID = kFakeClientID; - [FIRApp configureWithName:@"objAppName" options:options]; - FIRAuth *auth = [FIRAuth authWithApp:[FIRApp appNamed:@"objAppName"]]; - [auth setMainBundleUrlTypes:@[ @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]} ]]; - - FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:auth]; - [provider getCredentialWithUIDelegate:nil - completion:^(FIRAuthCredential *_Nullable credential, - NSError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; -} @end #endif // TARGET_OS_IOS diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index cca4e1ee3cb..567b3aa4bbd 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -72,13 +72,13 @@ class RPCBaseTests: XCTestCase { override func setUp() { rpcIssuer = FakeBackendRPCIssuer() - AuthBackend.setDefaultBackendImplementationWithRPCIssuer(issuer: rpcIssuer) + AuthBackend.setTestRPCIssuer(issuer: rpcIssuer) rpcImplementation = AuthBackend.implementation() } override func tearDown() { rpcIssuer = nil - AuthBackend.setDefaultBackendImplementationWithRPCIssuer(issuer: nil) + AuthBackend.resetRPCIssuer() } /** @fn checkRequest From cd3cc2151b05a437f940119b3b86c0c34ce3f773 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:00:59 +0330 Subject: [PATCH 129/258] Docs: enhance FirebaseMessaging change log (#13807) --- FirebaseMessaging/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseMessaging/CHANGELOG.md b/FirebaseMessaging/CHANGELOG.md index e8a696519d5..cf83a783db6 100644 --- a/FirebaseMessaging/CHANGELOG.md +++ b/FirebaseMessaging/CHANGELOG.md @@ -103,7 +103,7 @@ - [changed] Changed the location of source under FirebaseMessaging folder to fit the current repository organization. (#5476) # 4.3.1 -- [fixed] Fixed an issue that when a token is deleted, the token refresh notificaiton and delegate is not triggered. (#5338) +- [fixed] Fixed an issue that when a token is deleted, the token refresh notification and delegate is not triggered. (#5338) # 4.3.0 - [changed] Deprecated FCM direct channel messaging via `shouldEstablishDirectChannel`. Instead, use APNs for downstream message delivery. Add `content_available` key to your payload if you want to continue use legacy APIs, but we strongly recommend HTTP v1 API as it provides full APNs support. The deprecated API will be removed in Firebase 7. (#4710) @@ -157,11 +157,11 @@ - [feature] Adding macOS support for Messaging. You can now send push notification to your mac app with Firebase Messaging.(#2880) # 4.0.2 -- [fixed] Disable data protection when opening the Rmq2PeristentStore. (#2963) +- [fixed] Disable data protection when opening the Rmq2PersistentStore. (#2963) # 4.0.1 - [fixed] Fixed race condition checkin is deleted before writing during app start. This cleans up the corrupted checkin and fixes #2438. (#2860) -- [fixed] Separete APNS proxy methods in GULAppDelegateSwizzler so developers don't need to swizzle APNS related method unless explicitly requested, this fixes #2807. (#2835) +- [fixed] Separate APNS proxy methods in GULAppDelegateSwizzler so developers don't need to swizzle APNS related method unless explicitly requested, this fixes #2807. (#2835) - [changed] Clean up code. Remove extra layer of class. (#2853) # 4.0.0 From b1282a44656acd974ec2ce2cc6e28e1da95eb51c Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:01:24 +0330 Subject: [PATCH 130/258] Test: enhance FirebaseMessaging unit tests (#13806) --- .../Tests/UnitTests/FIRMessagingAuthKeychainTest.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseMessaging/Tests/UnitTests/FIRMessagingAuthKeychainTest.m b/FirebaseMessaging/Tests/UnitTests/FIRMessagingAuthKeychainTest.m index 4d64bd477b1..4297b7d12c7 100644 --- a/FirebaseMessaging/Tests/UnitTests/FIRMessagingAuthKeychainTest.m +++ b/FirebaseMessaging/Tests/UnitTests/FIRMessagingAuthKeychainTest.m @@ -71,7 +71,7 @@ - (void)tearDown { - (void)testKeyChainNoCorruptionWithUniqueAccount { // macOS only support one service and one account. #if TARGET_OS_IOS || TARGET_OS_TV - XCTestExpectation *noCurruptionExpectation = + XCTestExpectation *noCorruptionExpectation = [self expectationWithDescription:@"No corruption between different accounts."]; // Create a keychain with a service and a unique account NSString *service = [NSString stringWithFormat:@"%@:%@", kAuthorizedEntity, kScope]; @@ -134,7 +134,7 @@ - (void)testKeyChainNoCorruptionWithUniqueAccount { account:@"*" handler:^(NSError *_Nonnull error) { XCTAssertNil(error); - [noCurruptionExpectation fulfill]; + [noCorruptionExpectation fulfill]; }]; }]; }]; @@ -144,7 +144,7 @@ - (void)testKeyChainNoCorruptionWithUniqueAccount { - (void)testKeyChainNoCorruptionWithUniqueService { #if TARGET_OS_IOS || TARGET_OS_TV - XCTestExpectation *noCurruptionExpectation = + XCTestExpectation *noCorruptionExpectation = [self expectationWithDescription:@"No corruption between different services."]; // Create a keychain with a service and a unique account NSString *service1 = [NSString stringWithFormat:@"%@:%@", kAuthorizedEntity, kScope]; @@ -209,7 +209,7 @@ - (void)testKeyChainNoCorruptionWithUniqueService { account:@"*" handler:^(NSError *_Nonnull error) { XCTAssertNil(error); - [noCurruptionExpectation fulfill]; + [noCorruptionExpectation fulfill]; }]; }]; }]; From 4bee220a0d12808f8bd5b1b6772d536665ca9bad Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:03:52 +0330 Subject: [PATCH 131/258] Enhance FirebaseInAppMessaging unit test files (#13802) --- .../Tests/Unit/FIRIAMMessageClientCacheTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m b/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m index 2c7855be8f7..33678d7beac 100644 --- a/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m +++ b/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m @@ -328,7 +328,7 @@ - (void)testCallingStartAnalyticsEventListenFlow_ok { self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow; // m2 and m4 are messages rendered on 'test_event' Firebase Analytics event - // so we espect the analytics event listening flow to be started + // so we expect the analytics event listening flow to be started OCMExpect([mockAnalyticsEventFlow start]); [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]]; OCMVerifyAll((id)mockAnalyticsEventFlow); From 2d5c890da6f13664cfa778bd6371976d5248d770 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:04:01 +0330 Subject: [PATCH 132/258] Fix FirebaseInAppMessaging private flows method names (#13801) --- .../Sources/Private/Flows/FIRIAMMessageClientCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h b/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h index 6d40355cea0..2da61504f9b 100644 --- a/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h +++ b/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h @@ -84,7 +84,7 @@ API_AVAILABLE(ios(13.0), tvos(13.0)) - (nullable FIRIAMMessageDefinition *)nextOnFirebaseAnalyticEventDisplayMsg:(NSString *)eventName; // Call this after a message has been rendered to remove it from the cache. -- (void)removeMessageWithId:(NSString *)messgeId; +- (void)removeMessageWithId:(NSString *)messageId; // reset messages data - (void)setMessageData:(NSArray *)messages; From f53b45748fef6919c44b181f5e5746cd382ee616 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:04:14 +0330 Subject: [PATCH 133/258] Docs: enhance the FirebaseInAppMessaging runtime manager local comments (#13800) --- FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m index afaf6058b98..4e467b43704 100644 --- a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m +++ b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m @@ -155,7 +155,7 @@ - (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled { } - (BOOL)shouldRunSDKFlowsOnStartup { - // This can be controlled at 3 different levels in decsending priority. If a higher-priority + // This can be controlled at 3 different levels in descending priority. If a higher-priority // setting exists, the lower level settings are ignored. // 1. Setting made by the app by setting FIAM SDK's automaticDataCollectionEnabled flag. // 2. FIAM specific data collection setting in plist file. From fdb7c9caa102600954d72a3b9e080e7db1fa1a9a Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:04:26 +0330 Subject: [PATCH 134/258] Fix: static constant name in FirebaseInAppMessaging runtimes (#13799) --- .../Sources/Runtime/FIRIAMRuntimeManager.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m index 4e467b43704..55d69994dfe 100644 --- a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m +++ b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m @@ -67,7 +67,7 @@ @interface FIRIAMRuntimeManager () @property(nonatomic, nonnull) FIRIAMFetchResponseParser *responseParser; @end -static NSString *const _userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting = +static NSString *const _userDefaultsKeyForFIAMProgrammaticAutoDataCollectionSetting = @"firebase-iam-sdk-auto-data-collection"; @implementation FIRIAMRuntimeManager { @@ -97,7 +97,7 @@ - (void)testingModeSwitchedOn { - (FIRIAMAutoDataCollectionSetting)FIAMProgrammaticAutoDataCollectionSetting { id settingEntry = [[GULUserDefaults standardUserDefaults] - objectForKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting]; + objectForKey:_userDefaultsKeyForFIAMProgrammaticAutoDataCollectionSetting]; if (![settingEntry isKindOfClass:[NSNumber class]]) { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180014", @@ -192,7 +192,7 @@ - (void)resume { // persist the setting [[GULUserDefaults standardUserDefaults] setObject:@(YES) - forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting]; + forKey:_userDefaultsKeyForFIAMProgrammaticAutoDataCollectionSetting]; @synchronized(self) { if (!_running) { @@ -213,7 +213,7 @@ - (void)pause { // persist the setting [[GULUserDefaults standardUserDefaults] setObject:@(NO) - forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting]; + forKey:_userDefaultsKeyForFIAMProgrammaticAutoDataCollectionSetting]; @synchronized(self) { if (_running) { From e9e7fb877261bc70fc612062865bb5ac4b8d5edd Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:04:38 +0330 Subject: [PATCH 135/258] Fix a private runtime property name in FirebaseInAppMessaging (#13798) --- FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m | 2 +- .../Sources/Private/Runtime/FIRIAMSDKModeManager.h | 2 +- FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKModeManager.m | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m b/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m index 890fece4d57..1ac32ffb197 100644 --- a/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m +++ b/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m @@ -148,7 +148,7 @@ - (void)checkAndFetchForInitialAppLaunch:(BOOL)forInitialAppLaunch { if (sdkMode == FIRIAMSDKModeNewlyInstalled || sdkMode == FIRIAMSDKModeTesting) { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700007", @"OK to fetch due to current SDK mode being %@", - FIRIAMDescriptonStringForSDKMode(sdkMode)); + FIRIAMDescriptionStringForSDKMode(sdkMode)); fetchIsAllowedNow = YES; } else { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700008", diff --git a/FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h b/FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h index f67db3b91e1..e78c606cda8 100644 --- a/FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h +++ b/FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h @@ -41,7 +41,7 @@ typedef NS_ENUM(NSInteger, FIRIAMSDKMode) { }; // turn the sdk mode enum integer value into a descriptive string -NSString *FIRIAMDescriptonStringForSDKMode(FIRIAMSDKMode mode); +NSString *FIRIAMDescriptionStringForSDKMode(FIRIAMSDKMode mode); extern NSString *const kFIRIAMUserDefaultKeyForSDKMode; extern NSString *const kFIRIAMUserDefaultKeyForServerFetchCount; diff --git a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKModeManager.m b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKModeManager.m index 39d51af13ca..b13bde2939d 100644 --- a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKModeManager.m +++ b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMSDKModeManager.m @@ -24,7 +24,7 @@ #import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h" #import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h" -NSString *FIRIAMDescriptonStringForSDKMode(FIRIAMSDKMode mode) { +NSString *FIRIAMDescriptionStringForSDKMode(FIRIAMSDKMode mode) { switch (mode) { case FIRIAMSDKModeTesting: return @"Testing Instance"; @@ -76,7 +76,7 @@ - (instancetype)initWithUserDefaults:(GULUserDefaults *)userDefaults FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM290001", @"SDK is in mode of %@ and has seen %d fetches.", - FIRIAMDescriptonStringForSDKMode(_sdkMode), (int)_fetchCount); + FIRIAMDescriptionStringForSDKMode(_sdkMode), (int)_fetchCount); } return self; } From ea4c5fa96c4371a45ee862e109af93ef496ebee0 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:05:08 +0330 Subject: [PATCH 136/258] Docs: enhance FirebaseMessaging token sources comments (#13805) --- FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m b/FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m index 2921d0d62f0..d446ea6b3dc 100644 --- a/FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m +++ b/FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m @@ -744,7 +744,7 @@ - (void)setAPNSToken:(NSData *)APNSToken withUserInfo:(NSDictionary *)userInfo { handler:^(NSString *_Nullable token, NSError *_Nullable error){ // Do nothing as callback is not needed and the - // sub-funciton already handle errors. + // sub-function already handle errors. }]; } if ([self->_tokenStore cachedTokenInfos].count == 0) { @@ -752,7 +752,7 @@ - (void)setAPNSToken:(NSData *)APNSToken withUserInfo:(NSDictionary *)userInfo { scope:kFIRMessagingDefaultTokenScope options:tokenOptions handler:^(NSString *_Nullable FCMToken, NSError *_Nullable error){ - // Do nothing as callback is not needed and the sub-funciton + // Do nothing as callback is not needed and the sub-function // already handle errors. }]; } From 15c00a6372e3163dac6937bcceff24f00103d49c Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:05:43 +0330 Subject: [PATCH 137/258] Enhance FirebaseInstallation unit test methods (#13804) --- .../Source/Tests/Unit/FIRInstallationsStoreTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoreTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoreTests.m index f391b1d3cdf..0a7ce4cc085 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoreTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoreTests.m @@ -128,7 +128,7 @@ - (void)testInstallationID_WhenThereIsUserDefaultsAndNoKeychain_ThenNotFound { OCMVerifyAll(self.mockSecureStorage); } -- (void)testSaveInstallationWhenKeychainSucceds { +- (void)testSaveInstallationWhenKeychainSucceeds { FIRInstallationsItem *item = [FIRInstallationsItem createUnregisteredInstallationItem]; NSString *itemID = [item identifier]; // Reset user defaults key. From 6d724ca632615da567f434c24596fc681cd14004 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:06:47 +0330 Subject: [PATCH 138/258] Fix a method name in FirebaseInAppMessaging (#13789) --- .../Sources/Flows/FIRIAMFetchFlow.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m b/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m index 1ac32ffb197..e75cbdd3cc5 100644 --- a/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m +++ b/FirebaseInAppMessaging/Sources/Flows/FIRIAMFetchFlow.m @@ -95,9 +95,9 @@ - (void)sendFetchIsDoneNotification { object:self]; } -- (void)handleSuccessullyFetchedMessages:(NSArray *)messagesInResponse - withFetchWaitTime:(NSNumber *_Nullable)fetchWaitTime - requestImpressions:(NSArray *)requestImpressions { +- (void)handleSuccessfullyFetchedMessages:(NSArray *)messagesInResponse + withFetchWaitTime:(NSNumber *_Nullable)fetchWaitTime + requestImpressions:(NSArray *)requestImpressions { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700004", @"%lu messages were fetched successfully.", (unsigned long)messagesInResponse.count); @@ -233,9 +233,9 @@ - (void)checkAndFetchForInitialAppLaunch:(BOOL)forInitialAppLaunch { [self.activityLogger addLogRecord:record]; // Now handle the fetched messages. - [self handleSuccessullyFetchedMessages:messages - withFetchWaitTime:nextFetchWaitTime - requestImpressions:impressions]; + [self handleSuccessfullyFetchedMessages:messages + withFetchWaitTime:nextFetchWaitTime + requestImpressions:impressions]; if (forInitialAppLaunch) { [self checkForAppLaunchMessage]; From 05ca8142fea906c57289c1950aed121ffbf14d04 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:07:18 +0330 Subject: [PATCH 139/258] Fix a property name in FirebaseInAppMessaging (#13788) --- .../Sources/Flows/FIRIAMMessageClientCache.m | 6 +++--- .../Sources/Private/Flows/FIRIAMMessageClientCache.h | 2 +- .../Sources/Runtime/FIRIAMRuntimeManager.m | 2 +- .../Tests/Unit/FIRIAMMessageClientCacheTests.m | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m b/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m index 844272ceaa0..23501fad793 100644 --- a/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m +++ b/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m @@ -108,15 +108,15 @@ - (void)setupAnalyticsEventListening { } } - if (self.analycisEventDislayCheckFlow) { + if (self.analyticsEventDisplayCheckFlow) { if ([self.firebaseAnalyticEventsToWatch count] > 0) { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM160010", @"There are analytics event trigger based messages, enable listening"); - [self.analycisEventDislayCheckFlow start]; + [self.analyticsEventDisplayCheckFlow start]; } else { FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM160011", @"No analytics event trigger based messages, disable listening"); - [self.analycisEventDislayCheckFlow stop]; + [self.analyticsEventDisplayCheckFlow stop]; } } } diff --git a/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h b/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h index 2da61504f9b..c9ed28d92fa 100644 --- a/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h +++ b/FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h @@ -57,7 +57,7 @@ API_AVAILABLE(ios(13.0), tvos(13.0)) // analytics event listening based on the latest message definitions // make it weak to avoid retaining cycle @property(nonatomic, weak, nullable) - FIRIAMDisplayCheckOnAnalyticEventsFlow *analycisEventDislayCheckFlow; + FIRIAMDisplayCheckOnAnalyticEventsFlow *analyticsEventDisplayCheckFlow; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithBookkeeper:(id)bookKeeper diff --git a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m index 55d69994dfe..94d2ea8265c 100644 --- a/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m +++ b/FirebaseInAppMessaging/Sources/Runtime/FIRIAMRuntimeManager.m @@ -369,7 +369,7 @@ - (void)internalStartRuntimeWithSDKSettings:(FIRIAMSDKSettings *)settings { self.displayOnFIRAnalyticEventsFlow = [[FIRIAMDisplayCheckOnAnalyticEventsFlow alloc] initWithDisplayFlow:self.displayExecutor]; - self.messageCache.analycisEventDislayCheckFlow = self.displayOnFIRAnalyticEventsFlow; + self.messageCache.analyticsEventDisplayCheckFlow = self.displayOnFIRAnalyticEventsFlow; [self.messageCache loadMessageDataFromServerFetchStorage:self.fetchResultStorage withCompletion:^(BOOL success) { diff --git a/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m b/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m index 33678d7beac..d121ab58957 100644 --- a/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m +++ b/FirebaseInAppMessaging/Tests/Unit/FIRIAMMessageClientCacheTests.m @@ -325,7 +325,7 @@ - (void)testCallingStartAnalyticsEventListenFlow_ok { FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow = OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class); - self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow; + self.clientCache.analyticsEventDisplayCheckFlow = mockAnalyticsEventFlow; // m2 and m4 are messages rendered on 'test_event' Firebase Analytics event // so we expect the analytics event listening flow to be started @@ -339,7 +339,7 @@ - (void)testCallingStopAnalyticsEventListenFlow_ok { FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow = OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class); - self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow; + self.clientCache.analyticsEventDisplayCheckFlow = mockAnalyticsEventFlow; // m1 and m3 are messages rendered on app foreground triggers OCMExpect([mockAnalyticsEventFlow stop]); @@ -352,7 +352,7 @@ - (void)testCallingStartAndThenStopAnalyticsEventListenFlow_ok { FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow = OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class); - self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow; + self.clientCache.analyticsEventDisplayCheckFlow = mockAnalyticsEventFlow; // start is triggered on the setMessageData: call OCMExpect([mockAnalyticsEventFlow start]); From 80a5657ca692209d3c06fdcf92c9ccdbd0360c70 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:57:36 +0330 Subject: [PATCH 140/258] Enhance FirebaseInstallation unit test properties (#13803) --- .../Source/Tests/Unit/FIRInstallationsItemTests.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsItemTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsItemTests.m index 84a74416627..82a044b2139 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsItemTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsItemTests.m @@ -68,11 +68,11 @@ - (void)testValidate_InvalidItem { XCTAssertTrue( [validationError.localizedFailureReason containsString:@"invalid `registrationStatus`"]); - FIRInstallationsItem *registerredItem = [[FIRInstallationsItem alloc] initWithAppID:@"" - firebaseAppName:@""]; - registerredItem.registrationStatus = FIRInstallationStatusRegistered; + FIRInstallationsItem *registeredItem = [[FIRInstallationsItem alloc] initWithAppID:@"" + firebaseAppName:@""]; + registeredItem.registrationStatus = FIRInstallationStatusRegistered; - XCTAssertFalse([registerredItem isValid:&validationError]); + XCTAssertFalse([registeredItem isValid:&validationError]); XCTAssertTrue( [validationError.localizedFailureReason containsString:@"`appID` must not be empty"]); XCTAssertTrue([validationError.localizedFailureReason From 3c754ed99d2a894c211446044a74e7e4a59afaa0 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Fri, 4 Oct 2024 18:58:15 +0330 Subject: [PATCH 141/258] Fix FirebaseDatabase integration test leftovers (#13809) --- FirebaseDatabase/Tests/Integration/FEventTests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseDatabase/Tests/Integration/FEventTests.m b/FirebaseDatabase/Tests/Integration/FEventTests.m index 7caf92cf027..69da5c9479c 100644 --- a/FirebaseDatabase/Tests/Integration/FEventTests.m +++ b/FirebaseDatabase/Tests/Integration/FEventTests.m @@ -361,7 +361,7 @@ - (void)testOnceValueFiresExactlyOnce { }]; } -- (void)testOnceChildAddedFiresExaclyOnce { +- (void)testOnceChildAddedFiresExactlyOnce { __block int badCount = 0; // for(int i = 0; i < 100; i++) { @@ -400,7 +400,7 @@ - (void)testOnceChildAddedFiresExaclyOnce { NSLog(@"BADCOUNT: %d", badCount); } -- (void)testOnceValueFiresExacltyOnceEvenIfThereIsASetInsideCallback { +- (void)testOnceValueFiresExactlyOnceEvenIfThereIsASetInsideCallback { FIRDatabaseReference* path = [FTestHelpers getRandomNode]; __block BOOL firstCall = YES; __block BOOL done = NO; From 67834c366fe781342e6a09b9bdd5c4c269f6673f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 4 Oct 2024 08:46:35 -0700 Subject: [PATCH 142/258] Fix module build issues (#13797) --- .../Sources/FIRFADLogger.m | 13 ++-- FirebaseCore/Extension/FIRLogger.h | 45 ++++++++++++++ FirebaseCore/Sources/FIRLogger.m | 48 ++++++++++++++ Firestore/Source/API/FSTFirestoreComponent.mm | 2 +- .../firebase_metadata_provider_apple.mm | 2 +- Firestore/core/src/util/log_apple.mm | 62 ++++++++++++------- 6 files changed, 138 insertions(+), 34 deletions(-) diff --git a/FirebaseAppDistribution/Sources/FIRFADLogger.m b/FirebaseAppDistribution/Sources/FIRFADLogger.m index 5eb8b92acd1..d0141e7550c 100644 --- a/FirebaseAppDistribution/Sources/FIRFADLogger.m +++ b/FirebaseAppDistribution/Sources/FIRFADLogger.m @@ -14,7 +14,6 @@ #import "FirebaseAppDistribution/Sources/FIRFADLogger.h" #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" FIRLoggerService kFIRLoggerAppDistribution = @"[FirebaseAppDistribution]"; @@ -23,31 +22,27 @@ void FIRFADDebugLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); - FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerAppDistribution, AppDistributionMessageCode, message, - args_ptr); + FIRLogBasicDebug(kFIRLoggerAppDistribution, AppDistributionMessageCode, message, args_ptr); va_end(args_ptr); } void FIRFADInfoLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); - FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerAppDistribution, AppDistributionMessageCode, message, - args_ptr); + FIRLogBasicInfo(kFIRLoggerAppDistribution, AppDistributionMessageCode, message, args_ptr); va_end(args_ptr); } void FIRFADWarningLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); - FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerAppDistribution, AppDistributionMessageCode, message, - args_ptr); + FIRLogBasicWarning(kFIRLoggerAppDistribution, AppDistributionMessageCode, message, args_ptr); va_end(args_ptr); } void FIRFADErrorLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); - FIRLogBasic(FIRLoggerLevelError, kFIRLoggerAppDistribution, AppDistributionMessageCode, message, - args_ptr); + FIRLogBasicError(kFIRLoggerAppDistribution, AppDistributionMessageCode, message, args_ptr); va_end(args_ptr); } diff --git a/FirebaseCore/Extension/FIRLogger.h b/FirebaseCore/Extension/FIRLogger.h index 95adea51e73..629b5c4bbef 100644 --- a/FirebaseCore/Extension/FIRLogger.h +++ b/FirebaseCore/Extension/FIRLogger.h @@ -64,6 +64,11 @@ FIRLoggerLevel FIRGetLoggerLevel(void); */ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); +void FIRSetLoggerLevelNotice(void); +void FIRSetLoggerLevelWarning(void); +void FIRSetLoggerLevelError(void); +void FIRSetLoggerLevelDebug(void); + /** * Checks if the specified logger level is loggable given the current settings. * (required) log level (one of the FirebaseLoggerLevel enum values). @@ -71,6 +76,11 @@ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); */ BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent); +BOOL FIRIsLoggableLevelNotice(void); +BOOL FIRIsLoggableLevelWarning(void); +BOOL FIRIsLoggableLevelError(void); +BOOL FIRIsLoggableLevelDebug(void); + /** * Logs a message to the Xcode console and the device log. If running from AppStore, will * not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming. @@ -121,6 +131,41 @@ extern void FIRLogInfo(NSString *category, NSString *messageCode, NSString *mess extern void FIRLogDebug(NSString *category, NSString *messageCode, NSString *message, ...) NS_FORMAT_FUNCTION(3, 4); +/** + * This function is similar to the one above, except it takes a `va_list` instead of the listed + * variables. + * + * The following functions accept the following parameters in order: (required) service + * name of type FirebaseLoggerService. + * + * (required) message code starting from "I-" which means iOS, + * followed by a capitalized three-character service identifier and a six digit integer message + * ID that is unique within the service. An example of the message code is @"I-COR000001". + * See go/firebase-log-proposal for details. + * (required) message string which can be a format string. + * (optional) A va_list + */ +extern void FIRLogBasicError(NSString *category, + NSString *messageCode, + NSString *message, + va_list args_ptr); +extern void FIRLogBasicWarning(NSString *category, + NSString *messageCode, + NSString *message, + va_list args_ptr); +extern void FIRLogBasicNotice(NSString *category, + NSString *messageCode, + NSString *message, + va_list args_ptr); +extern void FIRLogBasicInfo(NSString *category, + NSString *messageCode, + NSString *message, + va_list args_ptr); +extern void FIRLogBasicDebug(NSString *category, + NSString *messageCode, + NSString *message, + va_list args_ptr); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/FirebaseCore/Sources/FIRLogger.m b/FirebaseCore/Sources/FIRLogger.m index 382a3a921ec..17f54a9dbd1 100644 --- a/FirebaseCore/Sources/FIRLogger.m +++ b/FirebaseCore/Sources/FIRLogger.m @@ -95,6 +95,26 @@ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { GULSetLoggerLevel((GULLoggerLevel)loggerLevel); } +void FIRSetLoggerLevelNotice(void) { + FIRLoggerInitialize(); + GULSetLoggerLevel(GULLoggerLevelNotice); +} + +void FIRSetLoggerLevelWarning(void) { + FIRLoggerInitialize(); + GULSetLoggerLevel(GULLoggerLevelWarning); +} + +void FIRSetLoggerLevelError(void) { + FIRLoggerInitialize(); + GULSetLoggerLevel(GULLoggerLevelError); +} + +void FIRSetLoggerLevelDebug(void) { + FIRLoggerInitialize(); + GULSetLoggerLevel(GULLoggerLevelDebug); +} + #ifdef DEBUG void FIRResetLogger(void) { extern void GULResetLogger(void); @@ -124,6 +144,22 @@ void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { return GULIsLoggableLevel((GULLoggerLevel)loggerLevel); } +BOOL FIRIsLoggableLevelNotice(void) { + return FIRIsLoggableLevel(FIRLoggerLevelNotice, NO); +} + +BOOL FIRIsLoggableLevelWarning(void) { + return FIRIsLoggableLevel(FIRLoggerLevelWarning, NO); +} + +BOOL FIRIsLoggableLevelError(void) { + return FIRIsLoggableLevel(FIRLoggerLevelError, NO); +} + +BOOL FIRIsLoggableLevelDebug(void) { + return FIRIsLoggableLevel(FIRLoggerLevelDebug, NO); +} + void FIRLogBasic(FIRLoggerLevel level, NSString *category, NSString *messageCode, @@ -135,6 +171,18 @@ void FIRLogBasic(FIRLoggerLevel level, messageCode, message, args_ptr); } +#define FIR_LOGGING_FUNCTION_BASIC(level) \ + void FIRLogBasic##level(NSString *category, NSString *messageCode, NSString *message, \ + va_list args_ptr) { \ + FIRLogBasic(FIRLoggerLevel##level, category, messageCode, message, args_ptr); \ + } + +FIR_LOGGING_FUNCTION_BASIC(Error) +FIR_LOGGING_FUNCTION_BASIC(Warning) +FIR_LOGGING_FUNCTION_BASIC(Notice) +FIR_LOGGING_FUNCTION_BASIC(Info) +FIR_LOGGING_FUNCTION_BASIC(Debug) + /** * Generates the logging functions using macros. * diff --git a/Firestore/Source/API/FSTFirestoreComponent.mm b/Firestore/Source/API/FSTFirestoreComponent.mm index ff6e89eadb0..d07ac36f899 100644 --- a/Firestore/Source/API/FSTFirestoreComponent.mm +++ b/Firestore/Source/API/FSTFirestoreComponent.mm @@ -28,7 +28,7 @@ #import "FirebaseCore/Extension/FIRComponentContainer.h" #import "FirebaseCore/Extension/FIRComponentType.h" #import "FirebaseCore/Extension/FIRLibrary.h" -#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #include "Firestore/core/include/firebase/firestore/firestore_version.h" diff --git a/Firestore/core/src/remote/firebase_metadata_provider_apple.mm b/Firestore/core/src/remote/firebase_metadata_provider_apple.mm index ea2a4c982b3..a2c7e287bd6 100644 --- a/Firestore/core/src/remote/firebase_metadata_provider_apple.mm +++ b/Firestore/core/src/remote/firebase_metadata_provider_apple.mm @@ -18,7 +18,7 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRHeartbeatLogger.h" -#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" +#import "FirebaseCore/Sources/FIROptionsInternal.h" #include "Firestore/core/src/util/string_apple.h" diff --git a/Firestore/core/src/util/log_apple.mm b/Firestore/core/src/util/log_apple.mm index 6d1e7d7ab6c..b57b156b403 100644 --- a/Firestore/core/src/util/log_apple.mm +++ b/Firestore/core/src/util/log_apple.mm @@ -24,7 +24,6 @@ #include #import "FirebaseCore/Extension/FIRLogger.h" -#import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h" #include "Firestore/core/src/util/string_apple.h" @@ -36,39 +35,46 @@ const FIRLoggerService kFIRLoggerFirestore = @"[FirebaseFirestore]"; -// Translates a C++ LogLevel to the equivalent Objective-C FIRLoggerLevel -FIRLoggerLevel ToFIRLoggerLevel(LogLevel level) { - switch (level) { - case kLogLevelDebug: - return FIRLoggerLevelDebug; - case kLogLevelNotice: - return FIRLoggerLevelNotice; - case kLogLevelWarning: - return FIRLoggerLevelWarning; - case kLogLevelError: - return FIRLoggerLevelError; - default: - // Unsupported log level. FIRSetLoggerLevel will deal with it. - return static_cast(-1); - } -} - // Actually logs a message via FIRLogger. This must be a C varargs function // so that we can call FIRLogBasic which takes a `va_list`. void LogMessageV(LogLevel level, NSString* format, ...) { va_list list; va_start(list, format); - FIRLogBasic(ToFIRLoggerLevel(level), kFIRLoggerFirestore, @"I-FST000001", - format, list); - + switch (level) { + case kLogLevelDebug: + FIRLogBasicDebug(kFIRLoggerFirestore, @"I-FST000001", format, list); + break; + case kLogLevelNotice: + FIRLogBasicNotice(kFIRLoggerFirestore, @"I-FST000001", format, list); + break; + case kLogLevelWarning: + FIRLogBasicWarning(kFIRLoggerFirestore, @"I-FST000001", format, list); + break; + case kLogLevelError: + FIRLogBasicError(kFIRLoggerFirestore, @"I-FST000001", format, list); + break; + } va_end(list); } } // namespace void LogSetLevel(LogLevel level) { - FIRSetLoggerLevel(ToFIRLoggerLevel(level)); + switch (level) { + case kLogLevelDebug: + FIRSetLoggerLevelDebug(); + break; + case kLogLevelNotice: + FIRSetLoggerLevelNotice(); + break; + case kLogLevelWarning: + FIRSetLoggerLevelWarning(); + break; + case kLogLevelError: + FIRSetLoggerLevelError(); + break; + } } // Note that FIRLogger's default level can be changed by persisting a @@ -82,7 +88,17 @@ void LogSetLevel(LogLevel level) { // defaults write firestore_util_test /google/firebase/debug_mode NO bool LogIsLoggable(LogLevel level) { - return FIRIsLoggableLevel(ToFIRLoggerLevel(level), false); + switch (level) { + case kLogLevelDebug: + return FIRIsLoggableLevelDebug(); + case kLogLevelNotice: + return FIRIsLoggableLevelNotice(); + case kLogLevelWarning: + return FIRIsLoggableLevelWarning(); + case kLogLevelError: + return FIRIsLoggableLevelError(); + } + return false; } void LogMessage(LogLevel level, const std::string& message) { From 34bc1ffc87893bb1fa10915c0f4a40a76ad57dab Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 4 Oct 2024 10:57:40 -0700 Subject: [PATCH 143/258] [storage] Docs build fixes (#13811) --- FirebaseStorage/Sources/AsyncAwait.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseStorage/Sources/AsyncAwait.swift b/FirebaseStorage/Sources/AsyncAwait.swift index e38a2525711..cdcea1bec46 100644 --- a/FirebaseStorage/Sources/AsyncAwait.swift +++ b/FirebaseStorage/Sources/AsyncAwait.swift @@ -22,7 +22,7 @@ public extension StorageReference { /// API may be a better option. /// /// - Parameters: - /// - size: The maximum size in bytes to download. If the download exceeds this size, + /// - maxSize: The maximum size in bytes to download. If the download exceeds this size, /// the task will be cancelled and an error will be thrown. /// - Throws: An error if the operation failed, for example if the data exceeded `maxSize`. /// - Returns: Data object. @@ -114,7 +114,7 @@ public extension StorageReference { /// Asynchronously downloads the object at the current path to a specified system filepath. /// /// - Parameters: - /// - fileUrl: A URL representing the system file path of the object to be uploaded. + /// - fileURL: A URL representing the system file path of the object to be uploaded. /// - onProgress: An optional closure function to return a `Progress` instance while the /// download proceeds. /// - Throws: An error if the operation failed, for example if Storage was unreachable From 85e92b44523f41b12a835ad6c82bb39e9281a646 Mon Sep 17 00:00:00 2001 From: Visu Date: Fri, 4 Oct 2024 11:14:40 -0700 Subject: [PATCH 144/258] Fix a crash related to thread sanitization on variables in FPRNetworkTrace class. (#13795) Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- FirebasePerformance/CHANGELOG.md | 3 +++ .../Sources/Instrumentation/FPRNetworkTrace.m | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/FirebasePerformance/CHANGELOG.md b/FirebasePerformance/CHANGELOG.md index e1f7548eec1..0863831c286 100644 --- a/FirebasePerformance/CHANGELOG.md +++ b/FirebasePerformance/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Fix a crash related to thread sanitization on FPRNetworkTrace class (#13581). + # 10.28.0 - Fix Crash from InstrumentUploadTaskWithStreamedRequest (#12983). - Replace SystemConfiguration with a more recent network monitoring API by Apple (#13079). diff --git a/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m b/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m index f08015c8cf4..380e57dae0e 100644 --- a/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m +++ b/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m @@ -233,7 +233,9 @@ - (NSTimeInterval)startTimeSinceEpoch { #pragma mark - Overrides - (void)setResponseCode:(int32_t)responseCode { - _responseCode = responseCode; + dispatch_sync(self.syncQueue, ^{ + _responseCode = responseCode; + }); if (responseCode != 0) { _hasValidResponseCode = YES; } @@ -279,7 +281,9 @@ - (void)didUploadFileWithURL:(NSURL *)URL { } - (void)didReceiveData:(NSData *)data { - self.responseSize = data.length; + dispatch_sync(self.syncQueue, ^{ + self.responseSize = data.length; + }); } - (void)didReceiveFileURL:(NSURL *)URL { @@ -290,7 +294,9 @@ - (void)didReceiveFileURL:(NSURL *)URL { if (error) { FPRLogNotice(kFPRNetworkTraceFileError, @"Unable to determine the size of file."); } else { - self.responseSize = value.unsignedIntegerValue; + dispatch_sync(self.syncQueue, ^{ + self.responseSize = value.unsignedIntegerValue; + }); } } } From 688af8041a6515aa778197ba71f1e6f2a03d711e Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 4 Oct 2024 17:20:43 -0400 Subject: [PATCH 145/258] [Vertex AI] Add sysInstructs, tools, genConfig to `countTokens` (#13813) --- FirebaseVertexAI/CHANGELOG.md | 3 ++ .../Sources/CountTokensRequest.swift | 8 ++++ .../Sources/GenerativeModel.swift | 3 ++ .../Tests/Integration/IntegrationTests.swift | 19 +++++++-- .../Tests/Unit/GenerativeModelTests.swift | 42 +++++++++++++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 25f3f3181e0..9a7d5c70e7c 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -31,6 +31,9 @@ - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) +- [changed] The response from `GenerativeModel.countTokens(...)` now includes + `systemInstruction`, `tools` and `generationConfig` in the `totalTokens` and + `totalBillableCharacters` counts, where applicable. (#13813) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/CountTokensRequest.swift b/FirebaseVertexAI/Sources/CountTokensRequest.swift index 6b052da19e6..128cb3b8ce6 100644 --- a/FirebaseVertexAI/Sources/CountTokensRequest.swift +++ b/FirebaseVertexAI/Sources/CountTokensRequest.swift @@ -17,7 +17,12 @@ import Foundation @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct CountTokensRequest { let model: String + let contents: [ModelContent] + let systemInstruction: ModelContent? + let tools: [Tool]? + let generationConfig: GenerationConfig? + let options: RequestOptions } @@ -49,6 +54,9 @@ public struct CountTokensResponse { extension CountTokensRequest: Encodable { enum CodingKeys: CodingKey { case contents + case systemInstruction + case tools + case generationConfig } } diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index a5a8933e435..dc069d88d03 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -272,6 +272,9 @@ public final class GenerativeModel { let countTokensRequest = try CountTokensRequest( model: modelResourceName, contents: content(), + systemInstruction: systemInstruction, + tools: tools, + generationConfig: generationConfig, options: requestOptions ) return try await generativeAIService.loadRequest(request: countTokensRequest) diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index 44eac05be22..0ccbb98e83a 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -19,7 +19,16 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class IntegrationTests: XCTestCase { // Set temperature, topP and topK to lowest allowed values to make responses more deterministic. - let generationConfig = GenerationConfig(temperature: 0.0, topP: 0.0, topK: 1) + let generationConfig = GenerationConfig( + temperature: 0.0, + topP: 0.0, + topK: 1, + responseMIMEType: "text/plain" + ) + let systemInstruction = ModelContent( + role: "system", + parts: "You are a friendly and helpful assistant." + ) var vertex: VertexAI! var model: GenerativeModel! @@ -40,7 +49,9 @@ final class IntegrationTests: XCTestCase { vertex = VertexAI.vertexAI() model = vertex.generativeModel( modelName: "gemini-1.5-flash", - generationConfig: generationConfig + generationConfig: generationConfig, + tools: [], + systemInstruction: systemInstruction ) } @@ -68,7 +79,7 @@ final class IntegrationTests: XCTestCase { let response = try await model.countTokens(prompt) - XCTAssertEqual(response.totalTokens, 6) - XCTAssertEqual(response.totalBillableCharacters, 16) + XCTAssertEqual(response.totalTokens, 14) + XCTAssertEqual(response.totalBillableCharacters, 51) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 6956160b072..c5e8332d2b8 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1234,6 +1234,48 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(response.totalBillableCharacters, 16) } + func testCountTokens_succeeds_allOptions() async throws { + MockURLProtocol.requestHandler = try httpRequestHandler( + forResource: "unary-success-total-tokens", + withExtension: "json" + ) + let generationConfig = GenerationConfig( + temperature: 0.5, + topP: 0.9, + topK: 3, + candidateCount: 1, + maxOutputTokens: 1024, + stopSequences: ["test-stop"], + responseMIMEType: "text/plain" + ) + let sumFunction = FunctionDeclaration( + name: "sum", + description: "Add two integers.", + parameters: ["x": .integer(), "y": .integer()] + ) + let systemInstruction = ModelContent( + role: "system", + parts: "You are a calculator. Use the provided tools." + ) + model = GenerativeModel( + name: testModelResourceName, + projectID: "my-project-id", + apiKey: "API_KEY", + generationConfig: generationConfig, + tools: [Tool(functionDeclarations: [sumFunction])], + systemInstruction: systemInstruction, + requestOptions: RequestOptions(), + appCheck: nil, + auth: nil, + urlSession: urlSession + ) + + let response = try await model.countTokens("Why is the sky blue?") + + XCTAssertEqual(response.totalTokens, 6) + XCTAssertEqual(response.totalBillableCharacters, 16) + } + func testCountTokens_succeeds_noBillableCharacters() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-no-billable-characters", From a366d6c4982df6d724c95f4ed399fe444593e9e5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 4 Oct 2024 18:20:16 -0400 Subject: [PATCH 146/258] [Vertex AI] Add image / function call count tokens integration tests (#13814) --- .../Sources/FunctionCalling.swift | 12 +++++ .../Tests/Integration/IntegrationTests.swift | 50 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 4641a6f400f..3fb17838d4c 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -21,6 +21,18 @@ public struct FunctionCall: Equatable, Sendable { /// The function parameters and values. public let args: JSONObject + + /// Constructs a new function call. + /// + /// > Note: A `FunctionCall` is typically received from the model, rather than created manually. + /// + /// - Parameters: + /// - name: The name of the function to call. + /// - args: The function parameters and values. + public init(name: String, args: JSONObject) { + self.name = name + self.args = args + } } /// Structured representation of a function declaration. diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index 0ccbb98e83a..8eddf79a648 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -74,7 +74,7 @@ final class IntegrationTests: XCTestCase { // MARK: - Count Tokens - func testCountTokens() async throws { + func testCountTokens_text() async throws { let prompt = "Why is the sky blue?" let response = try await model.countTokens(prompt) @@ -82,4 +82,52 @@ final class IntegrationTests: XCTestCase { XCTAssertEqual(response.totalTokens, 14) XCTAssertEqual(response.totalBillableCharacters, 51) } + + func testCountTokens_image_inlineData() async throws { + guard let image = UIImage(systemName: "cloud") else { + XCTFail("Image not found.") + return + } + + let response = try await model.countTokens(image) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + + func testCountTokens_image_fileData() async throws { + let fileData = ModelContent(parts: [.fileData( + mimetype: "image/jpeg", + uri: "gs://ios-opensource-samples.appspot.com/ios/public/blank.jpg" + )]) + + let response = try await model.countTokens([fileData]) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + + func testCountTokens_functionCalling() async throws { + let sumDeclaration = FunctionDeclaration( + name: "sum", + description: "Adds two integers.", + parameters: ["x": .integer(), "y": .integer()] + ) + model = vertex.generativeModel( + modelName: "gemini-1.5-flash", + tools: [Tool(functionDeclarations: [sumDeclaration])] + ) + let prompt = "What is 10 + 32?" + let sumCall = FunctionCall(name: "sum", args: ["x": .number(10), "y": .number(32)]) + let sumResponse = FunctionResponse(name: "sum", response: ["result": .number(42)]) + + let response = try await model.countTokens([ + ModelContent(role: "user", parts: [.text(prompt)]), + ModelContent(role: "model", parts: [.functionCall(sumCall)]), + ModelContent(role: "function", parts: [.functionResponse(sumResponse)]), + ]) + + XCTAssertEqual(response.totalTokens, 24) + XCTAssertEqual(response.totalBillableCharacters, 71) + } } From a1e4be79190830ac1f21d21bded93bb18090a708 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:58:21 -0400 Subject: [PATCH 147/258] [Core] Add logging level getter to FIRConfiguration (#13815) --- FirebaseCore/Sources/FIRConfiguration.m | 11 ++++++++++- FirebaseCore/Sources/FIRLogger.m | 1 + .../Sources/Public/FirebaseCore/FIRConfiguration.h | 3 +++ FirebaseCore/Tests/Unit/FIRConfigurationTest.m | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/FirebaseCore/Sources/FIRConfiguration.m b/FirebaseCore/Sources/FIRConfiguration.m index 83b3248c377..8a646bdd67e 100644 --- a/FirebaseCore/Sources/FIRConfiguration.m +++ b/FirebaseCore/Sources/FIRConfiguration.m @@ -17,6 +17,7 @@ #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h" extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel); +extern FIRLoggerLevel FIRGetLoggerLevel(void); @implementation FIRConfiguration @@ -40,7 +41,15 @@ - (instancetype)init { - (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel { NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin, @"Invalid logger level, %ld", (long)loggerLevel); - FIRSetLoggerLevel(loggerLevel); + @synchronized(self) { + FIRSetLoggerLevel(loggerLevel); + } +} + +- (FIRLoggerLevel)loggerLevel { + @synchronized(self) { + return FIRGetLoggerLevel(); + } } @end diff --git a/FirebaseCore/Sources/FIRLogger.m b/FirebaseCore/Sources/FIRLogger.m index 17f54a9dbd1..9ec89996660 100644 --- a/FirebaseCore/Sources/FIRLogger.m +++ b/FirebaseCore/Sources/FIRLogger.m @@ -87,6 +87,7 @@ void FIRLoggerInitialize(void) { } FIRLoggerLevel FIRGetLoggerLevel(void) { + FIRLoggerInitialize(); return (FIRLoggerLevel)GULGetLoggerLevel(); } diff --git a/FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h b/FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h index 408bcadb75f..e6c1f1d8545 100644 --- a/FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h +++ b/FirebaseCore/Sources/Public/FirebaseCore/FIRConfiguration.h @@ -40,6 +40,9 @@ NS_SWIFT_NAME(FirebaseConfiguration) */ - (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel; +/// Returns the logging level for internal Firebase logging. +- (FIRLoggerLevel)loggerLevel; + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseCore/Tests/Unit/FIRConfigurationTest.m b/FirebaseCore/Tests/Unit/FIRConfigurationTest.m index 9267a63b6be..03bd70ae508 100644 --- a/FirebaseCore/Tests/Unit/FIRConfigurationTest.m +++ b/FirebaseCore/Tests/Unit/FIRConfigurationTest.m @@ -28,4 +28,18 @@ - (void)testSharedInstance { XCTAssertNotNil(config.analyticsConfiguration); } +- (void)testGetDefaultLevel { + FIRConfiguration *config = [FIRConfiguration sharedInstance]; + FIRLoggerLevel defaultLevel = [config loggerLevel]; + XCTAssertEqual(defaultLevel, FIRLoggerLevelNotice); +} + +- (void)testSetAndGet { + FIRConfiguration *config = [FIRConfiguration sharedInstance]; + for (FIRLoggerLevel level = FIRLoggerLevelMin; level <= FIRLoggerLevelMax; level++) { + [config setLoggerLevel:level]; + XCTAssertEqual([config loggerLevel], level); + } +} + @end From ccc91014c4a8cde6fe95aa4da5fe990d1005e9e0 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:33:56 +0330 Subject: [PATCH 148/258] Fix: typos of the FirebaseRemoteConfig source code (#13825) --- FirebaseRemoteConfig/Sources/RCNConfigRealtime.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m b/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m index 921bb31dce3..0cfe3a13ca9 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m @@ -152,7 +152,7 @@ + (dispatch_queue_t)realtimeRemoteConfigSerialQueue { return realtimeRemoteConfigQueue; } -- (void)propogateErrors:(NSError *)error { +- (void)propagateErrors:(NSError *)error { __weak RCNConfigRealtime *weakSelf = self; dispatch_async(_realtimeLockQueue, ^{ __strong RCNConfigRealtime *strongSelf = weakSelf; @@ -402,7 +402,7 @@ - (void)retryHTTPConnection { }]; FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014", @"Cannot establish connection. Error: %@", error); - [self propogateErrors:error]; + [self propagateErrors:error]; } }); } @@ -455,7 +455,7 @@ - (void)fetchLatestConfig:(NSInteger)remainingAttempts targetVersion:(NSInteger) @"Failed to retrieve config due to fetch error. " @"Error: %@", error); - return [self propogateErrors:error]; + return [self propagateErrors:error]; } if (status == FIRRemoteConfigFetchStatusSuccess) { if ([strongSelf->_configFetch.templateVersionNumber @@ -507,7 +507,7 @@ - (void)autoFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVe }]; FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011", @"Ran out of fetch attempts, cannot find target config version."); - [self propogateErrors:error]; + [self propagateErrors:error]; return; } @@ -536,7 +536,7 @@ - (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataErr NSLocalizedDescriptionKey : @"The server is temporarily unavailable. Try again in a few minutes." }]; - [self propogateErrors:error]; + [self propagateErrors:error]; } else { NSInteger clientTemplateVersion = [_configFetch.templateVersionNumber integerValue]; if (updateTemplateVersion > clientTemplateVersion) { @@ -548,7 +548,7 @@ - (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataErr [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain code:FIRRemoteConfigUpdateErrorMessageInvalid userInfo:@{NSLocalizedDescriptionKey : @"Unable to parse ConfigUpdate."}]; - [self propogateErrors:error]; + [self propagateErrors:error]; } } @@ -567,7 +567,7 @@ - (void)URLSession:(NSURLSession *)session code:FIRRemoteConfigUpdateErrorStreamError userInfo:@{NSLocalizedDescriptionKey : strData}]; FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. %@", error); - [self propogateErrors:error]; + [self propagateErrors:error]; return; } From cf2e7bf50c7b26102256df76eb2d3cf3b9f3a2b9 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:34:06 +0330 Subject: [PATCH 149/258] Test: enhance FirebasePerformance Unit tests (#13823) --- .../Tests/Unit/Configurations/FPRRemoteConfigFlagsTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebasePerformance/Tests/Unit/Configurations/FPRRemoteConfigFlagsTest.m b/FirebasePerformance/Tests/Unit/Configurations/FPRRemoteConfigFlagsTest.m index 1e2519a31be..e8fefb09864 100644 --- a/FirebasePerformance/Tests/Unit/Configurations/FPRRemoteConfigFlagsTest.m +++ b/FirebasePerformance/Tests/Unit/Configurations/FPRRemoteConfigFlagsTest.m @@ -195,7 +195,7 @@ - (void)testConfigUpdateDoesNotHappenImmediately { } /** Validate the configuration update does not happen immediately after fetching. */ -- (void)testConfigUpdateHappensIfIntialFetchHasNotHappened { +- (void)testConfigUpdateHappensIfInitialFetchHasNotHappened { FPRFakeRemoteConfig *remoteConfig = [[FPRFakeRemoteConfig alloc] init]; remoteConfig.lastFetchStatus = FIRRemoteConfigFetchStatusNoFetchYet; remoteConfig.lastFetchTime = nil; From b1fdb68876bb9c39217b8ed5cf0f0dfb864788cd Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:34:22 +0330 Subject: [PATCH 150/258] Enhance FirebasePerformance TestApp files structure (#13821) --- .../TestApp/PerfTestRigApp.xcodeproj/project.pbxproj | 8 ++++---- .../Tests/{PerfContollerTests.m => PerfControllerTests.m} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename FirebasePerformance/Tests/TestApp/Tests/{PerfContollerTests.m => PerfControllerTests.m} (98%) diff --git a/FirebasePerformance/Tests/TestApp/PerfTestRigApp.xcodeproj/project.pbxproj b/FirebasePerformance/Tests/TestApp/PerfTestRigApp.xcodeproj/project.pbxproj index 960cf12afda..1cce34f02ab 100644 --- a/FirebasePerformance/Tests/TestApp/PerfTestRigApp.xcodeproj/project.pbxproj +++ b/FirebasePerformance/Tests/TestApp/PerfTestRigApp.xcodeproj/project.pbxproj @@ -40,7 +40,7 @@ F6EB95801ED78E4000178999 /* PerfTraceView+Accessibility.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EB955E1ED78E4000178999 /* PerfTraceView+Accessibility.m */; }; F6EB95811ED78E4000178999 /* PerfTraceView.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EB95601ED78E4000178999 /* PerfTraceView.m */; }; F6EB95861ED89C8800178999 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB95851ED89C8800178999 /* XCTest.framework */; }; - F6EB95871ED89C9C00178999 /* PerfContollerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EB95831ED79F2600178999 /* PerfContollerTests.m */; }; + F6EB95871ED89C9C00178999 /* PerfControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EB95831ED79F2600178999 /* PerfControllerTests.m */; }; F6EB95971ED89EC800178999 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6EB95961ED89EC800178999 /* Info.plist */; }; F6EB95981ED89EDD00178999 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6EB95961ED89EC800178999 /* Info.plist */; }; /* End PBXBuildFile section */ @@ -119,7 +119,7 @@ F6EB955E1ED78E4000178999 /* PerfTraceView+Accessibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "PerfTraceView+Accessibility.m"; sourceTree = ""; }; F6EB955F1ED78E4000178999 /* PerfTraceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PerfTraceView.h; sourceTree = ""; }; F6EB95601ED78E4000178999 /* PerfTraceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerfTraceView.m; sourceTree = ""; }; - F6EB95831ED79F2600178999 /* PerfContollerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerfContollerTests.m; sourceTree = ""; }; + F6EB95831ED79F2600178999 /* PerfControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PerfControllerTests.m; sourceTree = ""; }; F6EB95851ED89C8800178999 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; F6EB95961ED89EC800178999 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FB70C6B0B736E663A795A8A4 /* libPods-PerfTestRigApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PerfTestRigApp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -358,7 +358,7 @@ isa = PBXGroup; children = ( F6EB95961ED89EC800178999 /* Info.plist */, - F6EB95831ED79F2600178999 /* PerfContollerTests.m */, + F6EB95831ED79F2600178999 /* PerfControllerTests.m */, ); path = Tests; sourceTree = SOURCE_ROOT; @@ -600,7 +600,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F6EB95871ED89C9C00178999 /* PerfContollerTests.m in Sources */, + F6EB95871ED89C9C00178999 /* PerfControllerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FirebasePerformance/Tests/TestApp/Tests/PerfContollerTests.m b/FirebasePerformance/Tests/TestApp/Tests/PerfControllerTests.m similarity index 98% rename from FirebasePerformance/Tests/TestApp/Tests/PerfContollerTests.m rename to FirebasePerformance/Tests/TestApp/Tests/PerfControllerTests.m index 881a34449d7..5912d282d56 100644 --- a/FirebasePerformance/Tests/TestApp/Tests/PerfContollerTests.m +++ b/FirebasePerformance/Tests/TestApp/Tests/PerfControllerTests.m @@ -27,14 +27,14 @@ const NSUInteger kRequestsCount = 5; static NSString *const kTraceName = @"Trace 1"; -@interface PerfContollerTests : XCTestCase +@interface PerfControllerTests : XCTestCase - (void)tapStageButtonNTimes:(NSUInteger)tapsCount; - (void)tapCountButtonsNTimes:(NSUInteger)tapsCount; @end -@implementation PerfContollerTests { +@implementation PerfControllerTests { XCUIApplication *_application; } From 236022e1946f1ebde86e0874e128277454117ffb Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:34:30 +0330 Subject: [PATCH 151/258] docs: fix common sources comments (#13820) --- FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h b/FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h index 287a309ec57..3e5fc73b553 100644 --- a/FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h +++ b/FirebasePerformance/Sources/Common/FPRDiagnostics_Private.h @@ -21,7 +21,7 @@ */ @interface FPRDiagnostics () -/** FPRCongiguration to check if diagnostic is enabled. */ +/** FPRConfiguration to check if diagnostic is enabled. */ @property(class, nonatomic, readwrite) FPRConfigurations *configuration; @end From 4da2c15097380229a9e19c200c5bbf76f800949a Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:34:44 +0330 Subject: [PATCH 152/258] Improve FirebaseMessaging remote notification proxy source code (#13818) --- .../FIRMessagingRemoteNotificationsProxy.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/FirebaseMessaging/Sources/FIRMessagingRemoteNotificationsProxy.m b/FirebaseMessaging/Sources/FIRMessagingRemoteNotificationsProxy.m index a73367def7c..8d9de0982a8 100644 --- a/FirebaseMessaging/Sources/FIRMessagingRemoteNotificationsProxy.m +++ b/FirebaseMessaging/Sources/FIRMessagingRemoteNotificationsProxy.m @@ -300,10 +300,10 @@ - (void)swizzleSelector:(SEL)originalSelector IMP originalMethodImplementation = method_setImplementation(originalMethod, swizzledImplementation); - IMP nonexistantMethodImplementation = [self nonExistantMethodImplementationForClass:klass]; + IMP nonexistentMethodImplementation = [self nonExistentMethodImplementationForClass:klass]; if (originalMethodImplementation && - originalMethodImplementation != nonexistantMethodImplementation && + originalMethodImplementation != nonexistentMethodImplementation && originalMethodImplementation != swizzledImplementation) { [self saveOriginalImplementation:originalMethodImplementation forSelector:originalSelector]; } @@ -344,8 +344,8 @@ - (void)unswizzleSelector:(SEL)selector inClass:(Class)klass { // behavior as if the method was not implemented. // See: http://stackoverflow.com/a/8276527/9849 - IMP nonExistantMethodImplementation = [self nonExistantMethodImplementationForClass:klass]; - method_setImplementation(swizzledMethod, nonExistantMethodImplementation); + IMP nonExistentMethodImplementation = [self nonExistentMethodImplementationForClass:klass]; + method_setImplementation(swizzledMethod, nonExistentMethodImplementation); } } @@ -354,10 +354,10 @@ - (void)unswizzleSelector:(SEL)selector inClass:(Class)klass { // This is useful to generate from a stable, "known missing" selector, as the IMP can be compared // in case we are setting an implementation for a class that was previously "unswizzled" into a // non-existent implementation. -- (IMP)nonExistantMethodImplementationForClass:(Class)klass { - SEL nonExistantSelector = NSSelectorFromString(@"aNonExistantMethod"); - IMP nonExistantMethodImplementation = class_getMethodImplementation(klass, nonExistantSelector); - return nonExistantMethodImplementation; +- (IMP)nonExistentMethodImplementationForClass:(Class)klass { + SEL nonExistentSelector = NSSelectorFromString(@"aNonExistentMethod"); + IMP nonExistentMethodImplementation = class_getMethodImplementation(klass, nonExistentSelector); + return nonExistentMethodImplementation; } // A safe, non-leaky way return a property object by its name From 6612a6cb3422ff3deee5da3432cdd1a658963f60 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:36:50 +0330 Subject: [PATCH 153/258] Test: improve FirebaseRemoveConfig unit tests (#13826) --- FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m index 0d65190403c..00704690521 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m @@ -72,7 +72,7 @@ - (void)testConfigureConfigWithNilSenderID { XCTAssertThrows([rc configureConfig:app]); } -- (void)testConfigureConfigNotIntallingSenderID { +- (void)testConfigureConfigNotInstallingSenderID { id settingsMock = OCMClassMock([RCNConfigSettings class]); OCMStub([settingsMock instancesRespondToSelector:@selector(senderID)]).andReturn(NO); From b64316622de48627893672f04db34cc31d45614f Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:37:00 +0330 Subject: [PATCH 154/258] Fix: FirebasePerformance TestApp log messages in the source code (#13822) --- .../Source/ViewControllers/NetworkConnectionViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebasePerformance/Tests/TestApp/Source/ViewControllers/NetworkConnectionViewController.m b/FirebasePerformance/Tests/TestApp/Source/ViewControllers/NetworkConnectionViewController.m index 8d8426b9a17..4b278438518 100644 --- a/FirebasePerformance/Tests/TestApp/Source/ViewControllers/NetworkConnectionViewController.m +++ b/FirebasePerformance/Tests/TestApp/Source/ViewControllers/NetworkConnectionViewController.m @@ -145,7 +145,7 @@ - (void)networkConnectionViewDidTapRequestButton:(NetworkConnectionView *)connec PerfLog(@"Start perform network request"); [self.connection makeNetworkRequestWithSuccessCallback:^{ - PerfLog(@"Network operation complited with success"); + PerfLog(@"Network operation completed with success"); dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf updateUIForOperationCompleted]; weakSelf.connectionView.progressViewColor = [UIColor greenColor]; @@ -155,7 +155,7 @@ - (void)networkConnectionViewDidTapRequestButton:(NetworkConnectionView *)connec } failureCallback:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ - PerfLog(@"Network operation complited with fail: %@", error.localizedDescription); + PerfLog(@"Network operation completed with fail: %@", error.localizedDescription); [weakSelf updateUIForOperationCompleted]; weakSelf.connectionView.progressViewColor = [UIColor redColor]; self.connectionView.connectionStatus = ConnectionStatus_Fail; From cdbd2c8af38c8b2109fd8bfc1489c37fd9f076ac Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:37:11 +0330 Subject: [PATCH 155/258] Docs: fix app activity comments (#13819) --- .../Sources/AppActivity/FPRSessionManager+Private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h b/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h index ac88eb633fa..5d8f4531d57 100644 --- a/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h +++ b/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @property(atomic, nullable, readwrite) FPRSessionDetails *sessionDetails; /** - * Creates an instance of FPRSesssionManager with the notification center provided. All the + * Creates an instance of FPRSessionManager with the notification center provided. All the * notifications from the session manager will sent using this notification center. * * @param gaugeManager Gauge manager used by the session manager to work with gauges. From 079295c62809d21e6144b5cd46471f1a06b10e7d Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:37:23 +0330 Subject: [PATCH 156/258] Fix: a repeated typo in multiple test files of the FirebaseStorage (#13817) --- FirebaseStorage/Tests/ObjCIntegration/ObjCAPITests.m | 2 +- FirebaseStorage/Tests/ObjCIntegration/ObjCPPAPITests.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseStorage/Tests/ObjCIntegration/ObjCAPITests.m b/FirebaseStorage/Tests/ObjCIntegration/ObjCAPITests.m index a66038ab64f..ccf5aae81a5 100644 --- a/FirebaseStorage/Tests/ObjCIntegration/ObjCAPITests.m +++ b/FirebaseStorage/Tests/ObjCIntegration/ObjCAPITests.m @@ -207,7 +207,7 @@ - (void)FIRStorageTaskManagementApis:(id)task { [task resume]; } -- (void)FIRStorageTaskSnaphotApis:(FIRStorageTaskSnapshot *)snapshot { +- (void)FIRStorageTaskSnapshotApis:(FIRStorageTaskSnapshot *)snapshot { [snapshot task]; [snapshot metadata]; [snapshot reference]; diff --git a/FirebaseStorage/Tests/ObjCIntegration/ObjCPPAPITests.mm b/FirebaseStorage/Tests/ObjCIntegration/ObjCPPAPITests.mm index 35ce5696f65..1c90e79126c 100644 --- a/FirebaseStorage/Tests/ObjCIntegration/ObjCPPAPITests.mm +++ b/FirebaseStorage/Tests/ObjCIntegration/ObjCPPAPITests.mm @@ -205,7 +205,7 @@ - (void)FIRStorageTaskManagementApis:(id)task { [task resume]; } -- (void)FIRStorageTaskSnaphotApis:(FIRStorageTaskSnapshot *)snapshot { +- (void)FIRStorageTaskSnapshotApis:(FIRStorageTaskSnapshot *)snapshot { [snapshot task]; [snapshot metadata]; [snapshot reference]; From eb5e301a40ec6a284cdc59f51a94acc896cb0891 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 17:37:42 +0330 Subject: [PATCH 157/258] Docs: enhance FirebaseSessions's proto documentations (#13816) --- FirebaseSessions/ProtoSupport/Protos/sessions.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSessions/ProtoSupport/Protos/sessions.proto b/FirebaseSessions/ProtoSupport/Protos/sessions.proto index 7fe1a040e9d..274355062d9 100644 --- a/FirebaseSessions/ProtoSupport/Protos/sessions.proto +++ b/FirebaseSessions/ProtoSupport/Protos/sessions.proto @@ -17,7 +17,7 @@ // // 1. Get the session_event.proto from the backend // 2. Copy everything below the options, and past it here below the package -// declaraction. +// declaration. // 3. Remove all the datapol.semantic_type tags // 4. Remove "wireless_android_play_playlog." from // "wireless_android_play_playlog.NetworkConnectionInfo" From 53459a0c769942b44b0bd336f9eab49e549cc1fe Mon Sep 17 00:00:00 2001 From: Yan Zaitsev Date: Sat, 5 Oct 2024 19:43:55 +0530 Subject: [PATCH 158/258] Wrap _listeners access with _realtimeLockQueue in fetchLatestConfig completionHandler (#13776) --- FirebaseRemoteConfig/Sources/RCNConfigRealtime.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m b/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m index 0cfe3a13ca9..f7f5d1e44a1 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigRealtime.m @@ -462,10 +462,12 @@ - (void)fetchLatestConfig:(NSInteger)remainingAttempts targetVersion:(NSInteger) integerValue] >= targetVersion) { // only notify listeners if there is a change if ([update updatedKeys].count > 0) { - for (RCNConfigUpdateCompletion listener in strongSelf - ->_listeners) { - listener(update, nil); - } + dispatch_async(strongSelf->_realtimeLockQueue, ^{ + for (RCNConfigUpdateCompletion listener in strongSelf + ->_listeners) { + listener(update, nil); + } + }); } } else { FIRLogDebug( From d37a551e3d7f4257c31bcc82892131ca92f7b184 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 20:06:18 +0330 Subject: [PATCH 159/258] Test: a function name in FirebaseSessions' unit tests (#13828) --- FirebaseSessions/Tests/Unit/SessionStartEventTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSessions/Tests/Unit/SessionStartEventTests.swift b/FirebaseSessions/Tests/Unit/SessionStartEventTests.swift index 70713307ae1..21d5b5a2da5 100644 --- a/FirebaseSessions/Tests/Unit/SessionStartEventTests.swift +++ b/FirebaseSessions/Tests/Unit/SessionStartEventTests.swift @@ -216,7 +216,7 @@ class SessionStartEventTests: XCTestCase { } } - func test_newtworkInfo_onlyPresentWhenPerformanceInstalled() { + func test_networkInfo_onlyPresentWhenPerformanceInstalled() { let mockNetworkInfo = MockNetworkInfo() mockNetworkInfo.networkType = .mobile // Mobile Subtypes are always empty on non-iOS platforms, and From a3e7a2090f8ca43b6023e76a83454e27276d4c51 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Sat, 5 Oct 2024 20:06:32 +0330 Subject: [PATCH 160/258] Fix: a variable name in FirebaseSessions' source code (#13827) --- .../Sources/Installations+InstallationsProtocol.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift index 38ca1eafa9b..6a467523e0c 100644 --- a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift +++ b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift @@ -37,7 +37,7 @@ extension InstallationsProtocol { func installationID(completion: @escaping (Result<(String, String), Error>) -> Void) { var authTokenComplete = "" - var intallationComplete: String? + var installationComplete: String? var errorComplete: Error? let workingGroup = DispatchGroup() @@ -51,7 +51,7 @@ extension InstallationsProtocol { workingGroup.enter() installationID { (installationID: String?, error: Error?) in if let installationID { - intallationComplete = installationID + installationComplete = installationID } else if let error = error { errorComplete = error } @@ -67,8 +67,8 @@ extension InstallationsProtocol { completion(.failure(FirebaseSessionsError.SessionInstallationsTimeOutError)) return default: - if let intallationComplete { - completion(.success((intallationComplete, authTokenComplete))) + if let installationComplete { + completion(.success((installationComplete, authTokenComplete))) } else if let errorComplete { completion(.failure(errorComplete)) } From f27e34d2e5dab3affc3df323d3aeef17542cf57d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 7 Oct 2024 15:37:58 -0400 Subject: [PATCH 161/258] [Vertex AI] Replace `ModelContent.Part` enum with protocol/structs (#13767) --- FirebaseVertexAI.podspec | 1 + FirebaseVertexAI/CHANGELOG.md | 6 + .../ViewModels/FunctionCallingViewModel.swift | 34 ++- .../ViewModels/PhotoReasoningViewModel.swift | 2 +- FirebaseVertexAI/Sources/Chat.swift | 43 ++-- .../Sources/FunctionCalling.swift | 65 ------ .../Sources/GenerateContentResponse.swift | 14 +- .../Sources/GenerativeModel.swift | 60 ++---- FirebaseVertexAI/Sources/ModelContent.swift | 200 +++++++++--------- .../Sources/PartsRepresentable+Image.swift | 40 ++-- .../Sources/PartsRepresentable.swift | 47 ++-- .../Sources/Types/Internal/InternalPart.swift | 103 +++++++++ .../Sources/Types/Public/Part.swift | 134 ++++++++++++ .../Tests/Integration/IntegrationTests.swift | 20 +- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 5 +- .../Tests/Unit/GenerativeModelTests.swift | 24 +-- .../Tests/Unit/ModelContentTests.swift | 78 ------- FirebaseVertexAI/Tests/Unit/PartTests.swift | 157 ++++++++++++++ .../Tests/Unit/PartsRepresentableTests.swift | 74 +++---- .../Tests/Unit/Resources/blue.png | Bin 0 -> 69 bytes .../Tests/Unit/VertexAIAPITests.swift | 84 ++++---- Package.swift | 1 + 22 files changed, 697 insertions(+), 495 deletions(-) create mode 100644 FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift create mode 100644 FirebaseVertexAI/Sources/Types/Public/Part.swift delete mode 100644 FirebaseVertexAI/Tests/Unit/ModelContentTests.swift create mode 100644 FirebaseVertexAI/Tests/Unit/PartTests.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/blue.png diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index a9ee6fb77eb..0a9b059d3c1 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -62,6 +62,7 @@ Firebase SDK. ] unit_tests.resources = [ unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/**/*.{txt,json}', + unit_tests_dir + 'Resources/**/*', ] end end diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 9a7d5c70e7c..13ef2aad590 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -28,6 +28,12 @@ - [changed] **Breaking Change**: The `CountTokensError` enum has been removed; errors occurring in `GenerativeModel.countTokens(...)` are now thrown directly instead of being wrapped in a `CountTokensError.internalError`. (#13736) +- [changed] **Breaking Change**: The enum `ModelContent.Part` has been replaced + with a protocol named `Part` to avoid future breaking changes with new part + types. The new types `TextPart` and `FunctionCallPart` may be received when + generating content the types `TextPart`; additionally the types + `InlineDataPart`, `FileDataPart` and `FunctionResponsePart` may be provided + as input. (#13767) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index 110cab9ce27..f39540eb1a9 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -30,7 +30,7 @@ class FunctionCallingViewModel: ObservableObject { } /// Function calls pending processing - private var functionCalls = [FunctionCall]() + private var functionCalls = [FunctionCallPart]() private var model: GenerativeModel private var chat: Chat @@ -144,26 +144,26 @@ class FunctionCallingViewModel: ObservableObject { for part in candidate.content.parts { switch part { - case let .text(text): + case let textPart as TextPart: // replace pending message with backend response - messages[messages.count - 1].message += text + messages[messages.count - 1].message += textPart.text messages[messages.count - 1].pending = false - case let .functionCall(functionCall): - messages.insert(functionCall.chatMessage(), at: messages.count - 1) - functionCalls.append(functionCall) - case .inlineData, .fileData, .functionResponse: - fatalError("Unsupported response content.") + case let functionCallPart as FunctionCallPart: + messages.insert(functionCallPart.chatMessage(), at: messages.count - 1) + functionCalls.append(functionCallPart) + default: + fatalError("Unsupported response part: \(part)") } } } - func processFunctionCalls() async throws -> [FunctionResponse] { - var functionResponses = [FunctionResponse]() + func processFunctionCalls() async throws -> [FunctionResponsePart] { + var functionResponses = [FunctionResponsePart]() for functionCall in functionCalls { switch functionCall.name { case "get_exchange_rate": let exchangeRates = getExchangeRate(args: functionCall.args) - functionResponses.append(FunctionResponse( + functionResponses.append(FunctionResponsePart( name: "get_exchange_rate", response: exchangeRates )) @@ -208,7 +208,7 @@ class FunctionCallingViewModel: ObservableObject { } } -private extension FunctionCall { +private extension FunctionCallPart { func chatMessage() -> ChatMessage { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -228,7 +228,7 @@ private extension FunctionCall { } } -private extension FunctionResponse { +private extension FunctionResponsePart { func chatMessage() -> ChatMessage { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -248,12 +248,8 @@ private extension FunctionResponse { } } -private extension [FunctionResponse] { +private extension [FunctionResponsePart] { func modelContent() -> [ModelContent] { - return self.map { ModelContent( - role: "function", - parts: [ModelContent.Part.functionResponse($0)] - ) - } + return self.map { ModelContent(role: "function", parts: [$0]) } } } diff --git a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift index 3a85da05102..e433d96dfc2 100644 --- a/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift +++ b/FirebaseVertexAI/Sample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift @@ -62,7 +62,7 @@ class PhotoReasoningViewModel: ObservableObject { let prompt = "Look at the image(s), and then answer the following question: \(userInput)" - var images = [any ThrowingPartsRepresentable]() + var images = [any PartsRepresentable]() for item in selectedItems { if let data = try? await item.loadTransferable(type: Data.self) { guard let image = UIImage(data: data) else { diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index 10df040ab30..2ebab217ca8 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -35,7 +35,7 @@ public class Chat { /// - Parameter parts: The new content to send as a single chat message. /// - Returns: The model's response if no error occurred. /// - Throws: A ``GenerateContentError`` if an error occurred. - public func sendMessage(_ parts: any ThrowingPartsRepresentable...) async throws + public func sendMessage(_ parts: any PartsRepresentable...) async throws -> GenerateContentResponse { return try await sendMessage([ModelContent(parts: parts)]) } @@ -45,19 +45,10 @@ public class Chat { /// - Parameter content: The new content to send as a single chat message. /// - Returns: The model's response if no error occurred. /// - Throws: A ``GenerateContentError`` if an error occurred. - public func sendMessage(_ content: @autoclosure () throws -> [ModelContent]) async throws + public func sendMessage(_ content: [ModelContent]) async throws -> GenerateContentResponse { // Ensure that the new content has the role set. - let newContent: [ModelContent] - do { - newContent = try content().map(populateContentRole(_:)) - } catch let underlying { - if let contentError = underlying as? ImageConversionError { - throw GenerateContentError.promptImageContentError(underlying: contentError) - } else { - throw GenerateContentError.internalError(underlying: underlying) - } - } + let newContent = content.map(populateContentRole(_:)) // Send the history alongside the new message as context. let request = history + newContent @@ -85,7 +76,7 @@ public class Chat { /// - Parameter parts: The new content to send as a single chat message. /// - Returns: A stream containing the model's response or an error if an error occurred. @available(macOS 12.0, *) - public func sendMessageStream(_ parts: any ThrowingPartsRepresentable...) throws + public func sendMessageStream(_ parts: any PartsRepresentable...) throws -> AsyncThrowingStream { return try sendMessageStream([ModelContent(parts: parts)]) } @@ -95,24 +86,14 @@ public class Chat { /// - Parameter content: The new content to send as a single chat message. /// - Returns: A stream containing the model's response or an error if an error occurred. @available(macOS 12.0, *) - public func sendMessageStream(_ content: @autoclosure () throws -> [ModelContent]) throws + public func sendMessageStream(_ content: [ModelContent]) throws -> AsyncThrowingStream { - let resolvedContent: [ModelContent] - do { - resolvedContent = try content() - } catch let underlying { - if let contentError = underlying as? ImageConversionError { - throw GenerateContentError.promptImageContentError(underlying: contentError) - } - throw GenerateContentError.internalError(underlying: underlying) - } - return AsyncThrowingStream { continuation in Task { var aggregatedContent: [ModelContent] = [] // Ensure that the new content has the role set. - let newContent: [ModelContent] = resolvedContent.map(populateContentRole(_:)) + let newContent: [ModelContent] = content.map(populateContentRole(_:)) // Send the history alongside the new message as context. let request = history + newContent @@ -146,20 +127,20 @@ public class Chat { } private func aggregatedChunks(_ chunks: [ModelContent]) -> ModelContent { - var parts: [ModelContent.Part] = [] + var parts: [any Part] = [] var combinedText = "" for aggregate in chunks { // Loop through all the parts, aggregating the text and adding the images. for part in aggregate.parts { switch part { - case let .text(str): - combinedText += str + case let textPart as TextPart: + combinedText += textPart.text - case .inlineData, .fileData, .functionCall, .functionResponse: + default: // Don't combine it, just add to the content. If there's any text pending, add that as // a part. if !combinedText.isEmpty { - parts.append(.text(combinedText)) + parts.append(TextPart(combinedText)) combinedText = "" } @@ -169,7 +150,7 @@ public class Chat { } if !combinedText.isEmpty { - parts.append(.text(combinedText)) + parts.append(TextPart(combinedText)) } return ModelContent(role: "model", parts: parts) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 3fb17838d4c..69924f3cc4b 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -14,27 +14,6 @@ import Foundation -/// A predicted function call returned from the model. -public struct FunctionCall: Equatable, Sendable { - /// The name of the function to call. - public let name: String - - /// The function parameters and values. - public let args: JSONObject - - /// Constructs a new function call. - /// - /// > Note: A `FunctionCall` is typically received from the model, rather than created manually. - /// - /// - Parameters: - /// - name: The name of the function to call. - /// - args: The function parameters and values. - public init(name: String, args: JSONObject) { - self.name = name - self.args = args - } -} - /// Structured representation of a function declaration. /// /// This `FunctionDeclaration` is a representation of a block of code that can be used as a ``Tool`` @@ -136,50 +115,8 @@ public struct ToolConfig { } } -/// Result output from a ``FunctionCall``. -/// -/// Contains a string representing the `FunctionDeclaration.name` and a structured JSON object -/// containing any output from the function is used as context to the model. This should contain the -/// result of a ``FunctionCall`` made based on model prediction. -public struct FunctionResponse: Equatable, Sendable { - /// The name of the function that was called. - let name: String - - /// The function's response. - let response: JSONObject - - /// Constructs a new `FunctionResponse`. - /// - /// - Parameters: - /// - name: The name of the function that was called. - /// - response: The function's response. - public init(name: String, response: JSONObject) { - self.name = name - self.response = response - } -} - // MARK: - Codable Conformance -extension FunctionCall: Decodable { - enum CodingKeys: CodingKey { - case name - case args - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - if let args = try container.decodeIfPresent(JSONObject.self, forKey: .args) { - self.args = args - } else { - args = JSONObject() - } - } -} - -extension FunctionCall: Encodable {} - extension FunctionDeclaration: Encodable { enum CodingKeys: String, CodingKey { case name @@ -202,5 +139,3 @@ extension FunctionCallingConfig: Encodable {} extension FunctionCallingConfig.Mode: Encodable {} extension ToolConfig: Encodable {} - -extension FunctionResponse: Codable {} diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 66dd83aec16..c4ca48f2264 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -49,10 +49,12 @@ public struct GenerateContentResponse: Sendable { return nil } let textValues: [String] = candidate.content.parts.compactMap { part in - guard case let .text(text) = part else { + switch part { + case let textPart as TextPart: + return textPart.text + default: return nil } - return text } guard textValues.count > 0 else { VertexLog.error( @@ -65,15 +67,17 @@ public struct GenerateContentResponse: Sendable { } /// Returns function calls found in any `Part`s of the first candidate of the response, if any. - public var functionCalls: [FunctionCall] { + public var functionCalls: [FunctionCallPart] { guard let candidate = candidates.first else { return [] } return candidate.content.parts.compactMap { part in - guard case let .functionCall(functionCall) = part else { + switch part { + case let functionCallPart as FunctionCallPart: + return functionCallPart + default: return nil } - return functionCall } } diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index dc069d88d03..0ac58732e11 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -106,12 +106,11 @@ public final class GenerativeModel { /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) /// prompts, see `generateContent(_ content: @autoclosure () throws -> [ModelContent])`. /// - /// - Parameter content: The input(s) given to the model as a prompt (see - /// ``ThrowingPartsRepresentable`` + /// - Parameter content: The input(s) given to the model as a prompt (see ``PartsRepresentable`` /// for conforming types). /// - Returns: The content generated by the model. /// - Throws: A ``GenerateContentError`` if the request failed. - public func generateContent(_ parts: any ThrowingPartsRepresentable...) + public func generateContent(_ parts: any PartsRepresentable...) async throws -> GenerateContentResponse { return try await generateContent([ModelContent(parts: parts)]) } @@ -121,24 +120,22 @@ public final class GenerativeModel { /// - Parameter content: The input(s) given to the model as a prompt. /// - Returns: The generated content response from the model. /// - Throws: A ``GenerateContentError`` if the request failed. - public func generateContent(_ content: @autoclosure () throws -> [ModelContent]) async throws + public func generateContent(_ content: [ModelContent]) async throws -> GenerateContentResponse { + try content.throwIfError() let response: GenerateContentResponse + let generateContentRequest = GenerateContentRequest(model: modelResourceName, + contents: content, + generationConfig: generationConfig, + safetySettings: safetySettings, + tools: tools, + toolConfig: toolConfig, + systemInstruction: systemInstruction, + isStreaming: false, + options: requestOptions) do { - let generateContentRequest = try GenerateContentRequest(model: modelResourceName, - contents: content(), - generationConfig: generationConfig, - safetySettings: safetySettings, - tools: tools, - toolConfig: toolConfig, - systemInstruction: systemInstruction, - isStreaming: false, - options: requestOptions) response = try await generativeAIService.loadRequest(request: generateContentRequest) } catch { - if let imageError = error as? ImageConversionError { - throw GenerateContentError.promptImageContentError(underlying: imageError) - } throw GenerativeModel.generateContentError(from: error) } @@ -166,12 +163,11 @@ public final class GenerativeModel { /// prompts, see `generateContentStream(_ content: @autoclosure () throws -> [ModelContent])`. /// /// - Parameter content: The input(s) given to the model as a prompt (see - /// ``ThrowingPartsRepresentable`` - /// for conforming types). + /// ``PartsRepresentable`` for conforming types). /// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError`` /// error if an error occurred. @available(macOS 12.0, *) - public func generateContentStream(_ parts: any ThrowingPartsRepresentable...) throws + public func generateContentStream(_ parts: any PartsRepresentable...) throws -> AsyncThrowingStream { return try generateContentStream([ModelContent(parts: parts)]) } @@ -182,20 +178,11 @@ public final class GenerativeModel { /// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError`` /// error if an error occurred. @available(macOS 12.0, *) - public func generateContentStream(_ content: @autoclosure () throws -> [ModelContent]) throws + public func generateContentStream(_ content: [ModelContent]) throws -> AsyncThrowingStream { - let evaluatedContent: [ModelContent] - do { - evaluatedContent = try content() - } catch let underlying { - if let contentError = underlying as? ImageConversionError { - throw GenerateContentError.promptImageContentError(underlying: contentError) - } - throw GenerateContentError.internalError(underlying: underlying) - } - + try content.throwIfError() let generateContentRequest = GenerateContentRequest(model: modelResourceName, - contents: evaluatedContent, + contents: content, generationConfig: generationConfig, safetySettings: safetySettings, tools: tools, @@ -249,13 +236,12 @@ public final class GenerativeModel { /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) /// input, see `countTokens(_ content: @autoclosure () throws -> [ModelContent])`. /// - /// - Parameter content: The input(s) given to the model as a prompt (see - /// ``ThrowingPartsRepresentable`` + /// - Parameter content: The input(s) given to the model as a prompt (see ``PartsRepresentable`` /// for conforming types). /// - Returns: The results of running the model's tokenizer on the input; contains /// ``CountTokensResponse/totalTokens``. /// - Throws: A ``CountTokensError`` if the tokenization request failed. - public func countTokens(_ parts: any ThrowingPartsRepresentable...) async throws + public func countTokens(_ parts: any PartsRepresentable...) async throws -> CountTokensResponse { return try await countTokens([ModelContent(parts: parts)]) } @@ -267,11 +253,11 @@ public final class GenerativeModel { /// ``CountTokensResponse/totalTokens``. /// - Throws: A ``CountTokensError`` if the tokenization request failed or the input content was /// invalid. - public func countTokens(_ content: @autoclosure () throws -> [ModelContent]) async throws + public func countTokens(_ content: [ModelContent]) async throws -> CountTokensResponse { - let countTokensRequest = try CountTokensRequest( + let countTokensRequest = CountTokensRequest( model: modelResourceName, - contents: content(), + contents: content, systemInstruction: systemInstruction, tools: tools, generationConfig: generationConfig, diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index f5699a600fb..d215dd4ba15 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -14,60 +14,34 @@ import Foundation +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension [ModelContent] { + // TODO: Rename and refactor this. + func throwIfError() throws { + for content in self { + for part in content.parts { + switch part { + case let errorPart as ErrorPart: + throw errorPart.error + default: + break + } + } + } + } +} + /// A type describing data in media formats interpretable by an AI model. Each generative AI /// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value /// may comprise multiple heterogeneous ``ModelContent/Part``s. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct ModelContent: Equatable, Sendable { - /// A discrete piece of data in a media format interpretable by an AI model. Within a single value - /// of ``Part``, different data types may not mix. - public enum Part: Equatable, Sendable { - /// Text value. + enum InternalPart: Equatable, Sendable { case text(String) - - /// Data with a specified media type. Not all media types may be supported by the AI model. case inlineData(mimetype: String, Data) - - /// File data stored in Cloud Storage for Firebase, referenced by URI. - /// - /// > Note: Supported media types depends on the model; see [media requirements - /// > ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements) - /// > for details. - /// - /// - Parameters: - /// - mimetype: The IANA standard MIME type of the uploaded file, for example, `"image/jpeg"` - /// or `"video/mp4"`; see [media requirements - /// ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements) - /// for supported values. - /// - uri: The `"gs://"`-prefixed URI of the file in Cloud Storage for Firebase, for example, - /// `"gs://bucket-name/path/image.jpg"`. case fileData(mimetype: String, uri: String) - - /// A predicted function call returned from the model. case functionCall(FunctionCall) - - /// A response to a function call. case functionResponse(FunctionResponse) - - // MARK: Convenience Initializers - - /// Convenience function for populating a Part with JPEG data. - public static func jpeg(_ data: Data) -> Self { - return .inlineData(mimetype: "image/jpeg", data) - } - - /// Convenience function for populating a Part with PNG data. - public static func png(_ data: Data) -> Self { - return .inlineData(mimetype: "image/png", data) - } - - /// Returns the text contents of this ``Part``, if it contains text. - public var text: String? { - switch self { - case let .text(contents): return contents - default: return nil - } - } } /// The role of the entity creating the ``ModelContent``. For user-generated client requests, @@ -75,39 +49,88 @@ public struct ModelContent: Equatable, Sendable { public let role: String? /// The data parts comprising this ``ModelContent`` value. - public let parts: [Part] - - /// Creates a new value from any data or `Array` of data interpretable as a - /// ``Part``. See ``ThrowingPartsRepresentable`` for types that can be interpreted as `Part`s. - public init(role: String? = "user", parts: some ThrowingPartsRepresentable) throws { - self.role = role - try self.parts = parts.tryPartsValue() + public var parts: [any Part] { + var convertedParts = [any Part]() + for part in internalParts { + switch part { + case let .text(text): + convertedParts.append(TextPart(text)) + case let .inlineData(mimetype, data): + convertedParts.append(InlineDataPart(data: data, mimeType: mimetype)) + case let .fileData(mimetype, uri): + convertedParts.append(FileDataPart(uri: uri, mimeType: mimetype)) + case let .functionCall(functionCall): + convertedParts.append(FunctionCallPart(functionCall)) + case let .functionResponse(functionResponse): + convertedParts.append(FunctionResponsePart(functionResponse)) + } + } + return convertedParts } + // TODO: Refactor this + let internalParts: [InternalPart] + /// Creates a new value from any data or `Array` of data interpretable as a - /// ``Part``. See ``ThrowingPartsRepresentable`` for types that can be interpreted as `Part`s. + /// ``Part``. See ``PartsRepresentable`` for types that can be interpreted as `Part`s. public init(role: String? = "user", parts: some PartsRepresentable) { self.role = role - self.parts = parts.partsValue + var convertedParts = [InternalPart]() + for part in parts.partsValue { + switch part { + case let textPart as TextPart: + convertedParts.append(.text(textPart.text)) + case let inlineDataPart as InlineDataPart: + let inlineData = inlineDataPart.inlineData + convertedParts.append(.inlineData(mimetype: inlineData.mimeType, inlineData.data)) + case let fileDataPart as FileDataPart: + let fileData = fileDataPart.fileData + convertedParts.append(.fileData(mimetype: fileData.mimeType, uri: fileData.fileURI)) + case let functionCallPart as FunctionCallPart: + convertedParts.append(.functionCall(functionCallPart.functionCall)) + case let functionResponsePart as FunctionResponsePart: + convertedParts.append(.functionResponse(functionResponsePart.functionResponse)) + default: + fatalError() + } + } + internalParts = convertedParts } /// Creates a new value from a list of ``Part``s. - public init(role: String? = "user", parts: [Part]) { + public init(role: String? = "user", parts: [any Part]) { self.role = role - self.parts = parts + var convertedParts = [InternalPart]() + for part in parts { + switch part { + case let textPart as TextPart: + convertedParts.append(.text(textPart.text)) + case let inlineDataPart as InlineDataPart: + let inlineData = inlineDataPart.inlineData + convertedParts.append(.inlineData(mimetype: inlineData.mimeType, inlineData.data)) + case let fileDataPart as FileDataPart: + let fileData = fileDataPart.fileData + convertedParts.append(.fileData(mimetype: fileData.mimeType, uri: fileData.fileURI)) + case let functionCallPart as FunctionCallPart: + convertedParts.append(.functionCall(functionCallPart.functionCall)) + case let functionResponsePart as FunctionResponsePart: + convertedParts.append(.functionResponse(functionResponsePart.functionResponse)) + default: + fatalError() + } + } + internalParts = convertedParts } - /// Creates a new value from any data interpretable as a ``Part``. See - /// ``ThrowingPartsRepresentable`` - /// for types that can be interpreted as `Part`s. - public init(role: String? = "user", _ parts: any ThrowingPartsRepresentable...) throws { - let content = try parts.flatMap { try $0.tryPartsValue() } + /// Creates a new value from any data interpretable as a ``Part``. + /// See ``PartsRepresentable`` for types that can be interpreted as `Part`s. + public init(role: String? = "user", _ parts: any PartsRepresentable...) { + let content = parts.flatMap { $0.partsValue } self.init(role: role, parts: content) } - /// Creates a new value from any data interpretable as a ``Part``. See - /// ``ThrowingPartsRepresentable`` - /// for types that can be interpreted as `Part`s. + /// Creates a new value from any data interpretable as a ``Part``. + /// See ``PartsRepresentable``for types that can be interpreted as `Part`s. public init(role: String? = "user", _ parts: [PartsRepresentable]) { let content = parts.flatMap { $0.partsValue } self.init(role: role, parts: content) @@ -117,10 +140,15 @@ public struct ModelContent: Equatable, Sendable { // MARK: Codable Conformances @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension ModelContent: Codable {} +extension ModelContent: Codable { + enum CodingKeys: String, CodingKey { + case role + case internalParts = "parts" + } +} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension ModelContent.Part: Codable { +extension ModelContent.InternalPart: Codable { enum CodingKeys: String, CodingKey { case text case inlineData @@ -129,35 +157,15 @@ extension ModelContent.Part: Codable { case functionResponse } - enum InlineDataKeys: String, CodingKey { - case mimeType = "mime_type" - case bytes = "data" - } - - enum FileDataKeys: String, CodingKey { - case mimeType = "mime_type" - case uri = "file_uri" - } - public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case let .text(a0): - try container.encode(a0, forKey: .text) + case let .text(text): + try container.encode(text, forKey: .text) case let .inlineData(mimetype, bytes): - var inlineDataContainer = container.nestedContainer( - keyedBy: InlineDataKeys.self, - forKey: .inlineData - ) - try inlineDataContainer.encode(mimetype, forKey: .mimeType) - try inlineDataContainer.encode(bytes, forKey: .bytes) + try container.encode(InlineData(data: bytes, mimeType: mimetype), forKey: .inlineData) case let .fileData(mimetype: mimetype, url): - var fileDataContainer = container.nestedContainer( - keyedBy: FileDataKeys.self, - forKey: .fileData - ) - try fileDataContainer.encode(mimetype, forKey: .mimeType) - try fileDataContainer.encode(url, forKey: .uri) + try container.encode(FileData(fileURI: url, mimeType: mimetype), forKey: .fileData) case let .functionCall(functionCall): try container.encode(functionCall, forKey: .functionCall) case let .functionResponse(functionResponse): @@ -170,13 +178,11 @@ extension ModelContent.Part: Codable { if values.contains(.text) { self = try .text(values.decode(String.self, forKey: .text)) } else if values.contains(.inlineData) { - let dataContainer = try values.nestedContainer( - keyedBy: InlineDataKeys.self, - forKey: .inlineData - ) - let mimetype = try dataContainer.decode(String.self, forKey: .mimeType) - let bytes = try dataContainer.decode(Data.self, forKey: .bytes) - self = .inlineData(mimetype: mimetype, bytes) + let inlineData = try values.decode(InlineData.self, forKey: .inlineData) + self = .inlineData(mimetype: inlineData.mimeType, inlineData.data) + } else if values.contains(.fileData) { + let fileData = try values.decode(FileData.self, forKey: .fileData) + self = .fileData(mimetype: fileData.mimeType, uri: fileData.fileURI) } else if values.contains(.functionCall) { self = try .functionCall(values.decode(FunctionCall.self, forKey: .functionCall)) } else if values.contains(.functionResponse) { @@ -185,7 +191,7 @@ extension ModelContent.Part: Codable { let unexpectedKeys = values.allKeys.map { $0.stringValue } throw DecodingError.dataCorrupted(DecodingError.Context( codingPath: values.codingPath, - debugDescription: "Unexpected ModelContent.Part type(s): \(unexpectedKeys)" + debugDescription: "Unexpected Part type(s): \(unexpectedKeys)" )) } } diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift index 6b2cc977889..24d11be2c46 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift @@ -36,31 +36,31 @@ enum ImageConversionError: Error { } #if canImport(UIKit) - /// Enables images to be representable as ``ThrowingPartsRepresentable``. + /// Enables images to be representable as ``PartsRepresentable``. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - extension UIImage: ThrowingPartsRepresentable { - public func tryPartsValue() throws -> [ModelContent.Part] { + extension UIImage: PartsRepresentable { + public var partsValue: [any Part] { guard let data = jpegData(compressionQuality: imageCompressionQuality) else { - throw ImageConversionError.couldNotConvertToJPEG + return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)] } - return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] + return [InlineDataPart(data: data, mimeType: "image/jpeg")] } } #elseif canImport(AppKit) - /// Enables images to be representable as ``ThrowingPartsRepresentable``. + /// Enables images to be representable as ``PartsRepresentable``. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) - extension NSImage: ThrowingPartsRepresentable { - public func tryPartsValue() throws -> [ModelContent.Part] { + extension NSImage: PartsRepresentable { + public var partsValue: [any Part] { guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else { - throw ImageConversionError.invalidUnderlyingImage + return [ErrorPart(ImageConversionError.invalidUnderlyingImage)] } let bmp = NSBitmapImageRep(cgImage: cgImage) guard let data = bmp.representation(using: .jpeg, properties: [.compressionFactor: 0.8]) else { - throw ImageConversionError.couldNotConvertToJPEG + return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)] } - return [ModelContent.Part.inlineData(mimetype: "image/jpeg", data)] + return [InlineDataPart(data: data, mimeType: "image/jpeg")] } } #endif @@ -68,22 +68,22 @@ enum ImageConversionError: Error { #if !os(watchOS) // This code does not build on watchOS. /// Enables `CGImages` to be representable as model content. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *) - extension CGImage: ThrowingPartsRepresentable { - public func tryPartsValue() throws -> [ModelContent.Part] { + extension CGImage: PartsRepresentable { + public var partsValue: [any Part] { let output = NSMutableData() guard let imageDestination = CGImageDestinationCreateWithData( output, UTType.jpeg.identifier as CFString, 1, nil ) else { - throw ImageConversionError.couldNotAllocateDestination + return [ErrorPart(ImageConversionError.couldNotAllocateDestination)] } CGImageDestinationAddImage(imageDestination, self, nil) CGImageDestinationSetProperties(imageDestination, [ kCGImageDestinationLossyCompressionQuality: imageCompressionQuality, ] as CFDictionary) if CGImageDestinationFinalize(imageDestination) { - return [.inlineData(mimetype: "image/jpeg", output as Data)] + return [InlineDataPart(data: output as Data, mimeType: "image/jpeg")] } - throw ImageConversionError.couldNotConvertToJPEG + return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)] } } #endif // !os(watchOS) @@ -91,8 +91,8 @@ enum ImageConversionError: Error { #if canImport(CoreImage) /// Enables `CIImages` to be representable as model content. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *) - extension CIImage: ThrowingPartsRepresentable { - public func tryPartsValue() throws -> [ModelContent.Part] { + extension CIImage: PartsRepresentable { + public var partsValue: [any Part] { let context = CIContext() let jpegData = (colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)) .flatMap { @@ -102,9 +102,9 @@ enum ImageConversionError: Error { context.jpegRepresentation(of: self, colorSpace: $0, options: [:]) } if let jpegData = jpegData { - return [.inlineData(mimetype: "image/jpeg", jpegData)] + return [InlineDataPart(data: jpegData, mimeType: "image/jpeg")] } - throw ImageConversionError.couldNotConvertToJPEG + return [ErrorPart(ImageConversionError.couldNotConvertToJPEG)] } } #endif // canImport(CoreImage) diff --git a/FirebaseVertexAI/Sources/PartsRepresentable.swift b/FirebaseVertexAI/Sources/PartsRepresentable.swift index 7b9d9524b67..6ef63d3f182 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable.swift @@ -15,52 +15,33 @@ import Foundation /// A protocol describing any data that could be serialized to model-interpretable input data, -/// where the serialization process might fail with an error. +/// where the serialization process cannot fail with an error. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public protocol ThrowingPartsRepresentable { - func tryPartsValue() throws -> [ModelContent.Part] +public protocol PartsRepresentable { + var partsValue: [any Part] { get } } -/// A protocol describing any data that could be serialized to model-interpretable input data, -/// where the serialization process cannot fail with an error. For a failable conversion, see -/// ``ThrowingPartsRepresentable`` -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public protocol PartsRepresentable: ThrowingPartsRepresentable { - var partsValue: [ModelContent.Part] { get } -} - -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public extension PartsRepresentable { - func tryPartsValue() throws -> [ModelContent.Part] { - return partsValue - } -} - -/// Enables a ``ModelContent.Part`` to be passed in as ``ThrowingPartsRepresentable``. +/// Enables a ``Part`` to be used as a ``PartsRepresentable``. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension ModelContent.Part: ThrowingPartsRepresentable { - public typealias ErrorType = Never - public func tryPartsValue() throws -> [ModelContent.Part] { +public extension Part { + var partsValue: [any Part] { return [self] } } -/// Enable an `Array` of ``ThrowingPartsRepresentable`` values to be passed in as a single -/// ``ThrowingPartsRepresentable``. +/// Enable an `Array` of ``PartsRepresentable`` values to be passed in as a single +/// ``PartsRepresentable``. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension [ThrowingPartsRepresentable]: ThrowingPartsRepresentable { - public func tryPartsValue() throws -> [ModelContent.Part] { - return try compactMap { element in - try element.tryPartsValue() - } - .flatMap { $0 } +extension [PartsRepresentable]: PartsRepresentable { + public var partsValue: [any Part] { + return flatMap { $0.partsValue } } } -/// Enables a `String` to be passed in as ``ThrowingPartsRepresentable``. +/// Enables a `String` to be passed in as ``PartsRepresentable``. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension String: PartsRepresentable { - public var partsValue: [ModelContent.Part] { - return [.text(self)] + public var partsValue: [any Part] { + return [TextPart(self)] } } diff --git a/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift b/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift new file mode 100644 index 00000000000..8a62ae4fdd9 --- /dev/null +++ b/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift @@ -0,0 +1,103 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct InlineData: Codable, Equatable, Sendable { + let mimeType: String + let data: Data + + init(data: Data, mimeType: String) { + self.data = data + self.mimeType = mimeType + } +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct FileData: Codable, Equatable, Sendable { + let fileURI: String + let mimeType: String + + init(fileURI: String, mimeType: String) { + self.fileURI = fileURI + self.mimeType = mimeType + } +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct FunctionCall: Equatable, Sendable { + let name: String + let args: JSONObject + + init(name: String, args: JSONObject) { + self.name = name + self.args = args + } +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct FunctionResponse: Codable, Equatable, Sendable { + let name: String + let response: JSONObject + + init(name: String, response: JSONObject) { + self.name = name + self.response = response + } +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct ErrorPart: Part, Error { + let error: Error + + init(_ error: Error) { + self.error = error + } +} + +// MARK: - Codable Conformances + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension FunctionCall: Codable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + if let args = try container.decodeIfPresent(JSONObject.self, forKey: .args) { + self.args = args + } else { + args = JSONObject() + } + } +} + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension ErrorPart: Codable { + init(from decoder: any Decoder) throws { + fatalError("Decoding an ErrorPart is not supported.") + } + + func encode(to encoder: any Encoder) throws { + fatalError("Encoding an ErrorPart is not supported.") + } +} + +// MARK: - Equatable Conformances + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension ErrorPart: Equatable { + static func == (lhs: ErrorPart, rhs: ErrorPart) -> Bool { + fatalError("Comparing ErrorParts for equality is not supported.") + } +} diff --git a/FirebaseVertexAI/Sources/Types/Public/Part.swift b/FirebaseVertexAI/Sources/Types/Public/Part.swift new file mode 100644 index 00000000000..1eba33ae018 --- /dev/null +++ b/FirebaseVertexAI/Sources/Types/Public/Part.swift @@ -0,0 +1,134 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// A discrete piece of data in a media format interpretable by an AI model. +/// +/// Within a single value of ``Part``, different data types may not mix. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public protocol Part: PartsRepresentable, Codable, Sendable, Equatable {} + +/// A text part containing a string value. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct TextPart: Part { + /// Text value. + public let text: String + + public init(_ text: String) { + self.text = text + } +} + +/// Data with a specified media type. +/// +/// > Note: Not all media types may be supported by the AI model. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct InlineDataPart: Part { + let inlineData: InlineData + + public var data: Data { inlineData.data } + public var mimeType: String { inlineData.mimeType } + + public init(data: Data, mimeType: String) { + self.init(InlineData(data: data, mimeType: mimeType)) + } + + init(_ inlineData: InlineData) { + self.inlineData = inlineData + } +} + +/// File data stored in Cloud Storage for Firebase, referenced by URI. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct FileDataPart: Part { + let fileData: FileData + + public var uri: String { fileData.fileURI } + public var mimeType: String { fileData.mimeType } + + /// Constructs a new file data part. + /// + /// - Parameters: + /// - uri: The `"gs://"`-prefixed URI of the file in Cloud Storage for Firebase, for example, + /// `"gs://bucket-name/path/image.jpg"`. + /// - mimeType: The IANA standard MIME type of the uploaded file, for example, `"image/jpeg"` + /// or `"video/mp4"`; see [media requirements + /// ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements) + /// for supported values. + public init(uri: String, mimeType: String) { + self.init(FileData(fileURI: uri, mimeType: mimeType)) + } + + init(_ fileData: FileData) { + self.fileData = fileData + } +} + +/// A predicted function call returned from the model. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct FunctionCallPart: Part { + let functionCall: FunctionCall + + /// The name of the function to call. + public var name: String { functionCall.name } + + /// The function parameters and values. + public var args: JSONObject { functionCall.args } + + /// Constructs a new function call part. + /// + /// > Note: A `FunctionCallPart` is typically received from the model, rather than created + /// manually. + /// + /// - Parameters: + /// - name: The name of the function to call. + /// - args: The function parameters and values. + public init(name: String, args: JSONObject) { + self.init(FunctionCall(name: name, args: args)) + } + + init(_ functionCall: FunctionCall) { + self.functionCall = functionCall + } +} + +/// Result output from a ``FunctionCall``. +/// +/// Contains a string representing the `FunctionDeclaration.name` and a structured JSON object +/// containing any output from the function is used as context to the model. This should contain the +/// result of a ``FunctionCall`` made based on model prediction. +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct FunctionResponsePart: Part { + let functionResponse: FunctionResponse + + /// The name of the function that was called. + public var name: String { functionResponse.name } + + /// The function's response or return value. + public var response: JSONObject { functionResponse.response } + + /// Constructs a new `FunctionResponse`. + /// + /// - Parameters: + /// - name: The name of the function that was called. + /// - response: The function's response. + public init(name: String, response: JSONObject) { + self.init(FunctionResponse(name: name, response: response)) + } + + init(_ functionResponse: FunctionResponse) { + self.functionResponse = functionResponse + } +} diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index 8eddf79a648..d2789c574df 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -96,12 +96,12 @@ final class IntegrationTests: XCTestCase { } func testCountTokens_image_fileData() async throws { - let fileData = ModelContent(parts: [.fileData( - mimetype: "image/jpeg", - uri: "gs://ios-opensource-samples.appspot.com/ios/public/blank.jpg" - )]) + let fileData = FileDataPart( + uri: "gs://ios-opensource-samples.appspot.com/ios/public/blank.jpg", + mimeType: "image/jpeg" + ) - let response = try await model.countTokens([fileData]) + let response = try await model.countTokens(fileData) XCTAssertEqual(response.totalTokens, 266) XCTAssertEqual(response.totalBillableCharacters, 35) @@ -118,13 +118,13 @@ final class IntegrationTests: XCTestCase { tools: [Tool(functionDeclarations: [sumDeclaration])] ) let prompt = "What is 10 + 32?" - let sumCall = FunctionCall(name: "sum", args: ["x": .number(10), "y": .number(32)]) - let sumResponse = FunctionResponse(name: "sum", response: ["result": .number(42)]) + let sumCall = FunctionCallPart(name: "sum", args: ["x": .number(10), "y": .number(32)]) + let sumResponse = FunctionResponsePart(name: "sum", response: ["result": .number(42)]) let response = try await model.countTokens([ - ModelContent(role: "user", parts: [.text(prompt)]), - ModelContent(role: "model", parts: [.functionCall(sumCall)]), - ModelContent(role: "function", parts: [.functionResponse(sumResponse)]), + ModelContent(role: "user", parts: prompt), + ModelContent(role: "model", parts: sumCall), + ModelContent(role: "function", parts: sumResponse), ]) XCTAssertEqual(response.totalTokens, 24) diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 614559fe011..95ce8e7e43d 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -77,11 +77,12 @@ final class ChatTests: XCTestCase { } XCTAssertEqual(chat.history.count, 2) - XCTAssertEqual(chat.history[0].parts[0].text, input) + let part = try XCTUnwrap(chat.history[0].parts[0]) + let textPart = try XCTUnwrap(part as? TextPart) + XCTAssertEqual(textPart.text, input) let finalText = "1 2 3 4 5 6 7 8" let assembledExpectation = ModelContent(role: "model", parts: finalText) - XCTAssertEqual(chat.history[0].parts[0].text, input) XCTAssertEqual(chat.history[1], assembledExpectation) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index c5e8332d2b8..86d3e7f9c11 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -72,7 +72,7 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - let partText = try XCTUnwrap(part.text) + let partText = try XCTUnwrap(part as? TextPart).text XCTAssertTrue(partText.hasPrefix("1. **Use Freshly Ground Coffee**:")) XCTAssertEqual(response.text, partText) XCTAssertEqual(response.functionCalls, []) @@ -94,8 +94,9 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - XCTAssertEqual(part.text, "Mountain View, California") - XCTAssertEqual(response.text, part.text) + let textPart = try XCTUnwrap(part as? TextPart) + XCTAssertEqual(textPart.text, "Mountain View, California") + XCTAssertEqual(response.text, textPart.text) XCTAssertEqual(response.functionCalls, []) } @@ -150,9 +151,9 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - let partText = try XCTUnwrap(part.text) - XCTAssertTrue(partText.hasPrefix("Google")) - XCTAssertEqual(response.text, part.text) + let textPart = try XCTUnwrap(part as? TextPart) + XCTAssertTrue(textPart.text.hasPrefix("Google")) + XCTAssertEqual(response.text, textPart.text) let promptFeedback = try XCTUnwrap(response.promptFeedback) XCTAssertNil(promptFeedback.blockReason) XCTAssertEqual(promptFeedback.safetyRatings.sorted(), safetyRatingsNegligible) @@ -211,7 +212,7 @@ final class GenerativeModelTests: XCTestCase { let candidate = try XCTUnwrap(response.candidates.first) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - guard case let .functionCall(functionCall) = part else { + guard let functionCall = part as? FunctionCallPart else { XCTFail("Part is not a FunctionCall.") return } @@ -233,7 +234,7 @@ final class GenerativeModelTests: XCTestCase { let candidate = try XCTUnwrap(response.candidates.first) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - guard case let .functionCall(functionCall) = part else { + guard let functionCall = part as? FunctionCallPart else { XCTFail("Part is not a FunctionCall.") return } @@ -255,7 +256,7 @@ final class GenerativeModelTests: XCTestCase { let candidate = try XCTUnwrap(response.candidates.first) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) - guard case let .functionCall(functionCall) = part else { + guard let functionCall = part as? FunctionCallPart else { XCTFail("Part is not a FunctionCall.") return } @@ -1282,10 +1283,7 @@ final class GenerativeModelTests: XCTestCase { withExtension: "json" ) - let response = try await model.countTokens(ModelContent.Part.inlineData( - mimetype: "image/jpeg", - Data() - )) + let response = try await model.countTokens(InlineDataPart(data: Data(), mimeType: "image/jpeg")) XCTAssertEqual(response.totalTokens, 258) XCTAssertNil(response.totalBillableCharacters) diff --git a/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift b/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift deleted file mode 100644 index 67175af739b..00000000000 --- a/FirebaseVertexAI/Tests/Unit/ModelContentTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Foundation -import XCTest - -@testable import FirebaseVertexAI - -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -final class ModelContentTests: XCTestCase { - let decoder = JSONDecoder() - let encoder = JSONEncoder() - - override func setUp() { - encoder.outputFormatting = .init( - arrayLiteral: .prettyPrinted, .sortedKeys, .withoutEscapingSlashes - ) - } - - // MARK: - ModelContent.Part Decoding - - func testDecodeFunctionResponsePart() throws { - let functionName = "test-function-name" - let resultParameter = "test-result-parameter" - let resultValue = "test-result-value" - let json = """ - { - "functionResponse" : { - "name" : "\(functionName)", - "response" : { - "\(resultParameter)" : "\(resultValue)" - } - } - } - """ - let jsonData = try XCTUnwrap(json.data(using: .utf8)) - - let part = try decoder.decode(ModelContent.Part.self, from: jsonData) - - guard case let .functionResponse(functionResponse) = part else { - XCTFail("Decoded Part was not a FunctionResponse.") - return - } - XCTAssertEqual(functionResponse.name, functionName) - XCTAssertEqual(functionResponse.response, [resultParameter: .string(resultValue)]) - } - - // MARK: - ModelContent.Part Encoding - - func testEncodeFileDataPart() throws { - let mimeType = "image/jpeg" - let fileURI = "gs://test-bucket/image.jpg" - let fileDataPart = ModelContent.Part.fileData(mimetype: mimeType, uri: fileURI) - - let jsonData = try encoder.encode(fileDataPart) - - let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) - XCTAssertEqual(json, """ - { - "fileData" : { - "file_uri" : "\(fileURI)", - "mime_type" : "\(mimeType)" - } - } - """) - } -} diff --git a/FirebaseVertexAI/Tests/Unit/PartTests.swift b/FirebaseVertexAI/Tests/Unit/PartTests.swift new file mode 100644 index 00000000000..35c3b441d9f --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/PartTests.swift @@ -0,0 +1,157 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +import XCTest + +@testable import FirebaseVertexAI + +@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class PartTests: XCTestCase { + let decoder = JSONDecoder() + let encoder = JSONEncoder() + + override func setUp() { + encoder.outputFormatting = .init( + arrayLiteral: .prettyPrinted, .sortedKeys, .withoutEscapingSlashes + ) + } + + // MARK: - Part Decoding + + func testDecodeTextPart() throws { + let expectedText = "Hello, world!" + let json = """ + { + "text" : "\(expectedText)" + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(TextPart.self, from: jsonData) + + XCTAssertEqual(part.text, expectedText) + } + + func testDecodeInlineDataPart() throws { + let imageBase64 = try PartTests.blueSquareImage() + let mimeType = "image/png" + let json = """ + { + "inlineData" : { + "data" : "\(imageBase64)", + "mimeType" : "\(mimeType)" + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InlineDataPart.self, from: jsonData) + + XCTAssertEqual(part.data, Data(base64Encoded: imageBase64)) + XCTAssertEqual(part.mimeType, mimeType) + } + + func testDecodeFunctionResponsePart() throws { + let functionName = "test-function-name" + let resultParameter = "test-result-parameter" + let resultValue = "test-result-value" + let json = """ + { + "functionResponse" : { + "name" : "\(functionName)", + "response" : { + "\(resultParameter)" : "\(resultValue)" + } + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(FunctionResponsePart.self, from: jsonData) + + let functionResponse = part.functionResponse + XCTAssertEqual(functionResponse.name, functionName) + XCTAssertEqual(functionResponse.response, [resultParameter: .string(resultValue)]) + } + + // MARK: - Part Encoding + + func testEncodeTextPart() throws { + let expectedText = "Hello, world!" + let textPart = TextPart(expectedText) + + let jsonData = try encoder.encode(textPart) + + let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) + XCTAssertEqual(json, """ + { + "text" : "\(expectedText)" + } + """) + } + + func testEncodeInlineDataPart() throws { + let mimeType = "image/png" + let imageBase64 = try PartTests.blueSquareImage() + let imageBase64Data = Data(base64Encoded: imageBase64) + let inlineDataPart = InlineDataPart(data: imageBase64Data!, mimeType: mimeType) + + let jsonData = try encoder.encode(inlineDataPart) + + let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) + XCTAssertEqual(json, """ + { + "inlineData" : { + "data" : "\(imageBase64)", + "mimeType" : "\(mimeType)" + } + } + """) + } + + func testEncodeFileDataPart() throws { + let mimeType = "image/jpeg" + let fileURI = "gs://test-bucket/image.jpg" + let fileDataPart = FileDataPart(uri: fileURI, mimeType: mimeType) + + let jsonData = try encoder.encode(fileDataPart) + + let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) + XCTAssertEqual(json, """ + { + "fileData" : { + "fileURI" : "\(fileURI)", + "mimeType" : "\(mimeType)" + } + } + """) + } + + // MARK: - Helpers + + private static func bundle() -> Bundle { + #if SWIFT_PACKAGE + return Bundle.module + #else // SWIFT_PACKAGE + return Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + } + + private static func blueSquareImage() throws -> String { + let imageURL = bundle().url(forResource: "blue", withExtension: "png")! + let imageData = try Data(contentsOf: imageURL) + return imageData.base64EncodedString() + } +} diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift index 073f6582721..859b77f58c7 100644 --- a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift @@ -44,7 +44,7 @@ final class PartsRepresentableTests: XCTestCase { )! return ctx.makeImage()! } - let modelContent = try image.tryPartsValue() + let modelContent = image.partsValue XCTAssert(modelContent.count > 0, "Expected non-empty model content for CGImage: \(image)") } #endif // !os(watchOS) @@ -53,22 +53,22 @@ final class PartsRepresentableTests: XCTestCase { func testModelContentFromCIImageIsNotEmpty() throws { let image = CIImage(color: CIColor.red) .cropped(to: CGRect(origin: CGPointZero, size: CGSize(width: 16, height: 16))) - let modelContent = try image.tryPartsValue() + let modelContent = image.partsValue XCTAssert(modelContent.count > 0, "Expected non-empty model content for CGImage: \(image)") } func testModelContentFromInvalidCIImageThrows() throws { let image = CIImage.empty() - do { - _ = try image.tryPartsValue() - XCTFail("Expected model content from invalid image to error") - } catch let imageError as ImageConversionError { - guard case .couldNotConvertToJPEG = imageError else { - XCTFail("Expected JPEG conversion error, got \(imageError) instead.") - return - } - } catch { - XCTFail("Got unexpected error type: \(error)") + let modelContent = image.partsValue + let part = try XCTUnwrap(modelContent.first) + let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.") + let imageError = try XCTUnwrap( + errorPart.error as? ImageConversionError, + "Got unexpected error type: \(errorPart.error)" + ) + guard case .couldNotConvertToJPEG = imageError else { + XCTFail("Expected JPEG conversion error, got \(imageError) instead.") + return } } #endif // canImport(CoreImage) @@ -76,22 +76,22 @@ final class PartsRepresentableTests: XCTestCase { #if canImport(UIKit) && !os(visionOS) // These tests are stalling in CI on visionOS. func testModelContentFromInvalidUIImageThrows() throws { let image = UIImage() - do { - _ = try image.tryPartsValue() - XCTFail("Expected model content from invalid image to error") - } catch let imageError as ImageConversionError { - guard case .couldNotConvertToJPEG = imageError else { - XCTFail("Expected JPEG conversion error, got \(imageError) instead.") - return - } - } catch { - XCTFail("Got unexpected error type: \(error)") + let modelContent = image.partsValue + let part = try XCTUnwrap(modelContent.first) + let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.") + let imageError = try XCTUnwrap( + errorPart.error as? ImageConversionError, + "Got unexpected error type: \(errorPart.error)" + ) + guard case .couldNotConvertToJPEG = imageError else { + XCTFail("Expected JPEG conversion error, got \(imageError) instead.") + return } } func testModelContentFromUIImageIsNotEmpty() throws { let image = try XCTUnwrap(UIImage(systemName: "star.fill")) - let modelContent = try image.tryPartsValue() + let modelContent = image.partsValue XCTAssert(modelContent.count > 0, "Expected non-empty model content for UIImage: \(image)") } @@ -102,29 +102,23 @@ final class PartsRepresentableTests: XCTestCase { let rep = NSCIImageRep(ciImage: coreImage) let image = NSImage(size: rep.size) image.addRepresentation(rep) - let modelContent = try image.tryPartsValue() + let modelContent = image.partsValue XCTAssert(modelContent.count > 0, "Expected non-empty model content for NSImage: \(image)") } func testModelContentFromInvalidNSImageThrows() throws { let image = NSImage() - do { - _ = try image.tryPartsValue() - } catch { - guard let imageError = (error as? ImageConversionError) else { - XCTFail("Got unexpected error type: \(error)") - return - } - switch imageError { - case .invalidUnderlyingImage: - // Pass - return - case _: - XCTFail("Expected image conversion error, got \(imageError) instead") - return - } + let modelContent = image.partsValue + let part = try XCTUnwrap(modelContent.first) + let errorPart = try XCTUnwrap(part as? ErrorPart, "Expected ErrorPart.") + let imageError = try XCTUnwrap( + errorPart.error as? ImageConversionError, + "Got unexpected error type: \(errorPart.error)" + ) + guard case .invalidUnderlyingImage = imageError else { + XCTFail("Expected invalid underyling image conversion error, got \(imageError) instead.") + return } - XCTFail("Expected model content from invalid image to error") } #endif } diff --git a/FirebaseVertexAI/Tests/Unit/Resources/blue.png b/FirebaseVertexAI/Tests/Unit/Resources/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..a0cf28c6edb4babcfabb4ed2262c20df6e9fce46 GIT binary patch literal 69 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryJf1F&AsjQ43$p$)F)*EA+;e!< Rk|LlugQu&X%Q~loCIG=l5Geov literal 0 HcmV?d00001 diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index 1c469867f76..c187a997b6c 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -33,7 +33,10 @@ final class VertexAIAPITests: XCTestCase { stopSequences: ["..."], responseMIMEType: "text/plain") let filters = [SafetySetting(harmCategory: .dangerousContent, threshold: .blockOnlyHigh)] - let systemInstruction = ModelContent(role: "system", parts: [.text("Talk like a pirate.")]) + let systemInstruction = ModelContent( + role: "system", + parts: TextPart("Talk like a pirate.") + ) // Instantiate Vertex AI SDK - Default App let vertexAI = VertexAI.vertexAI() @@ -72,11 +75,13 @@ final class VertexAIAPITests: XCTestCase { // Full Typed Usage let pngData = Data() // .... - let contents = [ModelContent(role: "user", - parts: [ - .text("Is it a cat?"), - .png(pngData), - ])] + let contents = [ModelContent( + role: "user", + parts: [ + TextPart("Is it a cat?"), + InlineDataPart(data: pngData, mimeType: "image/png"), + ] + )] do { let response = try await genAI.generateContent(contents) @@ -93,13 +98,12 @@ final class VertexAIAPITests: XCTestCase { let _ = try await genAI.generateContent(str, "abc", "def") let _ = try await genAI.generateContent( str, - ModelContent.Part.fileData(mimetype: "image/jpeg", uri: "gs://test-bucket/image.jpg") + FileDataPart(uri: "gs://test-bucket/image.jpg", mimeType: "image/jpeg") ) #if canImport(UIKit) _ = try await genAI.generateContent(UIImage()) _ = try await genAI.generateContent([UIImage()]) - _ = try await genAI - .generateContent([str, UIImage(), ModelContent.Part.text(str)]) + _ = try await genAI.generateContent([str, UIImage(), TextPart(str)]) _ = try await genAI.generateContent(str, UIImage(), "def", UIImage()) _ = try await genAI.generateContent([str, UIImage(), "def", UIImage()]) _ = try await genAI.generateContent([ModelContent("def", UIImage()), @@ -111,51 +115,43 @@ final class VertexAIAPITests: XCTestCase { _ = try await genAI.generateContent([str, NSImage(), "def", NSImage()]) #endif - // ThrowingPartsRepresentable combinations. - let _ = ModelContent(parts: [.text(str)]) - let _ = ModelContent(role: "model", parts: [.text(str)]) + // PartsRepresentable combinations. + let _ = ModelContent(parts: [TextPart(str)]) + let _ = ModelContent(role: "model", parts: [TextPart(str)]) let _ = ModelContent(parts: "Constant String") let _ = ModelContent(parts: str) - // Note: This requires the `try` for some reason. Casting to explicit [PartsRepresentable] also - // doesn't work. - let _ = try ModelContent(parts: [str]) - // Note: without `as [any ThrowingPartsRepresentable]` this will fail to compile with "Cannot + let _ = ModelContent(parts: [str]) + // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot // convert value of type 'String' to expected element type - // 'Array.ArrayLiteralElement'. Not sure if there's a way we can get it to + // 'Array.ArrayLiteralElement'. Not sure if there's a way we can get it to // work. - let _ = try ModelContent(parts: [str, ModelContent.Part.inlineData( - mimetype: "foo", - Data() - )] as [any ThrowingPartsRepresentable]) + let _ = ModelContent( + parts: [str, InlineDataPart(data: Data(), mimeType: "foo")] as [any PartsRepresentable] + ) #if canImport(UIKit) - _ = try ModelContent(role: "user", parts: UIImage()) - _ = try ModelContent(role: "user", parts: [UIImage()]) - // Note: without `as [any ThrowingPartsRepresentable]` this will fail to compile with "Cannot - // convert - // value of type `[Any]` to expected type `[any ThrowingPartsRepresentable]`. Not sure if - // there's a + _ = ModelContent(role: "user", parts: UIImage()) + _ = ModelContent(role: "user", parts: [UIImage()]) + // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert + // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a // way we can get it to work. - _ = try ModelContent(parts: [str, UIImage()] as [any ThrowingPartsRepresentable]) + _ = ModelContent(parts: [str, UIImage()] as [any PartsRepresentable]) // Alternatively, you can explicitly declare the type in a variable and pass it in. - let representable2: [any ThrowingPartsRepresentable] = [str, UIImage()] - _ = try ModelContent(parts: representable2) - _ = try ModelContent(parts: [str, UIImage(), - ModelContent.Part.text(str)] as [any ThrowingPartsRepresentable]) + let representable2: [any PartsRepresentable] = [str, UIImage()] + _ = ModelContent(parts: representable2) + _ = + ModelContent(parts: [str, UIImage(), TextPart(str)] as [any PartsRepresentable]) #elseif canImport(AppKit) - _ = try ModelContent(role: "user", parts: NSImage()) - _ = try ModelContent(role: "user", parts: [NSImage()]) - // Note: without `as [any ThrowingPartsRepresentable]` this will fail to compile with "Cannot - // convert - // value of type `[Any]` to expected type `[any ThrowingPartsRepresentable]`. Not sure if - // there's a + _ = ModelContent(role: "user", parts: NSImage()) + _ = ModelContent(role: "user", parts: [NSImage()]) + // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert + // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a // way we can get it to work. - _ = try ModelContent(parts: [str, NSImage()] as [any ThrowingPartsRepresentable]) + _ = ModelContent(parts: [str, NSImage()] as [any PartsRepresentable]) // Alternatively, you can explicitly declare the type in a variable and pass it in. - let representable2: [any ThrowingPartsRepresentable] = [str, NSImage()] - _ = try ModelContent(parts: representable2) + let representable2: [any PartsRepresentable] = [str, NSImage()] + _ = ModelContent(parts: representable2) _ = - try ModelContent(parts: [str, NSImage(), - ModelContent.Part.text(str)] as [any ThrowingPartsRepresentable]) + ModelContent(parts: [str, NSImage(), TextPart(str)] as [any PartsRepresentable]) #endif // countTokens API @@ -189,7 +185,7 @@ final class VertexAIAPITests: XCTestCase { // Computed Properties let _: String? = response.text - let _: [FunctionCall] = response.functionCalls + let _: [FunctionCallPart] = response.functionCalls } // Result builder alternative diff --git a/Package.swift b/Package.swift index 4bfb5a9b228..f35d72bc623 100644 --- a/Package.swift +++ b/Package.swift @@ -1313,6 +1313,7 @@ let package = Package( path: "FirebaseVertexAI/Tests/Unit", resources: [ .process("vertexai-sdk-test-data/mock-responses"), + .process("Resources"), ], cSettings: [ .headerSearchPath("../../../"), From 14b94aa9f6735954f2d167551838a3eb72226702 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 7 Oct 2024 13:37:49 -0700 Subject: [PATCH 162/258] Updated podspec for internal FirebaseCore header (#13829) --- FirebaseCore/Sources/FIROptionsInternal.h | 2 +- FirebaseFirestoreInternal.podspec | 1 + scripts/check_imports.swift | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/FirebaseCore/Sources/FIROptionsInternal.h b/FirebaseCore/Sources/FIROptionsInternal.h index bdd0267ec92..8765614e17d 100644 --- a/FirebaseCore/Sources/FIROptionsInternal.h +++ b/FirebaseCore/Sources/FIROptionsInternal.h @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h" +#import /** * This header file exposes the initialization of FirebaseOptions to internal use. diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 184af24dac8..da34afc2f4d 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -45,6 +45,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, # that "quick open" by filename in Xcode will continue to work. s.source_files = [ 'FirebaseCore/Extension/*.h', + 'FirebaseCore/Sources/FIROptionsInternal.h', 'Firestore/Source/Public/FirebaseFirestore/*.h', 'Firestore/Source/**/*.{m,mm}', 'Firestore/Protos/nanopb/**/*.cc', diff --git a/scripts/check_imports.swift b/scripts/check_imports.swift index 89011f61b7f..ee058f4b0e2 100755 --- a/scripts/check_imports.swift +++ b/scripts/check_imports.swift @@ -102,6 +102,7 @@ private func checkFile(_ file: String, logger: ErrorLogger, inRepo repoURL: URL, let isPrivate = file.range(of: "/Sources/Private/") != nil || // Delete when FirebaseInstallations fixes directory structure. file.range(of: "Source/Library/Private/FirebaseInstallationsInternal.h") != nil || + file.range(of: "FirebaseCore/Sources/FIROptionsInternal.h") != nil || file.range(of: "FirebaseCore/Extension") != nil // Treat all files with names finishing on "Test" or "Tests" as files with tests. From 3df08833ac329f725ec74201cd91fc928fd41846 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 7 Oct 2024 16:42:24 -0400 Subject: [PATCH 163/258] [Vertex AI] Simplify `ModelContent` initializers (#13832) Co-authored-by: Ryan Wilson --- FirebaseVertexAI/CHANGELOG.md | 2 + .../ChatSample/Views/ErrorDetailsView.swift | 10 ++--- .../Sample/ChatSample/Views/ErrorView.swift | 2 +- FirebaseVertexAI/Sources/ModelContent.swift | 35 +---------------- .../Tests/Unit/VertexAIAPITests.swift | 38 +++++++------------ 5 files changed, 21 insertions(+), 66 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 13ef2aad590..1332b53f7e1 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -34,6 +34,8 @@ generating content the types `TextPart`; additionally the types `InlineDataPart`, `FileDataPart` and `FunctionResponsePart` may be provided as input. (#13767) +- [changed] **Breaking Change**: All initializers for `ModelContent` now require + the label `parts: `. (#13832) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index b8febfe0e40..4bc18345cfb 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -160,12 +160,11 @@ struct ErrorDetailsView: View { let error = GenerateContentError.responseStoppedEarly( reason: .maxTokens, response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. - """, - ]), + """), safetyRatings: [ SafetyRating(category: .dangerousContent, probability: .high), SafetyRating(category: .harassment, probability: .low), @@ -183,12 +182,11 @@ struct ErrorDetailsView: View { #Preview("Prompt Blocked") { let error = GenerateContentError.promptBlocked( response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. - """, - ]), + """), safetyRatings: [ SafetyRating(category: .dangerousContent, probability: .high), SafetyRating(category: .harassment, probability: .low), diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift index d4db2d67dc5..e43258557b4 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift @@ -37,7 +37,7 @@ struct ErrorView: View { NavigationView { let errorPromptBlocked = GenerateContentError.promptBlocked( response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: [ """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index d215dd4ba15..9697a97a5bf 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -71,32 +71,6 @@ public struct ModelContent: Equatable, Sendable { // TODO: Refactor this let internalParts: [InternalPart] - /// Creates a new value from any data or `Array` of data interpretable as a - /// ``Part``. See ``PartsRepresentable`` for types that can be interpreted as `Part`s. - public init(role: String? = "user", parts: some PartsRepresentable) { - self.role = role - var convertedParts = [InternalPart]() - for part in parts.partsValue { - switch part { - case let textPart as TextPart: - convertedParts.append(.text(textPart.text)) - case let inlineDataPart as InlineDataPart: - let inlineData = inlineDataPart.inlineData - convertedParts.append(.inlineData(mimetype: inlineData.mimeType, inlineData.data)) - case let fileDataPart as FileDataPart: - let fileData = fileDataPart.fileData - convertedParts.append(.fileData(mimetype: fileData.mimeType, uri: fileData.fileURI)) - case let functionCallPart as FunctionCallPart: - convertedParts.append(.functionCall(functionCallPart.functionCall)) - case let functionResponsePart as FunctionResponsePart: - convertedParts.append(.functionResponse(functionResponsePart.functionResponse)) - default: - fatalError() - } - } - internalParts = convertedParts - } - /// Creates a new value from a list of ``Part``s. public init(role: String? = "user", parts: [any Part]) { self.role = role @@ -124,14 +98,7 @@ public struct ModelContent: Equatable, Sendable { /// Creates a new value from any data interpretable as a ``Part``. /// See ``PartsRepresentable`` for types that can be interpreted as `Part`s. - public init(role: String? = "user", _ parts: any PartsRepresentable...) { - let content = parts.flatMap { $0.partsValue } - self.init(role: role, parts: content) - } - - /// Creates a new value from any data interpretable as a ``Part``. - /// See ``PartsRepresentable``for types that can be interpreted as `Part`s. - public init(role: String? = "user", _ parts: [PartsRepresentable]) { + public init(role: String? = "user", parts: any PartsRepresentable...) { let content = parts.flatMap { $0.partsValue } self.init(role: role, parts: content) } diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index c187a997b6c..9624ee6e52c 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -106,8 +106,8 @@ final class VertexAIAPITests: XCTestCase { _ = try await genAI.generateContent([str, UIImage(), TextPart(str)]) _ = try await genAI.generateContent(str, UIImage(), "def", UIImage()) _ = try await genAI.generateContent([str, UIImage(), "def", UIImage()]) - _ = try await genAI.generateContent([ModelContent("def", UIImage()), - ModelContent("def", UIImage())]) + _ = try await genAI.generateContent([ModelContent(parts: "def", UIImage()), + ModelContent(parts: "def", UIImage())]) #elseif canImport(AppKit) _ = try await genAI.generateContent(NSImage()) _ = try await genAI.generateContent([NSImage()]) @@ -121,37 +121,25 @@ final class VertexAIAPITests: XCTestCase { let _ = ModelContent(parts: "Constant String") let _ = ModelContent(parts: str) let _ = ModelContent(parts: [str]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot - // convert value of type 'String' to expected element type - // 'Array.ArrayLiteralElement'. Not sure if there's a way we can get it to - // work. - let _ = ModelContent( - parts: [str, InlineDataPart(data: Data(), mimeType: "foo")] as [any PartsRepresentable] - ) + let _ = ModelContent(parts: [str, InlineDataPart(data: Data(), mimeType: "foo")]) #if canImport(UIKit) _ = ModelContent(role: "user", parts: UIImage()) _ = ModelContent(role: "user", parts: [UIImage()]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert - // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a - // way we can get it to work. - _ = ModelContent(parts: [str, UIImage()] as [any PartsRepresentable]) - // Alternatively, you can explicitly declare the type in a variable and pass it in. + _ = ModelContent(parts: [str, UIImage()]) + // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile + // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`. let representable2: [any PartsRepresentable] = [str, UIImage()] _ = ModelContent(parts: representable2) - _ = - ModelContent(parts: [str, UIImage(), TextPart(str)] as [any PartsRepresentable]) + _ = ModelContent(parts: [str, UIImage(), TextPart(str)]) #elseif canImport(AppKit) _ = ModelContent(role: "user", parts: NSImage()) _ = ModelContent(role: "user", parts: [NSImage()]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert - // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a - // way we can get it to work. - _ = ModelContent(parts: [str, NSImage()] as [any PartsRepresentable]) - // Alternatively, you can explicitly declare the type in a variable and pass it in. + _ = ModelContent(parts: [str, NSImage()]) + // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile + // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`. let representable2: [any PartsRepresentable] = [str, NSImage()] _ = ModelContent(parts: representable2) - _ = - ModelContent(parts: [str, NSImage(), TextPart(str)] as [any PartsRepresentable]) + _ = ModelContent(parts: [str, NSImage(), TextPart(str)]) #endif // countTokens API @@ -160,8 +148,8 @@ final class VertexAIAPITests: XCTestCase { let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?", UIImage()) let _: CountTokensResponse = try await genAI.countTokens([ - ModelContent("What color is the Sky?", UIImage()), - ModelContent(UIImage(), "What color is the Sky?", UIImage()), + ModelContent(parts: "What color is the Sky?", UIImage()), + ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()), ]) #endif From 8cfaf2e5f21623fec3c376fe92a144a2dcdcaf2b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 7 Oct 2024 17:50:20 -0400 Subject: [PATCH 164/258] [Vertex AI] Sample app CI for Xcode 16 (#13833) --- .github/workflows/vertexai.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 2e427be3bb7..bd7302413c5 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -16,7 +16,16 @@ concurrency: jobs: spm-package-resolved: - runs-on: macos-14 + strategy: + matrix: + include: + - os: macos-13 + xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 + runs-on: ${{ matrix.os }} outputs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} env: @@ -30,7 +39,7 @@ jobs: - name: Generate cache key id: generate_cache_key run: | - cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + cache_key="${{ matrix.os }}-spm-${{ hashFiles('**/Package.resolved') }}" echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" - uses: actions/cache/save@v4 id: cache @@ -129,13 +138,13 @@ jobs: sample: strategy: matrix: - # Test build with debug and release configs (whether or not DEBUG is set and optimization level) - build: [build] include: - os: macos-13 - xcode: Xcode_15.0.1 - - os: macos-14 xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 runs-on: ${{ matrix.os }} needs: spm-package-resolved env: From 27ae28ff54523387c52b1daccaf13897a12d531d Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 17:26:45 +0330 Subject: [PATCH 165/258] Build: enhance api diff report scripts (#13840) --- scripts/api_diff_report/pr_commenter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/api_diff_report/pr_commenter.py b/scripts/api_diff_report/pr_commenter.py index 3b3dda72f3c..c2924d7c11f 100644 --- a/scripts/api_diff_report/pr_commenter.py +++ b/scripts/api_diff_report/pr_commenter.py @@ -28,7 +28,7 @@ STAGES_PROGRESS = "progress" STAGES_END = "end" -TITLE_PROGESS = "## ⏳  Detecting API diff in progress...\n" +TITLE_PROGRESS = "## ⏳  Detecting API diff in progress...\n" TITLE_END_DIFF = '## Apple API Diff Report\n' TITLE_END_NO_DIFF = "## ✅  No API diff detected\n" @@ -54,7 +54,7 @@ def main(): if stage == STAGES_PROGRESS: if comment_id: report = COMMENT_HIDDEN_IDENTIFIER - report += generate_markdown_title(TITLE_PROGESS, commit, run_id) + report += generate_markdown_title(TITLE_PROGRESS, commit, run_id) update_comment(token, comment_id, report) delete_label(token, pr_number, PR_LABEL) elif stage == STAGES_END: From c2142cd0cbd51461d02c4431800cc1e7984bcb2a Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 17:26:55 +0330 Subject: [PATCH 166/258] Docs: enhance code coverage report source documentations (#13841) --- .../Sources/UpdatedFilesCollector/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift index a55d285dab9..475da0e5b9b 100644 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift +++ b/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift @@ -103,7 +103,7 @@ struct UpdatedFilesCollector: ParsableCommand { } if let outputPath = outputSDKFileURL { do { - // Instead of directly writing Data to a file, trasnferring Data to + // Instead of directly writing Data to a file, transferring Data to // String can help trimming whitespaces and newlines in advance. let str = try String( decoding: JSONEncoder().encode(podspecsWithChangedFiles), From 4919a09f3cf1b1ed9d0e1e9277d3e024324b491d Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 17:40:52 +0330 Subject: [PATCH 167/258] Docs: enhance scripts comments (#13842) --- scripts/cpplint.py | 2 +- scripts/style.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cpplint.py b/scripts/cpplint.py index 9aa04b8d0a2..07e397599e2 100644 --- a/scripts/cpplint.py +++ b/scripts/cpplint.py @@ -1256,7 +1256,7 @@ def __init__(self): self._filters_backup = self.filters[:] self.counting = 'total' # In what way are we counting errors? self.errors_by_category = {} # string to int dict storing error counts - self.quiet = False # Suppress non-error messagess? + self.quiet = False # Suppress non-error messages? # output format: # "emacs" - format that emacs can parse (default) diff --git a/scripts/style.sh b/scripts/style.sh index d0bfaf8b2c1..01989affed7 100755 --- a/scripts/style.sh +++ b/scripts/style.sh @@ -95,7 +95,7 @@ clang_options=(-style=file) # Swift formatting options for the repo should be configured in # https://github.com/firebase/firebase-ios-sdk/blob/main/.swiftformat. -# These may be overriden with additional `.swiftformat` files in subdirectories. +# These may be overridden with additional `.swiftformat` files in subdirectories. swift_options=() if [[ $# -gt 0 && "$1" == "test-only" ]]; then From f89cd8d2e382d5e14180c811f8b8748a99814fa8 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 17:41:11 +0330 Subject: [PATCH 168/258] Build: enhance Integration testing scripts (#13838) --- .../scripts/build_with_environment.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IntegrationTesting/CocoapodsIntegrationTest/scripts/build_with_environment.sh b/IntegrationTesting/CocoapodsIntegrationTest/scripts/build_with_environment.sh index f413c9ed5b2..1c45d2521fd 100755 --- a/IntegrationTesting/CocoapodsIntegrationTest/scripts/build_with_environment.sh +++ b/IntegrationTesting/CocoapodsIntegrationTest/scripts/build_with_environment.sh @@ -92,17 +92,17 @@ fi # Convert path to absolute one in case the script is run from another directory. RESOLVED_GEMFILE="$(realpath ${GEMFILE})" -RESLOVED_PODFILE="$(realpath ${PODFILE})" +RESOLVED_PODFILE="$(realpath ${PODFILE})" echo "Gemfile = ${RESOLVED_GEMFILE}" -echo "Podfile = ${RESLOVED_PODFILE}" +echo "Podfile = ${RESOLVED_PODFILE}" # Make sure we build from the project root dir. scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" pushd "${scriptDir}/.." prepareBundle "${RESOLVED_GEMFILE}" -prepareCocoapods "${RESLOVED_PODFILE}" +prepareCocoapods "${RESOLVED_PODFILE}" runXcodebuild # Recover original directory just in case From dafb19ae828a9c40dd290978cee24d4f8e7efd67 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 18:07:42 +0330 Subject: [PATCH 169/258] Test: enhance Firestore integration tests (#13837) --- Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm index 99839f5a7d1..28822cb22e5 100644 --- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm @@ -189,13 +189,13 @@ - (void)testCanOverwriteDataAnExistingDocumentUsingSet { NSDictionary *initialData = @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}}; - NSDictionary *udpateData = @{@"desc" : @"NewDescription"}; + NSDictionary *updateData = @{@"desc" : @"NewDescription"}; [self writeDocumentRef:doc data:initialData]; - [self writeDocumentRef:doc data:udpateData]; + [self writeDocumentRef:doc data:updateData]; FIRDocumentSnapshot *document = [self readDocumentForRef:doc]; - XCTAssertEqualObjects(document.data, udpateData); + XCTAssertEqualObjects(document.data, updateData); } - (void)testCanMergeDataWithAnExistingDocumentUsingSet { From b63bddd93f40d5be12ce590f22787cdbac246df3 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 18:08:06 +0330 Subject: [PATCH 170/258] Fix: a repeated typo in python file (#13839) --- cmake/external/leveldb_patch_test.py | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/cmake/external/leveldb_patch_test.py b/cmake/external/leveldb_patch_test.py index b1d62526b92..713e11ed101 100644 --- a/cmake/external/leveldb_patch_test.py +++ b/cmake/external/leveldb_patch_test.py @@ -102,7 +102,7 @@ def test_snappy_include_is_amended(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -111,11 +111,11 @@ def test_snappy_include_is_amended(self): self.assertSequenceEqual(patched_lines, [ "target_include_directories(leveldb", - "# BEGIN: leveldb_include_start modification by MyCoolSript", + "# BEGIN: leveldb_include_start modification by MyCoolScript", "PRIVATE", "a/b", "c/d", - "# END: leveldb_include_start modification by MyCoolSript", + "# END: leveldb_include_start modification by MyCoolScript", "PUBLIC", "path1", "path2", @@ -132,7 +132,7 @@ def test_snappy_include_lines_adopt_indenting_and_eol_convention(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -141,11 +141,11 @@ def test_snappy_include_lines_adopt_indenting_and_eol_convention(self): self.assertSequenceEqual(patched_lines, [ "target_include_directories(leveldb\n", - "# BEGIN: leveldb_include_start modification by MyCoolSript \n", + "# BEGIN: leveldb_include_start modification by MyCoolScript \n", " PRIVATE \n", " a/b \n", " c/d \n", - "# END: leveldb_include_start modification by MyCoolSript \n", + "# END: leveldb_include_start modification by MyCoolScript \n", " PUBLIC \n", " path1 \n", " path2 \n", @@ -166,7 +166,7 @@ def test_snappy_include_line_does_not_affect_surrounding_lines(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -177,11 +177,11 @@ def test_snappy_include_line_does_not_affect_surrounding_lines(self): "aaa", "bbb", "target_include_directories(leveldb", - "# BEGIN: leveldb_include_start modification by MyCoolSript", + "# BEGIN: leveldb_include_start modification by MyCoolScript", "PRIVATE", "a/b", "c/d", - "# END: leveldb_include_start modification by MyCoolSript", + "# END: leveldb_include_start modification by MyCoolScript", "PUBLIC", "path1", "path2", @@ -196,7 +196,7 @@ def test_leveldb_snappy_link_line_is_commented_and_replaced(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -204,10 +204,10 @@ def test_leveldb_snappy_link_line_is_commented_and_replaced(self): patched_lines = tuple(patcher.patch()) self.assertSequenceEqual(patched_lines, [ - "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript", + "# BEGIN: leveldb_snappy_link_line modification by MyCoolScript", "# target_link_libraries(leveldb snappy)", "target_link_libraries(leveldb Snappy::Snappy)", - "# END: leveldb_snappy_link_line modification by MyCoolSript", + "# END: leveldb_snappy_link_line modification by MyCoolScript", ]) def test_leveldb_snappy_link_line_has_indent_and_eol_preserved(self): @@ -216,7 +216,7 @@ def test_leveldb_snappy_link_line_has_indent_and_eol_preserved(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -224,10 +224,10 @@ def test_leveldb_snappy_link_line_has_indent_and_eol_preserved(self): patched_lines = tuple(patcher.patch()) self.assertSequenceEqual(patched_lines, [ - "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript \n", + "# BEGIN: leveldb_snappy_link_line modification by MyCoolScript \n", " # target_link_libraries(leveldb snappy) \n", " target_link_libraries(leveldb Snappy::Snappy) \n", - "# END: leveldb_snappy_link_line modification by MyCoolSript \n", + "# END: leveldb_snappy_link_line modification by MyCoolScript \n", ]) def test_leveldb_snappy_link_line_does_not_affect_surrounding_lines(self): @@ -240,7 +240,7 @@ def test_leveldb_snappy_link_line_does_not_affect_surrounding_lines(self): ) patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) @@ -250,10 +250,10 @@ def test_leveldb_snappy_link_line_does_not_affect_surrounding_lines(self): self.assertSequenceEqual(patched_lines, [ "aaa", "bbb", - "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript", + "# BEGIN: leveldb_snappy_link_line modification by MyCoolScript", "# target_link_libraries(leveldb snappy)", "target_link_libraries(leveldb Snappy::Snappy)", - "# END: leveldb_snappy_link_line modification by MyCoolSript", + "# END: leveldb_snappy_link_line modification by MyCoolScript", "ccc", "ddd", ]) @@ -270,30 +270,30 @@ def test_all_patches_combined(self): patcher = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) patched_lines = tuple(patcher.patch()) self.assertSequenceEqual(patched_lines, [ - "# BEGIN: snappy_detect_line modification by MyCoolSript", + "# BEGIN: snappy_detect_line modification by MyCoolScript", """# check_library_exists(snappy snappy_compress "" HAVE_SNAPPY)""", """set(HAVE_SNAPPY ON CACHE BOOL "")""", - "# END: snappy_detect_line modification by MyCoolSript", + "# END: snappy_detect_line modification by MyCoolScript", "target_include_directories(leveldb", - "# BEGIN: leveldb_include_start modification by MyCoolSript", + "# BEGIN: leveldb_include_start modification by MyCoolScript", "PRIVATE", "a/b", "c/d", - "# END: leveldb_include_start modification by MyCoolSript", + "# END: leveldb_include_start modification by MyCoolScript", "PUBLIC", "path1", ")", - "# BEGIN: leveldb_snappy_link_line modification by MyCoolSript", + "# BEGIN: leveldb_snappy_link_line modification by MyCoolScript", "# target_link_libraries(leveldb snappy)", "target_link_libraries(leveldb Snappy::Snappy)", - "# END: leveldb_snappy_link_line modification by MyCoolSript", + "# END: leveldb_snappy_link_line modification by MyCoolScript", ]) def test_idempotence(self): @@ -308,14 +308,14 @@ def test_idempotence(self): patcher1 = leveldb_patch.CMakeListsPatcher( lines, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) patched_lines1 = tuple(patcher1.patch()) patcher2 = leveldb_patch.CMakeListsPatcher( patched_lines1, - script_name="MyCoolSript", + script_name="MyCoolScript", snappy_source_dir=pathlib.Path("a/b"), snappy_binary_dir=pathlib.Path("c/d"), ) From dced6670ced1ac2cd8088b7bb629d2bf7a46bc29 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 8 Oct 2024 07:38:51 -0700 Subject: [PATCH 171/258] Xcode_16 will soon require macos-15 in GHA (#13835) --- .github/workflows/abtesting.yml | 51 ++++++++++++++++++++---- .github/workflows/appdistribution.yml | 21 +++++++--- .github/workflows/auth.yml | 32 ++++++++++++--- .github/workflows/core.yml | 28 +++++++++++-- .github/workflows/core_internal.yml | 28 +++++++++++-- .github/workflows/crashlytics.yml | 28 +++++++++++-- .github/workflows/database.yml | 28 +++++++++++-- .github/workflows/dynamiclinks.yml | 9 ++++- .github/workflows/firebase_app_check.yml | 28 +++++++++++-- .github/workflows/firestore.yml | 27 +++++++++++-- .github/workflows/functions.yml | 28 +++++++++++-- .github/workflows/inappmessaging.yml | 9 ++++- .github/workflows/installations.yml | 28 +++++++++++-- .github/workflows/messaging.yml | 28 +++++++++++-- .github/workflows/mlmodeldownloader.yml | 28 +++++++++++-- .github/workflows/performance.yml | 16 ++++++-- .github/workflows/remoteconfig.yml | 28 +++++++++++-- .github/workflows/sessions.yml | 28 +++++++++++-- .github/workflows/spm.yml | 9 ++--- .github/workflows/storage.yml | 28 +++++++++++-- .github/workflows/vertexai.yml | 28 +++++++++++-- scripts/build.sh | 16 ++++++-- 22 files changed, 476 insertions(+), 78 deletions(-) diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index e16cb3ddb82..10fe8f6d8b3 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -22,10 +22,25 @@ jobs: strategy: matrix: - # TODO: macos tests are blocked by https://github.com/erikdoe/ocmock/pull/532 - target: [ios, tvos, macos --skip-tests, watchos] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: ios + - os: macos-14 + xcode: Xcode_15.4 + target: ios + - os: macos-15 + xcode: Xcode_16 + target: ios + - os: macos-15 + xcode: Xcode_16 + target: tvos + - os: macos-15 + xcode: Xcode_16 + target: macos + - os: macos-15 + xcode: Xcode_16 + target: watchos runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -71,9 +86,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index 8c80ed4116c..93e8df329ee 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -21,9 +21,13 @@ jobs: strategy: matrix: - target: [ios] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -35,7 +39,7 @@ jobs: - name: Build and test run: | scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAppDistribution.podspec \ - --platforms=${{ matrix.target }} + --platforms=ios spm-package-resolved: env: @@ -66,8 +70,13 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index 1c096c2fb37..a65db1340c8 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -58,7 +58,7 @@ jobs: matrix: podspec: [FirebaseAuthInterop.podspec, FirebaseAuth.podspec] target: [ios, tvos, macos --skip-tests --allow-warnings, watchos] - os: [macos-14] + os: [macos-15] xcode: [Xcode_16] runs-on: ${{ matrix.os }} steps: @@ -107,9 +107,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS spm, tvOS spm, macOS spmbuildonly, catalyst spm, watchOS spm] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS spm + - os: macos-14 + xcode: Xcode_15.4 + target: iOS spm + - os: macos-15 + xcode: Xcode_16 + target: iOS spm + - os: macos-15 + xcode: Xcode_16 + target: tvOS spm + - os: macos-15 + xcode: Xcode_16 + target: macOS spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: watchOS spm + - os: macos-15 + xcode: Xcode_16 + target: catalyst spm + - os: macos-15 + xcode: Xcode_16 + target: visionOS spm runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -139,7 +161,7 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v4 - uses: actions/cache/restore@v4 diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index d8f420f2af3..d3ac22b6b9b 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -64,9 +64,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/core_internal.yml b/.github/workflows/core_internal.yml index c1b38b07422..c9db19c0b3d 100644 --- a/.github/workflows/core_internal.yml +++ b/.github/workflows/core_internal.yml @@ -60,9 +60,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index ef4c0ba650c..fc303e70561 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -77,9 +77,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 17b1db109e6..86e1f42a109 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -86,9 +86,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dynamiclinks.yml b/.github/workflows/dynamiclinks.yml index 4fcd9624616..777cba16742 100644 --- a/.github/workflows/dynamiclinks.yml +++ b/.github/workflows/dynamiclinks.yml @@ -64,8 +64,13 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/firebase_app_check.yml b/.github/workflows/firebase_app_check.yml index d001709535a..613146e80fc 100644 --- a/.github/workflows/firebase_app_check.yml +++ b/.github/workflows/firebase_app_check.yml @@ -120,9 +120,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 4c98182f859..e57cb9d7088 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -405,7 +405,7 @@ jobs: - os: macos-13 platforms: 'ios' include: - - os: macos-14 + - os: macos-15 xcode: Xcode_16 - os: macos-13 xcode: Xcode_15.2 @@ -461,9 +461,28 @@ jobs: (github.event_name == 'pull_request') strategy: matrix: - target: [iOS, tvOS, macOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} env: FIREBASE_SOURCE_FIRESTORE: 1 diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index d4207217be3..226583412e0 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -108,9 +108,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/inappmessaging.yml b/.github/workflows/inappmessaging.yml index c766fd89f31..c8d70fbeff4 100644 --- a/.github/workflows/inappmessaging.yml +++ b/.github/workflows/inappmessaging.yml @@ -91,8 +91,13 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + - os: macos-14 + xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index 883fa696ae9..3a1da8aa802 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -85,9 +85,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index acc9d6d8c2c..8acccc947f0 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -107,9 +107,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS spm, tvOS spmbuildonly, macOS spmbuildonly, catalyst spmbuildonly, watchOS spmbuildonly] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS spm + - os: macos-14 + xcode: Xcode_15.4 + target: iOS spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: iOS spm + - os: macos-15 + xcode: Xcode_16 + target: tvOS spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: macOS spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: watchOS spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: catalyst spmbuildonly + - os: macos-15 + xcode: Xcode_16 + target: visionOS spmbuildonly runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/mlmodeldownloader.yml b/.github/workflows/mlmodeldownloader.yml index feae6c76383..81da1ab1a83 100644 --- a/.github/workflows/mlmodeldownloader.yml +++ b/.github/workflows/mlmodeldownloader.yml @@ -94,9 +94,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index f58bb912a18..67a89aef2ae 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -147,9 +147,19 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 200c19801cf..4fbf6aa191d 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -114,9 +114,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/sessions.yml b/.github/workflows/sessions.yml index c4edb685e1f..73fff13c7aa 100644 --- a/.github/workflows/sessions.yml +++ b/.github/workflows/sessions.yml @@ -78,9 +78,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index fec6410ca5d..8986f7b8d82 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -51,9 +51,8 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - os: [macos-14] include: - - os: macos-14 + - os: macos-15 xcode: Xcode_16 test: spm - os: macos-14 @@ -89,11 +88,10 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - os: [macos-14] include: - os: macos-14 xcode: Xcode_15.3 - - os: macos-14 + - os: macos-15 xcode: Xcode_16 runs-on: ${{ matrix.os }} steps: @@ -121,9 +119,8 @@ jobs: # visionOS isn't buildable from here (even with Firestore source) because the test # targets need Analytics. target: [tvOS, macOS, catalyst] - os: [macos-14] include: - - os: macos-14 + - os: macos-15 xcode: Xcode_16 - os: macos-14 xcode: Xcode_15.3 diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index b93f4b9141d..eb1dd9f8f0f 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -88,9 +88,31 @@ jobs: needs: [spm-package-resolved] strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index bd7302413c5..364fbb93bc7 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -50,9 +50,31 @@ jobs: spm-unit: strategy: matrix: - target: [iOS, tvOS, macOS, catalyst, watchOS] - os: [macos-14] - xcode: [Xcode_15.2, Xcode_16] + include: + - os: macos-13 + xcode: Xcode_15.2 + target: iOS + - os: macos-14 + xcode: Xcode_15.4 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: iOS + - os: macos-15 + xcode: Xcode_16 + target: tvOS + - os: macos-15 + xcode: Xcode_16 + target: macOS + - os: macos-15 + xcode: Xcode_16 + target: watchOS + - os: macos-15 + xcode: Xcode_16 + target: catalyst + - os: macos-15 + xcode: Xcode_16 + target: visionOS runs-on: ${{ matrix.os }} needs: spm-package-resolved env: diff --git a/scripts/build.sh b/scripts/build.sh index 1fd68410596..f3f5525a29b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -143,16 +143,28 @@ if [[ "$xcode_major" -lt 15 ]]; then -sdk 'iphonesimulator' -destination 'platform=iOS Simulator,name=iPhone 14' ) + watchos_flags=( + -sdk 'watchsimulator' + -destination 'platform=watchOS Simulator,name=Apple Watch Series 7 (45mm)' + ) elif [[ "$xcode_major" -lt 16 ]]; then ios_flags=( -sdk 'iphonesimulator' -destination 'platform=iOS Simulator,name=iPhone 15' ) + watchos_flags=( + -sdk 'watchsimulator' + -destination 'platform=watchOS Simulator,name=Apple Watch Series 7 (45mm)' + ) else ios_flags=( -sdk 'iphonesimulator' -destination 'platform=iOS Simulator,name=iPhone 16' ) + watchos_flags=( + -sdk 'watchsimulator' + -destination 'platform=watchOS Simulator,name=Apple Watch Series 10 (42mm)' + ) fi ios_device_flags=( @@ -173,10 +185,6 @@ tvos_flags=( -sdk "appletvsimulator" -destination 'platform=tvOS Simulator,name=Apple TV' ) -watchos_flags=( - -sdk 'watchsimulator' - -destination 'platform=watchOS Simulator,name=Apple Watch Series 7 (45mm)' -) visionos_flags=( -sdk 'xrsimulator' -destination 'platform=visionOS Simulator,name=Apple Vision Pro' From 10bbae0021af4d207cc63b64d6f42f8dfed13e81 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Tue, 8 Oct 2024 18:52:43 +0330 Subject: [PATCH 172/258] Docs: enhance the FirebaseVertexAI source documentation (#13836) --- FirebaseVertexAI/Sources/Schema.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseVertexAI/Sources/Schema.swift b/FirebaseVertexAI/Sources/Schema.swift index f24a4d203b3..dc435626b05 100644 --- a/FirebaseVertexAI/Sources/Schema.swift +++ b/FirebaseVertexAI/Sources/Schema.swift @@ -144,7 +144,7 @@ public class Schema { /// if `nullable` is set to `true`), or an `enum` with strings as raw values. /// /// **Example:** - /// The values `["north", "south", "east", "west"]` for an enumation of directions. + /// The values `["north", "south", "east", "west"]` for an enumeration of directions. /// ``` /// enum Direction: String, Decodable { /// case north, south, east, west From 7c4659cf807052013caeacb5e7fdddc33b3ee3d6 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 8 Oct 2024 12:02:03 -0400 Subject: [PATCH 173/258] [Vertex AI] Revert running `spm-package-resolved` on all macOS versions (#13846) --- .github/workflows/vertexai.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 364fbb93bc7..3d98ba70555 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -16,16 +16,7 @@ concurrency: jobs: spm-package-resolved: - strategy: - matrix: - include: - - os: macos-13 - xcode: Xcode_15.2 - - os: macos-14 - xcode: Xcode_15.4 - - os: macos-15 - xcode: Xcode_16 - runs-on: ${{ matrix.os }} + runs-on: macos-14 outputs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} env: @@ -39,7 +30,7 @@ jobs: - name: Generate cache key id: generate_cache_key run: | - cache_key="${{ matrix.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" - uses: actions/cache/save@v4 id: cache From 78f970b8458eb1d55c7a32fedd546e294faa7a53 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:26:47 -0400 Subject: [PATCH 174/258] [CoreInternal] Add async flush method (#13850) --- .../HeartbeatController.swift | 32 +++++ .../HeartbeatLogging/HeartbeatStorage.swift | 23 ++++ .../_ObjC_HeartbeatController.swift | 10 ++ .../HeartbeatLoggingIntegrationTests.swift | 25 ++++ .../Tests/Unit/HeartbeatControllerTests.swift | 44 ++++++ .../Tests/Unit/HeartbeatStorageTests.swift | 130 +++++++++++++++++- 6 files changed, 261 insertions(+), 3 deletions(-) diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift index 93b335aa2bf..e4a2cc4c335 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift @@ -126,6 +126,38 @@ public final class HeartbeatController { } } + @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) + public func flushAsync() async -> HeartbeatsPayload { + return await withCheckedContinuation { continuation in + let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in + guard let oldHeartbeatsBundle = heartbeatsBundle else { + return nil // Storage was empty. + } + // The new value that's stored will use the old's cache to prevent the + // logging of duplicates after flushing. + return HeartbeatsBundle( + capacity: self.heartbeatsStorageCapacity, + cache: oldHeartbeatsBundle.lastAddedHeartbeatDates + ) + } + + // Asynchronously gets and returns the stored heartbeats, resetting storage + // using the given transform. + storage.getAndSetAsync(using: resetTransform) { result in + switch result { + case let .success(heartbeatsBundle): + // If no heartbeats bundle was stored, return an empty payload. + continuation + .resume(returning: heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload + .emptyPayload) + case .failure: + // If the operation throws, assume no heartbeat(s) were retrieved or set. + continuation.resume(returning: HeartbeatsPayload.emptyPayload) + } + } + } + } + /// Synchronously flushes the heartbeat for today. /// /// If no heartbeat was logged today, the returned payload is empty. diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift index e5bb83f6795..ff428077613 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift @@ -20,6 +20,8 @@ protocol HeartbeatStorageProtocol { func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?) func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws -> HeartbeatsBundle? + func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?, + completion: @escaping (Result) -> Void) } /// Thread-safe storage object designed for transforming heartbeat data that is persisted to disk. @@ -134,6 +136,27 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { return heartbeatsBundle } + /// Asynchronously gets the current heartbeat data from storage and resets the storage using the + /// given transform block. + /// - Parameters: + /// - transform: An escaping block used to reset the currently stored heartbeat. + /// - completion: An escaping block used to process the heartbeat data that + /// was stored (before the `transform` was applied); otherwise, the error + /// that occurred. + func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?, + completion: @escaping (Result) -> Void) { + queue.async { + do { + let oldHeartbeatsBundle = try? self.load(from: self.storage) + let newHeartbeatsBundle = transform(oldHeartbeatsBundle) + try self.save(newHeartbeatsBundle, to: self.storage) + completion(.success(oldHeartbeatsBundle)) + } catch { + completion(.failure(error)) + } + } + } + /// Loads and decodes the stored heartbeats bundle from a given storage object. /// - Parameter storage: The storage container to read from. /// - Returns: The decoded `HeartbeatsBundle` loaded from storage; `nil` if storage is empty. diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift index 50cd24726ce..fdd13af13be 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift @@ -45,6 +45,16 @@ public class _ObjC_HeartbeatController: NSObject { return _ObjC_HeartbeatsPayload(heartbeatsPayload) } + /// Asynchronously flushes heartbeats from storage into a heartbeats payload. + /// + /// - Note: This API is thread-safe. + /// - Returns: A heartbeats payload for the flushed heartbeat(s). + @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) + public func flushAsync() async -> _ObjC_HeartbeatsPayload { + let heartbeatsPayload = await heartbeatController.flushAsync() + return _ObjC_HeartbeatsPayload(heartbeatsPayload) + } + /// Synchronously flushes the heartbeat for today. /// /// If no heartbeat was logged today, the returned payload is empty. diff --git a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift index b029c78a98a..b306405f4bd 100644 --- a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift +++ b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift @@ -52,6 +52,31 @@ class HeartbeatLoggingIntegrationTests: XCTestCase { ) } + @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) + func testLogAndFlushAsync() async throws { + // Given + let heartbeatController = HeartbeatController(id: #function) + let expectedDate = HeartbeatsPayload.dateFormatter.string(from: Date()) + // When + heartbeatController.log("dummy_agent") + let payload = await heartbeatController.flushAsync() + // Then + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + payload.headerValue(), + """ + { + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["\(expectedDate)"] + } + ] + } + """ + ) + } + /// This test may flake if it is executed during the transition from one day to the next. func testDoNotLogMoreThanOnceInACalendarDay() throws { // Given diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift index 25bd02cfa70..ddf3d1c5d9d 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift @@ -58,6 +58,39 @@ class HeartbeatControllerTests: XCTestCase { assertHeartbeatControllerFlushesEmptyPayload(controller) } + @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) + func testLogAndFlushAsync() async throws { + // Given + let controller = HeartbeatController( + storage: HeartbeatStorageFake(), + dateProvider: { self.date } + ) + + assertHeartbeatControllerFlushesEmptyPayload(controller) + + // When + controller.log("dummy_agent") + let heartbeatPayload = await controller.flushAsync() + + // Then + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + heartbeatPayload.headerValue(), + """ + { + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["2021-11-01"] + } + ] + } + """ + ) + + assertHeartbeatControllerFlushesEmptyPayload(controller) + } + func testLogAtEndOfTimePeriodAndAcceptAtStartOfNextOne() throws { // Given var testDate = date @@ -404,4 +437,15 @@ private class HeartbeatStorageFake: HeartbeatStorageProtocol { heartbeatsBundle = transform(heartbeatsBundle) return oldHeartbeatsBundle } + + func getAndSetAsync(using transform: @escaping (FirebaseCoreInternal.HeartbeatsBundle?) + -> FirebaseCoreInternal.HeartbeatsBundle?, + completion: @escaping (Result< + FirebaseCoreInternal.HeartbeatsBundle?, + any Error + >) -> Void) { + let oldHeartbeatsBundle = heartbeatsBundle + heartbeatsBundle = transform(heartbeatsBundle) + completion(.success(oldHeartbeatsBundle)) + } } diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift index 6cff37c608c..ed24275b88a 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift @@ -208,6 +208,46 @@ class HeartbeatStorageTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } + func testGetAndSetAsync_ReturnsOldValueAndSetsNewValue() throws { + // Given + let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake()) + + var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1) + dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date())) + + // When + let expectation1 = expectation(description: #function + "_1") + heartbeatStorage.getAndSetAsync { heartbeatsBundle in + // Assert that heartbeat storage is empty. + XCTAssertNil(heartbeatsBundle) + // Write new value. + return dummyHeartbeatsBundle + } completion: { result in + switch result { + case .success: break + case let .failure(error): XCTFail("Error: \(error)") + } + expectation1.fulfill() + } + + // Then + let expectation2 = expectation(description: #function + "_2") + XCTAssertNoThrow( + try heartbeatStorage.getAndSet { heartbeatsBundle in + // Assert old value is read. + XCTAssertEqual( + heartbeatsBundle?.makeHeartbeatsPayload(), + dummyHeartbeatsBundle.makeHeartbeatsPayload() + ) + // Write some new value. + expectation2.fulfill() + return heartbeatsBundle + } + ) + + wait(for: [expectation1, expectation2], timeout: 0.5, enforceOrder: true) + } + func testGetAndSet_WhenLoadFails_PassesNilToBlockAndReturnsNil() throws { // Given let expectation = expectation(description: #function) @@ -232,6 +272,41 @@ class HeartbeatStorageTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } + func testGetAndSetAsync_WhenLoadFails_PassesNilToBlockAndReturnsNil() throws { + // Given + let readExpectation = expectation(description: #function + "_1") + let transformExpectation = expectation(description: #function + "_2") + let completionExpectation = expectation(description: #function + "_3") + + let storageFake = StorageFake() + let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake) + + // When + storageFake.onRead = { + readExpectation.fulfill() + return try XCTUnwrap("BAD_DATA".data(using: .utf8)) + } + + // Then + heartbeatStorage.getAndSetAsync { heartbeatsBundle in + XCTAssertNil(heartbeatsBundle) + transformExpectation.fulfill() + return heartbeatsBundle + } completion: { result in + switch result { + case .success: break + case let .failure(error): XCTFail("Error: \(error)") + } + completionExpectation.fulfill() + } + + wait( + for: [readExpectation, transformExpectation, completionExpectation], + timeout: 0.5, + enforceOrder: true + ) + } + func testGetAndSet_WhenSaveFails_ThrowsError() throws { // Given let expectation = expectation(description: #function) @@ -250,7 +325,42 @@ class HeartbeatStorageTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testOperationsAreSynrononizedSerially() throws { + func testGetAndSetAsync_WhenSaveFails_ThrowsError() throws { + // Given + let transformExpectation = expectation(description: #function + "_1") + let writeExpectation = expectation(description: #function + "_2") + let completionExpectation = expectation(description: #function + "_3") + + let storageFake = StorageFake() + let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake) + + // When + storageFake.onWrite = { _ in + writeExpectation.fulfill() + throw StorageError.writeError + } + + // Then + heartbeatStorage.getAndSetAsync { heartbeatsBundle in + transformExpectation.fulfill() + XCTAssertNil(heartbeatsBundle) + return heartbeatsBundle + } completion: { result in + switch result { + case .success: XCTFail("Error: unexpected success") + case .failure: break + } + completionExpectation.fulfill() + } + + wait( + for: [transformExpectation, writeExpectation, completionExpectation], + timeout: 0.5, + enforceOrder: true + ) + } + + func testOperationsAreSyncrononizedSerially() throws { // Given let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake()) @@ -263,10 +373,24 @@ class HeartbeatStorageTests: XCTestCase { return heartbeatsBundle } - if /* randomChoice */ .random() { + switch Int.random(in: 1 ... 3) { + case 1: heartbeatStorage.readAndWriteAsync(using: transform) - } else { + case 2: XCTAssertNoThrow(try heartbeatStorage.getAndSet(using: transform)) + case 3: + let getAndSet = self.expectation(description: "GetAndSetAsync_\(i)") + heartbeatStorage.getAndSetAsync(using: transform) { result in + switch result { + case .success: break + case let .failure(error): + XCTFail("Unexpected: Error occurred in getAndSet_\(i), \(error)") + } + getAndSet.fulfill() + } + wait(for: [getAndSet], timeout: 1.0) + default: + XCTFail("Unexpected: Random number is out of range.") } return expectation From 289a1b91e319d768a1e63ab7fa91ae6502b0f3ef Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 8 Oct 2024 17:49:56 -0400 Subject: [PATCH 175/258] [Vertex AI] Replace sample `CustomStringConvertible` extensions (#13845) --- .../ChatSample/Views/ErrorDetailsView.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index 4bc18345cfb..f2459559776 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -16,8 +16,9 @@ import FirebaseVertexAI import MarkdownUI import SwiftUI -extension HarmCategory: CustomStringConvertible { - public var description: String { +private extension HarmCategory { + /// Returns a description of the `HarmCategory` suitable for displaying in the UI. + var displayValue: String { switch self { case .dangerousContent: "Dangerous content" case .harassment: "Harassment" @@ -28,8 +29,9 @@ extension HarmCategory: CustomStringConvertible { } } -extension SafetyRating.HarmProbability: CustomStringConvertible { - public var description: String { +private extension SafetyRating.HarmProbability { + /// Returns a description of the `HarmProbability` suitable for displaying in the UI. + var displayValue: String { switch self { case .high: "High" case .low: "Low" @@ -73,10 +75,9 @@ private struct SafetyRatingsSection: View { Section("Safety ratings") { List(ratings, id: \.self) { rating in HStack { - Text("\(String(describing: rating.category))") - .font(.subheadline) + Text(rating.category.displayValue).font(.subheadline) Spacer() - Text("\(String(describing: rating.probability))") + Text(rating.probability.displayValue) } } } From 7bd6887d01ce31c69ab43e33fa78cfe4c22cca9a Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 8 Oct 2024 18:31:09 -0400 Subject: [PATCH 176/258] [Vertex AI] Use `struct` instead of `enum` for `HarmCategory` (#13728) --- FirebaseVertexAI/CHANGELOG.md | 3 + .../ChatSample/Views/ErrorDetailsView.swift | 4 +- FirebaseVertexAI/Sources/Safety.swift | 71 ++++++++++++++----- .../Tests/Unit/GenerativeModelTests.swift | 14 ++-- 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 1332b53f7e1..3a194a4a8b4 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -36,6 +36,9 @@ as input. (#13767) - [changed] **Breaking Change**: All initializers for `ModelContent` now require the label `parts: `. (#13832) +- [changed] **Breaking Change**: `HarmCategory` is now a struct instead of an + enum type and the `unknown` case has been removed; in a `switch` statement, + use the `default:` case to cover unknown or unhandled categories. (#13728) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index f2459559776..11ba86bb1b9 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -24,7 +24,9 @@ private extension HarmCategory { case .harassment: "Harassment" case .hateSpeech: "Hate speech" case .sexuallyExplicit: "Sexually explicit" - case .unknown: "Unknown" + case .civicIntegrity: "Civic integrity" + default: + "Unknown HarmCategory: \(rawValue)" } } } diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index a57900e7317..3f6ce4658c1 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -97,21 +97,65 @@ public struct SafetySetting { } /// Categories describing the potential harm a piece of content may pose. -public enum HarmCategory: String, Sendable { - /// Unknown. A new server value that isn't recognized by the SDK. - case unknown = "HARM_CATEGORY_UNKNOWN" +public struct HarmCategory: Sendable, Equatable, Hashable { + enum Kind: String { + case harassment = "HARM_CATEGORY_HARASSMENT" + case hateSpeech = "HARM_CATEGORY_HATE_SPEECH" + case sexuallyExplicit = "HARM_CATEGORY_SEXUALLY_EXPLICIT" + case dangerousContent = "HARM_CATEGORY_DANGEROUS_CONTENT" + case civicIntegrity = "HARM_CATEGORY_CIVIC_INTEGRITY" + } /// Harassment content. - case harassment = "HARM_CATEGORY_HARASSMENT" + public static var harassment: HarmCategory { + return self.init(kind: .harassment) + } /// Negative or harmful comments targeting identity and/or protected attributes. - case hateSpeech = "HARM_CATEGORY_HATE_SPEECH" + public static var hateSpeech: HarmCategory { + return self.init(kind: .hateSpeech) + } /// Contains references to sexual acts or other lewd content. - case sexuallyExplicit = "HARM_CATEGORY_SEXUALLY_EXPLICIT" + public static var sexuallyExplicit: HarmCategory { + return self.init(kind: .sexuallyExplicit) + } /// Promotes or enables access to harmful goods, services, or activities. - case dangerousContent = "HARM_CATEGORY_DANGEROUS_CONTENT" + public static var dangerousContent: HarmCategory { + return self.init(kind: .dangerousContent) + } + + /// Content that may be used to harm civic integrity. + public static var civicIntegrity: HarmCategory { + return self.init(kind: .civicIntegrity) + } + + /// Returns the raw string representation of the `HarmCategory` value. + /// + /// > Note: This value directly corresponds to the values in the + /// > [REST API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/HarmCategory). + public let rawValue: String + + init(kind: Kind) { + rawValue = kind.rawValue + } + + init(rawValue: String) { + if Kind(rawValue: rawValue) == nil { + VertexLog.error( + code: .generateContentResponseUnrecognizedHarmCategory, + """ + Unrecognized HarmCategory with value "\(rawValue)": + - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ + release notes at https://firebase.google.com/support/release-notes/ios + - Search for "\(rawValue)" in the Firebase Apple SDK Issue Tracker at \ + https://github.com/firebase/firebase-ios-sdk/issues and file a Bug Report if none found + """ + ) + } + self.rawValue = rawValue + } } // MARK: - Codable Conformances @@ -139,17 +183,8 @@ extension SafetyRating: Decodable {} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension HarmCategory: Codable { public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedCategory = HarmCategory(rawValue: value) else { - VertexLog.error( - code: .generateContentResponseUnrecognizedHarmCategory, - "Unrecognized HarmCategory with value \"\(value)\"." - ) - self = .unknown - return - } - - self = decodedCategory + let rawValue = try decoder.singleValueContainer().decode(String.self) + self = HarmCategory(rawValue: rawValue) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 86d3e7f9c11..254f81e96fb 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -163,7 +163,7 @@ final class GenerativeModelTests: XCTestCase { let expectedSafetyRatings = [ SafetyRating(category: .harassment, probability: .medium), SafetyRating(category: .dangerousContent, probability: .unknown), - SafetyRating(category: .unknown, probability: .high), + SafetyRating(category: HarmCategory(rawValue: "FAKE_NEW_HARM_CATEGORY"), probability: .high), ] MockURLProtocol .requestHandler = try httpRequestHandler( @@ -972,18 +972,22 @@ final class GenerativeModelTests: XCTestCase { forResource: "streaming-success-unknown-safety-enum", withExtension: "txt" ) + let unknownSafetyRating = SafetyRating( + category: HarmCategory(rawValue: "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM"), + probability: .unknown + ) - var hadUnknown = false + var foundUnknownSafetyRating = false let stream = try model.generateContentStream("Hi") for try await content in stream { XCTAssertNotNil(content.text) if let ratings = content.candidates.first?.safetyRatings, - ratings.contains(where: { $0.category == .unknown }) { - hadUnknown = true + ratings.contains(where: { $0 == unknownSafetyRating }) { + foundUnknownSafetyRating = true } } - XCTAssertTrue(hadUnknown) + XCTAssertTrue(foundUnknownSafetyRating) } func testGenerateContentStream_successWithCitations() async throws { From a14dd48c83b011345315d89ef1cd8df613c1a586 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:35:26 -0400 Subject: [PATCH 177/258] [Core] Add async flush method (#13851) --- FirebaseCore/Extension/FIRHeartbeatLogger.h | 12 +++++- FirebaseCore/Sources/FIRHeartbeatLogger.m | 7 ++++ .../Tests/Unit/FIRHeartbeatLoggerTests.m | 39 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 6314f504f13..65793b3db75 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -68,13 +68,23 @@ NSString *_Nullable FIRHeaderValueFromHeartbeatsPayload(FIRHeartbeatsPayload *he - (void)log; #ifndef FIREBASE_BUILD_CMAKE -/// Flushes heartbeats from storage into a structured payload of heartbeats. +/// Synchronously flushes heartbeats from storage into a structured payload of heartbeats. /// /// This API is for clients using platform logging V2. /// /// @note This API is thread-safe. /// @return A payload of heartbeats. - (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload; + +/// Asynchronously flushes heartbeats from storage into a structured payload of heartbeats. +/// +/// This API is for clients using platform logging V2. +/// +/// @note This API is thread-safe. +/// @param completionHandler A completion handler to process the flushed payload of heartbeats. +- (void)flushHeartbeatsIntoPayloadWithCompletionHandler: + (void (^)(FIRHeartbeatsPayload *))completionHandler + API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)); #endif // FIREBASE_BUILD_CMAKE /// Gets today's corresponding heartbeat code. diff --git a/FirebaseCore/Sources/FIRHeartbeatLogger.m b/FirebaseCore/Sources/FIRHeartbeatLogger.m index 08cd396a99e..a5a2fcafe79 100644 --- a/FirebaseCore/Sources/FIRHeartbeatLogger.m +++ b/FirebaseCore/Sources/FIRHeartbeatLogger.m @@ -78,6 +78,13 @@ - (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { FIRHeartbeatsPayload *payload = [_heartbeatController flush]; return payload; } + +- (void)flushHeartbeatsIntoPayloadWithCompletionHandler: + (void (^)(FIRHeartbeatsPayload *))completionHandler { + [_heartbeatController flushAsyncWithCompletionHandler:^(FIRHeartbeatsPayload *payload) { + completionHandler(payload); + }]; +} #endif // FIREBASE_BUILD_CMAKE - (FIRDailyHeartbeatCode)heartbeatCodeForToday { diff --git a/FirebaseCore/Tests/Unit/FIRHeartbeatLoggerTests.m b/FirebaseCore/Tests/Unit/FIRHeartbeatLoggerTests.m index 27c69c9fe20..93f3d2bc879 100644 --- a/FirebaseCore/Tests/Unit/FIRHeartbeatLoggerTests.m +++ b/FirebaseCore/Tests/Unit/FIRHeartbeatLoggerTests.m @@ -125,6 +125,30 @@ - (void)testFlushing_UsingV2API_WhenHeartbeatsAreStored_ReturnsNonEmptyPayload { }]; } +- (void)testFlushingAsync_UsingV2API_WhenHeartbeatsAreStored_ReturnsNonEmptyPayload API_AVAILABLE( + ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)) { + // Given + FIRHeartbeatLogger *heartbeatLogger = self.heartbeatLogger; + NSString *expectedDate = [[self class] formattedStringForDate:[NSDate date]]; + // When + [heartbeatLogger log]; + XCTestExpectation *expectation = [self expectationWithDescription:@"async flush"]; + [heartbeatLogger + flushHeartbeatsIntoPayloadWithCompletionHandler:^(FIRHeartbeatsPayload *heartbeatsPayload) { + // Then + [self assertEncodedPayloadHeader:FIRHeaderValueFromHeartbeatsPayload(heartbeatsPayload) + isEqualToPayloadJSON:@{ + @"version" : @2, + @"heartbeats" : @[ + @{@"agent" : @"dummy_agent", + @"dates" : @[ expectedDate ]} + ] + }]; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; +} + - (void)testFlushing_UsingV2API_WhenNoHeartbeatsAreStored_ReturnsEmptyPayload { // Given FIRHeartbeatLogger *heartbeatLogger = self.heartbeatLogger; @@ -134,6 +158,21 @@ - (void)testFlushing_UsingV2API_WhenNoHeartbeatsAreStored_ReturnsEmptyPayload { [self assertHeartbeatsPayloadIsEmpty:heartbeatsPayload]; } +- (void)testFlushingAsync_UsingV2API_WhenNoHeartbeatsAreStored_ReturnsEmptyPayload API_AVAILABLE( + ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)) { + // Given + FIRHeartbeatLogger *heartbeatLogger = self.heartbeatLogger; + // When + XCTestExpectation *expectation = [self expectationWithDescription:@"async flush"]; + [heartbeatLogger + flushHeartbeatsIntoPayloadWithCompletionHandler:^(FIRHeartbeatsPayload *heartbeatsPayload) { + // Then + [self assertHeartbeatsPayloadIsEmpty:heartbeatsPayload]; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; +} + - (void)testLogAndFlushUsingV1API_AndThenFlushAgainUsingV2API_FlushesHeartbeatInTheFirstFlush { // Given FIRHeartbeatLogger *heartbeatLogger = self.heartbeatLogger; From 71070861db711d875281ae78bae1a6ace442f31c Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 8 Oct 2024 19:31:53 -0400 Subject: [PATCH 178/258] [Vertex AI] Make `Schema` constructor and `DataType` enum internal (#13852) --- FirebaseVertexAI/CHANGELOG.md | 4 +- .../Sources/Types/Internal/DataType.swift | 40 ++++++ .../Sources/{ => Types/Public}/Schema.swift | 125 +++++------------- 3 files changed, 75 insertions(+), 94 deletions(-) create mode 100644 FirebaseVertexAI/Sources/Types/Internal/DataType.swift rename FirebaseVertexAI/Sources/{ => Types/Public}/Schema.swift (72%) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 3a194a4a8b4..3aa08503f17 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -11,9 +11,9 @@ renamed to `inlineData`; no functionality changes. (#13700) - [changed] **Breaking Change**: The property `citationSources` of `CitationMetadata` has been renamed to `citations`. (#13702) -- [changed] **Breaking Change**: The constructor for `Schema` is now deprecated; +- [changed] **Breaking Change**: The constructor for `Schema` is now internal; use the new static methods `Schema.string(...)`, `Schema.object(...)`, etc., - instead. (#13616) + instead. (#13852) - [changed] **Breaking Change**: The constructor for `FunctionDeclaration` now accepts an array of *optional* parameters instead of a list of *required* parameters; if a parameter is not listed as optional it is assumed to be diff --git a/FirebaseVertexAI/Sources/Types/Internal/DataType.swift b/FirebaseVertexAI/Sources/Types/Internal/DataType.swift new file mode 100644 index 00000000000..f995eacddf2 --- /dev/null +++ b/FirebaseVertexAI/Sources/Types/Internal/DataType.swift @@ -0,0 +1,40 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// A data type. +/// +/// Contains the set of OpenAPI [data types](https://spec.openapis.org/oas/v3.0.3#data-types). +enum DataType: String { + /// A `String` type. + case string = "STRING" + + /// A floating-point number type. + case number = "NUMBER" + + /// An integer type. + case integer = "INTEGER" + + /// A boolean type. + case boolean = "BOOLEAN" + + /// An array type. + case array = "ARRAY" + + /// An object type. + case object = "OBJECT" +} + +// MARK: - Codable Conformance + +extension DataType: Encodable {} diff --git a/FirebaseVertexAI/Sources/Schema.swift b/FirebaseVertexAI/Sources/Types/Public/Schema.swift similarity index 72% rename from FirebaseVertexAI/Sources/Schema.swift rename to FirebaseVertexAI/Sources/Types/Public/Schema.swift index dc435626b05..0ff5b32ba47 100644 --- a/FirebaseVertexAI/Sources/Schema.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Schema.swift @@ -35,8 +35,10 @@ public class Schema { case custom(String) } + let dataType: DataType + /// The data type. - public let type: DataType + public var type: String { dataType.rawValue } /// The format of the data. public let format: String? @@ -47,58 +49,22 @@ public class Schema { /// Indicates if the value may be null. public let nullable: Bool? - /// Possible values of the element of type ``DataType/string`` with "enum" format. + /// Possible values of the element of type "STRING" with "enum" format. public let enumValues: [String]? - /// Schema of the elements of type ``DataType/array``. + /// Schema of the elements of type `"ARRAY"`. public let items: Schema? - /// Properties of type ``DataType/object``. + /// Properties of type `"OBJECT"`. public let properties: [String: Schema]? - /// Required properties of type ``DataType/object``. + /// Required properties of type `"OBJECT"`. public let requiredProperties: [String]? - /// Constructs a new `Schema`. - /// - /// - Parameters: - /// - type: The data type. - /// - format: The format of the data; used only for primitive datatypes. - /// Supported formats: - /// - ``DataType/integer``: int32, int64 - /// - ``DataType/number``: float, double - /// - ``DataType/string``: enum - /// - description: A brief description of the parameter; may be formatted as Markdown. - /// - nullable: Indicates if the value may be null. - /// - enumValues: Possible values of the element of type ``DataType/string`` with "enum" format. - /// For example, an enum `Direction` may be defined as `["EAST", NORTH", "SOUTH", "WEST"]`. - /// - items: Schema of the elements of type ``DataType/array``. - /// - properties: Properties of type ``DataType/object``. - /// - requiredProperties: Required properties of type ``DataType/object``. - @available(*, deprecated, message: """ - Use static methods `string(description:format:nullable:)`, `number(description:format:nullable:)`, - etc., instead. - """) - public convenience init(type: DataType, format: String? = nil, description: String? = nil, - nullable: Bool? = nil, enumValues: [String]? = nil, items: Schema? = nil, - properties: [String: Schema]? = nil, - requiredProperties: [String]? = nil) { - self.init( - type: type, - format: format, - description: description, - nullable: nullable ?? false, - enumValues: enumValues, - items: items, - properties: properties, - requiredProperties: requiredProperties - ) - } - required init(type: DataType, format: String? = nil, description: String? = nil, nullable: Bool = false, enumValues: [String]? = nil, items: Schema? = nil, properties: [String: Schema]? = nil, requiredProperties: [String]? = nil) { - self.type = type + dataType = type self.format = format self.description = description self.nullable = nullable @@ -110,8 +76,8 @@ public class Schema { /// Returns a `Schema` representing a string value. /// - /// This schema instructs the model to produce data of type ``DataType/string``, which is suitable - /// for decoding into a Swift `String` (or `String?`, if `nullable` is set to `true`). + /// This schema instructs the model to produce data of type `"STRING"`, which is suitable for + /// decoding into a Swift `String` (or `String?`, if `nullable` is set to `true`). /// /// > Tip: If a specific set of string values should be generated by the model (for example, /// > "north", "south", "east", or "west"), use ``enumeration(values:description:nullable:)`` @@ -139,9 +105,9 @@ public class Schema { /// Returns a `Schema` representing an enumeration of string values. /// - /// This schema instructs the model to produce data of type ``DataType/string`` with the - /// `format` `"enum"`. This data is suitable for decoding into a Swift `String` (or `String?`, - /// if `nullable` is set to `true`), or an `enum` with strings as raw values. + /// This schema instructs the model to produce data of type `"STRING"` with the `format` `"enum"`. + /// This data is suitable for decoding into a Swift `String` (or `String?`, if `nullable` is set + /// to `true`), or an `enum` with strings as raw values. /// /// **Example:** /// The values `["north", "south", "east", "west"]` for an enumeration of directions. @@ -171,9 +137,9 @@ public class Schema { /// Returns a `Schema` representing a single-precision floating-point number. /// - /// This schema instructs the model to produce data of type ``DataType/number`` with the - /// `format` `"float"`, which is suitable for decoding into a Swift `Float` (or `Float?`, if - /// `nullable` is set to `true`). + /// This schema instructs the model to produce data of type `"NUMBER"` with the `format` + /// `"float"`, which is suitable for decoding into a Swift `Float` (or `Float?`, if `nullable` is + /// set to `true`). /// /// > Important: This `Schema` provides a hint to the model that it should generate a /// > single-precision floating-point number, a `float`, but only guarantees that the value will @@ -195,9 +161,9 @@ public class Schema { /// Returns a `Schema` representing a double-precision floating-point number. /// - /// This schema instructs the model to produce data of type ``DataType/number`` with the - /// `format` `"double"`, which is suitable for decoding into a Swift `Double` (or `Double?`, if - /// `nullable` is set to `true`). + /// This schema instructs the model to produce data of type `"NUMBER"` with the `format` + /// `"double"`, which is suitable for decoding into a Swift `Double` (or `Double?`, if `nullable` + /// is set to `true`). /// /// > Important: This `Schema` provides a hint to the model that it should generate a /// > double-precision floating-point number, a `double`, but only guarantees that the value will @@ -219,9 +185,9 @@ public class Schema { /// Returns a `Schema` representing an integer value. /// - /// This schema instructs the model to produce data of type ``DataType/integer``, which is - /// suitable for decoding into a Swift `Int` (or `Int?`, if `nullable` is set to `true`) or other - /// integer types (such as `Int32`) based on the expected size of values being generated. + /// This schema instructs the model to produce data of type `"INTEGER"`, which is suitable for + /// decoding into a Swift `Int` (or `Int?`, if `nullable` is set to `true`) or other integer types + /// (such as `Int32`) based on the expected size of values being generated. /// /// > Important: If a `format` of ``IntegerFormat/int32`` or ``IntegerFormat/int64`` is /// > specified, this provides a hint to the model that it should generate 32-bit or 64-bit @@ -249,8 +215,8 @@ public class Schema { /// Returns a `Schema` representing a boolean value. /// - /// This schema instructs the model to produce data of type ``DataType/boolean``, which is - /// suitable for decoding into a Swift `Bool` (or `Bool?`, if `nullable` is set to `true`). + /// This schema instructs the model to produce data of type `"BOOLEAN"`, which is suitable for + /// decoding into a Swift `Bool` (or `Bool?`, if `nullable` is set to `true`). /// /// - Parameters: /// - description: An optional description of what the boolean should contain or represent; may @@ -263,10 +229,10 @@ public class Schema { /// Returns a `Schema` representing an array. /// - /// This schema instructs the model to produce data of type ``DataType/array``, which has elements - /// of any other ``DataType`` (including nested ``DataType/array``s). This data is suitable for - /// decoding into many Swift collection types, including `Array`, holding elements of types - /// suitable for decoding from the respective `items` type. + /// This schema instructs the model to produce data of type `"ARRAY"`, which has elements of any + /// other data type (including nested `"ARRAY"`s). This data is suitable for decoding into many + /// Swift collection types, including `Array`, holding elements of types suitable for decoding + /// from the respective `items` type. /// /// - Parameters: /// - items: The `Schema` of the elements that the array will hold. @@ -281,10 +247,10 @@ public class Schema { /// Returns a `Schema` representing an object. /// - /// This schema instructs the model to produce data of type ``DataType/object``, which has keys - /// of type ``DataType/string`` and values of any other ``DataType`` (including nested - /// ``DataType/object``s). This data is suitable for decoding into Swift keyed collection types, - /// including `Dictionary`, or other custom `struct` or `class` types. + /// This schema instructs the model to produce data of type `"OBJECT"`, which has keys of type + /// `"STRING"` and values of any other data type (including nested `"OBJECT"`s). This data is + /// suitable for decoding into Swift keyed collection types, including `Dictionary`, or other + /// custom `struct` or `class` types. /// /// **Example:** A `City` could be represented with the following object `Schema`. /// ``` @@ -331,34 +297,11 @@ public class Schema { } } -/// A data type. -/// -/// Contains the set of OpenAPI [data types](https://spec.openapis.org/oas/v3.0.3#data-types). -public enum DataType: String { - /// A `String` type. - case string = "STRING" - - /// A floating-point number type. - case number = "NUMBER" - - /// An integer type. - case integer = "INTEGER" - - /// A boolean type. - case boolean = "BOOLEAN" - - /// An array type. - case array = "ARRAY" - - /// An object type. - case object = "OBJECT" -} - // MARK: - Codable Conformance extension Schema: Encodable { enum CodingKeys: String, CodingKey { - case type + case dataType = "type" case format case description case nullable @@ -369,8 +312,6 @@ extension Schema: Encodable { } } -extension DataType: Encodable {} - // MARK: - RawRepresentable Conformance extension Schema.IntegerFormat: RawRepresentable { From 1e63a556a0056f814cbca7e8fa0594991ac7a093 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 8 Oct 2024 20:47:21 -0400 Subject: [PATCH 179/258] [Vertex AI] Use `struct` instead of `enum` for `HarmProbability` (#13854) --- FirebaseVertexAI/CHANGELOG.md | 7 +- .../ChatSample/Views/ErrorDetailsView.swift | 5 +- FirebaseVertexAI/Sources/Safety.swift | 79 +++++++++++++------ .../Tests/Unit/GenerativeModelTests.swift | 7 +- 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 3aa08503f17..5e90031a137 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -36,9 +36,10 @@ as input. (#13767) - [changed] **Breaking Change**: All initializers for `ModelContent` now require the label `parts: `. (#13832) -- [changed] **Breaking Change**: `HarmCategory` is now a struct instead of an - enum type and the `unknown` case has been removed; in a `switch` statement, - use the `default:` case to cover unknown or unhandled categories. (#13728) +- [changed] **Breaking Change**: `HarmCategory` and `HarmProbability` are now + structs instead of enums types and the `unknown` cases have been removed; in a + `switch` statement, use the `default:` case to cover unknown or unhandled + categories or probabilities. (#13728, #13854) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index 11ba86bb1b9..279f02b81fc 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -25,8 +25,7 @@ private extension HarmCategory { case .hateSpeech: "Hate speech" case .sexuallyExplicit: "Sexually explicit" case .civicIntegrity: "Civic integrity" - default: - "Unknown HarmCategory: \(rawValue)" + default: "Unknown HarmCategory: \(rawValue)" } } } @@ -39,7 +38,7 @@ private extension SafetyRating.HarmProbability { case .low: "Low" case .medium: "Medium" case .negligible: "Negligible" - case .unknown: "Unknown" + default: "Unknown HarmProbability: \(rawValue)" } } } diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 3f6ce4658c1..280771d0074 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -38,24 +38,66 @@ public struct SafetyRating: Equatable, Hashable, Sendable { self.probability = probability } - /// The probability that a given model output falls under a harmful content category. This does - /// not indicate the severity of harm for a piece of content. - public enum HarmProbability: String, Sendable { - /// Unknown. A new server value that isn't recognized by the SDK. - case unknown = "UNKNOWN" + /// The probability that a given model output falls under a harmful content category. + /// + /// > Note: This does not indicate the severity of harm for a piece of content. + public struct HarmProbability: Sendable, Equatable, Hashable { + enum Kind: String { + case negligible = "NEGLIGIBLE" + case low = "LOW" + case medium = "MEDIUM" + case high = "HIGH" + } - /// The probability is zero or close to zero. For benign content, the probability across all - /// categories will be this value. - case negligible = "NEGLIGIBLE" + /// The probability is zero or close to zero. + /// + /// For benign content, the probability across all categories will be this value. + public static var negligible: HarmProbability { + return self.init(kind: .negligible) + } /// The probability is small but non-zero. - case low = "LOW" + public static var low: HarmProbability { + return self.init(kind: .low) + } /// The probability is moderate. - case medium = "MEDIUM" + public static var medium: HarmProbability { + return self.init(kind: .medium) + } + + /// The probability is high. + /// + /// The content described is very likely harmful. + public static var high: HarmProbability { + return self.init(kind: .high) + } + + /// Returns the raw string representation of the `HarmProbability` value. + /// + /// > Note: This value directly corresponds to the values in the [REST + /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#SafetyRating). + public let rawValue: String - /// The probability is high. The content described is very likely harmful. - case high = "HIGH" + init(kind: Kind) { + rawValue = kind.rawValue + } + + init(rawValue: String) { + if Kind(rawValue: rawValue) == nil { + VertexLog.error( + code: .generateContentResponseUnrecognizedHarmProbability, + """ + Unrecognized HarmProbability with value "\(rawValue)": + - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ + release notes at https://firebase.google.com/support/release-notes/ios + - Search for "\(rawValue)" in the Firebase Apple SDK Issue Tracker at \ + https://github.com/firebase/firebase-ios-sdk/issues and file a Bug Report if none found + """ + ) + } + self.rawValue = rawValue + } } } @@ -163,17 +205,8 @@ public struct HarmCategory: Sendable, Equatable, Hashable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetyRating.HarmProbability: Decodable { public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedProbability = SafetyRating.HarmProbability(rawValue: value) else { - VertexLog.error( - code: .generateContentResponseUnrecognizedHarmProbability, - "Unrecognized HarmProbability with value \"\(value)\"." - ) - self = .unknown - return - } - - self = decodedProbability + let rawValue = try decoder.singleValueContainer().decode(String.self) + self = SafetyRating.HarmProbability(rawValue: rawValue) } } diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 254f81e96fb..21076991003 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -162,7 +162,10 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_success_unknownEnum_safetyRatings() async throws { let expectedSafetyRatings = [ SafetyRating(category: .harassment, probability: .medium), - SafetyRating(category: .dangerousContent, probability: .unknown), + SafetyRating( + category: .dangerousContent, + probability: SafetyRating.HarmProbability(rawValue: "FAKE_NEW_HARM_PROBABILITY") + ), SafetyRating(category: HarmCategory(rawValue: "FAKE_NEW_HARM_CATEGORY"), probability: .high), ] MockURLProtocol @@ -974,7 +977,7 @@ final class GenerativeModelTests: XCTestCase { ) let unknownSafetyRating = SafetyRating( category: HarmCategory(rawValue: "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM"), - probability: .unknown + probability: SafetyRating.HarmProbability(rawValue: "NEGLIGIBLE_UNKNOWN_ENUM") ) var foundUnknownSafetyRating = false From c3f8bc9391cfc7994b18318d91b2354bd75a42bb Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:21:39 -0400 Subject: [PATCH 180/258] [Auth] Use async flush method to get header value (#13853) --- .../Sources/Swift/Backend/AuthBackend.swift | 17 ++++++++--------- .../AuthBackendRPCImplementationTests.swift | 2 +- FirebaseCore/Extension/FIRHeartbeatLogger.h | 15 ++++++++++----- FirebaseCore/Sources/FIRHeartbeatLogger.m | 7 +++++++ .../Unit/FIRInstallationsAPIServiceTests.m | 5 +++++ .../UnitTests/FIRMessagingTokenOperationsTest.m | 5 +++++ 6 files changed, 36 insertions(+), 15 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 84bf5ec55c9..24726262840 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -98,6 +98,11 @@ class AuthBackend { class func request(withURL url: URL, contentType: String, requestConfiguration: AuthRequestConfiguration) async -> URLRequest { + // Kick off tasks for the async header values. + async let heartbeatsHeaderValue = requestConfiguration.heartbeatLogger?.asyncHeaderValue() + async let appCheckTokenHeaderValue = requestConfiguration.appCheck? + .getToken(forcingRefresh: true) + var request = URLRequest(url: url) request.setValue(contentType, forHTTPHeaderField: "Content-Type") let additionalFrameworkMarker = requestConfiguration @@ -106,13 +111,6 @@ class AuthBackend { request.setValue(clientVersion, forHTTPHeaderField: "X-Client-Version") request.setValue(Bundle.main.bundleIdentifier, forHTTPHeaderField: "X-Ios-Bundle-Identifier") request.setValue(requestConfiguration.appID, forHTTPHeaderField: "X-Firebase-GMPID") - if let heartbeatLogger = requestConfiguration.heartbeatLogger { - // The below call synchronously dispatches to a queue. To avoid blocking - // the shared concurrency queue, `async let` will spawn the process on - // a separate thread. - async let heartbeatsHeaderValue = heartbeatLogger.headerValue() - await request.setValue(heartbeatsHeaderValue, forHTTPHeaderField: "X-Firebase-Client") - } request.httpMethod = requestConfiguration.httpMethod let preferredLocalizations = Bundle.main.preferredLocalizations if preferredLocalizations.count > 0 { @@ -122,8 +120,9 @@ class AuthBackend { languageCode.count > 0 { request.setValue(languageCode, forHTTPHeaderField: "X-Firebase-Locale") } - if let appCheck = requestConfiguration.appCheck { - let tokenResult = await appCheck.getToken(forcingRefresh: false) + // Wait for the async header values. + await request.setValue(heartbeatsHeaderValue, forHTTPHeaderField: "X-Firebase-Client") + if let tokenResult = await appCheckTokenHeaderValue { if let error = tokenResult.error { AuthLog.logWarning(code: "I-AUT000018", message: "Error getting App Check token; using placeholder " + diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift index 436d777ca10..754fa761a54 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift @@ -534,7 +534,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { #if COCOAPODS || SWIFT_PACKAGE private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol { - func headerValue() -> String? { + func asyncHeaderValue() async -> String? { let payload = flushHeartbeatsIntoPayload() guard !payload.isEmpty else { return nil diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 65793b3db75..12986f8ec97 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -35,14 +35,19 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) { /// Asynchronously logs a heartbeat. - (void)log; -#ifndef FIREBASE_BUILD_CMAKE -/// Return the headerValue for the HeartbeatLogger. -- (NSString *_Nullable)headerValue; -#endif // FIREBASE_BUILD_CMAKE - /// Gets the heartbeat code for today. - (FIRDailyHeartbeatCode)heartbeatCodeForToday; +#ifndef FIREBASE_BUILD_CMAKE +/// Returns the header value for the heartbeat logger via the given completion handler.. +- (void)asyncHeaderValueWithCompletionHandler:(void (^)(NSString *_Nullable))completionHandler + API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)); + +/// Return the header value for the heartbeat logger. +- (NSString *_Nullable) + headerValue NS_SWIFT_UNAVAILABLE("Use `asyncHeaderValue() async -> String?` instead."); +#endif // FIREBASE_BUILD_CMAKE + @end #ifndef FIREBASE_BUILD_CMAKE diff --git a/FirebaseCore/Sources/FIRHeartbeatLogger.m b/FirebaseCore/Sources/FIRHeartbeatLogger.m index a5a2fcafe79..d1c77ee4f2f 100644 --- a/FirebaseCore/Sources/FIRHeartbeatLogger.m +++ b/FirebaseCore/Sources/FIRHeartbeatLogger.m @@ -74,6 +74,13 @@ - (NSString *_Nullable)headerValue { return FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]); } +- (void)asyncHeaderValueWithCompletionHandler:(void (^)(NSString *_Nullable))completionHandler + API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)) { + [self flushHeartbeatsIntoPayloadWithCompletionHandler:^(FIRHeartbeatsPayload *payload) { + completionHandler(FIRHeaderValueFromHeartbeatsPayload(payload)); + }]; +} + - (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { FIRHeartbeatsPayload *payload = [_heartbeatController flush]; return payload; diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsAPIServiceTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsAPIServiceTests.m index f54f1ea809d..93831313c45 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsAPIServiceTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsAPIServiceTests.m @@ -69,6 +69,11 @@ - (NSString *_Nullable)headerValue { return FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]); } +- (void)asyncHeaderValueWithCompletionHandler: + (nonnull void (^)(NSString *_Nullable))completionHandler { + [self doesNotRecognizeSelector:_cmd]; +} + @end #pragma mark - FIRInstallationsAPIService + Internal diff --git a/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenOperationsTest.m b/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenOperationsTest.m index 911c2a870d3..0459a8d3e38 100644 --- a/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenOperationsTest.m +++ b/FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenOperationsTest.m @@ -102,6 +102,11 @@ - (NSString *_Nullable)headerValue { return @"unimplemented"; } +- (void)asyncHeaderValueWithCompletionHandler: + (nonnull void (^)(NSString *_Nullable))completionHandler { + [self doesNotRecognizeSelector:_cmd]; +} + @end #pragma mark - FIRMessagingTokenOperationsTest From 2b75c5c9d185a1715165e0e144be4b96af1c4ce3 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 9 Oct 2024 08:23:18 -0700 Subject: [PATCH 181/258] Run fewer tests to reduce flakiness (#13857) --- .github/workflows/crashlytics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index fc303e70561..5638d5d35bc 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -27,7 +27,7 @@ jobs: target: [ios, tvos, macos, watchos --skip-tests] os: [macos-14] flags: [ - '--use-modular-headers', + '--use-modular-headers --skip-tests', '' ] xcode: [Xcode_15.2, Xcode_16] From 0e3e20d23a1f2e11b94616248ce91e7c58f6f368 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:23:40 -0400 Subject: [PATCH 182/258] [Auth] Restore MFA request setup to Firebase 10 behavior (#13858) --- FirebaseAuth/CHANGELOG.md | 4 ++++ .../Enroll/FinalizeMFAEnrollmentRequest.swift | 10 +++++----- .../Unit/FinalizeMFAEnrollmentRequestTests.swift | 12 ++++++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 71cb75a1d05..c677e63f057 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased +- [Fixed] Restore Firebase 10 behavior by ignoring `nil` display names used + during multi factor enrollment. (#13856) + # 11.3.0 - [Fixed] Restore Firebase 10 behavior by querying with the `kSecAttrSynchronizable` key when auth state is set to be shared across diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift index 773b448a136..e7c478e23bd 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift @@ -64,11 +64,11 @@ class FinalizeMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { } if let displayName = displayName { body["displayName"] = displayName - if let phoneVerificationInfo { - body["phoneVerificationInfo"] = phoneVerificationInfo.dictionary - } else if let totpVerificationInfo { - body["totpVerificationInfo"] = totpVerificationInfo.dictionary - } + } + if let phoneVerificationInfo { + body["phoneVerificationInfo"] = phoneVerificationInfo.dictionary + } else if let totpVerificationInfo { + body["totpVerificationInfo"] = totpVerificationInfo.dictionary } if let tenantID = tenantID { diff --git a/FirebaseAuth/Tests/Unit/FinalizeMFAEnrollmentRequestTests.swift b/FirebaseAuth/Tests/Unit/FinalizeMFAEnrollmentRequestTests.swift index e5143ca7058..5e306fa6c10 100644 --- a/FirebaseAuth/Tests/Unit/FinalizeMFAEnrollmentRequestTests.swift +++ b/FirebaseAuth/Tests/Unit/FinalizeMFAEnrollmentRequestTests.swift @@ -29,6 +29,14 @@ class FinalizeMFAEnrollmentRequestTests: RPCBaseTests { @brief Tests the Finalize MFA Enrollment using TOTP request. */ func testTOTPStartMFAEnrollmentRequest() async throws { + try await assertTOTPStartMFAEnrollmentRequest(displayName: "sparky") + } + + func testTOTPStartMFAEnrollmentRequest_WhenDisplayNameIsNil() async throws { + try await assertTOTPStartMFAEnrollmentRequest(displayName: nil) + } + + func assertTOTPStartMFAEnrollmentRequest(displayName: String?) async throws { let kIDToken = "idToken" let kDisplayName = "displayName" let kSessionInfo = "sessionInfo" @@ -40,7 +48,7 @@ class FinalizeMFAEnrollmentRequestTests: RPCBaseTests { let requestInfo = AuthProtoFinalizeMFATOTPEnrollmentRequestInfo(sessionInfo: kSessionInfo, verificationCode: kVerificationCode) let request = FinalizeMFAEnrollmentRequest(idToken: kIDToken, - displayName: kDisplayName, + displayName: displayName, totpVerificationInfo: requestInfo, requestConfiguration: requestConfiguration) @@ -55,7 +63,7 @@ class FinalizeMFAEnrollmentRequestTests: RPCBaseTests { value: kIDToken ) let requestDictionary = try XCTUnwrap(rpcIssuer.decodedRequest as? [String: AnyHashable]) - XCTAssertEqual(requestDictionary[kDisplayName], kDisplayName) + XCTAssertEqual(requestDictionary[kDisplayName], displayName) let totpInfo = try XCTUnwrap(requestDictionary[kTOTPVerificationInfo] as? [String: String]) XCTAssertEqual(totpInfo["verificationCode"], kVerificationCode) XCTAssertNil(requestDictionary[kPhoneVerificationInfo]) From 492e488765a022dc4544a1f3cac718c8c3dcd096 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 14:38:54 -0400 Subject: [PATCH 183/258] [Vertex AI] Extract common protobuf enum to struct decoding logic (#13859) --- .../Protocols/Internal/CodableProtoEnum.swift | 86 +++++++++++++++++++ FirebaseVertexAI/Sources/Safety.swift | 60 ++----------- 2 files changed, 92 insertions(+), 54 deletions(-) create mode 100644 FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift diff --git a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift new file mode 100644 index 00000000000..e7f4ebfd1b7 --- /dev/null +++ b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift @@ -0,0 +1,86 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// A type that can be decoded from a Protocol Buffer raw enum value. +/// +/// Protobuf enums are represented as strings in JSON. A default `Decodable` implementation is +/// provided when conforming to this type. +protocol DecodableProtoEnum: Decodable { + /// The type representing the valid values for the protobuf enum. + /// + /// > Important: This type must conform to `RawRepresentable` with the `RawValue == String`. + /// + /// This is typically a Swift enum, e.g.: + /// ``` + /// enum Kind: String { + /// case north = "WIND_DIRECTION_NORTH" + /// case south = "WIND_DIRECTION_SOUTH" + /// case east = "WIND_DIRECTION_EAST" + /// case west = "WIND_DIRECTION_WEST" + /// } + /// ``` + associatedtype Kind: RawRepresentable + + /// Returns the ``VertexLog/MessageCode`` associated with unrecognized (unknown) enum values. + var unrecognizedValueMessageCode: VertexLog.MessageCode { get } + + /// Create a new instance of the specified type from a raw enum value. + init(rawValue: String) + + /// Creates a new instance from the ``Kind``'s raw value. + /// + /// > Important: A default implementation is provided. + init(kind: Kind) + + /// Creates a new instance by decoding from the given decoder. + /// + /// > Important: A default implementation is provided. + init(from decoder: Decoder) throws +} + +/// Default `Decodable` implementation for types conforming to `DecodableProtoEnum`. +extension DecodableProtoEnum { + // Note: Initializer 'init(from:)' must be declared public because it matches a requirement in + // public protocol 'Decodable'. + public init(from decoder: Decoder) throws { + let rawValue = try decoder.singleValueContainer().decode(String.self) + + self = Self(rawValue: rawValue) + + if Kind(rawValue: rawValue) == nil { + VertexLog.error( + code: unrecognizedValueMessageCode, + """ + Unrecognized \(Self.self) with value "\(rawValue)": + - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ + release notes at https://firebase.google.com/support/release-notes/ios + - Search for "\(rawValue)" in the Firebase Apple SDK Issue Tracker at \ + https://github.com/firebase/firebase-ios-sdk/issues and file a Bug Report if none found + """ + ) + } + } +} + +/// Default implementation of `init(kind: Kind)` for types conforming to `DecodableProtoEnum`. +extension DecodableProtoEnum { + init(kind: Kind) { + self = Self(rawValue: kind.rawValue) + } +} + +/// A type that can be decoded and encoded from a Protocol Buffer raw enum value. +/// +/// See ``DecodableProtoEnum`` for more details. +protocol CodableProtoEnum: DecodableProtoEnum, Encodable {} diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 280771d0074..a3d548fd524 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -41,7 +41,7 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// The probability that a given model output falls under a harmful content category. /// /// > Note: This does not indicate the severity of harm for a piece of content. - public struct HarmProbability: Sendable, Equatable, Hashable { + public struct HarmProbability: DecodableProtoEnum, Hashable, Sendable { enum Kind: String { case negligible = "NEGLIGIBLE" case low = "LOW" @@ -79,24 +79,8 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#SafetyRating). public let rawValue: String - init(kind: Kind) { - rawValue = kind.rawValue - } - - init(rawValue: String) { - if Kind(rawValue: rawValue) == nil { - VertexLog.error( - code: .generateContentResponseUnrecognizedHarmProbability, - """ - Unrecognized HarmProbability with value "\(rawValue)": - - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ - release notes at https://firebase.google.com/support/release-notes/ios - - Search for "\(rawValue)" in the Firebase Apple SDK Issue Tracker at \ - https://github.com/firebase/firebase-ios-sdk/issues and file a Bug Report if none found - """ - ) - } - self.rawValue = rawValue + var unrecognizedValueMessageCode: VertexLog.MessageCode { + .generateContentResponseUnrecognizedHarmProbability } } } @@ -139,7 +123,7 @@ public struct SafetySetting { } /// Categories describing the potential harm a piece of content may pose. -public struct HarmCategory: Sendable, Equatable, Hashable { +public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { enum Kind: String { case harassment = "HARM_CATEGORY_HARASSMENT" case hateSpeech = "HARM_CATEGORY_HATE_SPEECH" @@ -179,48 +163,16 @@ public struct HarmCategory: Sendable, Equatable, Hashable { /// > [REST API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/HarmCategory). public let rawValue: String - init(kind: Kind) { - rawValue = kind.rawValue - } - - init(rawValue: String) { - if Kind(rawValue: rawValue) == nil { - VertexLog.error( - code: .generateContentResponseUnrecognizedHarmCategory, - """ - Unrecognized HarmCategory with value "\(rawValue)": - - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ - release notes at https://firebase.google.com/support/release-notes/ios - - Search for "\(rawValue)" in the Firebase Apple SDK Issue Tracker at \ - https://github.com/firebase/firebase-ios-sdk/issues and file a Bug Report if none found - """ - ) - } - self.rawValue = rawValue + var unrecognizedValueMessageCode: VertexLog.MessageCode { + .generateContentResponseUnrecognizedHarmCategory } } // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetyRating.HarmProbability: Decodable { - public init(from decoder: Decoder) throws { - let rawValue = try decoder.singleValueContainer().decode(String.self) - self = SafetyRating.HarmProbability(rawValue: rawValue) - } -} - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetyRating: Decodable {} -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension HarmCategory: Codable { - public init(from decoder: Decoder) throws { - let rawValue = try decoder.singleValueContainer().decode(String.self) - self = HarmCategory(rawValue: rawValue) - } -} - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting.HarmBlockThreshold: Encodable {} From 20c94134ba70c942aad908782d144c543a961456 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 16:30:43 -0400 Subject: [PATCH 184/258] [Vertex AI] Refactor `FinishReason` as a `struct` and add new values (#13860) --- FirebaseVertexAI/CHANGELOG.md | 8 +- .../Sources/GenerateContentResponse.swift | 87 +++++++++++++------ .../Tests/Unit/GenerativeModelTests.swift | 6 +- 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 5e90031a137..f5f072975c7 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -36,10 +36,10 @@ as input. (#13767) - [changed] **Breaking Change**: All initializers for `ModelContent` now require the label `parts: `. (#13832) -- [changed] **Breaking Change**: `HarmCategory` and `HarmProbability` are now - structs instead of enums types and the `unknown` cases have been removed; in a - `switch` statement, use the `default:` case to cover unknown or unhandled - categories or probabilities. (#13728, #13854) +- [changed] **Breaking Change**: `HarmCategory`, `HarmProbability`, and + `FinishReason` are now structs instead of enums types and the `unknown` cases + have been removed; in a `switch` statement, use the `default:` case to cover + unknown or unhandled values. (#13728, #13854, #13860) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index c4ca48f2264..079b759d26f 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -145,26 +145,76 @@ public struct Citation: Sendable { /// A value enumerating possible reasons for a model to terminate a content generation request. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public enum FinishReason: String, Sendable { - /// The finish reason is unknown. - case unknown = "FINISH_REASON_UNKNOWN" +public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { + enum Kind: String { + case stop = "STOP" + case maxTokens = "MAX_TOKENS" + case safety = "SAFETY" + case recitation = "RECITATION" + case other = "OTHER" + case blocklist = "BLOCKLIST" + case prohibitedContent = "PROHIBITED_CONTENT" + case spii = "SPII" + case malformedFunctionCall = "MALFORMED_FUNCTION_CALL" + } /// Natural stop point of the model or provided stop sequence. - case stop = "STOP" + public static var stop: FinishReason { + return self.init(kind: .stop) + } /// The maximum number of tokens as specified in the request was reached. - case maxTokens = "MAX_TOKENS" + public static var maxTokens: FinishReason { + return self.init(kind: .maxTokens) + } /// The token generation was stopped because the response was flagged for safety reasons. - /// NOTE: When streaming, the Candidate.content will be empty if content filters blocked the - /// output. - case safety = "SAFETY" + /// + /// > NOTE: When streaming, the ``CandidateResponse/content`` will be empty if content filters + /// > blocked the output. + public static var safety: FinishReason { + return self.init(kind: .safety) + } /// The token generation was stopped because the response was flagged for unauthorized citations. - case recitation = "RECITATION" + public static var recitation: FinishReason { + return self.init(kind: .recitation) + } /// All other reasons that stopped token generation. - case other = "OTHER" + public static var other: FinishReason { + return self.init(kind: .other) + } + + /// Token generation was stopped because the response contained forbidden terms. + public static var blocklist: FinishReason { + return self.init(kind: .blocklist) + } + + /// Token generation was stopped because the response contained potentially prohibited content. + public static var prohibitedContent: FinishReason { + return self.init(kind: .prohibitedContent) + } + + /// Token generation was stopped because of Sensitive Personally Identifiable Information (SPII). + public static var spii: FinishReason { + return self.init(kind: .spii) + } + + /// Token generation was stopped because the function call generated by the model was invalid. + public static var malformedFunctionCall: FinishReason { + return self.init(kind: .malformedFunctionCall) + } + + /// Returns the raw string representation of the `FinishReason` value. + /// + /// > Note: This value directly corresponds to the values in the [REST + /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#FinishReason). + public let rawValue: String + + var unrecognizedValueMessageCode: VertexLog.MessageCode { + .generateContentResponseUnrecognizedFinishReason + } } /// A metadata struct containing any feedback the model had on the prompt it was provided. @@ -333,23 +383,6 @@ extension Citation: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension FinishReason: Decodable { - public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedFinishReason = FinishReason(rawValue: value) else { - VertexLog.error( - code: .generateContentResponseUnrecognizedFinishReason, - "Unrecognized FinishReason with value \"\(value)\"." - ) - self = .unknown - return - } - - self = decodedFinishReason - } -} - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension PromptFeedback.BlockReason: Decodable { public init(from decoder: Decoder) throws { diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 21076991003..ae0f55361fa 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -608,12 +608,13 @@ final class GenerativeModelTests: XCTestCase { forResource: "unary-failure-unknown-enum-finish-reason", withExtension: "json" ) + let unknownFinishReason = FinishReason(rawValue: "FAKE_NEW_FINISH_REASON") do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw") } catch let GenerateContentError.responseStoppedEarly(reason, response) { - XCTAssertEqual(reason, .unknown) + XCTAssertEqual(reason, unknownFinishReason) XCTAssertEqual(response.text, "Some text") } catch { XCTFail("Should throw a responseStoppedEarly") @@ -921,6 +922,7 @@ final class GenerativeModelTests: XCTestCase { forResource: "streaming-failure-unknown-finish-enum", withExtension: "txt" ) + let unknownFinishReason = FinishReason(rawValue: "FAKE_ENUM") let stream = try model.generateContentStream("Hi") do { @@ -928,7 +930,7 @@ final class GenerativeModelTests: XCTestCase { XCTAssertNotNil(content.text) } } catch let GenerateContentError.responseStoppedEarly(reason, _) { - XCTAssertEqual(reason, .unknown) + XCTAssertEqual(reason, unknownFinishReason) return } From 67502af7fdd07be6644b84f315fac432b53718d1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 17:12:37 -0400 Subject: [PATCH 185/258] [Vertex AI] Add `EncodableProtoEnum` protocol and fix encoding (#13862) --- .../Protocols/Internal/CodableProtoEnum.swift | 65 ++++++++++++++----- .../Tests/Integration/IntegrationTests.swift | 8 +++ 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift index e7f4ebfd1b7..f73271a1acc 100644 --- a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift +++ b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// A type that can be decoded from a Protocol Buffer raw enum value. -/// -/// Protobuf enums are represented as strings in JSON. A default `Decodable` implementation is -/// provided when conforming to this type. -protocol DecodableProtoEnum: Decodable { +/// A type that represents a Protocol Buffer raw enum value. +protocol ProtoEnum { /// The type representing the valid values for the protobuf enum. /// /// > Important: This type must conform to `RawRepresentable` with the `RawValue == String`. @@ -32,8 +29,8 @@ protocol DecodableProtoEnum: Decodable { /// ``` associatedtype Kind: RawRepresentable - /// Returns the ``VertexLog/MessageCode`` associated with unrecognized (unknown) enum values. - var unrecognizedValueMessageCode: VertexLog.MessageCode { get } + /// Returns the raw string value of the enum. + var rawValue: String { get } /// Create a new instance of the specified type from a raw enum value. init(rawValue: String) @@ -42,14 +39,48 @@ protocol DecodableProtoEnum: Decodable { /// /// > Important: A default implementation is provided. init(kind: Kind) +} + +/// A type that can be decoded from a Protocol Buffer raw enum value. +/// +/// Protobuf enums are represented as strings in JSON. A default `Decodable` implementation is +/// provided when conforming to this type. +protocol DecodableProtoEnum: ProtoEnum, Decodable { + /// Returns the ``VertexLog/MessageCode`` associated with unrecognized (unknown) enum values. + var unrecognizedValueMessageCode: VertexLog.MessageCode { get } /// Creates a new instance by decoding from the given decoder. /// /// > Important: A default implementation is provided. - init(from decoder: Decoder) throws + init(from decoder: any Decoder) throws } -/// Default `Decodable` implementation for types conforming to `DecodableProtoEnum`. +/// A type that can be encoded as a Protocol Buffer enum value. +/// +/// Protobuf enums are represented as strings in JSON. A default `Encodable` implementation is +/// provided when conforming to this type. +protocol EncodableProtoEnum: ProtoEnum, Encodable { + /// Encodes this value into the given encoder. + /// + /// > Important: A default implementation is provided. + func encode(to encoder: any Encoder) throws +} + +/// A type that can be decoded and encoded from a Protocol Buffer raw enum value. +/// +/// See ``ProtoEnum``, ``DecodableProtoEnum`` and ``EncodableProtoEnum`` for more details. +protocol CodableProtoEnum: DecodableProtoEnum, EncodableProtoEnum {} + +// MARK: - Default Implementations + +// Default implementation of `init(kind: Kind)` for types conforming to `ProtoEnum`. +extension ProtoEnum { + init(kind: Kind) { + self = Self(rawValue: kind.rawValue) + } +} + +// Default `Decodable` implementation for types conforming to `DecodableProtoEnum`. extension DecodableProtoEnum { // Note: Initializer 'init(from:)' must be declared public because it matches a requirement in // public protocol 'Decodable'. @@ -73,14 +104,12 @@ extension DecodableProtoEnum { } } -/// Default implementation of `init(kind: Kind)` for types conforming to `DecodableProtoEnum`. -extension DecodableProtoEnum { - init(kind: Kind) { - self = Self(rawValue: kind.rawValue) +// Default `Encodable` implementation for types conforming to `EncodableProtoEnum`. +extension EncodableProtoEnum { + // Note: Method 'encode(to:)' must be declared public because it matches a requirement in public + // protocol 'Encodable'. + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) } } - -/// A type that can be decoded and encoded from a Protocol Buffer raw enum value. -/// -/// See ``DecodableProtoEnum`` for more details. -protocol CodableProtoEnum: DecodableProtoEnum, Encodable {} diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index d2789c574df..a1ee926273f 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -29,6 +29,13 @@ final class IntegrationTests: XCTestCase { role: "system", parts: "You are a friendly and helpful assistant." ) + let safetySettings = [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .dangerousContent, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .civicIntegrity, threshold: .blockLowAndAbove), + ] var vertex: VertexAI! var model: GenerativeModel! @@ -50,6 +57,7 @@ final class IntegrationTests: XCTestCase { model = vertex.generativeModel( modelName: "gemini-1.5-flash", generationConfig: generationConfig, + safetySettings: safetySettings, tools: [], systemInstruction: systemInstruction ) From 27cffd92bcae0198f68b899d769c7c76c9715c56 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 18:29:40 -0400 Subject: [PATCH 186/258] [Vertex AI] Refactor `HarmBlockThreshold` as a struct and add `.off` (#13863) --- FirebaseVertexAI/CHANGELOG.md | 6 ++++ FirebaseVertexAI/Sources/Safety.swift | 35 +++++++++++++++---- .../Tests/Integration/IntegrationTests.swift | 12 +++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index f5f072975c7..b6444fb81f0 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -46,6 +46,12 @@ - [changed] The response from `GenerativeModel.countTokens(...)` now includes `systemInstruction`, `tools` and `generationConfig` in the `totalTokens` and `totalBillableCharacters` counts, where applicable. (#13813) +- [added] Added a new `HarmCategory` `.civicIntegrity` for filtering content + that may be used to harm civic integrity. (#13728) +- [added] Added a new `HarmBlockThreshold` `.off`, which turns off the safety + filter. (#13863) +- [added] Added new `FinishReason` values `.blocklist`, `.prohibitedContent`, + `.spii` and `.malformedFunctionCall` that may be reported. (#13860) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index a3d548fd524..4e93a94bf45 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -90,18 +90,41 @@ public struct SafetyRating: Equatable, Hashable, Sendable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetySetting { /// Block at and beyond a specified ``SafetyRating/HarmProbability``. - public enum HarmBlockThreshold: String, Sendable { - // Content with `.negligible` will be allowed. - case blockLowAndAbove = "BLOCK_LOW_AND_ABOVE" + public struct HarmBlockThreshold: EncodableProtoEnum, Sendable { + enum Kind: String { + case blockLowAndAbove = "BLOCK_LOW_AND_ABOVE" + case blockMediumAndAbove = "BLOCK_MEDIUM_AND_ABOVE" + case blockOnlyHigh = "BLOCK_ONLY_HIGH" + case blockNone = "BLOCK_NONE" + case off = "OFF" + } + + /// Content with `.negligible` will be allowed. + public static var blockLowAndAbove: HarmBlockThreshold { + return self.init(kind: .blockLowAndAbove) + } /// Content with `.negligible` and `.low` will be allowed. - case blockMediumAndAbove = "BLOCK_MEDIUM_AND_ABOVE" + public static var blockMediumAndAbove: HarmBlockThreshold { + return self.init(kind: .blockMediumAndAbove) + } /// Content with `.negligible`, `.low`, and `.medium` will be allowed. - case blockOnlyHigh = "BLOCK_ONLY_HIGH" + public static var blockOnlyHigh: HarmBlockThreshold { + return self.init(kind: .blockOnlyHigh) + } /// All content will be allowed. - case blockNone = "BLOCK_NONE" + public static var blockNone: HarmBlockThreshold { + return self.init(kind: .blockNone) + } + + /// Turn off the safety filter. + public static var off: HarmBlockThreshold { + return self.init(kind: .off) + } + + let rawValue: String } enum CodingKeys: String, CodingKey { diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index a1ee926273f..51241c915c2 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -84,6 +84,18 @@ final class IntegrationTests: XCTestCase { func testCountTokens_text() async throws { let prompt = "Why is the sky blue?" + model = vertex.generativeModel( + modelName: "gemini-1.5-pro", + generationConfig: generationConfig, + safetySettings: [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockMediumAndAbove), + SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockOnlyHigh), + SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), + SafetySetting(harmCategory: .civicIntegrity, threshold: .off), + ], + systemInstruction: systemInstruction + ) let response = try await model.countTokens(prompt) From 4b263b6ce5f30fc642c3f6511027d797ec036469 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 18:53:17 -0400 Subject: [PATCH 187/258] [Vertex AI] Refactor `BlockReason` as a `struct` and add new values (#13861) --- FirebaseVertexAI/CHANGELOG.md | 2 + .../Sources/GenerateContentResponse.swift | 55 +++++++++++-------- .../Tests/Unit/GenerativeModelTests.swift | 3 +- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index b6444fb81f0..04ae2573ecd 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -52,6 +52,8 @@ filter. (#13863) - [added] Added new `FinishReason` values `.blocklist`, `.prohibitedContent`, `.spii` and `.malformedFunctionCall` that may be reported. (#13860) +- [added] Added new `BlockReason` values `.blocklist` and `.prohibitedContent` + that may be reported when a prompt is blocked. (#13861) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 079b759d26f..94b45cb45b1 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -221,15 +221,43 @@ public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct PromptFeedback: Sendable { /// A type describing possible reasons to block a prompt. - public enum BlockReason: String, Sendable { - /// The block reason is unknown. - case unknown = "UNKNOWN" + public struct BlockReason: DecodableProtoEnum, Hashable, Sendable { + enum Kind: String { + case safety = "SAFETY" + case other = "OTHER" + case blocklist = "BLOCKLIST" + case prohibitedContent = "PROHIBITED_CONTENT" + } /// The prompt was blocked because it was deemed unsafe. - case safety = "SAFETY" + public static var safety: BlockReason { + return self.init(kind: .safety) + } /// All other block reasons. - case other = "OTHER" + public static var other: BlockReason { + return self.init(kind: .other) + } + + /// The prompt was blocked because it contained terms from the terminology blocklist. + public static var blocklist: BlockReason { + return self.init(kind: .blocklist) + } + + /// The prompt was blocked due to prohibited content. + public static var prohibitedContent: BlockReason { + return self.init(kind: .prohibitedContent) + } + + /// Returns the raw string representation of the `BlockReason` value. + /// + /// > Note: This value directly corresponds to the values in the [REST + /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#BlockedReason). + public let rawValue: String + + var unrecognizedValueMessageCode: VertexLog.MessageCode { + .generateContentResponseUnrecognizedBlockReason + } } /// The reason a prompt was blocked, if it was blocked. @@ -383,23 +411,6 @@ extension Citation: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension PromptFeedback.BlockReason: Decodable { - public init(from decoder: Decoder) throws { - let value = try decoder.singleValueContainer().decode(String.self) - guard let decodedBlockReason = PromptFeedback.BlockReason(rawValue: value) else { - VertexLog.error( - code: .generateContentResponseUnrecognizedBlockReason, - "Unrecognized BlockReason with value \"\(value)\"." - ) - self = .unknown - return - } - - self = decodedBlockReason - } -} - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension PromptFeedback: Decodable { enum CodingKeys: CodingKey { diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index ae0f55361fa..5474a4675ef 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -627,13 +627,14 @@ final class GenerativeModelTests: XCTestCase { forResource: "unary-failure-unknown-enum-prompt-blocked", withExtension: "json" ) + let unknownBlockReason = PromptFeedback.BlockReason(rawValue: "FAKE_NEW_BLOCK_REASON") do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw") } catch let GenerateContentError.promptBlocked(response) { let promptFeedback = try XCTUnwrap(response.promptFeedback) - XCTAssertEqual(promptFeedback.blockReason, .unknown) + XCTAssertEqual(promptFeedback.blockReason, unknownBlockReason) } catch { XCTFail("Should throw a promptBlocked") } From 1e954e9fd3918007175f47dd649395f528d08857 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 9 Oct 2024 20:26:15 -0400 Subject: [PATCH 188/258] [Vertex AI] Refactored `FunctionCallingConfig.Mode` as a `struct` (#13864) --- .../Sources/FunctionCalling.swift | 58 +++++++++++++------ .../Tests/Integration/IntegrationTests.swift | 5 +- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 69924f3cc4b..9f62eff253e 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -73,33 +73,53 @@ public struct Tool { /// Configuration for specifying function calling behavior. public struct FunctionCallingConfig { - /// Defines the execution behavior for function calling by defining the - /// execution mode. - public enum Mode: String { - /// The default behavior for function calling. The model calls functions to answer queries at - /// its discretion. - case auto = "AUTO" + /// Defines the execution behavior for function calling by defining the execution mode. + public struct Mode: EncodableProtoEnum { + enum Kind: String { + case auto = "AUTO" + case any = "ANY" + case none = "NONE" + } + + /// The default behavior for function calling. + /// + /// The model calls functions to answer queries at its discretion. + public static var auto: Mode { + return self.init(kind: .auto) + } /// The model always predicts a provided function call to answer every query. - case any = "ANY" - - /// The model will never predict a function call to answer a query. This can also be achieved by - /// not passing any tools to the model. - case none = "NONE" + public static var any: Mode { + return self.init(kind: .any) + } + + /// The model will never predict a function call to answer a query. + /// + /// > Note: This can also be achieved by not passing any ``FunctionDeclaration`` tools + /// > when instantiating the model. + public static var none: Mode { + return self.init(kind: .none) + } + + let rawValue: String } - /// Specifies the mode in which function calling should execute. If - /// unspecified, the default value will be set to AUTO. + /// Specifies the mode in which function calling should execute. let mode: Mode? - /// A set of function names that, when provided, limits the functions the model - /// will call. - /// - /// This should only be set when the Mode is ANY. Function names - /// should match [FunctionDeclaration.name]. With mode set to ANY, model will - /// predict a function call from the set of function names provided. + /// A set of function names that, when provided, limits the functions the model will call. let allowedFunctionNames: [String]? + /// Creates a new `FunctionCallingConfig`. + /// + /// - Parameters: + /// - mode: Specifies the mode in which function calling should execute; if unspecified, the + /// default behavior will be ``Mode/auto``. + /// - allowedFunctionNames: A set of function names that, when provided, limits the functions + /// the model will call. + /// Note: This should only be set when the ``Mode`` is ``Mode/any``. Function names should match + /// `[FunctionDeclaration.name]`. With mode set to ``Mode/any``, the model will predict a + /// function call from the set of function names provided. public init(mode: FunctionCallingConfig.Mode? = nil, allowedFunctionNames: [String]? = nil) { self.mode = mode self.allowedFunctionNames = allowedFunctionNames diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index 51241c915c2..80b6f1ab528 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -59,6 +59,7 @@ final class IntegrationTests: XCTestCase { generationConfig: generationConfig, safetySettings: safetySettings, tools: [], + toolConfig: .init(functionCallingConfig: .init(mode: FunctionCallingConfig.Mode.none)), systemInstruction: systemInstruction ) } @@ -94,6 +95,7 @@ final class IntegrationTests: XCTestCase { SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), SafetySetting(harmCategory: .civicIntegrity, threshold: .off), ], + toolConfig: .init(functionCallingConfig: .init(mode: .auto)), systemInstruction: systemInstruction ) @@ -135,7 +137,8 @@ final class IntegrationTests: XCTestCase { ) model = vertex.generativeModel( modelName: "gemini-1.5-flash", - tools: [Tool(functionDeclarations: [sumDeclaration])] + tools: [Tool(functionDeclarations: [sumDeclaration])], + toolConfig: .init(functionCallingConfig: .init(mode: .any, allowedFunctionNames: ["sum"])) ) let prompt = "What is 10 + 32?" let sumCall = FunctionCallPart(name: "sum", args: ["x": .number(10), "y": .number(32)]) From ae484d301630831808f8a9c31922d2d502e30d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danni=20Stjerneg=C3=A5rd?= Date: Thu, 10 Oct 2024 15:50:09 +0200 Subject: [PATCH 189/258] Correct documentation for Callable default timeout (#13869) --- FirebaseFunctions/Sources/Callable+Codable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseFunctions/Sources/Callable+Codable.swift b/FirebaseFunctions/Sources/Callable+Codable.swift index 08753e75f36..489433a0a7e 100644 --- a/FirebaseFunctions/Sources/Callable+Codable.swift +++ b/FirebaseFunctions/Sources/Callable+Codable.swift @@ -17,7 +17,7 @@ import Foundation /// A `Callable` is reference to a particular Callable HTTPS trigger in Cloud Functions. public struct Callable { - /// The timeout to use when calling the function. Defaults to 60 seconds. + /// The timeout to use when calling the function. Defaults to 70 seconds. public var timeoutInterval: TimeInterval { get { callable.timeoutInterval From fde2baf3d445379f4798886b92a81ff563e3caaa Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 10 Oct 2024 11:32:43 -0400 Subject: [PATCH 190/258] [Vertex AI] Refactor `StringFormat` and `IntegerFormat` as structs (#13865) --- .../Sources/Types/Public/Schema.swift | 76 +++++++------------ .../Tests/Integration/IntegrationTests.swift | 21 +++++ 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/FirebaseVertexAI/Sources/Types/Public/Schema.swift b/FirebaseVertexAI/Sources/Types/Public/Schema.swift index 0ff5b32ba47..9496113a071 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Schema.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Schema.swift @@ -20,19 +20,41 @@ import Foundation /// [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema). public class Schema { /// Modifiers describing the expected format of a string `Schema`. - public enum StringFormat { + public struct StringFormat: EncodableProtoEnum { + // This enum is currently only used to conform `StringFormat` to `ProtoEnum`, which requires + // `associatedtype Kind: RawRepresentable`. + enum Kind: String { + // Providing a case resolves the error "An enum with no cases cannot declare a raw type". + case unused // TODO: Remove `unused` case when we have at least one specific string format. + } + /// A custom string format. - case custom(String) + public static func custom(_ format: String) -> StringFormat { + return self.init(rawValue: format) + } + + let rawValue: String } /// Modifiers describing the expected format of an integer `Schema`. - public enum IntegerFormat { + public struct IntegerFormat: EncodableProtoEnum { + enum Kind: String { + case int32 + case int64 + } + /// A 32-bit signed integer. - case int32 + public static let int32 = IntegerFormat(kind: .int32) + /// A 64-bit signed integer. - case int64 + public static let int64 = IntegerFormat(kind: .int64) + /// A custom integer format. - case custom(String) + public static func custom(_ format: String) -> IntegerFormat { + return self.init(rawValue: format) + } + + let rawValue: String } let dataType: DataType @@ -311,45 +333,3 @@ extension Schema: Encodable { case requiredProperties = "required" } } - -// MARK: - RawRepresentable Conformance - -extension Schema.IntegerFormat: RawRepresentable { - public init?(rawValue: String) { - switch rawValue { - case "int32": - self = .int32 - case "int64": - self = .int64 - default: - self = .custom(rawValue) - } - } - - public var rawValue: String { - switch self { - case .int32: - return "int32" - case .int64: - return "int64" - case let .custom(format): - return format - } - } -} - -extension Schema.StringFormat: RawRepresentable { - public init?(rawValue: String) { - switch rawValue { - default: - self = .custom(rawValue) - } - } - - public var rawValue: String { - switch self { - case let .custom(format): - return format - } - } -} diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index 80b6f1ab528..d8b25cd8ec4 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -153,4 +153,25 @@ final class IntegrationTests: XCTestCase { XCTAssertEqual(response.totalTokens, 24) XCTAssertEqual(response.totalBillableCharacters, 71) } + + func testCountTokens_jsonSchema() async throws { + model = vertex.generativeModel( + modelName: "gemini-1.5-flash", + generationConfig: GenerationConfig( + responseMIMEType: "application/json", + responseSchema: Schema.object(properties: [ + "startDate": .string(format: .custom("date")), + "yearsSince": .integer(format: .custom("int16")), + "hoursSince": .integer(format: .int32), + "minutesSince": .integer(format: .int64), + ]) + ) + ) + let prompt = "It is 2050-01-01, how many years, hours and minutes since 2000-01-01?" + + let response = try await model.countTokens(prompt) + + XCTAssertEqual(response.totalTokens, 34) + XCTAssertEqual(response.totalBillableCharacters, 59) + } } From 02025cef95a32b04d6bea8ec1bb72f2293dd37a5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 10 Oct 2024 11:50:44 -0400 Subject: [PATCH 191/258] [Vertex AI] Replace "enum" computed properties with `static let` (#13870) --- .../Sources/FunctionCalling.swift | 12 +--- .../Sources/GenerateContentResponse.swift | 62 +++++------------ .../Protocols/Internal/CodableProtoEnum.swift | 4 +- FirebaseVertexAI/Sources/Safety.swift | 66 +++++-------------- 4 files changed, 40 insertions(+), 104 deletions(-) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 9f62eff253e..4cda35aa4da 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -84,22 +84,16 @@ public struct FunctionCallingConfig { /// The default behavior for function calling. /// /// The model calls functions to answer queries at its discretion. - public static var auto: Mode { - return self.init(kind: .auto) - } + public static let auto = Mode(kind: .auto) /// The model always predicts a provided function call to answer every query. - public static var any: Mode { - return self.init(kind: .any) - } + public static let any = Mode(kind: .any) /// The model will never predict a function call to answer a query. /// /// > Note: This can also be achieved by not passing any ``FunctionDeclaration`` tools /// > when instantiating the model. - public static var none: Mode { - return self.init(kind: .none) - } + public static let none = Mode(kind: .none) let rawValue: String } diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 94b45cb45b1..07491765f90 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -159,52 +159,34 @@ public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { } /// Natural stop point of the model or provided stop sequence. - public static var stop: FinishReason { - return self.init(kind: .stop) - } + public static let stop = FinishReason(kind: .stop) /// The maximum number of tokens as specified in the request was reached. - public static var maxTokens: FinishReason { - return self.init(kind: .maxTokens) - } + public static let maxTokens = FinishReason(kind: .maxTokens) /// The token generation was stopped because the response was flagged for safety reasons. /// /// > NOTE: When streaming, the ``CandidateResponse/content`` will be empty if content filters /// > blocked the output. - public static var safety: FinishReason { - return self.init(kind: .safety) - } + public static let safety = FinishReason(kind: .safety) /// The token generation was stopped because the response was flagged for unauthorized citations. - public static var recitation: FinishReason { - return self.init(kind: .recitation) - } + public static let recitation = FinishReason(kind: .recitation) /// All other reasons that stopped token generation. - public static var other: FinishReason { - return self.init(kind: .other) - } + public static let other = FinishReason(kind: .other) /// Token generation was stopped because the response contained forbidden terms. - public static var blocklist: FinishReason { - return self.init(kind: .blocklist) - } + public static let blocklist = FinishReason(kind: .blocklist) /// Token generation was stopped because the response contained potentially prohibited content. - public static var prohibitedContent: FinishReason { - return self.init(kind: .prohibitedContent) - } + public static let prohibitedContent = FinishReason(kind: .prohibitedContent) /// Token generation was stopped because of Sensitive Personally Identifiable Information (SPII). - public static var spii: FinishReason { - return self.init(kind: .spii) - } + public static let spii = FinishReason(kind: .spii) /// Token generation was stopped because the function call generated by the model was invalid. - public static var malformedFunctionCall: FinishReason { - return self.init(kind: .malformedFunctionCall) - } + public static let malformedFunctionCall = FinishReason(kind: .malformedFunctionCall) /// Returns the raw string representation of the `FinishReason` value. /// @@ -212,9 +194,8 @@ public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#FinishReason). public let rawValue: String - var unrecognizedValueMessageCode: VertexLog.MessageCode { - .generateContentResponseUnrecognizedFinishReason - } + static let unrecognizedValueMessageCode = + VertexLog.MessageCode.generateContentResponseUnrecognizedFinishReason } /// A metadata struct containing any feedback the model had on the prompt it was provided. @@ -230,24 +211,16 @@ public struct PromptFeedback: Sendable { } /// The prompt was blocked because it was deemed unsafe. - public static var safety: BlockReason { - return self.init(kind: .safety) - } + public static let safety = BlockReason(kind: .safety) /// All other block reasons. - public static var other: BlockReason { - return self.init(kind: .other) - } + public static let other = BlockReason(kind: .other) /// The prompt was blocked because it contained terms from the terminology blocklist. - public static var blocklist: BlockReason { - return self.init(kind: .blocklist) - } + public static let blocklist = BlockReason(kind: .blocklist) /// The prompt was blocked due to prohibited content. - public static var prohibitedContent: BlockReason { - return self.init(kind: .prohibitedContent) - } + public static let prohibitedContent = BlockReason(kind: .prohibitedContent) /// Returns the raw string representation of the `BlockReason` value. /// @@ -255,9 +228,8 @@ public struct PromptFeedback: Sendable { /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#BlockedReason). public let rawValue: String - var unrecognizedValueMessageCode: VertexLog.MessageCode { - .generateContentResponseUnrecognizedBlockReason - } + static let unrecognizedValueMessageCode = + VertexLog.MessageCode.generateContentResponseUnrecognizedBlockReason } /// The reason a prompt was blocked, if it was blocked. diff --git a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift index f73271a1acc..4392157a897 100644 --- a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift +++ b/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift @@ -47,7 +47,7 @@ protocol ProtoEnum { /// provided when conforming to this type. protocol DecodableProtoEnum: ProtoEnum, Decodable { /// Returns the ``VertexLog/MessageCode`` associated with unrecognized (unknown) enum values. - var unrecognizedValueMessageCode: VertexLog.MessageCode { get } + static var unrecognizedValueMessageCode: VertexLog.MessageCode { get } /// Creates a new instance by decoding from the given decoder. /// @@ -91,7 +91,7 @@ extension DecodableProtoEnum { if Kind(rawValue: rawValue) == nil { VertexLog.error( - code: unrecognizedValueMessageCode, + code: Self.unrecognizedValueMessageCode, """ Unrecognized \(Self.self) with value "\(rawValue)": - Check for updates to the SDK as support for "\(rawValue)" may have been added; see \ diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 4e93a94bf45..d810613aecb 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -52,26 +52,18 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// The probability is zero or close to zero. /// /// For benign content, the probability across all categories will be this value. - public static var negligible: HarmProbability { - return self.init(kind: .negligible) - } + public static let negligible = HarmProbability(kind: .negligible) /// The probability is small but non-zero. - public static var low: HarmProbability { - return self.init(kind: .low) - } + public static let low = HarmProbability(kind: .low) /// The probability is moderate. - public static var medium: HarmProbability { - return self.init(kind: .medium) - } + public static let medium = HarmProbability(kind: .medium) /// The probability is high. /// /// The content described is very likely harmful. - public static var high: HarmProbability { - return self.init(kind: .high) - } + public static let high = HarmProbability(kind: .high) /// Returns the raw string representation of the `HarmProbability` value. /// @@ -79,9 +71,8 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#SafetyRating). public let rawValue: String - var unrecognizedValueMessageCode: VertexLog.MessageCode { - .generateContentResponseUnrecognizedHarmProbability - } + static let unrecognizedValueMessageCode = + VertexLog.MessageCode.generateContentResponseUnrecognizedHarmProbability } } @@ -100,29 +91,19 @@ public struct SafetySetting { } /// Content with `.negligible` will be allowed. - public static var blockLowAndAbove: HarmBlockThreshold { - return self.init(kind: .blockLowAndAbove) - } + public static let blockLowAndAbove = HarmBlockThreshold(kind: .blockLowAndAbove) /// Content with `.negligible` and `.low` will be allowed. - public static var blockMediumAndAbove: HarmBlockThreshold { - return self.init(kind: .blockMediumAndAbove) - } + public static let blockMediumAndAbove = HarmBlockThreshold(kind: .blockMediumAndAbove) /// Content with `.negligible`, `.low`, and `.medium` will be allowed. - public static var blockOnlyHigh: HarmBlockThreshold { - return self.init(kind: .blockOnlyHigh) - } + public static let blockOnlyHigh = HarmBlockThreshold(kind: .blockOnlyHigh) /// All content will be allowed. - public static var blockNone: HarmBlockThreshold { - return self.init(kind: .blockNone) - } + public static let blockNone = HarmBlockThreshold(kind: .blockNone) /// Turn off the safety filter. - public static var off: HarmBlockThreshold { - return self.init(kind: .off) - } + public static let off = HarmBlockThreshold(kind: .off) let rawValue: String } @@ -156,29 +137,19 @@ public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { } /// Harassment content. - public static var harassment: HarmCategory { - return self.init(kind: .harassment) - } + public static let harassment = HarmCategory(kind: .harassment) /// Negative or harmful comments targeting identity and/or protected attributes. - public static var hateSpeech: HarmCategory { - return self.init(kind: .hateSpeech) - } + public static let hateSpeech = HarmCategory(kind: .hateSpeech) /// Contains references to sexual acts or other lewd content. - public static var sexuallyExplicit: HarmCategory { - return self.init(kind: .sexuallyExplicit) - } + public static let sexuallyExplicit = HarmCategory(kind: .sexuallyExplicit) /// Promotes or enables access to harmful goods, services, or activities. - public static var dangerousContent: HarmCategory { - return self.init(kind: .dangerousContent) - } + public static let dangerousContent = HarmCategory(kind: .dangerousContent) /// Content that may be used to harm civic integrity. - public static var civicIntegrity: HarmCategory { - return self.init(kind: .civicIntegrity) - } + public static let civicIntegrity = HarmCategory(kind: .civicIntegrity) /// Returns the raw string representation of the `HarmCategory` value. /// @@ -186,9 +157,8 @@ public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { /// > [REST API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/HarmCategory). public let rawValue: String - var unrecognizedValueMessageCode: VertexLog.MessageCode { - .generateContentResponseUnrecognizedHarmCategory - } + static let unrecognizedValueMessageCode = + VertexLog.MessageCode.generateContentResponseUnrecognizedHarmCategory } // MARK: - Codable Conformances From 6059bbbc98a488c096d17c002fae181ab6a548e1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 10 Oct 2024 14:24:28 -0400 Subject: [PATCH 192/258] [Vertex AI] Switch to `firebasevertexai.googleapis.com` API (#13725) --- FirebaseVertexAI/CHANGELOG.md | 4 ++ FirebaseVertexAI/Sources/Constants.swift | 5 +- FirebaseVertexAI/Sources/Errors.swift | 16 ------ .../Sources/GenerativeAIRequest.swift | 4 +- .../Sources/GenerativeAIService.swift | 11 ---- .../Tests/Unit/GenerativeModelTests.swift | 50 ------------------- 6 files changed, 6 insertions(+), 84 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 04ae2573ecd..ad5e1b74f95 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,4 +1,8 @@ # 11.4.0 +- [changed] **Breaking Change**: The SDK is now Generally Available (GA); both + new and existing users must perform the + [Build with Gemini](https://console.firebase.google.com/project/_/genai/) + onboarding flow in the Firebase Console to use the updated API. (#13725) - [changed] **Breaking Change**: The `HarmCategory` enum is no longer nested inside the `SafetySetting` struct and the `unspecified` case has been removed. (#13686) diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseVertexAI/Sources/Constants.swift index 9ec76aabe95..587b1ad283c 100644 --- a/FirebaseVertexAI/Sources/Constants.swift +++ b/FirebaseVertexAI/Sources/Constants.swift @@ -17,8 +17,5 @@ import Foundation /// Constants associated with the Vertex AI for Firebase SDK. enum Constants { /// The Vertex AI backend endpoint URL. - /// - /// TODO(andrewheard): Update to "https://firebasevertexai.googleapis.com" after the Vertex AI in - /// Firebase API launch. - static let baseURL = "https://firebaseml.googleapis.com" + static let baseURL = "https://firebasevertexai.googleapis.com" } diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseVertexAI/Sources/Errors.swift index 57fbe826580..770dc3b12d3 100644 --- a/FirebaseVertexAI/Sources/Errors.swift +++ b/FirebaseVertexAI/Sources/Errors.swift @@ -31,11 +31,6 @@ struct RPCError: Error { self.details = details } - // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. - func isFirebaseMLServiceDisabledError() -> Bool { - return details.contains { $0.isFirebaseMLServiceDisabledErrorDetails() } - } - func isVertexAIInFirebaseServiceDisabledError() -> Bool { return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() } } @@ -95,17 +90,6 @@ struct ErrorDetails { return isErrorInfo() && reason == "SERVICE_DISABLED" && domain == "googleapis.com" } - // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. - func isFirebaseMLServiceDisabledErrorDetails() -> Bool { - guard isServiceDisabledError() else { - return false - } - guard let metadata, metadata["service"] == "firebaseml.googleapis.com" else { - return false - } - return true - } - func isVertexAIInFirebaseServiceDisabledErrorDetails() -> Bool { guard isServiceDisabledError() else { return false diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index 08ac22eaa5f..ac9046e0de4 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -31,9 +31,7 @@ public struct RequestOptions { let timeout: TimeInterval /// The API version to use in requests to the backend. - /// - /// TODO(andrewheard): Update to "v1beta" after the Vertex AI in Firebase API launch. - let apiVersion = "v2beta" + let apiVersion = "v1beta" /// Initializes a request options object. /// diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index ac429104352..ff49a17b07f 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -263,17 +263,6 @@ struct GenerativeAIService { // Log specific RPC errors that cannot be mitigated or handled by user code. // These errors do not produce specific GenerateContentError or CountTokensError cases. private func logRPCError(_ error: RPCError) { - // TODO(andrewheard): Remove this check after the Vertex AI in Firebase API launch. - if error.isFirebaseMLServiceDisabledError() { - VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ - The Vertex AI in Firebase SDK requires the Firebase ML API (`firebaseml.googleapis.com`) to \ - be enabled in your Firebase project. Enable this API by visiting the Firebase Console at - https://console.firebase.google.com/project/\(projectID)/genai/ and clicking "Get started". \ - If you enabled this API recently, wait a few minutes for the action to propagate to our \ - systems and then retry. - """) - } - if error.isVertexAIInFirebaseServiceDisabledError() { VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ The Vertex AI in Firebase SDK requires the Vertex AI in Firebase API \ diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 5474a4675ef..de14552c251 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -457,30 +457,6 @@ final class GenerativeModelTests: XCTestCase { } } - // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. - func testGenerateContent_failure_firebaseMLAPINotEnabled() async throws { - let expectedStatusCode = 403 - MockURLProtocol - .requestHandler = try httpRequestHandler( - forResource: "unary-failure-firebaseml-api-not-enabled", - withExtension: "json", - statusCode: expectedStatusCode - ) - - do { - _ = try await model.generateContent(testPrompt) - XCTFail("Should throw GenerateContentError.internalError; no error thrown.") - } catch let GenerateContentError.internalError(error as RPCError) { - XCTAssertEqual(error.httpResponseCode, expectedStatusCode) - XCTAssertEqual(error.status, .permissionDenied) - XCTAssertTrue(error.message.starts(with: "Firebase ML API has not been used in project")) - XCTAssertTrue(error.isFirebaseMLServiceDisabledError()) - return - } catch { - XCTFail("Should throw GenerateContentError.internalError(RPCError); error thrown: \(error)") - } - } - func testGenerateContent_failure_firebaseVertexAIAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -805,32 +781,6 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should have caught an error.") } - // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. - func testGenerateContentStream_failure_firebaseMLAPINotEnabled() async throws { - let expectedStatusCode = 403 - MockURLProtocol - .requestHandler = try httpRequestHandler( - forResource: "unary-failure-firebaseml-api-not-enabled", - withExtension: "json", - statusCode: expectedStatusCode - ) - - do { - let stream = try model.generateContentStream(testPrompt) - for try await _ in stream { - XCTFail("No content is there, this shouldn't happen.") - } - } catch let GenerateContentError.internalError(error as RPCError) { - XCTAssertEqual(error.httpResponseCode, expectedStatusCode) - XCTAssertEqual(error.status, .permissionDenied) - XCTAssertTrue(error.message.starts(with: "Firebase ML API has not been used in project")) - XCTAssertTrue(error.isFirebaseMLServiceDisabledError()) - return - } - - XCTFail("Should have caught an error.") - } - func testGenerateContentStream_failure_vertexAIInFirebaseAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol From 063b3ce980c0c3560f38b62dccbc6759a2e63c99 Mon Sep 17 00:00:00 2001 From: tsunghung <78230356+tsunghung@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:57:23 -0700 Subject: [PATCH 193/258] Analytics 11.4.0 (#13872) --- FirebaseAnalytics.podspec | 2 +- GoogleAppMeasurement.podspec | 2 +- Package.swift | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index e762baa4c5c..a66f0bd62d7 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/7c5cc65f2b583ee1/FirebaseAnalytics-11.3.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/edf73aefd77661bd/FirebaseAnalytics-11.4.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index 1e6204dda17..9e0adaad993 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/d86efe4f4164be5a/GoogleAppMeasurement-11.3.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/16e615e7058d8274/GoogleAppMeasurement-11.4.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/Package.swift b/Package.swift index f35d72bc623..75f33326255 100644 --- a/Package.swift +++ b/Package.swift @@ -303,8 +303,8 @@ let package = Package( ), .binaryTarget( name: "FirebaseAnalytics", - url: "https://dl.google.com/firebase/ios/swiftpm/11.3.0/FirebaseAnalytics.zip", - checksum: "1d4c06ccb6ffbf44e80934ab9190d2473421f94e7116c95c13cb2744c3873852" + url: "https://dl.google.com/firebase/ios/swiftpm/11.4.0/FirebaseAnalytics.zip", + checksum: "fb0d7cd992ffdcd82ed5c5fdb83e50ac983664f1dde81b140a0ddaa1aa66baae" ), .testTarget( name: "AnalyticsSwiftUnit", @@ -1343,7 +1343,7 @@ func googleAppMeasurementDependency() -> Package.Dependency { return .package(url: appMeasurementURL, branch: "main") } - return .package(url: appMeasurementURL, exact: "11.3.0") + return .package(url: appMeasurementURL, exact: "11.4.0") } func abseilDependency() -> Package.Dependency { From 3eaa04d044140b24800b3b59695f4a3f601bea59 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 10 Oct 2024 20:02:08 -0400 Subject: [PATCH 194/258] [Vertex AI] Refactor function calling APIs (#13873) --- FirebaseVertexAI/CHANGELOG.md | 12 ++- .../ViewModels/FunctionCallingViewModel.swift | 2 +- .../Sources/FunctionCalling.swift | 98 ++++++++++--------- .../Tests/Integration/IntegrationTests.swift | 8 +- 4 files changed, 67 insertions(+), 53 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index ad5e1b74f95..f2facff2411 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -15,10 +15,10 @@ renamed to `inlineData`; no functionality changes. (#13700) - [changed] **Breaking Change**: The property `citationSources` of `CitationMetadata` has been renamed to `citations`. (#13702) -- [changed] **Breaking Change**: The constructor for `Schema` is now internal; - use the new static methods `Schema.string(...)`, `Schema.object(...)`, etc., +- [changed] **Breaking Change**: The initializer for `Schema` is now internal; + use the new type methods `Schema.string(...)`, `Schema.object(...)`, etc., instead. (#13852) -- [changed] **Breaking Change**: The constructor for `FunctionDeclaration` now +- [changed] **Breaking Change**: The initializer for `FunctionDeclaration` now accepts an array of *optional* parameters instead of a list of *required* parameters; if a parameter is not listed as optional it is assumed to be required. (#13616) @@ -44,6 +44,12 @@ `FinishReason` are now structs instead of enums types and the `unknown` cases have been removed; in a `switch` statement, use the `default:` case to cover unknown or unhandled values. (#13728, #13854, #13860) +- [changed] **Breaking Change**: The `Tool` initializer is now internal; use the + new type method `functionDeclarations(_:)` to create a `Tool` for function + calling. (#13873) +- [changed] **Breaking Change**: The `FunctionCallingConfig` initializer and + `Mode` enum are now internal; use one of the new type methods `auto()`, + `any(allowedFunctionNames:)`, or `none()` to create a config. (#13873) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift index f39540eb1a9..7a67175112f 100644 --- a/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift +++ b/FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift @@ -40,7 +40,7 @@ class FunctionCallingViewModel: ObservableObject { init() { model = VertexAI.vertexAI().generativeModel( modelName: "gemini-1.5-flash", - tools: [Tool(functionDeclarations: [ + tools: [.functionDeclarations([ FunctionDeclaration( name: "get_exchange_rate", description: "Get the exchange rate for currencies between countries", diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 4cda35aa4da..7bf366a7132 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -25,7 +25,7 @@ public struct FunctionDeclaration { /// A brief description of the function. let description: String - /// Describes the parameters to this function; must be of type ``DataType/object``. + /// Describes the parameters to this function; must be of type `DataType.object`. let parameters: Schema? /// Constructs a new `FunctionDeclaration`. @@ -47,55 +47,49 @@ public struct FunctionDeclaration { } } -/// Helper tools that the model may use to generate response. +/// A helper tool that the model may use when generating responses. /// -/// A `Tool` is a piece of code that enables the system to interact with external systems to -/// perform an action, or set of actions, outside of knowledge and scope of the model. +/// A `Tool` is a piece of code that enables the system to interact with external systems to perform +/// an action, or set of actions, outside of knowledge and scope of the model. public struct Tool { /// A list of `FunctionDeclarations` available to the model. let functionDeclarations: [FunctionDeclaration]? - /// Constructs a new `Tool`. + init(functionDeclarations: [FunctionDeclaration]?) { + self.functionDeclarations = functionDeclarations + } + + /// Creates a tool that allows the model to perform function calling. + /// + /// Function calling can be used to provide data to the model that was not known at the time it + /// was trained (for example, the current date or weather conditions) or to allow it to interact + /// with external systems (for example, making an API request or querying/updating a database). + /// For more details and use cases, see [Introduction to function + /// calling](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling). /// /// - Parameters: /// - functionDeclarations: A list of `FunctionDeclarations` available to the model that can be /// used for function calling. /// The model or system does not execute the function. Instead the defined function may be - /// returned as a ``FunctionCall`` in ``ModelContent/Part/functionCall(_:)`` with arguments to - /// the client side for execution. The model may decide to call a subset of these functions by - /// populating ``FunctionCall`` in the response. The next conversation turn may contain a - /// ``FunctionResponse`` in ``ModelContent/Part/functionResponse(_:)`` with the - /// ``ModelContent/role`` "function", providing generation context for the next model turn. - public init(functionDeclarations: [FunctionDeclaration]?) { - self.functionDeclarations = functionDeclarations + /// returned as a ``FunctionCallPart`` with arguments to the client side for execution. The + /// model may decide to call none, some or all of the declared functions; this behavior may be + /// configured by specifying a ``ToolConfig`` when instantiating the model. When a + /// ``FunctionCallPart`` is received, the next conversation turn may contain a + /// ``FunctionResponsePart`` in ``ModelContent/parts`` with a ``ModelContent/role`` of + /// `"function"`; this response contains the result of executing the function on the client, + /// providing generation context for the model's next turn. + public static func functionDeclarations(_ functionDeclarations: [FunctionDeclaration]) -> Tool { + return self.init(functionDeclarations: functionDeclarations) } } /// Configuration for specifying function calling behavior. public struct FunctionCallingConfig { /// Defines the execution behavior for function calling by defining the execution mode. - public struct Mode: EncodableProtoEnum { - enum Kind: String { - case auto = "AUTO" - case any = "ANY" - case none = "NONE" - } - - /// The default behavior for function calling. - /// - /// The model calls functions to answer queries at its discretion. - public static let auto = Mode(kind: .auto) - - /// The model always predicts a provided function call to answer every query. - public static let any = Mode(kind: .any) - - /// The model will never predict a function call to answer a query. - /// - /// > Note: This can also be achieved by not passing any ``FunctionDeclaration`` tools - /// > when instantiating the model. - public static let none = Mode(kind: .none) - - let rawValue: String + enum Mode: String { + case auto = "AUTO" + case any = "ANY" + case none = "NONE" } /// Specifies the mode in which function calling should execute. @@ -104,20 +98,34 @@ public struct FunctionCallingConfig { /// A set of function names that, when provided, limits the functions the model will call. let allowedFunctionNames: [String]? - /// Creates a new `FunctionCallingConfig`. - /// - /// - Parameters: - /// - mode: Specifies the mode in which function calling should execute; if unspecified, the - /// default behavior will be ``Mode/auto``. - /// - allowedFunctionNames: A set of function names that, when provided, limits the functions - /// the model will call. - /// Note: This should only be set when the ``Mode`` is ``Mode/any``. Function names should match - /// `[FunctionDeclaration.name]`. With mode set to ``Mode/any``, the model will predict a - /// function call from the set of function names provided. - public init(mode: FunctionCallingConfig.Mode? = nil, allowedFunctionNames: [String]? = nil) { + init(mode: FunctionCallingConfig.Mode? = nil, allowedFunctionNames: [String]? = nil) { self.mode = mode self.allowedFunctionNames = allowedFunctionNames } + + /// Creates a function calling config where the model calls functions at its discretion. + /// + /// > Note: This is the default behavior. + public static func auto() -> FunctionCallingConfig { + return FunctionCallingConfig(mode: .auto) + } + + /// Creates a function calling config where the model will always call a provided function. + /// + /// - Parameters: + /// - allowedFunctionNames: A set of function names that, when provided, limits the functions + /// that the model will call. + public static func any(allowedFunctionNames: [String]? = nil) -> FunctionCallingConfig { + return FunctionCallingConfig(mode: .any, allowedFunctionNames: allowedFunctionNames) + } + + /// Creates a function calling config where the model will never call a function. + /// + /// > Note: This can also be achieved by not passing any ``FunctionDeclaration`` tools when + /// > instantiating the model. + public static func none() -> FunctionCallingConfig { + return FunctionCallingConfig(mode: FunctionCallingConfig.Mode.none) + } } /// Tool configuration for any `Tool` specified in the request. diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index d8b25cd8ec4..fee87108da7 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -59,7 +59,7 @@ final class IntegrationTests: XCTestCase { generationConfig: generationConfig, safetySettings: safetySettings, tools: [], - toolConfig: .init(functionCallingConfig: .init(mode: FunctionCallingConfig.Mode.none)), + toolConfig: .init(functionCallingConfig: .none()), systemInstruction: systemInstruction ) } @@ -95,7 +95,7 @@ final class IntegrationTests: XCTestCase { SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), SafetySetting(harmCategory: .civicIntegrity, threshold: .off), ], - toolConfig: .init(functionCallingConfig: .init(mode: .auto)), + toolConfig: .init(functionCallingConfig: .auto()), systemInstruction: systemInstruction ) @@ -137,8 +137,8 @@ final class IntegrationTests: XCTestCase { ) model = vertex.generativeModel( modelName: "gemini-1.5-flash", - tools: [Tool(functionDeclarations: [sumDeclaration])], - toolConfig: .init(functionCallingConfig: .init(mode: .any, allowedFunctionNames: ["sum"])) + tools: [.functionDeclarations([sumDeclaration])], + toolConfig: .init(functionCallingConfig: .any(allowedFunctionNames: ["sum"])) ) let prompt = "What is 10 + 32?" let sumCall = FunctionCallPart(name: "sum", args: ["x": .number(10), "y": .number(32)]) From 4e50fd819435322c9b4f5ebc0d4404e8fee85b94 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 10 Oct 2024 20:17:21 -0400 Subject: [PATCH 195/258] [Vertex AI] Remove from zip build (#13871) --- ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index cd38e8f6238..afe41de0e37 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -53,8 +53,8 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), - Pod("FirebaseVertexAI", zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), + Pod("FirebaseVertexAI", releasing: false, zip: false), ] ) From 53d9eaca7b4b76fc7885838d8211615173a749bd Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:20:45 -0400 Subject: [PATCH 196/258] [Release] Update changelogs for 11.4.0 (#13879) --- Crashlytics/CHANGELOG.md | 2 +- FirebaseAuth/CHANGELOG.md | 2 +- FirebasePerformance/CHANGELOG.md | 2 +- Firestore/CHANGELOG.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Crashlytics/CHANGELOG.md b/Crashlytics/CHANGELOG.md index a0b1f0fdd31..961cadfbe5a 100644 --- a/Crashlytics/CHANGELOG.md +++ b/Crashlytics/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.4.0 - [fixed] Updated `upload-symbols` to version 3.18 with support for upload mutiple DWARF contents in a dSYM bundle(#13543). # 10.28.1 diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index c677e63f057..440d428a13a 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.4.0 - [Fixed] Restore Firebase 10 behavior by ignoring `nil` display names used during multi factor enrollment. (#13856) diff --git a/FirebasePerformance/CHANGELOG.md b/FirebasePerformance/CHANGELOG.md index 0863831c286..b0de57ecb27 100644 --- a/FirebasePerformance/CHANGELOG.md +++ b/FirebasePerformance/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.4.0 - [fixed] Fix a crash related to thread sanitization on FPRNetworkTrace class (#13581). # 10.28.0 diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 0635a24af70..5781296165b 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.4.0 - [changed] Prepare Firestore cache to support session token. # 11.3.0 From 26841dae5b8c5c373f2900c5ec2056affa3d45ff Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:50:45 -0400 Subject: [PATCH 197/258] [Auth] Fix crash when using TOTP MFA auth (#13880) --- FirebaseAuth/CHANGELOG.md | 1 + FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 440d428a13a..9d290018c7a 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,6 +1,7 @@ # 11.4.0 - [Fixed] Restore Firebase 10 behavior by ignoring `nil` display names used during multi factor enrollment. (#13856) +- [Fixed] Fix crash when enrolling account with TOTP MFA. (#13880) # 11.3.0 - [Fixed] Restore Firebase 10 behavior by querying with the diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index 8b6cd0b0512..744dbb2750f 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -114,6 +114,7 @@ import Foundation } } } + return } else if assertion.factorID != PhoneMultiFactorInfo.PhoneMultiFactorID { return } From 84f28c12ef0f9aa47e33b2345815d1d11ef06af1 Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Mon, 14 Oct 2024 17:14:38 +0200 Subject: [PATCH 198/258] =?UTF-8?q?Fixed=20`DispatchGroup`=E2=80=99s=20Exc?= =?UTF-8?q?essive=20Wait=20in=20`FunctionsContext`=20(#13887)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Internal/FunctionsContext.swift | 14 +++++-- .../Tests/Unit/ContextProviderTests.swift | 37 +++++++++++++++++++ .../AppCheckFake/FIRAppCheckFake.m | 6 +-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift index cd0990afd22..7daaa6032d1 100644 --- a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift +++ b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift @@ -66,11 +66,17 @@ struct FunctionsContextProvider { dispatchGroup.enter() if options?.requireLimitedUseAppCheckTokens == true { - appCheck.getLimitedUseToken? { tokenResult in - // Send only valid token to functions. - if tokenResult.error == nil { - limitedUseAppCheckToken = tokenResult.token + // `getLimitedUseToken(completion:)` is an optional protocol method. + // If it’s not implemented, we still need to leave the dispatch group. + if let limitedUseTokenClosure = appCheck.getLimitedUseToken { + limitedUseTokenClosure { tokenResult in + // Send only valid token to functions. + if tokenResult.error == nil { + limitedUseAppCheckToken = tokenResult.token + } + dispatchGroup.leave() } + } else { dispatchGroup.leave() } } else { diff --git a/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift b/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift index 9d73c43de1d..db565bcdb01 100644 --- a/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift +++ b/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift @@ -107,6 +107,25 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testContextWithAppCheckWithoutOptionalMethods() { + let appCheck = AppCheckFakeWithoutOptionalMethods(tokenResult: appCheckTokenSuccess) + let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheck) + let expectation = + expectation(description: "Verify non-implemented method for limited-use tokens") + provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in + XCTAssertNotNil(context) + XCTAssertNil(error) + XCTAssertNil(context.authToken) + XCTAssertNil(context.fcmToken) + XCTAssertNil(context.appCheckToken) + // If the method for limited-use tokens is not implemented, the value should be `nil`: + XCTAssertNil(context.limitedUseAppCheckToken) + expectation.fulfill() + } + // Importantly, `getContext(options:_:)` must still finish in a timely manner: + waitForExpectations(timeout: 0.1) + } + func testAllContextsAvailableSuccess() { appCheckFake.tokenResult = appCheckTokenSuccess let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) @@ -149,3 +168,21 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } } + +// MARK: - Utilities + +private class AppCheckFakeWithoutOptionalMethods: NSObject, AppCheckInterop { + let tokenResult: FIRAppCheckTokenResultInterop + + init(tokenResult: FIRAppCheckTokenResultInterop) { + self.tokenResult = tokenResult + } + + func getToken(forcingRefresh: Bool, completion handler: @escaping AppCheckTokenHandlerInterop) { + handler(tokenResult) + } + + func tokenDidChangeNotificationName() -> String { "AppCheckFakeTokenDidChangeNotification" } + func notificationTokenKey() -> String { "AppCheckFakeTokenNotificationKey" } + func notificationAppNameKey() -> String { "AppCheckFakeAppNameNotificationKey" } +} diff --git a/SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m b/SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m index 4e669a94a23..0cd4ec91e5e 100644 --- a/SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m +++ b/SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m @@ -45,15 +45,15 @@ - (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler } - (nonnull NSString *)notificationAppNameKey { - return @"FakeAppCheckTokenDidChangeNotification"; + return @"AppCheckFakeAppNameNotificationKey"; } - (nonnull NSString *)notificationTokenKey { - return @"FakeTokenNotificationKey"; + return @"AppCheckFakeTokenNotificationKey"; } - (nonnull NSString *)tokenDidChangeNotificationName { - return @"FakeAppCheckTokenDidChangeNotification"; + return @"AppCheckFakeTokenDidChangeNotification"; } @end From 9cb9895e23c91af2fd3d4e5ecc73872df51ff7a9 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 14 Oct 2024 13:36:37 -0400 Subject: [PATCH 199/258] [Vertex AI] Add `HarmSeverity` enum and `SafetyRating` properties (#13875) --- FirebaseVertexAI/CHANGELOG.md | 3 + .../ChatSample/Views/ErrorDetailsView.swift | 72 ++++++++++++-- .../Sample/ChatSample/Views/ErrorView.swift | 64 +++++++++---- FirebaseVertexAI/Sources/Safety.swift | 95 ++++++++++++++++++- FirebaseVertexAI/Sources/VertexLog.swift | 1 + .../Tests/Unit/GenerativeModelTests.swift | 77 ++++++++++++--- 6 files changed, 273 insertions(+), 39 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index f2facff2411..346ef9a70bf 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -58,6 +58,9 @@ `totalBillableCharacters` counts, where applicable. (#13813) - [added] Added a new `HarmCategory` `.civicIntegrity` for filtering content that may be used to harm civic integrity. (#13728) +- [added] Added `probabilityScore`, `severity` and `severityScore` in + `SafetyRating` to provide more fine-grained detail on blocked responses. + (#13875) - [added] Added a new `HarmBlockThreshold` `.off`, which turns off the safety filter. (#13863) - [added] Added new `FinishReason` values `.blocklist`, `.prohibitedContent`, diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index 279f02b81fc..236a1f7d4b0 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -168,10 +168,38 @@ struct ErrorDetailsView: View { Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. """), safetyRatings: [ - SafetyRating(category: .dangerousContent, probability: .high), - SafetyRating(category: .harassment, probability: .low), - SafetyRating(category: .hateSpeech, probability: .low), - SafetyRating(category: .sexuallyExplicit, probability: .low), + SafetyRating( + category: .dangerousContent, + probability: .medium, + probabilityScore: 0.8, + severity: .medium, + severityScore: 0.9, + blocked: false + ), + SafetyRating( + category: .harassment, + probability: .low, + probabilityScore: 0.5, + severity: .low, + severityScore: 0.6, + blocked: false + ), + SafetyRating( + category: .hateSpeech, + probability: .low, + probabilityScore: 0.3, + severity: .medium, + severityScore: 0.2, + blocked: false + ), + SafetyRating( + category: .sexuallyExplicit, + probability: .low, + probabilityScore: 0.2, + severity: .negligible, + severityScore: 0.5, + blocked: false + ), ], finishReason: FinishReason.maxTokens, citationMetadata: nil), @@ -190,10 +218,38 @@ struct ErrorDetailsView: View { Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. """), safetyRatings: [ - SafetyRating(category: .dangerousContent, probability: .high), - SafetyRating(category: .harassment, probability: .low), - SafetyRating(category: .hateSpeech, probability: .low), - SafetyRating(category: .sexuallyExplicit, probability: .low), + SafetyRating( + category: .dangerousContent, + probability: .low, + probabilityScore: 0.8, + severity: .medium, + severityScore: 0.9, + blocked: false + ), + SafetyRating( + category: .harassment, + probability: .low, + probabilityScore: 0.5, + severity: .low, + severityScore: 0.6, + blocked: false + ), + SafetyRating( + category: .hateSpeech, + probability: .low, + probabilityScore: 0.3, + severity: .medium, + severityScore: 0.2, + blocked: false + ), + SafetyRating( + category: .sexuallyExplicit, + probability: .low, + probabilityScore: 0.2, + severity: .negligible, + severityScore: 0.5, + blocked: false + ), ], finishReason: FinishReason.other, citationMetadata: nil), diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift index e43258557b4..3efce09a119 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift @@ -36,22 +36,54 @@ struct ErrorView: View { #Preview { NavigationView { let errorPromptBlocked = GenerateContentError.promptBlocked( - response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", parts: [ - """ - A _hypothetical_ model response. - Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. - """, - ]), - safetyRatings: [ - SafetyRating(category: .dangerousContent, probability: .high), - SafetyRating(category: .harassment, probability: .low), - SafetyRating(category: .hateSpeech, probability: .low), - SafetyRating(category: .sexuallyExplicit, probability: .low), - ], - finishReason: FinishReason.other, - citationMetadata: nil), - ]) + response: GenerateContentResponse( + candidates: [ + CandidateResponse( + content: ModelContent(role: "model", parts: [ + """ + A _hypothetical_ model response. + Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. + """, + ]), + safetyRatings: [ + SafetyRating( + category: .dangerousContent, + probability: .high, + probabilityScore: 0.8, + severity: .medium, + severityScore: 0.9, + blocked: true + ), + SafetyRating( + category: .harassment, + probability: .low, + probabilityScore: 0.5, + severity: .low, + severityScore: 0.6, + blocked: false + ), + SafetyRating( + category: .hateSpeech, + probability: .low, + probabilityScore: 0.3, + severity: .medium, + severityScore: 0.2, + blocked: false + ), + SafetyRating( + category: .sexuallyExplicit, + probability: .low, + probabilityScore: 0.2, + severity: .negligible, + severityScore: 0.5, + blocked: false + ), + ], + finishReason: FinishReason.other, + citationMetadata: nil + ), + ] + ) ) List { MessageView(message: ChatMessage.samples[0]) diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index d810613aecb..2ff4fe85f1c 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -26,16 +26,50 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// The model-generated probability that the content falls under the specified harm ``category``. /// - /// See ``HarmProbability`` for a list of possible values. + /// See ``HarmProbability`` for a list of possible values. This is a discretized representation + /// of the ``probabilityScore``. /// /// > Important: This does not indicate the severity of harm for a piece of content. public let probability: HarmProbability + /// The confidence score that the response is associated with the corresponding harm ``category``. + /// + /// The probability safety score is a confidence score between 0.0 and 1.0, rounded to one decimal + /// place; it is discretized into a ``HarmProbability`` in ``probability``. See [probability + /// scores](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters#comparison_of_probability_scores_and_severity_scores) + /// in the Google Cloud documentation for more details. + public let probabilityScore: Float + + /// The severity reflects the magnitude of how harmful a model response might be. + /// + /// See ``HarmSeverity`` for a list of possible values. This is a discretized representation of + /// the ``severityScore``. + public let severity: HarmSeverity + + /// The severity score is the magnitude of how harmful a model response might be. + /// + /// The severity score ranges from 0.0 to 1.0, rounded to one decimal place; it is discretized + /// into a ``HarmSeverity`` in ``severity``. See [severity scores](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters#comparison_of_probability_scores_and_severity_scores) + /// in the Google Cloud documentation for more details. + public let severityScore: Float + + /// If true, the response was blocked. + public let blocked: Bool + /// Initializes a new `SafetyRating` instance with the given category and probability. /// Use this initializer for SwiftUI previews or tests. - public init(category: HarmCategory, probability: HarmProbability) { + public init(category: HarmCategory, + probability: HarmProbability, + probabilityScore: Float, + severity: HarmSeverity, + severityScore: Float, + blocked: Bool) { self.category = category self.probability = probability + self.probabilityScore = probabilityScore + self.severity = severity + self.severityScore = severityScore + self.blocked = blocked } /// The probability that a given model output falls under a harmful content category. @@ -74,6 +108,37 @@ public struct SafetyRating: Equatable, Hashable, Sendable { static let unrecognizedValueMessageCode = VertexLog.MessageCode.generateContentResponseUnrecognizedHarmProbability } + + /// The magnitude of how harmful a model response might be for the respective ``HarmCategory``. + public struct HarmSeverity: DecodableProtoEnum, Hashable, Sendable { + enum Kind: String { + case negligible = "HARM_SEVERITY_NEGLIGIBLE" + case low = "HARM_SEVERITY_LOW" + case medium = "HARM_SEVERITY_MEDIUM" + case high = "HARM_SEVERITY_HIGH" + } + + /// Negligible level of harm severity. + public static let negligible = HarmSeverity(kind: .negligible) + + /// Low level of harm severity. + public static let low = HarmSeverity(kind: .low) + + /// Medium level of harm severity. + public static let medium = HarmSeverity(kind: .medium) + + /// High level of harm severity. + public static let high = HarmSeverity(kind: .high) + + /// Returns the raw string representation of the `HarmSeverity` value. + /// + /// > Note: This value directly corresponds to the values in the [REST + /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#HarmSeverity). + public let rawValue: String + + static let unrecognizedValueMessageCode = + VertexLog.MessageCode.generateContentResponseUnrecognizedHarmSeverity + } } /// A type used to specify a threshold for harmful content, beyond which the model will return a @@ -164,7 +229,31 @@ public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { // MARK: - Codable Conformances @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension SafetyRating: Decodable {} +extension SafetyRating: Decodable { + enum CodingKeys: CodingKey { + case category + case probability + case probabilityScore + case severity + case severityScore + case blocked + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + category = try container.decode(HarmCategory.self, forKey: .category) + probability = try container.decode(HarmProbability.self, forKey: .probability) + + // The following 3 fields are only omitted in our test data. + probabilityScore = try container.decodeIfPresent(Float.self, forKey: .probabilityScore) ?? 0.0 + severity = try container.decodeIfPresent(HarmSeverity.self, forKey: .severity) ?? + HarmSeverity(rawValue: "HARM_SEVERITY_UNSPECIFIED") + severityScore = try container.decodeIfPresent(Float.self, forKey: .severityScore) ?? 0.0 + + // The blocked field is only included when true. + blocked = try container.decodeIfPresent(Bool.self, forKey: .blocked) ?? false + } +} @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting.HarmBlockThreshold: Encodable {} diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index bd400c200c2..7ffaf78f0fc 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -49,6 +49,7 @@ enum VertexLog { case generateContentResponseUnrecognizedBlockThreshold = 3004 case generateContentResponseUnrecognizedHarmProbability = 3005 case generateContentResponseUnrecognizedHarmCategory = 3006 + case generateContentResponseUnrecognizedHarmSeverity = 3007 // SDK State Errors case generateContentResponseNoCandidates = 4000 diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index de14552c251..5ffa94daf64 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -23,10 +23,38 @@ import XCTest final class GenerativeModelTests: XCTestCase { let testPrompt = "What sorts of questions can I ask you?" let safetyRatingsNegligible: [SafetyRating] = [ - .init(category: .sexuallyExplicit, probability: .negligible), - .init(category: .hateSpeech, probability: .negligible), - .init(category: .harassment, probability: .negligible), - .init(category: .dangerousContent, probability: .negligible), + .init( + category: .sexuallyExplicit, + probability: .negligible, + probabilityScore: 0.1431877, + severity: .negligible, + severityScore: 0.11027937, + blocked: false + ), + .init( + category: .hateSpeech, + probability: .negligible, + probabilityScore: 0.029035643, + severity: .negligible, + severityScore: 0.05613278, + blocked: false + ), + .init( + category: .harassment, + probability: .negligible, + probabilityScore: 0.087252244, + severity: .negligible, + severityScore: 0.04509957, + blocked: false + ), + .init( + category: .dangerousContent, + probability: .negligible, + probabilityScore: 0.2641685, + severity: .negligible, + severityScore: 0.082253955, + blocked: false + ), ].sorted() let testModelResourceName = "projects/test-project-id/locations/test-location/publishers/google/models/test-model" @@ -69,7 +97,7 @@ final class GenerativeModelTests: XCTestCase { let candidate = try XCTUnwrap(response.candidates.first) let finishReason = try XCTUnwrap(candidate.finishReason) XCTAssertEqual(finishReason, .stop) - XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible) + XCTAssertEqual(candidate.safetyRatings.count, 4) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) let partText = try XCTUnwrap(part as? TextPart).text @@ -148,7 +176,7 @@ final class GenerativeModelTests: XCTestCase { let candidate = try XCTUnwrap(response.candidates.first) let finishReason = try XCTUnwrap(candidate.finishReason) XCTAssertEqual(finishReason, .stop) - XCTAssertEqual(candidate.safetyRatings.sorted(), safetyRatingsNegligible) + XCTAssertEqual(candidate.safetyRatings.count, 4) XCTAssertEqual(candidate.content.parts.count, 1) let part = try XCTUnwrap(candidate.content.parts.first) let textPart = try XCTUnwrap(part as? TextPart) @@ -156,17 +184,35 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(response.text, textPart.text) let promptFeedback = try XCTUnwrap(response.promptFeedback) XCTAssertNil(promptFeedback.blockReason) - XCTAssertEqual(promptFeedback.safetyRatings.sorted(), safetyRatingsNegligible) + XCTAssertEqual(promptFeedback.safetyRatings.count, 4) } func testGenerateContent_success_unknownEnum_safetyRatings() async throws { let expectedSafetyRatings = [ - SafetyRating(category: .harassment, probability: .medium), + SafetyRating( + category: .harassment, + probability: .medium, + probabilityScore: 0.0, + severity: .init(rawValue: "HARM_SEVERITY_UNSPECIFIED"), + severityScore: 0.0, + blocked: false + ), SafetyRating( category: .dangerousContent, - probability: SafetyRating.HarmProbability(rawValue: "FAKE_NEW_HARM_PROBABILITY") + probability: SafetyRating.HarmProbability(rawValue: "FAKE_NEW_HARM_PROBABILITY"), + probabilityScore: 0.0, + severity: .init(rawValue: "HARM_SEVERITY_UNSPECIFIED"), + severityScore: 0.0, + blocked: false + ), + SafetyRating( + category: HarmCategory(rawValue: "FAKE_NEW_HARM_CATEGORY"), + probability: .high, + probabilityScore: 0.0, + severity: .init(rawValue: "HARM_SEVERITY_UNSPECIFIED"), + severityScore: 0.0, + blocked: false ), - SafetyRating(category: HarmCategory(rawValue: "FAKE_NEW_HARM_CATEGORY"), probability: .high), ] MockURLProtocol .requestHandler = try httpRequestHandler( @@ -839,8 +885,11 @@ final class GenerativeModelTests: XCTestCase { for try await _ in stream { XCTFail("Content shouldn't be shown, this shouldn't happen.") } - } catch let GenerateContentError.responseStoppedEarly(reason, _) { + } catch let GenerateContentError.responseStoppedEarly(reason, response) { XCTAssertEqual(reason, .safety) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.finishReason, reason) + XCTAssertTrue(candidate.safetyRatings.contains { $0.blocked }) return } @@ -930,7 +979,11 @@ final class GenerativeModelTests: XCTestCase { ) let unknownSafetyRating = SafetyRating( category: HarmCategory(rawValue: "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM"), - probability: SafetyRating.HarmProbability(rawValue: "NEGLIGIBLE_UNKNOWN_ENUM") + probability: SafetyRating.HarmProbability(rawValue: "NEGLIGIBLE_UNKNOWN_ENUM"), + probabilityScore: 0.0, + severity: SafetyRating.HarmSeverity(rawValue: "HARM_SEVERITY_UNSPECIFIED"), + severityScore: 0.0, + blocked: false ) var foundUnknownSafetyRating = false From 178a5224f6fd32a97489ff29370c85f02a7a28b3 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 14 Oct 2024 17:25:38 -0400 Subject: [PATCH 200/258] [Vertex AI] Add `HarmBlockMethod` enum and `method` property (#13876) --- FirebaseVertexAI/CHANGELOG.md | 3 ++ FirebaseVertexAI/Sources/Safety.swift | 34 ++++++++++++++++++- .../Tests/Integration/IntegrationTests.swift | 8 ++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 346ef9a70bf..c3b6b5462ef 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -63,6 +63,9 @@ (#13875) - [added] Added a new `HarmBlockThreshold` `.off`, which turns off the safety filter. (#13863) +- [added] Added an optional `HarmBlockMethod` parameter `method` in + `SafetySetting` that configures whether responses are blocked based on the + `probability` and/or `severity` of content being in a `HarmCategory`. (#13876) - [added] Added new `FinishReason` values `.blocklist`, `.prohibitedContent`, `.spii` and `.malformedFunctionCall` that may be reported. (#13860) - [added] Added new `BlockReason` values `.blocklist` and `.prohibitedContent` diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 2ff4fe85f1c..655046db98a 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -173,9 +173,26 @@ public struct SafetySetting { let rawValue: String } + /// The method of computing whether the ``SafetySetting/HarmBlockThreshold`` has been exceeded. + public struct HarmBlockMethod: EncodableProtoEnum, Sendable { + enum Kind: String { + case severity = "SEVERITY" + case probability = "PROBABILITY" + } + + /// Use both probability and severity scores. + public static let severity = HarmBlockMethod(kind: .severity) + + /// Use only the probability score. + public static let probability = HarmBlockMethod(kind: .probability) + + let rawValue: String + } + enum CodingKeys: String, CodingKey { case harmCategory = "category" case threshold + case method } /// The category this safety setting should be applied to. @@ -184,10 +201,25 @@ public struct SafetySetting { /// The threshold describing what content should be blocked. public let threshold: HarmBlockThreshold + /// The method of computing whether the ``threshold`` has been exceeded. + public let method: HarmBlockMethod? + /// Initializes a new safety setting with the given category and threshold. - public init(harmCategory: HarmCategory, threshold: HarmBlockThreshold) { + /// + /// - Parameters: + /// - harmCategory: The category this safety setting should be applied to. + /// - threshold: The threshold describing what content should be blocked. + /// - method: The method of computing whether the threshold has been exceeded; if not specified, + /// the default method is ``HarmBlockMethod/severity`` for most models. See [harm block + /// methods](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters#how_to_configure_safety_filters) + /// in the Google Cloud documentation for more details. + /// > Note: For models older than `gemini-1.5-flash` and `gemini-1.5-pro`, the default method + /// > is ``HarmBlockMethod/probability``. + public init(harmCategory: HarmCategory, threshold: HarmBlockThreshold, + method: HarmBlockMethod? = nil) { self.harmCategory = harmCategory self.threshold = threshold + self.method = method } } diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift index fee87108da7..b884a41e9a5 100644 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift @@ -30,8 +30,8 @@ final class IntegrationTests: XCTestCase { parts: "You are a friendly and helpful assistant." ) let safetySettings = [ - SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), - SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .probability), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove, method: .severity), SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockLowAndAbove), SafetySetting(harmCategory: .dangerousContent, threshold: .blockLowAndAbove), SafetySetting(harmCategory: .civicIntegrity, threshold: .blockLowAndAbove), @@ -89,11 +89,11 @@ final class IntegrationTests: XCTestCase { modelName: "gemini-1.5-pro", generationConfig: generationConfig, safetySettings: [ - SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .severity), SafetySetting(harmCategory: .hateSpeech, threshold: .blockMediumAndAbove), SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockOnlyHigh), SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), - SafetySetting(harmCategory: .civicIntegrity, threshold: .off), + SafetySetting(harmCategory: .civicIntegrity, threshold: .off, method: .probability), ], toolConfig: .init(functionCallingConfig: .auto()), systemInstruction: systemInstruction From 0ab96c61ce49f81e1cab268ed58056cf73a84d2e Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Mon, 14 Oct 2024 14:42:47 -0700 Subject: [PATCH 201/258] fix typos in release notes (#13890) --- Crashlytics/CHANGELOG.md | 2 +- FirebaseAuth/CHANGELOG.md | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Crashlytics/CHANGELOG.md b/Crashlytics/CHANGELOG.md index 961cadfbe5a..b13fb66749b 100644 --- a/Crashlytics/CHANGELOG.md +++ b/Crashlytics/CHANGELOG.md @@ -1,5 +1,5 @@ # 11.4.0 -- [fixed] Updated `upload-symbols` to version 3.18 with support for upload mutiple DWARF contents in a dSYM bundle(#13543). +- [fixed] Updated `upload-symbols` to version 3.18 with support for uploading multiple DWARF contents in a dSYM bundle (#13543). # 10.28.1 - [changed] Reverted "Add SIGTERM support (#12881)" (#13117) diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index 9d290018c7a..d5e3b31c3ba 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,28 +1,28 @@ # 11.4.0 -- [Fixed] Restore Firebase 10 behavior by ignoring `nil` display names used +- [fixed] Restore Firebase 10 behavior by ignoring `nil` display names used during multi factor enrollment. (#13856) -- [Fixed] Fix crash when enrolling account with TOTP MFA. (#13880) +- [fixed] Fix crash when enrolling account with TOTP MFA. (#13880) # 11.3.0 -- [Fixed] Restore Firebase 10 behavior by querying with the +- [fixed] Restore Firebase 10 behavior by querying with the `kSecAttrSynchronizable` key when auth state is set to be shared across devices. (#13584) -- [Fixed] Prevent a bad memory access crash by using non-ObjC, native Swift +- [fixed] Prevent a bad memory access crash by using non-ObjC, native Swift types in the SDK's networking layer, and moving synchronous work off of the shared Swift concurrency queue. (#13650) -- [Fixed] Restore Firebase 10 behavior by forwarding errors from interrupted +- [fixed] Restore Firebase 10 behavior by forwarding errors from interrupted reCAPTCHA or OIDC login flows. (#13645) # 11.2.0 -- [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 +- [fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16 betas. (#13480) -- [Fixed] Fixed Phone Auth via Sandbox APNS tokens that broke in 11.0.0. (#13479) -- [Fixed] Fixed crash when fetching sign in methods due to unexpected nil. +- [fixed] Fixed Phone Auth via Sandbox APNS tokens that broke in 11.0.0. (#13479) +- [fixed] Fixed crash when fetching sign in methods due to unexpected nil. Previously, fetching sign in methods could return both a `nil` array of sign in methods and a `nil` error. In such cases, an empty array is instead returned with the `nil` error. (#13550) -- [Fixed] Fixed user session persistence in multi tenant projects. Introduced in 11.0.0. (#13565) -- [Fixed] Fixed encoding crash that occurs when using TOTP multi-factor +- [fixed] Fixed user session persistence in multi tenant projects. Introduced in 11.0.0. (#13565) +- [fixed] Fixed encoding crash that occurs when using TOTP multi-factor authentication. Note that this fix will not be in the 11.2.0 zip and Carthage distributions, but will be included from 11.3.0 onwards. (#13591) From e1d1ad967e3d9d81ec14e4e5cbafbacbf6db6223 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 14 Oct 2024 17:50:24 -0400 Subject: [PATCH 202/258] [Vertex AI] Add `blockReasonMessage` to `PromptFeedback` (#13891) --- FirebaseVertexAI/CHANGELOG.md | 2 + .../Sources/GenerateContentResponse.swift | 9 +++- .../Tests/Unit/GenerativeModelTests.swift | 49 ++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index c3b6b5462ef..d0d95a82070 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -70,6 +70,8 @@ `.spii` and `.malformedFunctionCall` that may be reported. (#13860) - [added] Added new `BlockReason` values `.blocklist` and `.prohibitedContent` that may be reported when a prompt is blocked. (#13861) +- [added] Added the `PromptFeedback` property `blockReasonMessage` that *may* be + provided alongside the `blockReason`. (#13891) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 07491765f90..d786676ec80 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -235,12 +235,17 @@ public struct PromptFeedback: Sendable { /// The reason a prompt was blocked, if it was blocked. public let blockReason: BlockReason? + /// A human-readable description of the ``blockReason``. + public let blockReasonMessage: String? + /// The safety ratings of the prompt. public let safetyRatings: [SafetyRating] /// Initializer for SwiftUI previews or tests. - public init(blockReason: BlockReason?, safetyRatings: [SafetyRating]) { + public init(blockReason: BlockReason?, blockReasonMessage: String? = nil, + safetyRatings: [SafetyRating]) { self.blockReason = blockReason + self.blockReasonMessage = blockReasonMessage self.safetyRatings = safetyRatings } } @@ -387,6 +392,7 @@ extension Citation: Decodable { extension PromptFeedback: Decodable { enum CodingKeys: CodingKey { case blockReason + case blockReasonMessage case safetyRatings } @@ -396,6 +402,7 @@ extension PromptFeedback: Decodable { PromptFeedback.BlockReason.self, forKey: .blockReason ) + blockReasonMessage = try container.decodeIfPresent(String.self, forKey: .blockReasonMessage) if let safetyRatings = try container.decodeIfPresent( [SafetyRating].self, forKey: .safetyRatings diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 5ffa94daf64..7b6b1c15336 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -619,6 +619,29 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should throw") } catch let GenerateContentError.promptBlocked(response) { XCTAssertNil(response.text) + let promptFeedback = try XCTUnwrap(response.promptFeedback) + XCTAssertEqual(promptFeedback.blockReason, PromptFeedback.BlockReason.safety) + XCTAssertNil(promptFeedback.blockReasonMessage) + } catch { + XCTFail("Should throw a promptBlocked") + } + } + + func testGenerateContent_failure_promptBlockedSafetyWithMessage() async throws { + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-failure-prompt-blocked-safety-with-message", + withExtension: "json" + ) + + do { + _ = try await model.generateContent(testPrompt) + XCTFail("Should throw") + } catch let GenerateContentError.promptBlocked(response) { + XCTAssertNil(response.text) + let promptFeedback = try XCTUnwrap(response.promptFeedback) + XCTAssertEqual(promptFeedback.blockReason, PromptFeedback.BlockReason.safety) + XCTAssertEqual(promptFeedback.blockReasonMessage, "Reasons") } catch { XCTFail("Should throw a promptBlocked") } @@ -909,7 +932,31 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Content shouldn't be shown, this shouldn't happen.") } } catch let GenerateContentError.promptBlocked(response) { - XCTAssertEqual(response.promptFeedback?.blockReason, .safety) + let promptFeedback = try XCTUnwrap(response.promptFeedback) + XCTAssertEqual(promptFeedback.blockReason, .safety) + XCTAssertNil(promptFeedback.blockReasonMessage) + return + } + + XCTFail("Should have caught an error.") + } + + func testGenerateContentStream_failurePromptBlockedSafetyWithMessage() async throws { + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "streaming-failure-prompt-blocked-safety-with-message", + withExtension: "txt" + ) + + do { + let stream = try model.generateContentStream("Hi") + for try await _ in stream { + XCTFail("Content shouldn't be shown, this shouldn't happen.") + } + } catch let GenerateContentError.promptBlocked(response) { + let promptFeedback = try XCTUnwrap(response.promptFeedback) + XCTAssertEqual(promptFeedback.blockReason, .safety) + XCTAssertEqual(promptFeedback.blockReasonMessage, "Reasons") return } From ab94252dec494b0e7637319b9e8403c2c1361f5f Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Mon, 14 Oct 2024 16:07:08 -0700 Subject: [PATCH 203/258] add docsgen invocations to scripts (#13892) --- scripts/docsgen/jazzy_generate_mixed.sh | 47 +++++++++++++++++++++++++ scripts/docsgen/jazzy_generate_objc.sh | 44 +++++++++++++++++++++++ scripts/docsgen/jazzy_generate_swift.sh | 39 ++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100755 scripts/docsgen/jazzy_generate_mixed.sh create mode 100755 scripts/docsgen/jazzy_generate_objc.sh create mode 100755 scripts/docsgen/jazzy_generate_swift.sh diff --git a/scripts/docsgen/jazzy_generate_mixed.sh b/scripts/docsgen/jazzy_generate_mixed.sh new file mode 100755 index 00000000000..e69a5769d9c --- /dev/null +++ b/scripts/docsgen/jazzy_generate_mixed.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -ex + +if [[ "$#" != 4 ]] +then + echo -e "Usage: ./jazzy_generate.sh output_dir xcworkspace_path umbrella_header_path module_name" + exit 1 +fi + +output_dir=$1 +workspace_path=$2 +umbrella_header_path=$3 +module_name=$4 + +SWIFT_VERSION="$(xcrun swift -version | cut -d " " -f 4)" + +# Generate temporary sourcekitten files. +mkdir -p "$output_dir" +sourcekitten \ + doc --objc "$umbrella_header_path" \ + -- -x objective-c -isysroot "$(xcrun --show-sdk-path --sdk iphonesimulator)" \ + -I "$workspace_path" -fmodules > "$output_dir"/objcDoc.json +sourcekitten \ + doc -- -workspace "$workspace_path" \ + -scheme "$module_name" > "$output_dir"/swiftDoc.json + +# Generates reference docs using jazzy +jazzy \ + --author "Firebase" \ + --module "$module_name" \ + --output "$output_dir" \ + --sourcekitten-sourcefile "$output_dir/swiftDoc.json,$output_dir/objcDoc.json" diff --git a/scripts/docsgen/jazzy_generate_objc.sh b/scripts/docsgen/jazzy_generate_objc.sh new file mode 100755 index 00000000000..3cedba7c8c4 --- /dev/null +++ b/scripts/docsgen/jazzy_generate_objc.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -ex + +if [[ "$#" != 4 ]] +then + echo -e "Usage: ./jazzy_generate.sh output_dir xcworkspace_path umbrella_header_path module_name" + exit 1 +fi + +output_dir=$1 +workspace_path=$2 +umbrella_header_path=$3 +module_name=$4 + +SWIFT_VERSION="$(xcrun swift -version | cut -d " " -f 4)" + +# Generate temporary sourcekitten files. +mkdir -p "$output_dir" +sourcekitten \ + doc --objc "$umbrella_header_path" \ + -- -x objective-c -isysroot "$(xcrun --show-sdk-path --sdk iphonesimulator)" \ + -I "$workspace_path" -fmodules > "$output_dir"/objcDoc.json + +# Generates reference docs using jazzy +jazzy \ + --author "Firebase" \ + --module "$module_name" \ + --output "$output_dir" \ + --sourcekitten-sourcefile "$output_dir"/objcDoc.json diff --git a/scripts/docsgen/jazzy_generate_swift.sh b/scripts/docsgen/jazzy_generate_swift.sh new file mode 100755 index 00000000000..4b6b9591cf4 --- /dev/null +++ b/scripts/docsgen/jazzy_generate_swift.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -ex + +if [[ "$#" != 3 ]] +then + echo -e "Usage: ./jazzy_generate.sh output_dir xcworkspace_path module_name" + exit 1 +fi + +output_dir=$1 +workspace_path=$2 +module_name=$3 + +SWIFT_VERSION="$(xcrun swift -version | cut -d " " -f 4)" + +jazzy \ + --clean \ + --author "Firebase" \ + --swift-version "${SWIFT_VERSION}" \ + --sdk iphonesimulator \ + --build-tool-arguments -workspace,"$workspace_path",-scheme,"$module_name" \ + --module "$module_name" \ + --output "$output_dir" \ + --swift-build-tool xcodebuild From b5aee64738721fdbdbc251548311bb6132dd786c Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:21:29 -0400 Subject: [PATCH 204/258] [Release] Update Firestore SPM binary for M155 (#13894) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 75f33326255..3b4de208afa 100644 --- a/Package.swift +++ b/Package.swift @@ -1512,8 +1512,8 @@ func firestoreTargets() -> [Target] { } else { return .binaryTarget( name: "FirebaseFirestoreInternal", - url: "https://dl.google.com/firebase/ios/bin/firestore/11.3.0/rc0/FirebaseFirestoreInternal.zip", - checksum: "214f91ae3ad87fce55155ea3e6dacda4f237e703ea3935353c7c6819eddb1c17" + url: "https://dl.google.com/firebase/ios/bin/firestore/11.4.0/rc0/FirebaseFirestoreInternal.zip", + checksum: "2b467ccf81306a5b7f0e7e41d2f3aa384998235306a0c33d1c973209241e9cdb" ) } }() From b99e3d756776971918824faccb4f0b3c1012dae4 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 15 Oct 2024 15:33:56 -0400 Subject: [PATCH 205/258] [Vertex AI] Fix invalid DocC links and missing docs (#13896) --- .../Sources/FunctionCalling.swift | 2 + .../Sources/GenerativeModel.swift | 40 +++++++++---------- FirebaseVertexAI/Sources/ModelContent.swift | 2 +- .../Sources/Types/Public/Part.swift | 4 +- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 7bf366a7132..69c90b18e10 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -35,6 +35,8 @@ public struct FunctionDeclaration { /// with a maximum length of 63. /// - description: A brief description of the function. /// - parameters: Describes the parameters to this function. + /// - optionalParameters: The names of parameters that may be omitted by the model in function + /// calls; by default, all parameters are considered required. public init(name: String, description: String, parameters: [String: Schema], optionalParameters: [String] = []) { self.name = name diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 0ac58732e11..c920a0daa88 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -97,17 +97,17 @@ public final class GenerativeModel { } /// Generates content from String and/or image inputs, given to the model as a prompt, that are - /// representable as one or more ``ModelContent/Part``s. + /// representable as one or more ``Part``s. /// - /// Since ``ModelContent/Part``s do not specify a role, this method is intended for generating - /// content from + /// Since ``Part``s do not specify a role, this method is intended for generating content from /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) - /// prompts, see `generateContent(_ content: @autoclosure () throws -> [ModelContent])`. + /// prompts, see `generateContent(_ content: [ModelContent])`. /// - /// - Parameter content: The input(s) given to the model as a prompt (see ``PartsRepresentable`` - /// for conforming types). + /// - Parameters: + /// - parts: The input(s) given to the model as a prompt (see ``PartsRepresentable`` for + /// conforming types). /// - Returns: The content generated by the model. /// - Throws: A ``GenerateContentError`` if the request failed. public func generateContent(_ parts: any PartsRepresentable...) @@ -153,17 +153,17 @@ public final class GenerativeModel { } /// Generates content from String and/or image inputs, given to the model as a prompt, that are - /// representable as one or more ``ModelContent/Part``s. + /// representable as one or more ``Part``s. /// - /// Since ``ModelContent/Part``s do not specify a role, this method is intended for generating - /// content from + /// Since ``Part``s do not specify a role, this method is intended for generating content from /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) /// prompts, see `generateContentStream(_ content: @autoclosure () throws -> [ModelContent])`. /// - /// - Parameter content: The input(s) given to the model as a prompt (see - /// ``PartsRepresentable`` for conforming types). + /// - Parameters: + /// - parts: The input(s) given to the model as a prompt (see ``PartsRepresentable`` for + /// conforming types). /// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError`` /// error if an error occurred. @available(macOS 12.0, *) @@ -228,21 +228,20 @@ public final class GenerativeModel { } /// Runs the model's tokenizer on String and/or image inputs that are representable as one or more - /// ``ModelContent/Part``s. + /// ``Part``s. /// - /// Since ``ModelContent/Part``s do not specify a role, this method is intended for tokenizing + /// Since ``Part``s do not specify a role, this method is intended for tokenizing /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) /// input, see `countTokens(_ content: @autoclosure () throws -> [ModelContent])`. /// - /// - Parameter content: The input(s) given to the model as a prompt (see ``PartsRepresentable`` - /// for conforming types). + /// - Parameters: + /// - parts: The input(s) given to the model as a prompt (see ``PartsRepresentable`` for + /// conforming types). /// - Returns: The results of running the model's tokenizer on the input; contains /// ``CountTokensResponse/totalTokens``. - /// - Throws: A ``CountTokensError`` if the tokenization request failed. - public func countTokens(_ parts: any PartsRepresentable...) async throws - -> CountTokensResponse { + public func countTokens(_ parts: any PartsRepresentable...) async throws -> CountTokensResponse { return try await countTokens([ModelContent(parts: parts)]) } @@ -251,10 +250,7 @@ public final class GenerativeModel { /// - Parameter content: The input given to the model as a prompt. /// - Returns: The results of running the model's tokenizer on the input; contains /// ``CountTokensResponse/totalTokens``. - /// - Throws: A ``CountTokensError`` if the tokenization request failed or the input content was - /// invalid. - public func countTokens(_ content: [ModelContent]) async throws - -> CountTokensResponse { + public func countTokens(_ content: [ModelContent]) async throws -> CountTokensResponse { let countTokensRequest = CountTokensRequest( model: modelResourceName, contents: content, diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index 9697a97a5bf..885d209e0a1 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -33,7 +33,7 @@ extension [ModelContent] { /// A type describing data in media formats interpretable by an AI model. Each generative AI /// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value -/// may comprise multiple heterogeneous ``ModelContent/Part``s. +/// may comprise multiple heterogeneous ``Part``s. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct ModelContent: Equatable, Sendable { enum InternalPart: Equatable, Sendable { diff --git a/FirebaseVertexAI/Sources/Types/Public/Part.swift b/FirebaseVertexAI/Sources/Types/Public/Part.swift index 1eba33ae018..8cc25de1db2 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Part.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Part.swift @@ -104,11 +104,11 @@ public struct FunctionCallPart: Part { } } -/// Result output from a ``FunctionCall``. +/// Result output from a function call. /// /// Contains a string representing the `FunctionDeclaration.name` and a structured JSON object /// containing any output from the function is used as context to the model. This should contain the -/// result of a ``FunctionCall`` made based on model prediction. +/// result of a ``FunctionCallPart`` made based on model prediction. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionResponsePart: Part { let functionResponse: FunctionResponse From 4ca14136900714f645d4c54e81c9486fce1a75a0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 15 Oct 2024 18:45:48 -0400 Subject: [PATCH 206/258] [Vertex AI] Add `Citation.publicationDate` (#13893) --- FirebaseVertexAI/CHANGELOG.md | 2 + .../Sources/GenerateContentResponse.swift | 24 ++ .../Sources/Types/Internal/ProtoDate.swift | 117 +++++++++ FirebaseVertexAI/Sources/VertexLog.swift | 4 + .../Tests/Unit/GenerativeModelTests.swift | 20 +- .../Tests/Unit/Types/ProtoDateTests.swift | 236 ++++++++++++++++++ 6 files changed, 401 insertions(+), 2 deletions(-) create mode 100644 FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index d0d95a82070..5c777915ead 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -72,6 +72,8 @@ that may be reported when a prompt is blocked. (#13861) - [added] Added the `PromptFeedback` property `blockReasonMessage` that *may* be provided alongside the `blockReason`. (#13891) +- [added] Added an optional `publicationDate` property that *may* be provided in + `Citation`. (#13893) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index d786676ec80..fa736cbd1f0 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -141,6 +141,11 @@ public struct Citation: Sendable { /// The license the cited source work is distributed under, if specified. public let license: String? + + /// The publication date of the cited source, if available. + /// + /// > Tip: `DateComponents` can be converted to a `Date` using the `date` computed property. + public let publicationDate: DateComponents? } /// A value enumerating possible reasons for a model to terminate a content generation request. @@ -363,28 +368,47 @@ extension Citation: Decodable { case uri case title case license + case publicationDate } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0 endIndex = try container.decode(Int.self, forKey: .endIndex) + if let uri = try container.decodeIfPresent(String.self, forKey: .uri), !uri.isEmpty { self.uri = uri } else { uri = nil } + if let title = try container.decodeIfPresent(String.self, forKey: .title), !title.isEmpty { self.title = title } else { title = nil } + if let license = try container.decodeIfPresent(String.self, forKey: .license), !license.isEmpty { self.license = license } else { license = nil } + + if let publicationProtoDate = try container.decodeIfPresent( + ProtoDate.self, + forKey: .publicationDate + ) { + publicationDate = publicationProtoDate.dateComponents + if let publicationDate, !publicationDate.isValidDate { + VertexLog.warning( + code: .decodedInvalidCitationPublicationDate, + "Decoded an invalid citation publication date: \(publicationDate)" + ) + } + } else { + publicationDate = nil + } } } diff --git a/FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift b/FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift new file mode 100644 index 00000000000..fed7e63e943 --- /dev/null +++ b/FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift @@ -0,0 +1,117 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// Represents a whole or partial calendar date, such as a birthday. +/// +/// The time of day and time zone are either specified elsewhere or are insignificant. The date is +/// relative to the Gregorian Calendar. This can represent one of the following: +/// - A full date, with non-zero year, month, and day values +/// - A month and day value, with a zero year, such as an anniversary +/// - A year on its own, with zero month and day values +/// - A year and month value, with a zero day, such as a credit card expiration date +/// +/// This represents a +/// [`google.type.Date`](https://cloud.google.com/vertex-ai/docs/reference/rest/Shared.Types/Date). +struct ProtoDate { + /// Year of the date. + /// + /// Must be from 1 to 9999, or 0 to specify a date without a year. + let year: Int? + + /// Month of a year. + /// + /// Must be from 1 to 12, or 0 to specify a year without a month and day. + let month: Int? + + /// Day of a month. + /// + /// Must be from 1 to 31 and valid for the year and month, or 0 to specify a year by itself or a + /// year and month where the day isn't significant. + let day: Int? + + /// Returns the a `DateComponents` representation of the `ProtoDate`. + /// + /// > Note: This uses the Gregorian `Calendar` to match the `google.type.Date` definition. + var dateComponents: DateComponents { + DateComponents( + calendar: Calendar(identifier: .gregorian), + year: year, + month: month, + day: day + ) + } +} + +// MARK: - Codable Conformance + +extension ProtoDate: Decodable { + enum CodingKeys: CodingKey { + case year + case month + case day + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let year = try container.decodeIfPresent(Int.self, forKey: .year), year != 0 { + if year < 0 || year > 9999 { + VertexLog.warning( + code: .decodedInvalidProtoDateYear, + """ + Invalid year: \(year); must be from 1 to 9999, or 0 for a date without a specified year. + """ + ) + } + self.year = year + } else { + year = nil + } + + if let month = try container.decodeIfPresent(Int.self, forKey: .month), month != 0 { + if month < 0 || month > 12 { + VertexLog.warning( + code: .decodedInvalidProtoDateMonth, + """ + Invalid month: \(month); must be from 1 to 12, or 0 for a year date without a specified \ + month and day. + """ + ) + } + self.month = month + } else { + month = nil + } + + if let day = try container.decodeIfPresent(Int.self, forKey: .day), day != 0 { + if day < 0 || day > 31 { + VertexLog.warning( + code: .decodedInvalidProtoDateDay, + "Invalid day: \(day); must be from 1 to 31, or 0 for a date without a specified day." + ) + } + self.day = day + } else { + day = nil + } + + guard year != nil || month != nil || day != nil else { + throw DecodingError.dataCorrupted(.init( + codingPath: [CodingKeys.year, CodingKeys.month, CodingKeys.day], + debugDescription: "Invalid date: missing year, month and day" + )) + } + } +} diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index 7ffaf78f0fc..2ce1dac3cb0 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -50,6 +50,10 @@ enum VertexLog { case generateContentResponseUnrecognizedHarmProbability = 3005 case generateContentResponseUnrecognizedHarmCategory = 3006 case generateContentResponseUnrecognizedHarmSeverity = 3007 + case decodedInvalidProtoDateYear = 3008 + case decodedInvalidProtoDateMonth = 3009 + case decodedInvalidProtoDateDay = 3010 + case decodedInvalidCitationPublicationDate = 3011 // SDK State Errors case generateContentResponseNoCandidates = 4000 diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 7b6b1c15336..f9035949f1a 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -134,6 +134,12 @@ final class GenerativeModelTests: XCTestCase { forResource: "unary-success-citations", withExtension: "json" ) + let expectedPublicationDate = DateComponents( + calendar: Calendar(identifier: .gregorian), + year: 2019, + month: 5, + day: 10 + ) let response = try await model.generateContent(testPrompt) @@ -149,8 +155,10 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(citationSource1.endIndex, 128) XCTAssertNil(citationSource1.title) XCTAssertNil(citationSource1.license) + XCTAssertNil(citationSource1.publicationDate) let citationSource2 = try XCTUnwrap(citationMetadata.citations[1]) XCTAssertEqual(citationSource2.title, "some-citation-2") + XCTAssertEqual(citationSource2.publicationDate, expectedPublicationDate) XCTAssertEqual(citationSource2.startIndex, 130) XCTAssertEqual(citationSource2.endIndex, 265) XCTAssertNil(citationSource2.uri) @@ -161,6 +169,7 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(citationSource3.endIndex, 431) XCTAssertEqual(citationSource3.license, "mit") XCTAssertNil(citationSource3.title) + XCTAssertNil(citationSource3.publicationDate) } func testGenerateContent_success_quoteReply() async throws { @@ -1052,6 +1061,12 @@ final class GenerativeModelTests: XCTestCase { forResource: "streaming-success-citations", withExtension: "txt" ) + let expectedPublicationDate = DateComponents( + calendar: Calendar(identifier: .gregorian), + year: 2014, + month: 3, + day: 30 + ) let stream = try model.generateContentStream("Hi") var citations = [Citation]() @@ -1072,18 +1087,19 @@ final class GenerativeModelTests: XCTestCase { .contains { $0.startIndex == 0 && $0.endIndex == 128 && $0.uri == "https://www.example.com/some-citation-1" && $0.title == nil - && $0.license == nil + && $0.license == nil && $0.publicationDate == nil }) XCTAssertTrue(citations .contains { $0.startIndex == 130 && $0.endIndex == 265 && $0.uri == nil && $0.title == "some-citation-2" && $0.license == nil + && $0.publicationDate == expectedPublicationDate }) XCTAssertTrue(citations .contains { $0.startIndex == 272 && $0.endIndex == 431 && $0.uri == "https://www.example.com/some-citation-3" && $0.title == nil - && $0.license == "mit" + && $0.license == "mit" && $0.publicationDate == nil }) XCTAssertFalse(citations.contains { $0.uri?.isEmpty ?? false }) XCTAssertFalse(citations.contains { $0.title?.isEmpty ?? false }) diff --git a/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift b/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift new file mode 100644 index 00000000000..0e6816c43df --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift @@ -0,0 +1,236 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest + +@testable import FirebaseVertexAI + +final class ProtoDateTests: XCTestCase { + let decoder = JSONDecoder() + + // MARK: - Date Components Tests + + // A full date, with non-zero year, month, and day values. + func testProtoDate_fullDate_dateComponents() { + let year = 2024 + let month = 12 + let day = 31 + let protoDate = ProtoDate(year: year, month: month, day: day) + + let dateComponents = protoDate.dateComponents + + XCTAssertTrue(dateComponents.isValidDate) + XCTAssertEqual(dateComponents.year, year) + XCTAssertEqual(dateComponents.month, month) + XCTAssertEqual(dateComponents.day, day) + } + + // A month and day value, with a zero year, such as an anniversary. + func testProtoDate_monthDay_dateComponents() { + let month = 7 + let day = 1 + let protoDate = ProtoDate(year: nil, month: month, day: day) + + let dateComponents = protoDate.dateComponents + + XCTAssertTrue(dateComponents.isValidDate) + XCTAssertNil(dateComponents.year) + XCTAssertEqual(dateComponents.month, month) + XCTAssertEqual(dateComponents.day, day) + } + + // A year on its own, with zero month and day values. + func testProtoDate_yearOnly_dateComponents() { + let year = 2024 + let protoDate = ProtoDate(year: year, month: nil, day: nil) + + let dateComponents = protoDate.dateComponents + + XCTAssertTrue(dateComponents.isValidDate) + XCTAssertEqual(dateComponents.year, year) + XCTAssertNil(dateComponents.month) + XCTAssertNil(dateComponents.day) + } + + // A year and month value, with a zero day, such as a credit card expiration date + func testProtoDate_yearMonth_dateComponents() { + let year = 2024 + let month = 8 + let protoDate = ProtoDate(year: year, month: month, day: nil) + + let dateComponents = protoDate.dateComponents + + XCTAssertTrue(dateComponents.isValidDate) + XCTAssertEqual(protoDate.year, year) + XCTAssertEqual(protoDate.month, month) + XCTAssertEqual(protoDate.day, nil) + } + + // MARK: - Decoding Tests + + func testDecodeProtoDate_fullDate() throws { + let json = """ + { + "year" : 2024, + "month" : 12, + "day" : 31 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertEqual(protoDate.year, 2024) + XCTAssertEqual(protoDate.month, 12) + XCTAssertEqual(protoDate.day, 31) + } + + func testDecodeProtoDate_monthDay() throws { + let json = """ + { + "year": 0, + "month" : 12, + "day" : 31 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertNil(protoDate.year) + XCTAssertEqual(protoDate.month, 12) + XCTAssertEqual(protoDate.day, 31) + } + + func testDecodeProtoDate_monthDay_defaultsOmitted() throws { + let json = """ + { + "month" : 12, + "day" : 31 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertNil(protoDate.year) + XCTAssertEqual(protoDate.month, 12) + XCTAssertEqual(protoDate.day, 31) + } + + func testDecodeProtoDate_yearOnly() throws { + let json = """ + { + "year": 2024, + "month" : 0, + "day" : 0 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertEqual(protoDate.year, 2024) + XCTAssertNil(protoDate.month) + XCTAssertNil(protoDate.day) + } + + func testDecodeProtoDate_yearOnly_defaultsOmitted() throws { + let json = """ + { + "year": 2024 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertEqual(protoDate.year, 2024) + XCTAssertNil(protoDate.month) + XCTAssertNil(protoDate.day) + } + + func testDecodeProtoDate_yearMonth() throws { + let json = """ + { + "year": 2024, + "month" : 12, + "day": 0 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertEqual(protoDate.year, 2024) + XCTAssertEqual(protoDate.month, 12) + XCTAssertNil(protoDate.day) + } + + func testDecodeProtoDate_yearMonth_defaultsOmitted() throws { + let json = """ + { + "year": 2024, + "month" : 12 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let protoDate = try decoder.decode(ProtoDate.self, from: jsonData) + + XCTAssertEqual(protoDate.year, 2024) + XCTAssertEqual(protoDate.month, 12) + XCTAssertNil(protoDate.day) + } + + func testDecodeProtoDate_emptyDate_throws() throws { + let json = """ + { + "year": 0, + "month" : 0, + "day": 0 + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + do { + _ = try decoder.decode(ProtoDate.self, from: jsonData) + } catch let DecodingError.dataCorrupted(context) { + XCTAssertEqual( + context.codingPath as? [ProtoDate.CodingKeys], + [ProtoDate.CodingKeys.year, ProtoDate.CodingKeys.month, ProtoDate.CodingKeys.day] + ) + XCTAssertTrue(context.debugDescription.contains("Invalid date")) + return + } + XCTFail("Expected a DecodingError.") + } + + func testDecodeProtoDate_emptyDate_defaultsOmitted_throws() throws { + let json = "{}" + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + do { + _ = try decoder.decode(ProtoDate.self, from: jsonData) + } catch let DecodingError.dataCorrupted(context) { + XCTAssertEqual( + context.codingPath as? [ProtoDate.CodingKeys], + [ProtoDate.CodingKeys.year, ProtoDate.CodingKeys.month, ProtoDate.CodingKeys.day] + ) + XCTAssertTrue(context.debugDescription.contains("Invalid date")) + return + } + XCTFail("Expected a DecodingError.") + } +} From 61f2ee3f8f4226f099efc11f573fcb489c81d371 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 15 Oct 2024 19:12:50 -0400 Subject: [PATCH 207/258] [Vertex AI] Rename `CandidateResponse` to `Candidate` (#13897) --- FirebaseVertexAI/CHANGELOG.md | 2 ++ .../Sample/ChatSample/Views/ErrorDetailsView.swift | 4 ++-- .../Sample/ChatSample/Views/ErrorView.swift | 2 +- FirebaseVertexAI/Sources/GenerateContentResponse.swift | 10 +++++----- FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 5c777915ead..cb4ef58083a 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -50,6 +50,8 @@ - [changed] **Breaking Change**: The `FunctionCallingConfig` initializer and `Mode` enum are now internal; use one of the new type methods `auto()`, `any(allowedFunctionNames:)`, or `none()` to create a config. (#13873) +- [changed] **Breaking Change**: The `CandidateResponse` type is now named + `Candidate`. (#13897) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index 236a1f7d4b0..38c4ed0c410 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -162,7 +162,7 @@ struct ErrorDetailsView: View { let error = GenerateContentError.responseStoppedEarly( reason: .maxTokens, response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", parts: + Candidate(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. @@ -212,7 +212,7 @@ struct ErrorDetailsView: View { #Preview("Prompt Blocked") { let error = GenerateContentError.promptBlocked( response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", parts: + Candidate(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift index 3efce09a119..a5d43c30b2d 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift @@ -38,7 +38,7 @@ struct ErrorView: View { let errorPromptBlocked = GenerateContentError.promptBlocked( response: GenerateContentResponse( candidates: [ - CandidateResponse( + Candidate( content: ModelContent(role: "model", parts: [ """ A _hypothetical_ model response. diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index fa736cbd1f0..f9d8e1d349c 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -30,7 +30,7 @@ public struct GenerateContentResponse: Sendable { } /// A list of candidate response content, ordered from best to worst. - public let candidates: [CandidateResponse] + public let candidates: [Candidate] /// A value containing the safety ratings for the response, or, if the request was blocked, a /// reason for blocking the request. @@ -82,7 +82,7 @@ public struct GenerateContentResponse: Sendable { } /// Initializer for SwiftUI previews or tests. - public init(candidates: [CandidateResponse], promptFeedback: PromptFeedback? = nil, + public init(candidates: [Candidate], promptFeedback: PromptFeedback? = nil, usageMetadata: UsageMetadata? = nil) { self.candidates = candidates self.promptFeedback = promptFeedback @@ -93,7 +93,7 @@ public struct GenerateContentResponse: Sendable { /// A struct representing a possible reply to a content generation prompt. Each content generation /// prompt may produce multiple candidate responses. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct CandidateResponse: Sendable { +public struct Candidate: Sendable { /// The response's content. public let content: ModelContent @@ -279,7 +279,7 @@ extension GenerateContentResponse: Decodable { } if let candidates = try container.decodeIfPresent( - [CandidateResponse].self, + [Candidate].self, forKey: .candidates ) { self.candidates = candidates @@ -309,7 +309,7 @@ extension GenerateContentResponse.UsageMetadata: Decodable { } @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension CandidateResponse: Decodable { +extension Candidate: Decodable { enum CodingKeys: CodingKey { case content case safetyRatings diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index 9624ee6e52c..f113bb7bf49 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -162,7 +162,7 @@ final class VertexAIAPITests: XCTestCase { func generateContentResponseAPI() { let response = GenerateContentResponse(candidates: []) - let _: [CandidateResponse] = response.candidates + let _: [Candidate] = response.candidates let _: PromptFeedback? = response.promptFeedback // Usage Metadata From 4b96944a6b5cd02a3e8b78d341af590c61ea8aca Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 15 Oct 2024 20:43:53 -0400 Subject: [PATCH 208/258] [Vertex AI] Add `presencePenalty` and `frequencyPenalty` (#13899) --- FirebaseVertexAI/CHANGELOG.md | 2 ++ .../Sources/GenerationConfig.swift | 33 +++++++++++++++++++ .../Tests/Unit/GenerationConfigTests.swift | 6 ++++ 3 files changed, 41 insertions(+) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index cb4ef58083a..95d70ca6c0a 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -76,6 +76,8 @@ provided alongside the `blockReason`. (#13891) - [added] Added an optional `publicationDate` property that *may* be provided in `Citation`. (#13893) +- [added] Added `presencePenalty` and `frequencyPenalty` parameters to + `GenerationConfig`. (#13899) # 11.3.0 - [added] Added `Decodable` conformance for `FunctionResponse`. (#13606) diff --git a/FirebaseVertexAI/Sources/GenerationConfig.swift b/FirebaseVertexAI/Sources/GenerationConfig.swift index 3f3a4b6f214..8598624aea0 100644 --- a/FirebaseVertexAI/Sources/GenerationConfig.swift +++ b/FirebaseVertexAI/Sources/GenerationConfig.swift @@ -58,6 +58,34 @@ public struct GenerationConfig { /// (unbounded). public let maxOutputTokens: Int? + /// Controls the likelihood of repeating the same words or phrases already generated in the text. + /// + /// Higher values increase the penalty of repetition, resulting in more diverse output. The + /// maximum value for `presencePenalty` is up to, but not including, `2.0`; the minimum value is + /// `-2.0`. + /// + /// > Note: While both `presencePenalty` and ``frequencyPenalty`` discourage repetition, + /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase has + /// > already appeared, whereas `frequencyPenalty` increases the penalty for *each* repetition of + /// > a word/phrase. + /// + /// > Important: Supported by `gemini-1.5-pro-002` and` gemini-1.5-flash-002` only. + public let presencePenalty: Float? + + /// Controls the likelihood of repeating words, with the penalty increasing for each repetition. + /// + /// Higher values increase the penalty of repetition, resulting in more diverse output. The + /// maximum value for `frequencyPenalty` is up to, but not including, `2.0`; the minimum value is + /// `-2.0`. + /// + /// > Note: While both `frequencyPenalty` and ``presencePenalty`` discourage repetition, + /// > `frequencyPenalty` increases the penalty for *each* repetition of a word/phrase, whereas + /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase has + /// > already appeared. + /// + /// > Important: Supported by `gemini-1.5-pro-002` and` gemini-1.5-flash-002` only. + public let frequencyPenalty: Float? + /// A set of up to 5 `String`s that will stop output generation. If /// specified, the API will stop at the first appearance of a stop sequence. /// The stop sequence will not be included as part of the response. @@ -88,11 +116,14 @@ public struct GenerationConfig { /// - Parameter topK: See ``topK`` /// - Parameter candidateCount: See ``candidateCount`` /// - Parameter maxOutputTokens: See ``maxOutputTokens`` + /// - Parameter presencePenalty: See ``presencePenalty`` + /// - Parameter frequencyPenalty: See ``frequencyPenalty`` /// - Parameter stopSequences: See ``stopSequences`` /// - Parameter responseMIMEType: See ``responseMIMEType`` /// - Parameter responseSchema: See ``responseSchema`` public init(temperature: Float? = nil, topP: Float? = nil, topK: Int? = nil, candidateCount: Int? = nil, maxOutputTokens: Int? = nil, + presencePenalty: Float? = nil, frequencyPenalty: Float? = nil, stopSequences: [String]? = nil, responseMIMEType: String? = nil, responseSchema: Schema? = nil) { // Explicit init because otherwise if we re-arrange the above variables it changes the API @@ -102,6 +133,8 @@ public struct GenerationConfig { self.topK = topK self.candidateCount = candidateCount self.maxOutputTokens = maxOutputTokens + self.presencePenalty = presencePenalty + self.frequencyPenalty = frequencyPenalty self.stopSequences = stopSequences self.responseMIMEType = responseMIMEType self.responseSchema = responseSchema diff --git a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift index 43cbfe6dd96..232807d08da 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift @@ -47,6 +47,8 @@ final class GenerationConfigTests: XCTestCase { let topK = 40 let candidateCount = 2 let maxOutputTokens = 256 + let presencePenalty: Float = 0.5 + let frequencyPenalty: Float = 0.75 let stopSequences = ["END", "DONE"] let responseMIMEType = "application/json" let generationConfig = GenerationConfig( @@ -55,6 +57,8 @@ final class GenerationConfigTests: XCTestCase { topK: topK, candidateCount: candidateCount, maxOutputTokens: maxOutputTokens, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, stopSequences: stopSequences, responseMIMEType: responseMIMEType, responseSchema: .array(items: .string()) @@ -66,7 +70,9 @@ final class GenerationConfigTests: XCTestCase { XCTAssertEqual(json, """ { "candidateCount" : \(candidateCount), + "frequencyPenalty" : \(frequencyPenalty), "maxOutputTokens" : \(maxOutputTokens), + "presencePenalty" : \(presencePenalty), "responseMIMEType" : "\(responseMIMEType)", "responseSchema" : { "items" : { From 78a181d48fd46bbeced32edb3b1436172c540712 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 16 Oct 2024 11:18:06 -0400 Subject: [PATCH 209/258] [Vertex AI] Increase macOS minimum to 12.0 (#13903) --- FirebaseVertexAI/CHANGELOG.md | 3 +++ FirebaseVertexAI/Sources/Chat.swift | 2 +- .../Sources/CountTokensRequest.swift | 10 +++---- .../Sources/FunctionCalling.swift | 9 +++++++ .../Sources/GenerateContentError.swift | 2 +- .../Sources/GenerateContentRequest.swift | 6 ++--- .../Sources/GenerateContentResponse.swift | 26 ++++++++++--------- .../Sources/GenerationConfig.swift | 4 +-- .../Sources/GenerativeAIRequest.swift | 4 +-- .../Sources/GenerativeAIService.swift | 2 +- .../Sources/GenerativeModel.swift | 2 +- FirebaseVertexAI/Sources/JSONValue.swift | 5 ++++ FirebaseVertexAI/Sources/ModelContent.swift | 8 +++--- .../Sources/PartsRepresentable+Image.swift | 8 +++--- .../Sources/PartsRepresentable.swift | 8 +++--- FirebaseVertexAI/Sources/Safety.swift | 14 ++++++---- .../Sources/Types/Internal/InternalPart.swift | 16 ++++++------ .../Sources/Types/Public/Part.swift | 12 ++++----- .../Sources/Types/Public/Schema.swift | 4 +++ FirebaseVertexAI/Sources/VertexAI.swift | 2 +- .../Tests/Unit/GenerationConfigTests.swift | 2 +- .../Tests/Unit/JSONValueTests.swift | 1 + FirebaseVertexAI/Tests/Unit/PartTests.swift | 2 +- .../Tests/Unit/PartsRepresentableTests.swift | 2 +- .../Tests/Unit/VertexAIAPITests.swift | 2 +- .../Tests/Unit/VertexComponentTests.swift | 2 +- 26 files changed, 93 insertions(+), 65 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 95d70ca6c0a..02aa129a852 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -52,6 +52,9 @@ `any(allowedFunctionNames:)`, or `none()` to create a config. (#13873) - [changed] **Breaking Change**: The `CandidateResponse` type is now named `Candidate`. (#13897) +- [changed] **Breaking Change**: The minimum deployment target for the SDK is + now macOS 12.0; all other platform minimums remain the same at iOS 15.0, + macCatalyst 15.0, tvOS 15.0, and watchOS 8.0. (#13903) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseVertexAI/Sources/Chat.swift index 2ebab217ca8..717bf29aae2 100644 --- a/FirebaseVertexAI/Sources/Chat.swift +++ b/FirebaseVertexAI/Sources/Chat.swift @@ -16,7 +16,7 @@ import Foundation /// An object that represents a back-and-forth chat with a model, capturing the history and saving /// the context in memory between each message sent. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public class Chat { private let model: GenerativeModel diff --git a/FirebaseVertexAI/Sources/CountTokensRequest.swift b/FirebaseVertexAI/Sources/CountTokensRequest.swift index 128cb3b8ce6..6c36d96b4c0 100644 --- a/FirebaseVertexAI/Sources/CountTokensRequest.swift +++ b/FirebaseVertexAI/Sources/CountTokensRequest.swift @@ -14,7 +14,7 @@ import Foundation -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct CountTokensRequest { let model: String @@ -26,7 +26,7 @@ struct CountTokensRequest { let options: RequestOptions } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CountTokensRequest: GenerativeAIRequest { typealias Response = CountTokensResponse @@ -36,7 +36,7 @@ extension CountTokensRequest: GenerativeAIRequest { } /// The model's response to a count tokens request. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct CountTokensResponse { /// The total number of tokens in the input given to the model as a prompt. public let totalTokens: Int @@ -50,7 +50,7 @@ public struct CountTokensResponse { // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CountTokensRequest: Encodable { enum CodingKeys: CodingKey { case contents @@ -60,5 +60,5 @@ extension CountTokensRequest: Encodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CountTokensResponse: Decodable {} diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseVertexAI/Sources/FunctionCalling.swift index 69c90b18e10..60514a6f5f4 100644 --- a/FirebaseVertexAI/Sources/FunctionCalling.swift +++ b/FirebaseVertexAI/Sources/FunctionCalling.swift @@ -18,6 +18,7 @@ import Foundation /// /// This `FunctionDeclaration` is a representation of a block of code that can be used as a ``Tool`` /// by the model and executed by the client. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionDeclaration { /// The name of the function. let name: String @@ -53,6 +54,7 @@ public struct FunctionDeclaration { /// /// A `Tool` is a piece of code that enables the system to interact with external systems to perform /// an action, or set of actions, outside of knowledge and scope of the model. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct Tool { /// A list of `FunctionDeclarations` available to the model. let functionDeclarations: [FunctionDeclaration]? @@ -86,6 +88,7 @@ public struct Tool { } /// Configuration for specifying function calling behavior. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionCallingConfig { /// Defines the execution behavior for function calling by defining the execution mode. enum Mode: String { @@ -131,6 +134,7 @@ public struct FunctionCallingConfig { } /// Tool configuration for any `Tool` specified in the request. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct ToolConfig { let functionCallingConfig: FunctionCallingConfig? @@ -141,6 +145,7 @@ public struct ToolConfig { // MARK: - Codable Conformance +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension FunctionDeclaration: Encodable { enum CodingKeys: String, CodingKey { case name @@ -156,10 +161,14 @@ extension FunctionDeclaration: Encodable { } } +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension Tool: Encodable {} +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension FunctionCallingConfig: Encodable {} +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension FunctionCallingConfig.Mode: Encodable {} +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ToolConfig: Encodable {} diff --git a/FirebaseVertexAI/Sources/GenerateContentError.swift b/FirebaseVertexAI/Sources/GenerateContentError.swift index b5b52d0acd5..76ce516f3cf 100644 --- a/FirebaseVertexAI/Sources/GenerateContentError.swift +++ b/FirebaseVertexAI/Sources/GenerateContentError.swift @@ -15,7 +15,7 @@ import Foundation /// Errors that occur when generating content from a model. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public enum GenerateContentError: Error { /// An internal error occurred. See the underlying error for more context. case internalError(underlying: Error) diff --git a/FirebaseVertexAI/Sources/GenerateContentRequest.swift b/FirebaseVertexAI/Sources/GenerateContentRequest.swift index 87333bbfec1..ffa98fe4159 100644 --- a/FirebaseVertexAI/Sources/GenerateContentRequest.swift +++ b/FirebaseVertexAI/Sources/GenerateContentRequest.swift @@ -14,7 +14,7 @@ import Foundation -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct GenerateContentRequest { /// Model name. let model: String @@ -28,7 +28,7 @@ struct GenerateContentRequest { let options: RequestOptions } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerateContentRequest: Encodable { enum CodingKeys: String, CodingKey { case contents @@ -40,7 +40,7 @@ extension GenerateContentRequest: Encodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerateContentRequest: GenerativeAIRequest { typealias Response = GenerateContentResponse diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index f9d8e1d349c..3472807f1bb 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -15,9 +15,10 @@ import Foundation /// The model's response to a generate content request. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct GenerateContentResponse: Sendable { /// Token usage metadata for processing the generate content request. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct UsageMetadata: Sendable { /// The number of tokens in the request prompt. public let promptTokenCount: Int @@ -92,7 +93,7 @@ public struct GenerateContentResponse: Sendable { /// A struct representing a possible reply to a content generation prompt. Each content generation /// prompt may produce multiple candidate responses. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct Candidate: Sendable { /// The response's content. public let content: ModelContent @@ -118,14 +119,14 @@ public struct Candidate: Sendable { } /// A collection of source attributions for a piece of content. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct CitationMetadata: Sendable { /// A list of individual cited sources and the parts of the content to which they apply. public let citations: [Citation] } /// A struct describing a source attribution. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct Citation: Sendable { /// The inclusive beginning of a sequence in a model response that derives from a cited source. public let startIndex: Int @@ -149,7 +150,7 @@ public struct Citation: Sendable { } /// A value enumerating possible reasons for a model to terminate a content generation request. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { enum Kind: String { case stop = "STOP" @@ -204,9 +205,10 @@ public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { } /// A metadata struct containing any feedback the model had on the prompt it was provided. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct PromptFeedback: Sendable { /// A type describing possible reasons to block a prompt. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct BlockReason: DecodableProtoEnum, Hashable, Sendable { enum Kind: String { case safety = "SAFETY" @@ -257,7 +259,7 @@ public struct PromptFeedback: Sendable { // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerateContentResponse: Decodable { enum CodingKeys: CodingKey { case candidates @@ -291,7 +293,7 @@ extension GenerateContentResponse: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerateContentResponse.UsageMetadata: Decodable { enum CodingKeys: CodingKey { case promptTokenCount @@ -308,7 +310,7 @@ extension GenerateContentResponse.UsageMetadata: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension Candidate: Decodable { enum CodingKeys: CodingKey { case content @@ -357,10 +359,10 @@ extension Candidate: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CitationMetadata: Decodable {} -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension Citation: Decodable { enum CodingKeys: CodingKey { case startIndex @@ -412,7 +414,7 @@ extension Citation: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension PromptFeedback: Decodable { enum CodingKeys: CodingKey { case blockReason diff --git a/FirebaseVertexAI/Sources/GenerationConfig.swift b/FirebaseVertexAI/Sources/GenerationConfig.swift index 8598624aea0..fa472dd37e1 100644 --- a/FirebaseVertexAI/Sources/GenerationConfig.swift +++ b/FirebaseVertexAI/Sources/GenerationConfig.swift @@ -16,7 +16,7 @@ import Foundation /// A struct defining model parameters to be used when sending generative AI /// requests to the backend model. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct GenerationConfig { /// A parameter controlling the degree of randomness in token selection. A /// temperature of zero is deterministic, always choosing the @@ -143,5 +143,5 @@ public struct GenerationConfig { // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerationConfig: Encodable {} diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index ac9046e0de4..b792830120e 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -14,7 +14,7 @@ import Foundation -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) protocol GenerativeAIRequest: Encodable { associatedtype Response: Decodable @@ -24,7 +24,7 @@ protocol GenerativeAIRequest: Encodable { } /// Configuration parameters for sending requests to the backend. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct RequestOptions { /// The request’s timeout interval in seconds; if not specified uses the default value for a /// `URLRequest`. diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index ff49a17b07f..30a24c13c6b 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -18,7 +18,7 @@ import FirebaseCore import Foundation import os.log -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct GenerativeAIService { /// The language of the SDK in the format `gl-/`. static let languageTag = "gl-swift/5" diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index c920a0daa88..0d2ea829f55 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -18,7 +18,7 @@ import Foundation /// A type that represents a remote multimodal model (like Gemini), with the ability to generate /// content based on various input types. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public final class GenerativeModel { /// The resource name of the model in the backend; has the format "models/model-name". let modelResourceName: String diff --git a/FirebaseVertexAI/Sources/JSONValue.swift b/FirebaseVertexAI/Sources/JSONValue.swift index c12e8aecb0f..f1333888d27 100644 --- a/FirebaseVertexAI/Sources/JSONValue.swift +++ b/FirebaseVertexAI/Sources/JSONValue.swift @@ -18,12 +18,14 @@ import Foundation /// /// This may be decoded from, or encoded to, a /// [`google.protobuf.Struct`](https://protobuf.dev/reference/protobuf/google.protobuf/#struct). +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public typealias JSONObject = [String: JSONValue] /// Represents a value in one of JSON's data types. /// /// This may be decoded from, or encoded to, a /// [`google.protobuf.Value`](https://protobuf.dev/reference/protobuf/google.protobuf/#value). +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public enum JSONValue: Sendable { /// A `null` value. case null @@ -44,6 +46,7 @@ public enum JSONValue: Sendable { case array([JSONValue]) } +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension JSONValue: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() @@ -68,6 +71,7 @@ extension JSONValue: Decodable { } } +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension JSONValue: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -93,4 +97,5 @@ extension JSONValue: Encodable { } } +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension JSONValue: Equatable {} diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index 885d209e0a1..ba87736e648 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -14,7 +14,7 @@ import Foundation -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension [ModelContent] { // TODO: Rename and refactor this. func throwIfError() throws { @@ -34,7 +34,7 @@ extension [ModelContent] { /// A type describing data in media formats interpretable by an AI model. Each generative AI /// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value /// may comprise multiple heterogeneous ``Part``s. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct ModelContent: Equatable, Sendable { enum InternalPart: Equatable, Sendable { case text(String) @@ -106,7 +106,7 @@ public struct ModelContent: Equatable, Sendable { // MARK: Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ModelContent: Codable { enum CodingKeys: String, CodingKey { case role @@ -114,7 +114,7 @@ extension ModelContent: Codable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ModelContent.InternalPart: Codable { enum CodingKeys: String, CodingKey { case text diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift index 24d11be2c46..5bedeb8f3d1 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift @@ -37,7 +37,7 @@ enum ImageConversionError: Error { #if canImport(UIKit) /// Enables images to be representable as ``PartsRepresentable``. - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension UIImage: PartsRepresentable { public var partsValue: [any Part] { guard let data = jpegData(compressionQuality: imageCompressionQuality) else { @@ -49,7 +49,7 @@ enum ImageConversionError: Error { #elseif canImport(AppKit) /// Enables images to be representable as ``PartsRepresentable``. - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension NSImage: PartsRepresentable { public var partsValue: [any Part] { guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else { @@ -67,7 +67,7 @@ enum ImageConversionError: Error { #if !os(watchOS) // This code does not build on watchOS. /// Enables `CGImages` to be representable as model content. - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *) + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *) extension CGImage: PartsRepresentable { public var partsValue: [any Part] { let output = NSMutableData() @@ -90,7 +90,7 @@ enum ImageConversionError: Error { #if canImport(CoreImage) /// Enables `CIImages` to be representable as model content. - @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, *) + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, *) extension CIImage: PartsRepresentable { public var partsValue: [any Part] { let context = CIContext() diff --git a/FirebaseVertexAI/Sources/PartsRepresentable.swift b/FirebaseVertexAI/Sources/PartsRepresentable.swift index 6ef63d3f182..82d0688df38 100644 --- a/FirebaseVertexAI/Sources/PartsRepresentable.swift +++ b/FirebaseVertexAI/Sources/PartsRepresentable.swift @@ -16,13 +16,13 @@ import Foundation /// A protocol describing any data that could be serialized to model-interpretable input data, /// where the serialization process cannot fail with an error. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public protocol PartsRepresentable { var partsValue: [any Part] { get } } /// Enables a ``Part`` to be used as a ``PartsRepresentable``. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public extension Part { var partsValue: [any Part] { return [self] @@ -31,7 +31,7 @@ public extension Part { /// Enable an `Array` of ``PartsRepresentable`` values to be passed in as a single /// ``PartsRepresentable``. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension [PartsRepresentable]: PartsRepresentable { public var partsValue: [any Part] { return flatMap { $0.partsValue } @@ -39,7 +39,7 @@ extension [PartsRepresentable]: PartsRepresentable { } /// Enables a `String` to be passed in as ``PartsRepresentable``. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension String: PartsRepresentable { public var partsValue: [any Part] { return [TextPart(self)] diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseVertexAI/Sources/Safety.swift index 655046db98a..decb54ba0e8 100644 --- a/FirebaseVertexAI/Sources/Safety.swift +++ b/FirebaseVertexAI/Sources/Safety.swift @@ -17,7 +17,7 @@ import Foundation /// A type defining potentially harmful media categories and their model-assigned ratings. A value /// of this type may be assigned to a category for every model-generated response, not just /// responses that exceed a certain threshold. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetyRating: Equatable, Hashable, Sendable { /// The category describing the potential harm a piece of content may pose. /// @@ -75,6 +75,7 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// The probability that a given model output falls under a harmful content category. /// /// > Note: This does not indicate the severity of harm for a piece of content. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct HarmProbability: DecodableProtoEnum, Hashable, Sendable { enum Kind: String { case negligible = "NEGLIGIBLE" @@ -110,6 +111,7 @@ public struct SafetyRating: Equatable, Hashable, Sendable { } /// The magnitude of how harmful a model response might be for the respective ``HarmCategory``. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct HarmSeverity: DecodableProtoEnum, Hashable, Sendable { enum Kind: String { case negligible = "HARM_SEVERITY_NEGLIGIBLE" @@ -143,7 +145,7 @@ public struct SafetyRating: Equatable, Hashable, Sendable { /// A type used to specify a threshold for harmful content, beyond which the model will return a /// fallback response instead of generated content. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct SafetySetting { /// Block at and beyond a specified ``SafetyRating/HarmProbability``. public struct HarmBlockThreshold: EncodableProtoEnum, Sendable { @@ -174,6 +176,7 @@ public struct SafetySetting { } /// The method of computing whether the ``SafetySetting/HarmBlockThreshold`` has been exceeded. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct HarmBlockMethod: EncodableProtoEnum, Sendable { enum Kind: String { case severity = "SEVERITY" @@ -224,6 +227,7 @@ public struct SafetySetting { } /// Categories describing the potential harm a piece of content may pose. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { enum Kind: String { case harassment = "HARM_CATEGORY_HARASSMENT" @@ -260,7 +264,7 @@ public struct HarmCategory: CodableProtoEnum, Hashable, Sendable { // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetyRating: Decodable { enum CodingKeys: CodingKey { case category @@ -287,8 +291,8 @@ extension SafetyRating: Decodable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting.HarmBlockThreshold: Encodable {} -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension SafetySetting: Encodable {} diff --git a/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift b/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift index 8a62ae4fdd9..872f394abd1 100644 --- a/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift +++ b/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift @@ -14,7 +14,7 @@ import Foundation -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct InlineData: Codable, Equatable, Sendable { let mimeType: String let data: Data @@ -25,7 +25,7 @@ struct InlineData: Codable, Equatable, Sendable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct FileData: Codable, Equatable, Sendable { let fileURI: String let mimeType: String @@ -36,7 +36,7 @@ struct FileData: Codable, Equatable, Sendable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct FunctionCall: Equatable, Sendable { let name: String let args: JSONObject @@ -47,7 +47,7 @@ struct FunctionCall: Equatable, Sendable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct FunctionResponse: Codable, Equatable, Sendable { let name: String let response: JSONObject @@ -58,7 +58,7 @@ struct FunctionResponse: Codable, Equatable, Sendable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct ErrorPart: Part, Error { let error: Error @@ -69,7 +69,7 @@ struct ErrorPart: Part, Error { // MARK: - Codable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension FunctionCall: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -82,7 +82,7 @@ extension FunctionCall: Codable { } } -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ErrorPart: Codable { init(from decoder: any Decoder) throws { fatalError("Decoding an ErrorPart is not supported.") @@ -95,7 +95,7 @@ extension ErrorPart: Codable { // MARK: - Equatable Conformances -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ErrorPart: Equatable { static func == (lhs: ErrorPart, rhs: ErrorPart) -> Bool { fatalError("Comparing ErrorParts for equality is not supported.") diff --git a/FirebaseVertexAI/Sources/Types/Public/Part.swift b/FirebaseVertexAI/Sources/Types/Public/Part.swift index 8cc25de1db2..41de624c56c 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Part.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Part.swift @@ -17,11 +17,11 @@ import Foundation /// A discrete piece of data in a media format interpretable by an AI model. /// /// Within a single value of ``Part``, different data types may not mix. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public protocol Part: PartsRepresentable, Codable, Sendable, Equatable {} /// A text part containing a string value. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct TextPart: Part { /// Text value. public let text: String @@ -34,7 +34,7 @@ public struct TextPart: Part { /// Data with a specified media type. /// /// > Note: Not all media types may be supported by the AI model. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct InlineDataPart: Part { let inlineData: InlineData @@ -51,7 +51,7 @@ public struct InlineDataPart: Part { } /// File data stored in Cloud Storage for Firebase, referenced by URI. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FileDataPart: Part { let fileData: FileData @@ -77,7 +77,7 @@ public struct FileDataPart: Part { } /// A predicted function call returned from the model. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionCallPart: Part { let functionCall: FunctionCall @@ -109,7 +109,7 @@ public struct FunctionCallPart: Part { /// Contains a string representing the `FunctionDeclaration.name` and a structured JSON object /// containing any output from the function is used as context to the model. This should contain the /// result of a ``FunctionCallPart`` made based on model prediction. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionResponsePart: Part { let functionResponse: FunctionResponse diff --git a/FirebaseVertexAI/Sources/Types/Public/Schema.swift b/FirebaseVertexAI/Sources/Types/Public/Schema.swift index 9496113a071..93cf4f55127 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Schema.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Schema.swift @@ -18,8 +18,10 @@ import Foundation /// /// These types can be objects, but also primitives and arrays. Represents a select subset of an /// [OpenAPI 3.0 schema object](https://spec.openapis.org/oas/v3.0.3#schema). +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public class Schema { /// Modifiers describing the expected format of a string `Schema`. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct StringFormat: EncodableProtoEnum { // This enum is currently only used to conform `StringFormat` to `ProtoEnum`, which requires // `associatedtype Kind: RawRepresentable`. @@ -37,6 +39,7 @@ public class Schema { } /// Modifiers describing the expected format of an integer `Schema`. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct IntegerFormat: EncodableProtoEnum { enum Kind: String { case int32 @@ -321,6 +324,7 @@ public class Schema { // MARK: - Codable Conformance +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension Schema: Encodable { enum CodingKeys: String, CodingKey { case dataType = "type" diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 896f2982c61..ad378c067d1 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -21,7 +21,7 @@ import Foundation @_implementationOnly import FirebaseCoreExtension /// The Vertex AI for Firebase SDK provides access to Gemini models directly from your app. -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public class VertexAI { // MARK: - Public APIs diff --git a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift index 232807d08da..e6bfe7cf09b 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift @@ -16,7 +16,7 @@ import FirebaseVertexAI import Foundation import XCTest -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class GenerationConfigTests: XCTestCase { let encoder = JSONEncoder() diff --git a/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift b/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift index 74196230887..77b88b686e1 100644 --- a/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift +++ b/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift @@ -15,6 +15,7 @@ import XCTest @testable import FirebaseVertexAI +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class JSONValueTests: XCTestCase { let decoder = JSONDecoder() let encoder = JSONEncoder() diff --git a/FirebaseVertexAI/Tests/Unit/PartTests.swift b/FirebaseVertexAI/Tests/Unit/PartTests.swift index 35c3b441d9f..d48600e9013 100644 --- a/FirebaseVertexAI/Tests/Unit/PartTests.swift +++ b/FirebaseVertexAI/Tests/Unit/PartTests.swift @@ -17,7 +17,7 @@ import XCTest @testable import FirebaseVertexAI -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartTests: XCTestCase { let decoder = JSONDecoder() let encoder = JSONEncoder() diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift index 859b77f58c7..cf5e6a1eadf 100644 --- a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift @@ -25,7 +25,7 @@ import XCTest @testable import FirebaseVertexAI -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartsRepresentableTests: XCTestCase { #if !os(watchOS) func testModelContentFromCGImageIsNotEmpty() throws { diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index f113bb7bf49..56dff8dd02b 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -21,7 +21,7 @@ import XCTest import UIKit // For UIImage extensions. #endif -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class VertexAIAPITests: XCTestCase { func codeSamples() async throws { let app = FirebaseApp.app() diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift index 64a3edcc6b8..bea85bf96e7 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift @@ -20,7 +20,7 @@ import XCTest @testable import FirebaseVertexAI -@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) class VertexComponentTests: XCTestCase { static let projectID = "test-project-id" static let apiKey = "test-api-key" From 1f1f308b3f48ce8d57c63022b82156bf49c0fc8b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 16 Oct 2024 18:40:50 -0400 Subject: [PATCH 210/258] [Vertex AI] Add code snippets for use in docs (#13653) --- .../Snippets/FirebaseAppSnippetsUtil.swift | 46 ++++++++ .../Snippets/FunctionCallingSnippets.swift | 109 ++++++++++++++++++ .../Snippets/StructuredOutputSnippets.swift | 95 +++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift new file mode 100644 index 00000000000..013b0dcab6d --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift @@ -0,0 +1,46 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import Foundation +import XCTest + +extension FirebaseApp { + static let projectIDEnvVar = "PROJECT_ID" + static let appIDEnvVar = "APP_ID" + static let apiKeyEnvVar = "API_KEY" + + static func configureForSnippets() throws { + let environment = ProcessInfo.processInfo.environment + guard let projectID = environment[projectIDEnvVar] else { + throw XCTSkip("No Firebase Project ID specified in environment variable \(projectIDEnvVar).") + } + guard let appID = environment[appIDEnvVar] else { + throw XCTSkip("No Google App ID specified in environment variable \(appIDEnvVar).") + } + guard let apiKey = environment[apiKeyEnvVar] else { + throw XCTSkip("No API key specified in environment variable \(apiKeyEnvVar).") + } + + let options = FirebaseOptions(googleAppID: appID, gcmSenderID: "") + options.projectID = projectID + options.apiKey = apiKey + + FirebaseApp.configure(options: options) + guard FirebaseApp.isDefaultAppConfigured() else { + XCTFail("Default Firebase app not configured.") + return + } + } +} diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift new file mode 100644 index 00000000000..60d4438cad5 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -0,0 +1,109 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseVertexAI +import XCTest + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class FunctionCallingSnippets: XCTestCase { + override func setUpWithError() throws { + try FirebaseApp.configureForSnippets() + } + + override func tearDown() async throws { + if let app = FirebaseApp.app() { + await app.delete() + } + } + + func testFunctionCalling() async throws { + // This function calls a hypothetical external API that returns + // a collection of weather information for a given location on a given date. + func fetchWeather(city: String, state: String, date: String) -> JSONObject { + // TODO(developer): Write a standard function that would call an external weather API. + + // For demo purposes, this hypothetical response is hardcoded here in the expected format. + return [ + "temperature": .number(38), + "chancePrecipitation": .string("56%"), + "cloudConditions": .string("partlyCloudy"), + ] + } + + let fetchWeatherTool = FunctionDeclaration( + name: "fetchWeather", + description: "Get the weather conditions for a specific city on a specific date.", + parameters: [ + "location": .object( + properties: [ + "city": .string(description: "The city of the location."), + "state": .string(description: "The US state of the location."), + ], + description: """ + The name of the city and its state for which to get the weather. Only cities in the + USA are supported. + """ + ), + "date": .string( + description: """ + The date for which to get the weather. Date must be in the format: YYYY-MM-DD. + """ + ), + ] + ) + + // Initialize the Vertex AI service and the generative model. + // Use a model that supports function calling, like a Gemini 1.5 model. + let model = VertexAI.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // Provide the function declaration to the model. + tools: [.functionDeclarations([fetchWeatherTool])] + ) + + let chat = model.startChat() + let prompt = "What was the weather in Boston on October 17, 2024?" + + // Send the user's question (the prompt) to the model using multi-turn chat. + let response = try await chat.sendMessage(prompt) + + var functionResponses = [FunctionResponsePart]() + + // When the model responds with one or more function calls, invoke the function(s). + for functionCall in response.functionCalls { + if functionCall.name == "fetchWeather" { + // TODO(developer): Handle invalid arguments. + guard case let .object(location) = functionCall.args["location"] else { fatalError() } + guard case let .string(city) = location["city"] else { fatalError() } + guard case let .string(state) = location["state"] else { fatalError() } + guard case let .string(date) = functionCall.args["date"] else { fatalError() } + + functionResponses.append(FunctionResponsePart( + name: functionCall.name, + response: fetchWeather(city: city, state: state, date: date) + )) + } + // TODO(developer): Handle other potential function calls, if any. + } + + // Send the response(s) from the function back to the model so that the model can use it + // to generate its final response. + let finalResponse = try await chat.sendMessage( + [ModelContent(role: "function", parts: functionResponses)] + ) + + // Log the text response. + print(finalResponse.text ?? "No text in response.") + } +} diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift new file mode 100644 index 00000000000..4a1046083a2 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -0,0 +1,95 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseVertexAI +import XCTest + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class StructuredOutputSnippets: XCTestCase { + override func setUpWithError() throws { + try FirebaseApp.configureForSnippets() + } + + override func tearDown() async throws { + if let app = FirebaseApp.app() { + await app.delete() + } + } + + func testStructuredOutputJSONBasic() async throws { + // Provide a JSON schema object using a standard format. + // Later, pass this schema object into `responseSchema` in the generation config. + let jsonSchema = Schema.object( + properties: [ + "characters": Schema.array( + items: .object( + properties: [ + "name": .string(), + "age": .integer(), + "species": .string(), + "accessory": .enumeration(values: ["hat", "belt", "shoes"]), + ], + optionalProperties: ["accessory"] + ) + ), + ] + ) + + // Initialize the Vertex AI service and the generative model. + // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. + let model = VertexAI.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // In the generation config, set the `responseMimeType` to `application/json` + // and pass the JSON schema object into `responseSchema`. + generationConfig: GenerationConfig( + responseMIMEType: "application/json", + responseSchema: jsonSchema + ) + ) + + let prompt = "For use in a children's card game, generate 10 animal-based characters." + + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } + + func testStructuredOutputEnumBasic() async throws { + // Provide an enum schema object using a standard format. + // Later, pass this schema object into `responseSchema` in the generation config. + let enumSchema = Schema.enumeration(values: ["drama", "comedy", "documentary"]) + + // Initialize the Vertex AI service and the generative model. + // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. + let model = VertexAI.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // In the generation config, set the `responseMimeType` to `text/x.enum` + // and pass the enum schema object into `responseSchema`. + generationConfig: GenerationConfig( + responseMIMEType: "text/x.enum", + responseSchema: enumSchema + ) + ) + + let prompt = """ + The film aims to educate and inform viewers about real-life subjects, events, or people. + It offers a factual record of a particular topic by combining interviews, historical footage, + and narration. The primary purpose of a film is to present information and provide insights + into various aspects of reality. + """ + + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } +} From c2493691d0f6a6d2b2bb824afa0cf1db3d2d54c8 Mon Sep 17 00:00:00 2001 From: Ydna <0xEFEFEF@gmail.com> Date: Thu, 17 Oct 2024 21:58:03 +0800 Subject: [PATCH 211/258] fix: correct `LocalizedError` conformance (#13895) --- FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift index 7269c4dd803..74d08d7f8dc 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/RingBuffer.swift @@ -22,7 +22,7 @@ struct RingBuffer: Sequence { private var tailIndex: Array.Index /// Error types for `RingBuffer` operations. - enum Error: LocalizedError { + enum Error: Swift.Error { case outOfBoundsPush(pushIndex: Array.Index, endIndex: Array.Index) var errorDescription: String { From 8328630971a8fdd8072b36bb22bef732eb15e1f0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Oct 2024 10:09:34 -0400 Subject: [PATCH 212/258] [Vertex AI] Make `GenerationConfig` properties internal (#13904) --- FirebaseVertexAI/CHANGELOG.md | 3 + .../Sources/GenerateContentResponse.swift | 4 +- .../Sources/GenerationConfig.swift | 192 ++++++++++-------- 3 files changed, 110 insertions(+), 89 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 02aa129a852..64a595f8073 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -55,6 +55,9 @@ - [changed] **Breaking Change**: The minimum deployment target for the SDK is now macOS 12.0; all other platform minimums remain the same at iOS 15.0, macCatalyst 15.0, tvOS 15.0, and watchOS 8.0. (#13903) +- [changed] **Breaking Change**: All of the public properties of + `GenerationConfig` are now `internal`; they all remain configurable in the + initializer. (#13904) - [changed] The default request timeout is now 180 seconds instead of the platform-default value of 60 seconds for a `URLRequest`; this timeout may still be customized in `RequestOptions`. (#13722) diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseVertexAI/Sources/GenerateContentResponse.swift index 3472807f1bb..b7b4f1c536a 100644 --- a/FirebaseVertexAI/Sources/GenerateContentResponse.swift +++ b/FirebaseVertexAI/Sources/GenerateContentResponse.swift @@ -172,8 +172,8 @@ public struct FinishReason: DecodableProtoEnum, Hashable, Sendable { /// The token generation was stopped because the response was flagged for safety reasons. /// - /// > NOTE: When streaming, the ``CandidateResponse/content`` will be empty if content filters - /// > blocked the output. + /// > NOTE: When streaming, the ``Candidate/content`` will be empty if content filters blocked the + /// > output. public static let safety = FinishReason(kind: .safety) /// The token generation was stopped because the response was flagged for unauthorized citations. diff --git a/FirebaseVertexAI/Sources/GenerationConfig.swift b/FirebaseVertexAI/Sources/GenerationConfig.swift index fa472dd37e1..5c49e60f274 100644 --- a/FirebaseVertexAI/Sources/GenerationConfig.swift +++ b/FirebaseVertexAI/Sources/GenerationConfig.swift @@ -18,109 +18,127 @@ import Foundation /// requests to the backend model. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct GenerationConfig { - /// A parameter controlling the degree of randomness in token selection. A - /// temperature of zero is deterministic, always choosing the - /// highest-probability response. Typical values are between 0 and 1 - /// inclusive. Defaults to 0 if unspecified. - public let temperature: Float? + /// Controls the degree of randomness in token selection. + let temperature: Float? - /// The `topP` parameter changes how the model selects tokens for output. - /// Tokens are selected from the most to least probable until the sum of - /// their probabilities equals the `topP` value. For example, if tokens A, B, - /// and C have probabilities of 0.3, 0.2, and 0.1 respectively and the topP - /// value is 0.5, then the model will select either A or B as the next token - /// by using the `temperature` and exclude C as a candidate. - /// Defaults to 0.95 if unset. - public let topP: Float? + /// Controls diversity of generated text. + let topP: Float? - /// The `topK` parameter changes how the model selects tokens for output. A - /// `topK` of 1 means the selected token is the most probable among all the - /// tokens in the model's vocabulary, while a `topK` of 3 means that the next - /// token is selected from among the 3 most probable using the `temperature`. - /// For each token selection step, the `topK` tokens with the highest - /// probabilities are sampled. Tokens are then further filtered based on - /// `topP` with the final token selected using `temperature` sampling. - /// Defaults to 40 if unspecified. - public let topK: Int? + /// Limits the number of highest probability words considered. + let topK: Int? - /// The maximum number of generated response messages to return. This value - /// must be between [1, 8], inclusive. If unset, this will default to 1. - /// - /// - Note: Only unique candidates are returned. Higher temperatures are more - /// likely to produce unique candidates. Setting `temperature` to 0 will - /// always produce exactly one candidate regardless of the - /// `candidateCount`. - public let candidateCount: Int? + /// The number of response variations to return. + let candidateCount: Int? - /// Specifies the maximum number of tokens that can be generated in the - /// response. The number of tokens per word varies depending on the - /// language outputted. The maximum value is capped at 1024. Defaults to 0 - /// (unbounded). - public let maxOutputTokens: Int? + /// Maximum number of tokens that can be generated in the response. + let maxOutputTokens: Int? /// Controls the likelihood of repeating the same words or phrases already generated in the text. - /// - /// Higher values increase the penalty of repetition, resulting in more diverse output. The - /// maximum value for `presencePenalty` is up to, but not including, `2.0`; the minimum value is - /// `-2.0`. - /// - /// > Note: While both `presencePenalty` and ``frequencyPenalty`` discourage repetition, - /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase has - /// > already appeared, whereas `frequencyPenalty` increases the penalty for *each* repetition of - /// > a word/phrase. - /// - /// > Important: Supported by `gemini-1.5-pro-002` and` gemini-1.5-flash-002` only. - public let presencePenalty: Float? + let presencePenalty: Float? /// Controls the likelihood of repeating words, with the penalty increasing for each repetition. - /// - /// Higher values increase the penalty of repetition, resulting in more diverse output. The - /// maximum value for `frequencyPenalty` is up to, but not including, `2.0`; the minimum value is - /// `-2.0`. - /// - /// > Note: While both `frequencyPenalty` and ``presencePenalty`` discourage repetition, - /// > `frequencyPenalty` increases the penalty for *each* repetition of a word/phrase, whereas - /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase has - /// > already appeared. - /// - /// > Important: Supported by `gemini-1.5-pro-002` and` gemini-1.5-flash-002` only. - public let frequencyPenalty: Float? + let frequencyPenalty: Float? - /// A set of up to 5 `String`s that will stop output generation. If - /// specified, the API will stop at the first appearance of a stop sequence. - /// The stop sequence will not be included as part of the response. - public let stopSequences: [String]? + /// A set of up to 5 `String`s that will stop output generation. + let stopSequences: [String]? /// Output response MIME type of the generated candidate text. - /// - /// Supported MIME types: - /// - `text/plain`: Text output; the default behavior if unspecified. - /// - `application/json`: JSON response in the candidates. - public let responseMIMEType: String? + let responseMIMEType: String? /// Output schema of the generated candidate text. - /// If set, a compatible ``responseMIMEType`` must also be set. - /// - /// Compatible MIME types: - /// - `application/json`: Schema for JSON response. - /// - /// Refer to the [Control generated - /// output](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output) - /// guide for more details. - public let responseSchema: Schema? + let responseSchema: Schema? /// Creates a new `GenerationConfig` value. /// - /// - Parameter temperature: See ``temperature`` - /// - Parameter topP: See ``topP`` - /// - Parameter topK: See ``topK`` - /// - Parameter candidateCount: See ``candidateCount`` - /// - Parameter maxOutputTokens: See ``maxOutputTokens`` - /// - Parameter presencePenalty: See ``presencePenalty`` - /// - Parameter frequencyPenalty: See ``frequencyPenalty`` - /// - Parameter stopSequences: See ``stopSequences`` - /// - Parameter responseMIMEType: See ``responseMIMEType`` - /// - Parameter responseSchema: See ``responseSchema`` + /// See the + /// [Configure model parameters](https://firebase.google.com/docs/vertex-ai/model-parameters) + /// guide and the + /// [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// for more details. + /// + /// - Parameters: + /// - temperature:Controls the randomness of the language model's output. Higher values (for + /// example, 1.0) make the text more random and creative, while lower values (for example, + /// 0.1) make it more focused and deterministic. + /// + /// > Note: A temperature of 0 means that the highest probability tokens are always selected. + /// > In this case, responses for a given prompt are mostly deterministic, but a small amount + /// > of variation is still possible. + /// + /// > Important: The range of supported temperature values depends on the model; see the + /// > [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// > for more details. + /// - topP: Controls diversity of generated text. Higher values (e.g., 0.9) produce more diverse + /// text, while lower values (e.g., 0.5) make the output more focused. + /// + /// The supported range is 0.0 to 1.0. + /// + /// > Important: The default `topP` value depends on the model; see the + /// [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// for more details. + /// - topK: Limits the number of highest probability words the model considers when generating + /// text. For example, a topK of 40 means only the 40 most likely words are considered for the + /// next token. A higher value increases diversity, while a lower value makes the output more + /// deterministic. + /// + /// The supported range is 1 to 40. + /// + /// > Important: Support for `topK` and the default value depends on the model; see the + /// [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// for more details. + /// - candidateCount: The number of response variations to return; defaults to 1 if not set. + /// Support for multiple candidates depends on the model; see the + /// [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// for more details. + /// - maxOutputTokens: Maximum number of tokens that can be generated in the response. + /// See the configure model parameters [documentation](https://firebase.google.com/docs/vertex-ai/model-parameters?platform=ios#max-output-tokens) + /// for more details. + /// - presencePenalty: Controls the likelihood of repeating the same words or phrases already + /// generated in the text. Higher values increase the penalty of repetition, resulting in more + /// diverse output. + /// + /// > Note: While both `presencePenalty` and `frequencyPenalty` discourage repetition, + /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase + /// > has already appeared, whereas `frequencyPenalty` increases the penalty for *each* + /// > repetition of a word/phrase. + /// + /// > Important: The range of supported `presencePenalty` values depends on the model; see the + /// > [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// > for more details + /// - frequencyPenalty: Controls the likelihood of repeating words or phrases, with the penalty + /// increasing for each repetition. Higher values increase the penalty of repetition, + /// resulting in more diverse output. + /// + /// > Note: While both `frequencyPenalty` and `presencePenalty` discourage repetition, + /// > `frequencyPenalty` increases the penalty for *each* repetition of a word/phrase, whereas + /// > `presencePenalty` applies the same penalty regardless of how many times the word/phrase + /// > has already appeared. + /// + /// > Important: The range of supported `frequencyPenalty` values depends on the model; see + /// > the + /// > [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// > for more details + /// - stopSequences: A set of up to 5 `String`s that will stop output generation. If specified, + /// the API will stop at the first appearance of a stop sequence. The stop sequence will not + /// be included as part of the response. See the + /// [Cloud documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#generationconfig) + /// for more details. + /// - responseMIMEType: Output response MIME type of the generated candidate text. + /// + /// Supported MIME types: + /// - `text/plain`: Text output; the default behavior if unspecified. + /// - `application/json`: JSON response in the candidates. + /// - `text/x.enum`: For classification tasks, output an enum value as defined in the + /// `responseSchema`. + /// - responseSchema: Output schema of the generated candidate text. If set, a compatible + /// `responseMIMEType` must also be set. + /// + /// Compatible MIME types: + /// - `application/json`: Schema for JSON response. + /// + /// Refer to the + /// [Control generated output](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output) + /// guide for more details. public init(temperature: Float? = nil, topP: Float? = nil, topK: Int? = nil, candidateCount: Int? = nil, maxOutputTokens: Int? = nil, presencePenalty: Float? = nil, frequencyPenalty: Float? = nil, From ea6f84e0b4dca66458ac31d45c892b05f58d9658 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Oct 2024 16:55:38 -0400 Subject: [PATCH 213/258] [Vertex AI] Use `GoogleService-Info.plist` in snippets tests (#13923) --- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 6 +-- .../Tests/Unit/GenerativeModelTests.swift | 6 +-- .../Snippets/FirebaseAppSnippetsUtil.swift | 47 ++++++++++++------- .../Snippets/FunctionCallingSnippets.swift | 9 ++-- .../Tests/Unit/Snippets/README.md | 10 ++++ .../Snippets/StructuredOutputSnippets.swift | 9 ++-- .../Unit/TestUtilities/BundleTestUtil.swift | 31 ++++++++++++ 7 files changed, 82 insertions(+), 36 deletions(-) create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/README.md create mode 100644 FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 95ce8e7e43d..1c4988faf7c 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -32,11 +32,7 @@ final class ChatTests: XCTestCase { } func testMergingText() async throws { - #if SWIFT_PACKAGE - let bundle = Bundle.module - #else // SWIFT_PACKAGE - let bundle = Bundle(for: Self.self) - #endif // SWIFT_PACKAGE + let bundle = BundleTestUtil.bundle() let fileURL = try XCTUnwrap(bundle.url( forResource: "streaming-success-basic-reply-parts", withExtension: "txt" diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index f9035949f1a..dc9acd02d55 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1446,11 +1446,7 @@ final class GenerativeModelTests: XCTestCase { #if os(watchOS) throw XCTSkip("Custom URL protocols are unsupported in watchOS 2 and later.") #endif // os(watchOS) - #if SWIFT_PACKAGE - let bundle = Bundle.module - #else // SWIFT_PACKAGE - let bundle = Bundle(for: Self.self) - #endif // SWIFT_PACKAGE + let bundle = BundleTestUtil.bundle() let fileURL = try XCTUnwrap(bundle.url(forResource: name, withExtension: ext)) return { request in let requestURL = try XCTUnwrap(request.url) diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift index 013b0dcab6d..f463fbda188 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift @@ -17,30 +17,41 @@ import Foundation import XCTest extension FirebaseApp { - static let projectIDEnvVar = "PROJECT_ID" - static let appIDEnvVar = "APP_ID" - static let apiKeyEnvVar = "API_KEY" - - static func configureForSnippets() throws { - let environment = ProcessInfo.processInfo.environment - guard let projectID = environment[projectIDEnvVar] else { - throw XCTSkip("No Firebase Project ID specified in environment variable \(projectIDEnvVar).") - } - guard let appID = environment[appIDEnvVar] else { - throw XCTSkip("No Google App ID specified in environment variable \(appIDEnvVar).") + /// Configures the default `FirebaseApp` for use in snippets tests. + /// + /// Uses a `GoogleService-Info.plist` file from the + /// [`Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) + /// directory. + /// + /// > Note: This is typically called in a snippet test's set up; overriding + /// > `setUpWithError() throws` works well since it supports throwing errors. + static func configureDefaultAppForSnippets() throws { + guard let plistPath = BundleTestUtil.bundle().path( + forResource: "GoogleService-Info", + ofType: "plist" + ) else { + throw XCTSkip("No GoogleService-Info.plist found in FirebaseVertexAI/Tests/Unit/Resources.") } - guard let apiKey = environment[apiKeyEnvVar] else { - throw XCTSkip("No API key specified in environment variable \(apiKeyEnvVar).") - } - - let options = FirebaseOptions(googleAppID: appID, gcmSenderID: "") - options.projectID = projectID - options.apiKey = apiKey + let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath)) FirebaseApp.configure(options: options) + guard FirebaseApp.isDefaultAppConfigured() else { XCTFail("Default Firebase app not configured.") return } } + + /// Deletes the default `FirebaseApp` if configured. + /// + /// > Note: This is typically called in a snippet test's tear down; overriding + /// > `tearDown() async throws` works well since deletion is asynchronous. + static func deleteDefaultAppForSnippets() async { + // Checking if `isDefaultAppConfigured()` before calling `FirebaseApp.app()` suppresses a log + // message that "The default Firebase app has not yet been configured." during `tearDown` when + // the tests are skipped. This reduces extraneous noise in the test logs. + if FirebaseApp.isDefaultAppConfigured(), let app = FirebaseApp.app() { + await app.delete() + } + } } diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index 60d4438cad5..492574dc11d 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -16,16 +16,17 @@ import FirebaseCore import FirebaseVertexAI import XCTest +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class FunctionCallingSnippets: XCTestCase { override func setUpWithError() throws { - try FirebaseApp.configureForSnippets() + try FirebaseApp.configureDefaultAppForSnippets() } override func tearDown() async throws { - if let app = FirebaseApp.app() { - await app.delete() - } + await FirebaseApp.deleteDefaultAppForSnippets() } func testFunctionCalling() async throws { diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/README.md b/FirebaseVertexAI/Tests/Unit/Snippets/README.md new file mode 100644 index 00000000000..8d03458c456 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/README.md @@ -0,0 +1,10 @@ +# Vertex AI in Firebase Code Snippet Tests + +These "tests" are for verifying that the code snippets provided in our +documentation continue to compile. They are intentionally skipped in CI but can +be manually run to verify expected behavior / outputs. + +To run the tests, place a valid `GoogleService-Info.plist` file in the +[`FirebaseVertexAI/Tests/Unit/Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) +folder. They may then be invoked individually or alongside the rest of the unit +tests in Xcode. diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index 4a1046083a2..1ad137188c5 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -16,16 +16,17 @@ import FirebaseCore import FirebaseVertexAI import XCTest +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class StructuredOutputSnippets: XCTestCase { override func setUpWithError() throws { - try FirebaseApp.configureForSnippets() + try FirebaseApp.configureDefaultAppForSnippets() } override func tearDown() async throws { - if let app = FirebaseApp.app() { - await app.delete() - } + await FirebaseApp.deleteDefaultAppForSnippets() } func testStructuredOutputJSONBasic() async throws { diff --git a/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift b/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift new file mode 100644 index 00000000000..272be41c1e4 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift @@ -0,0 +1,31 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// `Bundle` test utilities. +final class BundleTestUtil { + /// Returns the `Bundle` for the test module or target containing the file. + /// + /// This abstracts away the `Bundle` differences between SPM and CocoaPods tests. + static func bundle() -> Bundle { + #if SWIFT_PACKAGE + return Bundle.module + #else // SWIFT_PACKAGE + return Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + } + + private init() {} +} From b317cbc0c6aa30606fdda013b4524bc44f55d54f Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Oct 2024 18:15:00 -0400 Subject: [PATCH 214/258] [Vertex AI] Add GA changelog entry (#13927) --- FirebaseVertexAI/CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 64a595f8073..78cceb8d547 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,4 +1,21 @@ # 11.4.0 +- [feature] Vertex AI in Firebase is now Generally Available (GA) and can be + used in production apps. +

    + Use the Vertex AI in Firebase library to call the Vertex AI Gemini API + directly from your app. This client library is built specifically for use with + Swift apps, offering security options against unauthorized clients as well as + integrations with other Firebase services. +

    + Note: Vertex AI in Firebase is currently only available in Swift Package + Manager and CocoaPods. Stay tuned for the next release for the Zip and + Carthage distributions. +

    + - If you're new to this library, visit the + [getting started guide](http://firebase.google.com/docs/vertex-ai/get-started?platform=ios). + - If you used the preview version of the library, visit the + [migration guide](https://firebase.google.com/docs/vertex-ai/migrate-to-ga?platform=ios) + to learn about some important updates. - [changed] **Breaking Change**: The SDK is now Generally Available (GA); both new and existing users must perform the [Build with Gemini](https://console.firebase.google.com/project/_/genai/) From 78c1c2f6ca64ebab0f258e13545828b5cb18ba91 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Oct 2024 18:33:25 -0400 Subject: [PATCH 215/258] [Vertex AI] Add text-only `generateContent` and Chat snippets (#13926) --- .../Tests/Unit/Snippets/ChatSnippets.swift | 67 +++++++++++++++++++ .../Tests/Unit/Snippets/TextSnippets.swift | 55 +++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift new file mode 100644 index 00000000000..2d96b90e2fd --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -0,0 +1,67 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseVertexAI +import XCTest + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class ChatSnippets: XCTestCase { + lazy var model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testChatNonStreaming() async throws { + // Optionally specify existing chat history + let history = [ + ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."), + ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"), + ] + + // Initialize the chat with optional chat history + let chat = model.startChat(history: history) + + // To generate text output, call sendMessage and pass in the message + let response = try await chat.sendMessage("How many paws are in my house?") + print(response.text ?? "No text in response.") + } + + func testChatStreaming() async throws { + // Optionally specify existing chat history + let history = [ + ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."), + ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"), + ] + + // Initialize the chat with optional chat history + let chat = model.startChat(history: history) + + // To stream generated text output, call sendMessageStream and pass in the message + let contentStream = try chat.sendMessageStream("How many paws are in my house?") + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } +} diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift new file mode 100644 index 00000000000..bd7c70fa06b --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift @@ -0,0 +1,55 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseVertexAI +import XCTest + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class TextSnippets: XCTestCase { + lazy var model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testTextOnlyNonStreaming() async throws { + // Provide a prompt that contains text + let prompt = "Write a story about a magic backpack." + + // To generate text output, call generateContent with the text input + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } + + func testTextOnlyStreaming() async throws { + // Provide a prompt that contains text + let prompt = "Write a story about a magic backpack." + + // To stream generated text output, call generateContentStream with the text input + let contentStream = try model.generateContentStream(prompt) + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } +} From bb888cdcdd11159b2aa9a1f5d957d968caa0b431 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 18 Oct 2024 14:45:33 -0400 Subject: [PATCH 216/258] [Vertex AI] Add multimodal code snippets (#13929) --- .../Tests/Unit/Resources/animals.mp4 | Bin 0 -> 10881 bytes .../Unit/Snippets/MultimodalSnippets.swift | 128 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 b/FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8abcffb1ebaadd92d037f3770d65330c547c43c4 GIT binary patch literal 10881 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 Date: Fri, 18 Oct 2024 15:58:57 -0400 Subject: [PATCH 217/258] [Vertex AI] Changelog fixes (#13932) --- FirebaseVertexAI/CHANGELOG.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 78cceb8d547..cd290c5c1be 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,6 +1,6 @@ # 11.4.0 - [feature] Vertex AI in Firebase is now Generally Available (GA) and can be - used in production apps. + used in production apps. (#13725)

    Use the Vertex AI in Firebase library to call the Vertex AI Gemini API directly from your app. This client library is built specifically for use with @@ -16,10 +16,6 @@ - If you used the preview version of the library, visit the [migration guide](https://firebase.google.com/docs/vertex-ai/migrate-to-ga?platform=ios) to learn about some important updates. -- [changed] **Breaking Change**: The SDK is now Generally Available (GA); both - new and existing users must perform the - [Build with Gemini](https://console.firebase.google.com/project/_/genai/) - onboarding flow in the Firebase Console to use the updated API. (#13725) - [changed] **Breaking Change**: The `HarmCategory` enum is no longer nested inside the `SafetySetting` struct and the `unspecified` case has been removed. (#13686) @@ -28,8 +24,6 @@ - [changed] **Breaking Change**: The `unspecified` case has been removed from the `FinishReason`, `BlockReason` and `HarmProbability` enums; this scenario is now handled by the existing `unknown` case. (#13699) -- [changed] **Breaking Change**: The `data` case in the `Part` enum has been - renamed to `inlineData`; no functionality changes. (#13700) - [changed] **Breaking Change**: The property `citationSources` of `CitationMetadata` has been renamed to `citations`. (#13702) - [changed] **Breaking Change**: The initializer for `Schema` is now internal; @@ -52,9 +46,8 @@ - [changed] **Breaking Change**: The enum `ModelContent.Part` has been replaced with a protocol named `Part` to avoid future breaking changes with new part types. The new types `TextPart` and `FunctionCallPart` may be received when - generating content the types `TextPart`; additionally the types - `InlineDataPart`, `FileDataPart` and `FunctionResponsePart` may be provided - as input. (#13767) + generating content; additionally the types `InlineDataPart`, `FileDataPart` + and `FunctionResponsePart` may be provided as input. (#13767) - [changed] **Breaking Change**: All initializers for `ModelContent` now require the label `parts: `. (#13832) - [changed] **Breaking Change**: `HarmCategory`, `HarmProbability`, and From 9f2f41f5eed09a8adb31456559341da1813b9f04 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:24:35 -0400 Subject: [PATCH 218/258] [Config] Mark two `RCNConfigSettings` properties `atomic` (#13925) --- FirebaseRemoteConfig/CHANGELOG.md | 4 ++++ FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index f98ceaeab36..0f65853597c 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased +- [fixed] Mark two internal properties as `atomic` to prevent concurrency + related crash. (#13898) + # 11.0.0 - [fixed] RemoteConfigValue stringValue is now `nonnull`. This may break some builds. (#10870) - [removed] **Breaking change**: The deprecated `FirebaseRemoteConfigSwift` diff --git a/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h index 034c50c7330..26b82172681 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h @@ -41,9 +41,11 @@ /// Device data version of checkin information. @property(nonatomic, copy) NSString *deviceDataVersion; /// InstallationsID. -@property(nonatomic, copy) NSString *configInstallationsIdentifier; +/// @note The property is atomic because it is accessed across multiple threads. +@property(atomic, copy) NSString *configInstallationsIdentifier; /// Installations token. -@property(nonatomic, copy) NSString *configInstallationsToken; +/// @note The property is atomic because it is accessed across multiple threads. +@property(atomic, copy) NSString *configInstallationsToken; /// A list of successful fetch timestamps in milliseconds. /// TODO Not used anymore. Safe to remove. From c4189d7300877a9ceb406631d23934488e259ebf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:20:12 -0400 Subject: [PATCH 219/258] [Release] Carthage artifacts for 11.4.0 (#13940) --- ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json | 1 + .../CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json | 1 + 19 files changed, 19 insertions(+) diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index 6ec8c9eabaf..f20a644d7d8 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseABTesting-1bc00d2361fabe31.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseABTesting-0d51fde82d49f9e8.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseABTesting-2233510ff87da3b6.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseABTesting-4d0b187af6fd8d67.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/ABTesting-d0fdf10c43e985b1.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/ABTesting-d0fdf10c43e985b1.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/ABTesting-a71d17cadc209af9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json index 82b3a67dec8..23252863595 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/Google-Mobile-Ads-SDK-35e22051f01c0eaa.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/Google-Mobile-Ads-SDK-4f24527af297e7f1.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/Google-Mobile-Ads-SDK-80ba4cb995505158.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/Google-Mobile-Ads-SDK-3df614a58e6a5fa6.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/AdMob-8a654a42c33bbcc8.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/AdMob-63dab3b525b94cd9.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/AdMob-134752c6180a2a41.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index b656f35e81b..7d6c0b1d9c6 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalytics-c0c45b49d7c16d39.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalytics-a93a6c81da535385.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalytics-fd2c71a90d62b88a.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAnalytics-525b465eb296d09e.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Analytics-2468c231ebeb7922.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Analytics-bc8101d420b896c5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Analytics-d2b6a6b0242db786.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json index d0f054a6012..18dfed2db67 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalyticsOnDeviceConversion-e0b5f6e47b71efce.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalyticsOnDeviceConversion-09d94624a2de0ac8.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalyticsOnDeviceConversion-918bc6e0b7a2fd94.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAnalyticsOnDeviceConversion-1640c514418a23da.zip", "9.0.0": "https://dl.google.com/dl/firebase/ios/carthage/9.0.0/FirebaseAnalyticsOnDeviceConversion-31aedde70a736b8a.zip", "9.1.0": "https://dl.google.com/dl/firebase/ios/carthage/9.1.0/FirebaseAnalyticsOnDeviceConversion-f13b5a47d1e3978d.zip", "9.2.0": "https://dl.google.com/dl/firebase/ios/carthage/9.2.0/FirebaseAnalyticsOnDeviceConversion-2ebf567c4d97de12.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index 26f64420cff..2d7321fbe98 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppCheck-b44ecc329b8672d0.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppCheck-d0c5f46e6a2bf4a3.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppCheck-89c39bdcf0bb90fe.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAppCheck-9b0c4a9489968b07.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseAppCheck-9ef1d217cf057203.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseAppCheck-fc03215d9fe45d3a.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseAppCheck-6ebe9e9539f06003.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index 4ecc3d7eb40..9aca0a85f2b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppDistribution-9415636f92f6e4be.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppDistribution-9b05f4873b275347.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppDistribution-6d2eccaccfd3145f.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAppDistribution-20ac94ca344af731.zip", "6.31.0": "https://dl.google.com/dl/firebase/ios/carthage/6.31.0/FirebaseAppDistribution-07f6a2cf7f576a8a.zip", "6.32.0": "https://dl.google.com/dl/firebase/ios/carthage/6.32.0/FirebaseAppDistribution-a9c4f5db794508ca.zip", "6.33.0": "https://dl.google.com/dl/firebase/ios/carthage/6.33.0/FirebaseAppDistribution-448a96d2ade54581.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index 973bdb437c6..36522271aac 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAuth-41423c3255e3355e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAuth-eade26b5390baf84.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAuth-93dd2965b3f79b98.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAuth-5faf6dc3bb16c732.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Auth-0fa76ba0f7956220.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Auth-5ddd2b4351012c7a.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Auth-5e248984d78d7284.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index ad98d54e1d3..7381237c17b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseCrashlytics-573b0427dec2b08b.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseCrashlytics-13851523ad6df088.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseCrashlytics-282a6f3cf3445787.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseCrashlytics-d5c125d6416f6e0a.zip", "6.15.0": "https://dl.google.com/dl/firebase/ios/carthage/6.15.0/FirebaseCrashlytics-1c6d22d5b73c84fd.zip", "6.16.0": "https://dl.google.com/dl/firebase/ios/carthage/6.16.0/FirebaseCrashlytics-938e5fd0e2eab3b3.zip", "6.17.0": "https://dl.google.com/dl/firebase/ios/carthage/6.17.0/FirebaseCrashlytics-fa09f0c8f31ed5d9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index 8ce95a163fc..99a776b8c34 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDatabase-64e6eeeecc70e513.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDatabase-06dbb1f7d3c8a3e1.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDatabase-38634b55050b94fe.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseDatabase-ed125984da534e96.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Database-1f7a820452722c7d.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Database-1f7a820452722c7d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Database-59a12d87456b3e1c.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json index c827bfe99a1..adb21067b99 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDynamicLinks-09b22cea086a30bc.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDynamicLinks-e61c61fa80e5ea8a.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDynamicLinks-95f7e222d8456304.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseDynamicLinks-f3f9d6cc60c8b832.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/DynamicLinks-6a76740211df73f5.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/DynamicLinks-6a76740211df73f5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/DynamicLinks-6a76740211df73f5.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index 28c11b4d781..dc4e8e4c326 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFirestore-5f76b2878966eea4.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFirestore-43af85b854ac842e.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFirestore-e1283f8cd2e0f3ec.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseFirestore-f5864e67ddbbc9e8.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Firestore-68fc02c229d0cc69.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Firestore-87a804ab561d91db.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Firestore-ecb3eea7bde7e8e8.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index aeab120b1cb..456117effe1 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFunctions-87cffbdd6cbb9512.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFunctions-307f00117c2efc62.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFunctions-02693a7583303912.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseFunctions-8fce8623ed1c6b86.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Functions-f4c426016dd41e38.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Functions-c6c44427c3034736.zip", "5.0.0": "https://dl.google.com/dl/firebase/ios/carthage/5.0.0/Functions-146f34c401bd459b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index 7cf389b3056..8c45cdd1a76 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/GoogleSignIn-dabfaced725377c4.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/GoogleSignIn-4e8837ef9594b57b.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/GoogleSignIn-8ce1c31ca2236212.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/GoogleSignIn-59eb371d148a2e3a.zip", "6.0.0": "https://dl.google.com/dl/firebase/ios/carthage/6.0.0/GoogleSignIn-de9c5d5e8eb6d6ea.zip", "6.1.0": "https://dl.google.com/dl/firebase/ios/carthage/6.1.0/GoogleSignIn-8c82f2870573a793.zip", "6.10.0": "https://dl.google.com/dl/firebase/ios/carthage/6.10.0/GoogleSignIn-ff3aef61c4a55b05.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 26058fc69d9..b7a994f8a62 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseInAppMessaging-7aa1595a55b0f2bd.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseInAppMessaging-6fae0a778e9d3efa.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseInAppMessaging-3a1a331c86520356.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseInAppMessaging-a8054099dd2918b3.zip", "5.10.0": "https://dl.google.com/dl/firebase/ios/carthage/5.10.0/InAppMessaging-a7a3f933362f6e95.zip", "5.11.0": "https://dl.google.com/dl/firebase/ios/carthage/5.11.0/InAppMessaging-fa28ce1b88fbca93.zip", "5.12.0": "https://dl.google.com/dl/firebase/ios/carthage/5.12.0/InAppMessaging-fa28ce1b88fbca93.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index c42076d4160..6def9a51b34 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMLModelDownloader-4029775a5484e3d2.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMLModelDownloader-d8649822e63fbf7f.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMLModelDownloader-517f51af92733a7f.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseMLModelDownloader-069609cbcde7e789.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseMLModelDownloader-8f972757fb181320.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseMLModelDownloader-058ad59fa6dc0111.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseMLModelDownloader-286479a966d2fb37.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index 26df462fbc4..9bdf4b5b932 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMessaging-3edcc27744f3aa8e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMessaging-70e63bb9d9590ded.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMessaging-8a39834fead3c581.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseMessaging-2d09725e8b98d199.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Messaging-a22ef2b5f2f30f82.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Messaging-94fa4e090c7e9185.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Messaging-2a00a1c64a19d176.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 5c004ca194f..666b7238816 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebasePerformance-e983e4ab114b6122.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebasePerformance-aa174ee3102722d9.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebasePerformance-a489ac7a27d9b53d.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebasePerformance-9a6f62e80c2324f4.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Performance-d8693eb892bfa05b.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Performance-0a400f9460f7a71d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Performance-f5b4002ab96523e4.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index 04635990e12..f6e8410b08b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseRemoteConfig-bf6cbcdd97aa9c46.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseRemoteConfig-9a298869ce3cc6db.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseRemoteConfig-940ed38696414882.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseRemoteConfig-ec432e976582d0eb.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/RemoteConfig-7e9635365ccd4a17.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/RemoteConfig-e7928fcb6311c439.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/RemoteConfig-9ab1ca5f360a1780.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index e05ef1d977b..46f2d604ad5 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -32,6 +32,7 @@ "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseStorage-f483c715e48ec023.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseStorage-b9b969b0d1254065.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseStorage-0435eeaa87324cd4.zip", + "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseStorage-0b7a2306152984a2.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Storage-6b3e77e1a7fdbc61.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Storage-4721c35d2b90a569.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Storage-821299369b9d0fb2.zip", From 67490359546b6d3ced5a2239a9aa5d12dc413e5f Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:50:36 +0330 Subject: [PATCH 220/258] Firestore: review bundle reader tests (#13919) --- Firestore/core/test/unit/bundle/bundle_reader_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/core/test/unit/bundle/bundle_reader_test.cc b/Firestore/core/test/unit/bundle/bundle_reader_test.cc index b09c9267f05..9e87a724b7d 100644 --- a/Firestore/core/test/unit/bundle/bundle_reader_test.cc +++ b/Firestore/core/test/unit/bundle/bundle_reader_test.cc @@ -528,7 +528,7 @@ TEST_F(BundleReaderTest, FailsWhenSecondElementMissing) { EXPECT_NOT_OK(reader.reader_status()); } -TEST_F(BundleReaderTest, FailsWhenNoEnoughtDataCanBeRead) { +TEST_F(BundleReaderTest, FailsWhenNotEnoughDataCanBeRead) { const auto& bundle = BuildBundle("bundle-1", testutil::Version(6000004000), 0); BundleReader reader(bundle_serializer, ToByteStream("1" + bundle)); From 1773a9a72a8c720bbc24b3284cee45f46f3dfbce Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:50:47 +0330 Subject: [PATCH 221/258] FirebaseStorage: review storage path tests (#13916) --- FirebaseStorage/Tests/Unit/StoragePathTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseStorage/Tests/Unit/StoragePathTests.swift b/FirebaseStorage/Tests/Unit/StoragePathTests.swift index 206d9e4e90e..a6106dd42b3 100644 --- a/FirebaseStorage/Tests/Unit/StoragePathTests.swift +++ b/FirebaseStorage/Tests/Unit/StoragePathTests.swift @@ -175,7 +175,7 @@ class StoragePathTests: XCTestCase { XCTAssertEqual(parent?.stringValue(), "gs://bucket/path") } - func testParentChildPathOnlySlashs() { + func testParentChildPathOnlySlashes() { let path = StoragePath(with: "bucket", object: "/////") let parent = path.parent() XCTAssertNil(parent) From af39ebe21b684cea153041c44f5096eece785a5a Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:51:00 +0330 Subject: [PATCH 222/258] test: review firestore event manager tests (#13911) --- Firestore/core/test/unit/core/event_manager_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/core/event_manager_test.cc b/Firestore/core/test/unit/core/event_manager_test.cc index 5d48a9aae0c..2a9d7a49f75 100644 --- a/Firestore/core/test/unit/core/event_manager_test.cc +++ b/Firestore/core/test/unit/core/event_manager_test.cc @@ -74,7 +74,7 @@ class MockEventSource : public core::QueryEventSource { MOCK_METHOD1(StopListeningToRemoteStoreOnly, void(const core::Query&)); }; -TEST(EventManagerTest, HandlesManyListnersPerQuery) { +TEST(EventManagerTest, HandlesManyListenersPerQuery) { core::Query query = Query("foo/bar"); auto listener1 = NoopQueryListener(query); auto listener2 = NoopQueryListener(query); @@ -94,7 +94,7 @@ TEST(EventManagerTest, HandlesManyListnersPerQuery) { event_manager.RemoveQueryListener(listener1); } -TEST(EventManagerTest, HandlesManyCacheListnersPerQuery) { +TEST(EventManagerTest, HandlesManyCacheListenersPerQuery) { core::Query query = Query("foo/bar"); auto listener1 = NoopQueryCacheListener(query); auto listener2 = NoopQueryCacheListener(query); From 2c124f1b852706a0483ebcbdef8ef861374dd655 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:51:12 +0330 Subject: [PATCH 223/258] Firestore: review serializer tests (#13915) --- Firestore/core/test/unit/remote/serializer_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index 0de665273ee..14b08b1e13f 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -1078,13 +1078,13 @@ TEST_F(SerializerTest, EncodesEmptyDocument) { TEST_F(SerializerTest, EncodesNonEmptyDocument) { DocumentKey key = DocumentKey::FromPathString("path/to/the/doc"); ObjectValue fields{ - Map("foo", "bar", "two", 2, "nested", Map("fourty-two", 42))}; + Map("foo", "bar", "two", 2, "nested", Map("forty-two", 42))}; SnapshotVersion update_time = SnapshotVersion{{1234, 5678}}; v1::Value inner_proto; google::protobuf::Map& inner_fields = *inner_proto.mutable_map_value()->mutable_fields(); - inner_fields["fourty-two"] = ValueProto(int64_t{42}); + inner_fields["forty-two"] = ValueProto(int64_t{42}); v1::BatchGetDocumentsResponse proto; v1::Document* doc_proto = proto.mutable_found(); From 9834ad5bae822cee4f140e81175f1737288a9ee4 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:51:24 +0330 Subject: [PATCH 224/258] FirebaseAuth: improve readme file (#13913) --- FirebaseAuth/Tests/SampleSwift/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/Tests/SampleSwift/README.md b/FirebaseAuth/Tests/SampleSwift/README.md index 18f4054f8e5..928f71d029f 100644 --- a/FirebaseAuth/Tests/SampleSwift/README.md +++ b/FirebaseAuth/Tests/SampleSwift/README.md @@ -191,7 +191,7 @@ As we mentioned above, we will need to configure dynamic links for this auth flo - Configure the following steps as you please and then hit **Create**! - Dynamic links use your app's bundle identifier as a url scheme by default. In Xcode, [add a custom URL scheme for your **bundle identifier**](https://developers.google.com/identity/sign-in/ios/start-integrating#add_a_url_scheme_to_your_project). - - Last todo! Navigate to `sendSignInLink()` in `PasswordlessViewController.swift. Within the method, there is a `stringURL` constant. Paste in the long deeplink you created from the steps above for the `authroizedDomain` property above the method. It should look something like: + - Last todo! Navigate to `sendSignInLink()` in `PasswordlessViewController.swift. Within the method, there is a `stringURL` constant. Paste in the long deeplink you created from the steps above for the `authorizedDomain` property above the method. It should look something like: ```swift let stringURL = "https://\(authorizedDomain)/login" ``` From 0e80c7c76d98d1510cff58c0965e99d71baeac40 Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:51:36 +0330 Subject: [PATCH 225/258] test: fixed a repeated typo in a property name inside FirebaseDynamicLinks (#13907) --- FirebaseDynamicLinks/Tests/Unit/FDLURLComponentsTests.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseDynamicLinks/Tests/Unit/FDLURLComponentsTests.m b/FirebaseDynamicLinks/Tests/Unit/FDLURLComponentsTests.m index 9bd86cb0257..e7d9dce6a79 100644 --- a/FirebaseDynamicLinks/Tests/Unit/FDLURLComponentsTests.m +++ b/FirebaseDynamicLinks/Tests/Unit/FDLURLComponentsTests.m @@ -482,11 +482,11 @@ - (void)testFDLComponentsFactoryReturnsInstanceWithAllNilProperties { - (void)testFDLComponentsCreatesSimplestLinkCorrectly { NSString *linkString = @"https://google.com"; - NSString *endcodedLinkString = @"https%3A%2F%2Fgoogle%2Ecom"; + NSString *encodedLinkString = @"https%3A%2F%2Fgoogle%2Ecom"; NSURL *link = [NSURL URLWithString:linkString]; NSString *expectedURLString = - [NSString stringWithFormat:@"%@/?link=%@", kFDLURLDomain, endcodedLinkString]; + [NSString stringWithFormat:@"%@/?link=%@", kFDLURLDomain, encodedLinkString]; NSURL *expectedURL = [NSURL URLWithString:expectedURLString]; FIRDynamicLinkComponents *components = @@ -498,11 +498,11 @@ - (void)testFDLComponentsCreatesSimplestLinkCorrectly { - (void)testFDLComponentsCustomDomainWithPath { NSString *linkString = @"https://google.com"; - NSString *endcodedLinkString = @"https%3A%2F%2Fgoogle%2Ecom"; + NSString *encodedLinkString = @"https%3A%2F%2Fgoogle%2Ecom"; NSURL *link = [NSURL URLWithString:linkString]; NSString *expectedURLString = - [NSString stringWithFormat:@"%@/?link=%@", kFDLURLCustomDomain, endcodedLinkString]; + [NSString stringWithFormat:@"%@/?link=%@", kFDLURLCustomDomain, encodedLinkString]; NSURL *expectedURL = [NSURL URLWithString:expectedURLString]; FIRDynamicLinkComponents *components = From 9c423fbbbfa53af64f9a246653181722d14a18ca Mon Sep 17 00:00:00 2001 From: Seyed Mojtaba Hosseini Zeidabadi Date: Mon, 21 Oct 2024 22:51:54 +0330 Subject: [PATCH 226/258] Firestore: review app check credentials provider tests (#13914) --- .../credentials/firebase_app_check_credentials_provider_test.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/core/test/unit/credentials/firebase_app_check_credentials_provider_test.mm b/Firestore/core/test/unit/credentials/firebase_app_check_credentials_provider_test.mm index b9ee572a90b..9bc1cb3cc24 100644 --- a/Firestore/core/test/unit/credentials/firebase_app_check_credentials_provider_test.mm +++ b/Firestore/core/test/unit/credentials/firebase_app_check_credentials_provider_test.mm @@ -181,7 +181,7 @@ - (nonnull NSString*)tokenDidChangeNotificationName { // Regression test for https://github.com/firebase/firebase-ios-sdk/issues/8895 TEST(FirebaseAppCheckCredentialsProviderTest, - ListenForTokenChangesIgnoresUnrelatedNotifcations) { + ListenForTokenChangesIgnoresUnrelatedNotifications) { auto token_promise = std::make_shared>(); FIRApp* app = testutil::AppForUnitTesting(); From af304a9d707dd08c880dee3e0a8f30a2bd7cda72 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:42:54 -0400 Subject: [PATCH 227/258] [Infra] Update `FirebaseVertexAI` row in FirebaseManifest.swift (#13941) --- ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index afe41de0e37..cd38e8f6238 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -53,8 +53,8 @@ public let shared = Manifest( Pod("FirebasePerformance", platforms: ["ios", "tvos"], zip: true), Pod("FirebaseStorage", zip: true), Pod("FirebaseMLModelDownloader", isBeta: true, zip: true), + Pod("FirebaseVertexAI", zip: true), Pod("Firebase", allowWarnings: true, platforms: ["ios", "tvos", "macos"], zip: true), - Pod("FirebaseVertexAI", releasing: false, zip: false), ] ) From 7a10b3bb3482b53720e71a69fd12f633c4531832 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:44:48 -0400 Subject: [PATCH 228/258] [Release] Update versions for Release 11.5.0 (#13943) --- Firebase.podspec | 48 +++++++++---------- FirebaseABTesting.podspec | 2 +- FirebaseAnalytics.podspec | 6 +-- FirebaseAnalyticsOnDeviceConversion.podspec | 4 +- FirebaseAppCheck.podspec | 2 +- FirebaseAppCheckInterop.podspec | 2 +- FirebaseAppDistribution.podspec | 2 +- FirebaseAuth.podspec | 2 +- FirebaseAuthInterop.podspec | 2 +- FirebaseCore.podspec | 2 +- FirebaseCoreExtension.podspec | 2 +- FirebaseCoreInternal.podspec | 2 +- FirebaseCrashlytics.podspec | 2 +- FirebaseDatabase.podspec | 2 +- FirebaseDynamicLinks.podspec | 2 +- FirebaseFirestore.podspec | 4 +- FirebaseFirestoreInternal.podspec | 2 +- FirebaseFunctions.podspec | 2 +- FirebaseInAppMessaging.podspec | 2 +- FirebaseInstallations.podspec | 2 +- FirebaseMLModelDownloader.podspec | 2 +- FirebaseMessaging.podspec | 2 +- FirebaseMessagingInterop.podspec | 2 +- FirebasePerformance.podspec | 2 +- FirebaseRemoteConfig.podspec | 2 +- FirebaseRemoteConfigInterop.podspec | 2 +- FirebaseSessions.podspec | 2 +- FirebaseSharedSwift.podspec | 2 +- FirebaseStorage.podspec | 2 +- FirebaseVertexAI.podspec | 2 +- GoogleAppMeasurement.podspec | 4 +- ...leAppMeasurementOnDeviceConversion.podspec | 2 +- Package.swift | 2 +- .../FirebaseManifest/FirebaseManifest.swift | 2 +- 34 files changed, 62 insertions(+), 62 deletions(-) diff --git a/Firebase.podspec b/Firebase.podspec index 17eedda5a20..50b992a43d1 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase' s.description = <<-DESC @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 11.4.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 11.4.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 11.4.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 11.5.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 11.5.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 11.5.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '11.4.0' + ss.dependency 'FirebaseCore', '11.5.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -79,13 +79,13 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.4.0' + ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.5.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 11.4.0' + ss.dependency 'FirebaseABTesting', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -95,13 +95,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'AppDistribution' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseAppDistribution', '~> 11.4.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 11.5.0-beta' ss.ios.deployment_target = '13.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 11.4.0' + ss.dependency 'FirebaseAppCheck', '~> 11.5.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -110,7 +110,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Auth' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAuth', '~> 11.4.0' + ss.dependency 'FirebaseAuth', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -120,7 +120,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Crashlytics' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseCrashlytics', '~> 11.4.0' + ss.dependency 'FirebaseCrashlytics', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' @@ -130,7 +130,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Database' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseDatabase', '~> 11.4.0' + ss.dependency 'FirebaseDatabase', '~> 11.5.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -140,13 +140,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'DynamicLinks' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.4.0' + ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.5.0' ss.ios.deployment_target = '13.0' end s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 11.4.0' + ss.dependency 'FirebaseFirestore', '~> 11.5.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -154,7 +154,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Functions' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFunctions', '~> 11.4.0' + ss.dependency 'FirebaseFunctions', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -164,20 +164,20 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'InAppMessaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.4.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.4.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.5.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.5.0-beta' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 11.4.0' + ss.dependency 'FirebaseInstallations', '~> 11.5.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 11.4.0' + ss.dependency 'FirebaseMessaging', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -187,7 +187,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'MLModelDownloader' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMLModelDownloader', '~> 11.4.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 11.5.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -197,15 +197,15 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Performance' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebasePerformance', '~> 11.4.0' - ss.tvos.dependency 'FirebasePerformance', '~> 11.4.0' + ss.ios.dependency 'FirebasePerformance', '~> 11.5.0' + ss.tvos.dependency 'FirebasePerformance', '~> 11.5.0' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 11.4.0' + ss.dependency 'FirebaseRemoteConfig', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -215,7 +215,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Storage' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseStorage', '~> 11.4.0' + ss.dependency 'FirebaseStorage', '~> 11.5.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 5c1bb26ea88..527deb13b95 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index a66f0bd62d7..485ea5f6819 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -37,12 +37,12 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement', '11.4.0' + ss.dependency 'GoogleAppMeasurement', '11.5.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'WithoutAdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.4.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.5.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAnalyticsOnDeviceConversion.podspec b/FirebaseAnalyticsOnDeviceConversion.podspec index 5eb6b5ef267..e03608aa555 100644 --- a/FirebaseAnalyticsOnDeviceConversion.podspec +++ b/FirebaseAnalyticsOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalyticsOnDeviceConversion' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'On device conversion measurement plugin for FirebaseAnalytics. Not intended for direct use.' s.description = <<-DESC @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.cocoapods_version = '>= 1.12.0' - s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.4.0' + s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.5.0' s.static_framework = true diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 878e5eaf8e1..0e76b2ef9b2 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 87dce8bb492..0496fe9832b 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Interfaces that allow other Firebase SDKs to use AppCheck functionality.' s.description = <<-DESC diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index e7ad8c46c7c..b5ee21b9daf 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '11.4.0-beta' + s.version = '11.5.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 91093bc7d99..2312ee8f897 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index 35664ff629a..91dbd50ff5b 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 71913ba8939..00aafddc64e 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Core' s.description = <<-DESC diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 3cc916fcf71..8e9f8422058 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index df449a9b8c6..30fc10e914a 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 49d7b6d9f7a..51e50f01606 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index 23cd7419910..a38baf8ff6f 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index b3335df303c..cc90a1e2afa 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Dynamic Links' s.description = <<-DESC diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 3cb7f40c64b..f6f858cd53c 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. @@ -37,7 +37,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.dependency 'FirebaseCore', '~> 11.4' s.dependency 'FirebaseCoreExtension', '~> 11.4' - s.dependency 'FirebaseFirestoreInternal', '11.4.0' + s.dependency 'FirebaseFirestoreInternal', '11.5.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' end diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index da34afc2f4d..8ec6142ac65 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index fefc00769ee..0e36c04d6f2 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index 9a5380e2aab..ef293d91c0a 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '11.4.0-beta' + s.version = '11.5.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 9fb102de507..d77548778da 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Installations' s.description = <<-DESC diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index 443d4c41347..b0a6cf37385 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '11.4.0-beta' + s.version = '11.5.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index ea99ba09367..6087c41b53e 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Messaging' s.description = <<-DESC diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 5037e809945..001c05f57c4 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Messaging functionality.' s.description = <<-DESC diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 19ee4956ed6..519bd9246cd 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Performance' s.description = <<-DESC diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 10c05004b35..4bc701d8940 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index d0b2dce1f8a..197604250f2 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfigInterop' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' s.description = <<-DESC diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 5103d4cac35..6f0debc2470 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Sessions' s.description = <<-DESC diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index b3dc45ce87d..ab2345bb104 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 57e25e7f5a1..28bf0563a62 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Firebase Storage' s.description = <<-DESC diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 0a9b059d3c1..e0779aaa40d 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseVertexAI' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Vertex AI in Firebase SDK' s.description = <<-DESC diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index 9e0adaad993..aa4ec6509b7 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -37,7 +37,7 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.4.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.5.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index 8ba795d836d..8250337d28d 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/GoogleAppMeasurementOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurementOnDeviceConversion' - s.version = '11.4.0' + s.version = '11.5.0' s.summary = <<-SUMMARY On device conversion measurement plugin for Google App Measurement. Not intended for direct use. diff --git a/Package.swift b/Package.swift index 3b4de208afa..55dfbba46e0 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -let firebaseVersion = "11.4.0" +let firebaseVersion = "11.5.0" let package = Package( name: "Firebase", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index cd38e8f6238..17d32d4c985 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -21,7 +21,7 @@ import Foundation /// The version and releasing fields of the non-Firebase pods should be reviewed every release. /// The array should be ordered so that any pod's dependencies precede it in the list. public let shared = Manifest( - version: "11.4.0", + version: "11.5.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), From a7e7eb1f893c1f973cae7648fa25f999956054ea Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:12:58 -0400 Subject: [PATCH 229/258] [Infra] Merge branch 'release-11.4' into 'main' (#13958) --- .../AuthBackendRPCImplementationTests.swift | 5 ++ FirebaseCore/CHANGELOG.md | 8 ++++ FirebaseCore/Extension/FIRHeartbeatLogger.h | 3 +- .../HeartbeatController.swift | 48 +++++++++---------- .../_ObjC_HeartbeatController.swift | 10 ++-- .../HeartbeatLoggingIntegrationTests.swift | 37 ++++++++------ .../Tests/Unit/HeartbeatControllerTests.swift | 38 ++++++++------- FirebaseCore/Sources/FIRHeartbeatLogger.m | 3 +- 8 files changed, 88 insertions(+), 64 deletions(-) diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift index 754fa761a54..7180313df34 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift @@ -534,6 +534,11 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { #if COCOAPODS || SWIFT_PACKAGE private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol { + func headerValue() -> String? { + // `asyncHeaderValue` should be used instead. + fatalError("FakeHeartbeatLogger headerValue should not be used in tests.") + } + func asyncHeaderValue() async -> String? { let payload = flushHeartbeatsIntoPayload() guard !payload.isEmpty else { diff --git a/FirebaseCore/CHANGELOG.md b/FirebaseCore/CHANGELOG.md index 16a6b9d1290..89ed80fcf55 100644 --- a/FirebaseCore/CHANGELOG.md +++ b/FirebaseCore/CHANGELOG.md @@ -1,3 +1,11 @@ +# Firebase 11.4.2 +- [fixed] CocoaPods only release to fix iOS 12 build failure resulting from + incomplete implementation in the FirebaseCoreInternal CocoaPod. + +# Firebase 11.4.1 +- [fixed] CocoaPods only release to revert breaking change in + `FirebaseCoreExtension` SDK. (#13942) + # Firebase 11.4.0 - [fixed] Fixed issue building documentation with some Firebase products. (#13756) diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 12986f8ec97..962974e1bca 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -44,8 +44,7 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) { API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)); /// Return the header value for the heartbeat logger. -- (NSString *_Nullable) - headerValue NS_SWIFT_UNAVAILABLE("Use `asyncHeaderValue() async -> String?` instead."); +- (NSString *_Nullable)headerValue; #endif // FIREBASE_BUILD_CMAKE @end diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift index e4a2cc4c335..e7f4ece2781 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift @@ -126,34 +126,30 @@ public final class HeartbeatController { } } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - public func flushAsync() async -> HeartbeatsPayload { - return await withCheckedContinuation { continuation in - let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in - guard let oldHeartbeatsBundle = heartbeatsBundle else { - return nil // Storage was empty. - } - // The new value that's stored will use the old's cache to prevent the - // logging of duplicates after flushing. - return HeartbeatsBundle( - capacity: self.heartbeatsStorageCapacity, - cache: oldHeartbeatsBundle.lastAddedHeartbeatDates - ) + public func flushAsync(completionHandler: @escaping (HeartbeatsPayload) -> Void) { + let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in + guard let oldHeartbeatsBundle = heartbeatsBundle else { + return nil // Storage was empty. } + // The new value that's stored will use the old's cache to prevent the + // logging of duplicates after flushing. + return HeartbeatsBundle( + capacity: self.heartbeatsStorageCapacity, + cache: oldHeartbeatsBundle.lastAddedHeartbeatDates + ) + } - // Asynchronously gets and returns the stored heartbeats, resetting storage - // using the given transform. - storage.getAndSetAsync(using: resetTransform) { result in - switch result { - case let .success(heartbeatsBundle): - // If no heartbeats bundle was stored, return an empty payload. - continuation - .resume(returning: heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload - .emptyPayload) - case .failure: - // If the operation throws, assume no heartbeat(s) were retrieved or set. - continuation.resume(returning: HeartbeatsPayload.emptyPayload) - } + // Asynchronously gets and returns the stored heartbeats, resetting storage + // using the given transform. + storage.getAndSetAsync(using: resetTransform) { result in + switch result { + case let .success(heartbeatsBundle): + // If no heartbeats bundle was stored, return an empty payload. + completionHandler(heartbeatsBundle?.makeHeartbeatsPayload() ?? HeartbeatsPayload + .emptyPayload) + case .failure: + // If the operation throws, assume no heartbeat(s) were retrieved or set. + completionHandler(HeartbeatsPayload.emptyPayload) } } } diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift index fdd13af13be..c60a1e11cc5 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift @@ -49,10 +49,12 @@ public class _ObjC_HeartbeatController: NSObject { /// /// - Note: This API is thread-safe. /// - Returns: A heartbeats payload for the flushed heartbeat(s). - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - public func flushAsync() async -> _ObjC_HeartbeatsPayload { - let heartbeatsPayload = await heartbeatController.flushAsync() - return _ObjC_HeartbeatsPayload(heartbeatsPayload) + public func flushAsync(completionHandler: @escaping (_ObjC_HeartbeatsPayload) -> Void) { + // TODO: When minimum version moves to iOS 13.0, restore the async version + // removed in #13952. + heartbeatController.flushAsync { heartbeatsPayload in + completionHandler(_ObjC_HeartbeatsPayload(heartbeatsPayload)) + } } /// Synchronously flushes the heartbeat for today. diff --git a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift index b306405f4bd..3a9823aa94a 100644 --- a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift +++ b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift @@ -52,29 +52,36 @@ class HeartbeatLoggingIntegrationTests: XCTestCase { ) } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - func testLogAndFlushAsync() async throws { + func testLogAndFlushAsync() throws { // Given let heartbeatController = HeartbeatController(id: #function) let expectedDate = HeartbeatsPayload.dateFormatter.string(from: Date()) + let expectation = self.expectation(description: #function) // When heartbeatController.log("dummy_agent") - let payload = await heartbeatController.flushAsync() - // Then - try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( - payload.headerValue(), - """ - { - "version": 2, - "heartbeats": [ + heartbeatController.flushAsync { payload in + // Then + do { + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + payload.headerValue(), + """ { - "agent": "dummy_agent", - "dates": ["\(expectedDate)"] + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["\(expectedDate)"] + } + ] } - ] + """ + ) + expectation.fulfill() + } catch { + XCTFail("Unexpected error: \(error)") } - """ - ) + } + waitForExpectations(timeout: 1.0) } /// This test may flake if it is executed during the transition from one day to the next. diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift index ddf3d1c5d9d..82691ed3f24 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift @@ -58,35 +58,41 @@ class HeartbeatControllerTests: XCTestCase { assertHeartbeatControllerFlushesEmptyPayload(controller) } - @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *) - func testLogAndFlushAsync() async throws { + func testLogAndFlushAsync() throws { // Given let controller = HeartbeatController( storage: HeartbeatStorageFake(), dateProvider: { self.date } ) + let expectation = expectation(description: #function) assertHeartbeatControllerFlushesEmptyPayload(controller) // When controller.log("dummy_agent") - let heartbeatPayload = await controller.flushAsync() - - // Then - try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( - heartbeatPayload.headerValue(), - """ - { - "version": 2, - "heartbeats": [ + controller.flushAsync { heartbeatPayload in + // Then + do { + try HeartbeatLoggingTestUtils.assertEqualPayloadStrings( + heartbeatPayload.headerValue(), + """ { - "agent": "dummy_agent", - "dates": ["2021-11-01"] + "version": 2, + "heartbeats": [ + { + "agent": "dummy_agent", + "dates": ["2021-11-01"] + } + ] } - ] + """ + ) + expectation.fulfill() + } catch { + XCTFail("Unexpected error: \(error)") } - """ - ) + } + waitForExpectations(timeout: 1.0) assertHeartbeatControllerFlushesEmptyPayload(controller) } diff --git a/FirebaseCore/Sources/FIRHeartbeatLogger.m b/FirebaseCore/Sources/FIRHeartbeatLogger.m index d1c77ee4f2f..4becd085c22 100644 --- a/FirebaseCore/Sources/FIRHeartbeatLogger.m +++ b/FirebaseCore/Sources/FIRHeartbeatLogger.m @@ -87,7 +87,8 @@ - (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { } - (void)flushHeartbeatsIntoPayloadWithCompletionHandler: - (void (^)(FIRHeartbeatsPayload *))completionHandler { + (void (^)(FIRHeartbeatsPayload *))completionHandler + API_AVAILABLE(ios(13.0), macosx(10.15), macCatalyst(13.0), tvos(13.0), watchos(6.0)) { [_heartbeatController flushAsyncWithCompletionHandler:^(FIRHeartbeatsPayload *payload) { completionHandler(payload); }]; From 4b25fc456862376f50cfcc932cbd29772d5e59b0 Mon Sep 17 00:00:00 2001 From: rizafran <56452638+rizafran@users.noreply.github.com> Date: Thu, 24 Oct 2024 00:39:49 +0800 Subject: [PATCH 230/258] Added Marathi language in Remote Config Locale List (#13597) --- FirebaseRemoteConfig/Sources/RCNDevice.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/RCNDevice.m b/FirebaseRemoteConfig/Sources/RCNDevice.m index 48cda112f5e..3819ff26fbd 100644 --- a/FirebaseRemoteConfig/Sources/RCNDevice.m +++ b/FirebaseRemoteConfig/Sources/RCNDevice.m @@ -60,6 +60,7 @@ RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) { return [[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] lowercaseString]; } +// TODO(rizafran): To migrate to use ISOLanguageCodes in the future NSDictionary *FIRRemoteConfigFirebaseLocaleMap(void) { return @{ // Albanian @@ -82,8 +83,6 @@ RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) { @"fi" : @[ @"fi", @"fi_FI" ], // Hebrew @"he" : @[ @"he", @"iw_IL" ], - // Hindi - @"hi" : @[ @"hi_IN" ], // Hungarian @"hu" : @[ @"hu", @"hu_HU" ], // Icelandic @@ -153,6 +152,9 @@ RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) { @"de" : @[ @"de", @"de_AT", @"de_DE", @"de_LU", @"de_CH", @"de-DE" ], // Greek @"el" : @[ @"el", @"el_CY", @"el_GR" ], + // India + @"hi_IN" : + @[ @"hi_IN", @"ta_IN", @"te_IN", @"mr_IN", @"bn_IN", @"gu_IN", @"kn_IN", @"pa_Guru_IN" ], // Italian @"it" : @[ @"it", @"it_IT", @"it_CH", @"it-IT" ], // Japanese @@ -188,6 +190,7 @@ RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) { } return locales; } + NSString *FIRRemoteConfigDeviceLocale(void) { NSArray *locales = FIRRemoteConfigAppManagerLocales(); NSArray *preferredLocalizations = From 66f44fc6441dde08e7eb1cf3865b09a5cb99722f Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:18:38 -0400 Subject: [PATCH 231/258] [Core] Make instance manager conform to Swift 6 principles (#13933) --- .../HeartbeatLogging/HeartbeatStorage.swift | 39 +++++++++++++++---- .../Tests/Unit/HeartbeatStorageTests.swift | 30 ++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift index ff428077613..7fd808b4457 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift @@ -51,7 +51,26 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { // MARK: - Instance Management /// Statically allocated cache of `HeartbeatStorage` instances keyed by string IDs. - private static var cachedInstances: [String: WeakContainer] = [:] + #if compiler(>=6) + // In Swift 6, this property is not concurrency-safe because it is + // nonisolated global shared mutable state. Because this target's + // deployment version does not support Swift concurrency, it is marked as + // `nonisolated(unsafe)` to disable concurrency-safety checks. The + // property's access is protected by an external synchronization mechanism + // (see `instancesLock` property). + private nonisolated(unsafe) static var cachedInstances: [ + String: WeakContainer + ] = [:] + #else + // TODO(Xcode 16): Delete this block when minimum supported Xcode is + // Xcode 16. + private static var cachedInstances: [ + String: WeakContainer + ] = [:] + #endif // compiler(>=6) + + /// Used to synchronize concurrent access to the `cachedInstances` property. + private static let instancesLock = NSLock() /// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise, /// makes a new instance with the given `id`. @@ -59,12 +78,14 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { /// - Parameter id: A string identifier. /// - Returns: A `HeartbeatStorage` instance. static func getInstance(id: String) -> HeartbeatStorage { - if let cachedInstance = cachedInstances[id]?.object { - return cachedInstance - } else { - let newInstance = HeartbeatStorage.makeHeartbeatStorage(id: id) - cachedInstances[id] = WeakContainer(object: newInstance) - return newInstance + instancesLock.withLock { + if let cachedInstance = cachedInstances[id]?.object { + return cachedInstance + } else { + let newInstance = HeartbeatStorage.makeHeartbeatStorage(id: id) + cachedInstances[id] = WeakContainer(object: newInstance) + return newInstance + } } } @@ -88,7 +109,9 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { deinit { // Removes the instance if it was cached. - Self.cachedInstances.removeValue(forKey: id) + _ = Self.instancesLock.withLock { + Self.cachedInstances.removeValue(forKey: id) + } } // MARK: - HeartbeatStorageProtocol diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift index ed24275b88a..79f4cc5abf6 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift @@ -399,6 +399,36 @@ class HeartbeatStorageTests: XCTestCase { // Then wait(for: expectations, timeout: 1.0, enforceOrder: true) } + + func testForMemoryLeakInInstanceManager() { + // Given + let id = "testID" + var weakRefs: [WeakContainer] = [] + // Lock is used to synchronize `weakRefs` during concurrent access. + let weakRefsLock = NSLock() + + // When + // Simulate concurrent access. This will help expose race conditions that could cause a crash. + let group = DispatchGroup() + for _ in 0 ..< 100 { + group.enter() + DispatchQueue.global().async { + let instance = HeartbeatStorage.getInstance(id: id) + weakRefsLock.withLock { + weakRefs.append(WeakContainer(object: instance)) + } + group.leave() + } + } + group.wait() + + // Then + // The `weakRefs` array's references should all be nil; otherwise, something is being + // unexpectedly strongly retained. + for weakRef in weakRefs { + XCTAssertNil(weakRef.object, "Potential memory leak detected.") + } + } } private class StorageFake: Storage { From 36da449b15619d301302faeba639c7893d576220 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 23 Oct 2024 10:28:27 -0700 Subject: [PATCH 232/258] Keep FirebaseCore, FirebaseCoreExtension and FirebaseCoreInternal pod versions aligned to latest (#13953) --- FirebaseABTesting.podspec | 2 +- FirebaseAnalytics.podspec | 2 +- FirebaseAppCheck.podspec | 2 +- FirebaseAppDistribution.podspec | 2 +- FirebaseAuth.podspec | 4 ++-- FirebaseCombineSwift.podspec | 2 +- FirebaseCore.podspec | 2 +- FirebaseCoreExtension.podspec | 2 +- FirebaseCrashlytics.podspec | 2 +- FirebaseDatabase.podspec | 2 +- FirebaseDynamicLinks.podspec | 2 +- FirebaseFirestore.podspec | 4 ++-- FirebaseFirestoreInternal.podspec | 2 +- FirebaseFunctions.podspec | 4 ++-- FirebaseInAppMessaging.podspec | 2 +- FirebaseInstallations.podspec | 2 +- FirebaseMLModelDownloader.podspec | 4 ++-- FirebaseMessaging.podspec | 2 +- FirebasePerformance.podspec | 2 +- FirebaseRemoteConfig.podspec | 2 +- FirebaseSessions.podspec | 4 ++-- FirebaseStorage.podspec | 4 ++-- FirebaseVertexAI.podspec | 4 ++-- .../Sources/FirebaseReleaser/InitializeRelease.swift | 12 +++++++----- 24 files changed, 37 insertions(+), 35 deletions(-) diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 527deb13b95..d988bbac272 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -52,7 +52,7 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 485ea5f6819..125dd7e6f01 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.libraries = 'c++', 'sqlite3', 'z' s.frameworks = 'StoreKit' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/MethodSwizzler', '~> 8.0' diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 0e76b2ef9b2..a80ebd8451a 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -46,7 +46,7 @@ Pod::Spec.new do |s| s.dependency 'AppCheckCore', '~> 11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index b5ee21b9daf..c5230bda665 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -30,7 +30,7 @@ iOS SDK for App Distribution for Firebase. ] s.public_header_files = base_dir + 'Public/FirebaseAppDistribution/*.h' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' s.dependency 'FirebaseInstallations', '~> 11.0' diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 2312ee8f897..67f32a30b74 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -58,8 +58,8 @@ supports email and password accounts, as well as several 3rd party authenticatio s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' diff --git a/FirebaseCombineSwift.podspec b/FirebaseCombineSwift.podspec index 1670e68b9d4..a181b9b1a42 100644 --- a/FirebaseCombineSwift.podspec +++ b/FirebaseCombineSwift.podspec @@ -51,7 +51,7 @@ for internal testing only. It should not be published. s.osx.framework = 'AppKit' s.tvos.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseAuth', '~> 11.0' s.dependency 'FirebaseFunctions', '~> 11.0' s.dependency 'FirebaseFirestore', '~> 11.0' diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 00aafddc64e..c2ccb23fddc 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -53,7 +53,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration # Remember to also update version in `cmake/external/GoogleUtilities.cmake` s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/Logger', '~> 8.0' - s.dependency 'FirebaseCoreInternal', '~> 11.0' + s.dependency 'FirebaseCoreInternal', '11.5' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 8e9f8422058..59dfad21719 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -34,5 +34,5 @@ Pod::Spec.new do |s| "#{s.module_name}_Privacy" => 'FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' end diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 51e50f01606..5426a42bbee 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -59,7 +59,7 @@ Pod::Spec.new do |s| cp -f ./Crashlytics/CrashlyticsInputFiles.xcfilelist ./CrashlyticsInputFiles.xcfilelist PREPARE_COMMAND_END - s.dependency 'FirebaseCore', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseSessions', '~> 11.0' s.dependency 'FirebaseRemoteConfigInterop', '~> 11.0' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index a38baf8ff6f..834211167ba 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -47,7 +47,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.macos.frameworks = 'CFNetwork', 'Security', 'SystemConfiguration' s.watchos.frameworks = 'CFNetwork', 'Security', 'WatchKit' s.dependency 'leveldb-library', '~> 1.22' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index cc90a1e2afa..3d6ce244d8c 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -34,7 +34,7 @@ Firebase Dynamic Links are deep links that enhance user experience and increase } s.frameworks = 'QuartzCore' s.weak_framework = 'WebKit' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index f6f858cd53c..ff55ec358f1 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -35,8 +35,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, "#{s.module_name}_Privacy" => 'Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'FirebaseFirestoreInternal', '11.5.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 8ec6142ac65..33a61dbca85 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -93,7 +93,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, } s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' abseil_version = '~> 1.20240116.1' s.dependency 'abseil/algorithm', abseil_version diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index 0e36c04d6f2..fb9d748f4cb 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -35,8 +35,8 @@ Cloud Functions for Firebase. 'FirebaseFunctions/Sources/**/*.swift', ] - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseMessagingInterop', '~> 11.0' diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index ef293d91c0a..9aa37623b15 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -80,7 +80,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseABTesting', '~> 11.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index d77548778da..377902f1df6 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -45,7 +45,7 @@ Pod::Spec.new do |s| } s.framework = 'Security' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'PromisesObjC', '~> 2.4' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index b0a6cf37385..6ca985941e8 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -36,8 +36,8 @@ Pod::Spec.new do |s| ] s.framework = 'Foundation' - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 6087c41b53e..c826363f9e5 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -62,7 +62,7 @@ device, and it is completely free. s.osx.framework = 'SystemConfiguration' s.weak_framework = 'UserNotifications' s.dependency 'FirebaseInstallations', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Reachability', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 519bd9246cd..1215fa057f4 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -59,7 +59,7 @@ Firebase Performance library to measure performance of Mobile and Web Apps. s.ios.framework = 'CoreTelephony' s.framework = 'QuartzCore' s.framework = 'SystemConfiguration' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseRemoteConfig', '~> 11.0' s.dependency 'FirebaseSessions', '~> 11.0' diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 4bc701d8940..d8dfbe3aa84 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -52,7 +52,7 @@ app update. } s.dependency 'FirebaseABTesting', '~> 11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.0' + s.dependency 'FirebaseCore', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/NSData+zlib', '~> 8.0' diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 6f0debc2470..f36099d413d 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -39,8 +39,8 @@ Pod::Spec.new do |s| base_dir + 'SourcesObjC/**/*.{c,h,m,mm}', ] - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 28bf0563a62..bcbe118149b 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -39,8 +39,8 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index e0779aaa40d..a6aa6ea5b56 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -46,8 +46,8 @@ Firebase SDK. s.dependency 'FirebaseAppCheckInterop', '~> 11.4' s.dependency 'FirebaseAuthInterop', '~> 11.4' - s.dependency 'FirebaseCore', '~> 11.4' - s.dependency 'FirebaseCoreExtension', '~> 11.4' + s.dependency 'FirebaseCore', '11.5' + s.dependency 'FirebaseCoreExtension', '11.5' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' diff --git a/ReleaseTooling/Sources/FirebaseReleaser/InitializeRelease.swift b/ReleaseTooling/Sources/FirebaseReleaser/InitializeRelease.swift index f43ed5131b7..93e4d643fe7 100644 --- a/ReleaseTooling/Sources/FirebaseReleaser/InitializeRelease.swift +++ b/ReleaseTooling/Sources/FirebaseReleaser/InitializeRelease.swift @@ -51,9 +51,12 @@ enum InitializeRelease { } else { updatePodspecVersion(pod: pod, version: version, path: path) - // Pods depending on GoogleAppMeasurement and FirebaseFirestoreInternal specs - // should pin the dependency to the new version. - if pod.name.hasPrefix("GoogleAppMeasurement") || pod.name == "FirebaseFirestoreInternal" { + // Pods dependencies to update to latest. + if pod.name.hasPrefix("GoogleAppMeasurement") || + pod.name == "FirebaseCore" || + pod.name == "FirebaseCoreExtension" || + pod.name == "FirebaseCoreInternal" || + pod.name == "FirebaseFirestoreInternal" { updateDependenciesToLatest( dependency: pod.name, pods: manifest.pods, @@ -82,8 +85,7 @@ enum InitializeRelease { Shell.executeCommand(command, workingDir: path) } - /// Pods depending on GoogleAppMeasurement and FirebaseFirestoreInternal specs - /// should pin the dependency to the new version. + /// Update dependencies that we want pinned to the latest version. private static func updateDependenciesToLatest(dependency: String, pods: [Pod], version: String, From 0f73266ef98be7d488ce098a4da9ff85eab03870 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 25 Oct 2024 10:05:12 -0700 Subject: [PATCH 233/258] Tweaks for flaky tests (#13971) --- .github/workflows/database.yml | 2 ++ .github/workflows/remoteconfig.yml | 10 +++++++++- .github/workflows/storage.yml | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 86e1f42a109..7570be9c9bd 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -53,6 +53,8 @@ jobs: run: scripts/setup_bundler.sh - name: Install xcpretty run: gem install xcpretty + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: IntegrationTest # Only iOS to mitigate flakes. run: scripts/third_party/travis/retry.sh scripts/build.sh Database iOS integration diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 4fbf6aa191d..eb5cae871c7 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -118,27 +118,35 @@ jobs: - os: macos-13 xcode: Xcode_15.2 target: iOS + test: spm - os: macos-14 xcode: Xcode_15.4 target: iOS + test: spm - os: macos-15 xcode: Xcode_16 target: iOS + test: spm - os: macos-15 xcode: Xcode_16 target: tvOS + test: spm - os: macos-15 xcode: Xcode_16 target: macOS + test: spm - os: macos-15 xcode: Xcode_16 target: watchOS + test: spmbuildonly - os: macos-15 xcode: Xcode_16 target: catalyst + test: spm - os: macos-15 xcode: Xcode_16 target: visionOS + test: spm runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -153,7 +161,7 @@ jobs: - name: Unit Tests run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigUnit ${{ matrix.target }} spm - name: Fake Console tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigFakeConsole ${{ matrix.target }} spm + run: scripts/third_party/travis/retry.sh ./scripts/build.sh RemoteConfigFakeConsole ${{ matrix.target }} ${{ matrix.test }} catalyst: # Don't run on private repo unless it is a PR. diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index eb1dd9f8f0f..4df045aae24 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -24,8 +24,8 @@ jobs: matrix: language: [Swift, ObjC] include: - - os: macos-14 - xcode: Xcode_15.4 + - os: macos-15 + xcode: Xcode_16 env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} runs-on: ${{ matrix.os }} From 1077879bfa9927d6926e711658ce6041ef06eae0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 25 Oct 2024 18:29:23 -0400 Subject: [PATCH 234/258] [Vertex AI] Add test app for integration tests (#13960) --- .github/workflows/vertexai.yml | 31 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 63 ++ .../Resources/Assets.xcassets/Contents.json | 6 + .../Preview Assets.xcassets/Contents.json | 6 + .../TestApp/Resources/TestApp.entitlements | 10 + .../Tests/TestApp/Sources/ContentView.swift | 31 + .../Tests/TestApp/Sources/TestApp.swift | 31 + .../Tests/Integration/IntegrationTests.swift | 214 +++++++ .../VertexAITestApp.xcodeproj/project.pbxproj | 556 ++++++++++++++++++ scripts/build.sh | 9 + .../VertexAI/TestApp-Credentials.swift.gpg | Bin 0 -> 602 bytes .../TestApp-GoogleService-Info.plist.gpg | Bin 0 -> 621 bytes 13 files changed, 968 insertions(+) create mode 100644 FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements create mode 100644 FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift create mode 100644 FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift create mode 100644 FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift create mode 100644 FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj create mode 100644 scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg create mode 100644 scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 3d98ba70555..9cc70054bbb 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -125,6 +125,37 @@ jobs: retry_wait_seconds: 120 command: scripts/build.sh FirebaseVertexAIIntegration ${{ matrix.target }} spm + testapp-integration: + strategy: + matrix: + target: [iOS] + os: [macos-15] + include: + - os: macos-15 + xcode: Xcode_16 + runs-on: ${{ matrix.os }} + needs: spm-package-resolved + env: + TEST_RUNNER_FIRAAppCheckDebugToken: ${{ secrets.VERTEXAI_INTEGRATION_FAC_DEBUG_TOKEN }} + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + secrets_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }} + steps: + - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} + - name: Install Secret GoogleService-Info.plist + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \ + FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info.plist "$secrets_passphrase" + - name: Install Secret Credentials.swift + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg \ + FirebaseVertexAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase" + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Run IntegrationTests + run: scripts/build.sh VertexIntegration ${{ matrix.target }} + pod-lib-lint: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..532cd729c61 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,63 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements b/FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements new file mode 100644 index 00000000000..ee95ab7e582 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift b/FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift new file mode 100644 index 00000000000..52af5939455 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift @@ -0,0 +1,31 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift new file mode 100644 index 00000000000..2226e352203 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift @@ -0,0 +1,31 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAppCheck +import FirebaseCore +import SwiftUI + +@main +struct TestApp: App { + init() { + AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory()) + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift new file mode 100644 index 00000000000..b90a13515a6 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -0,0 +1,214 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAuth +import FirebaseCore +import FirebaseStorage +import FirebaseVertexAI +import XCTest + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class IntegrationTests: XCTestCase { + // Set temperature, topP and topK to lowest allowed values to make responses more deterministic. + let generationConfig = GenerationConfig( + temperature: 0.0, + topP: 0.0, + topK: 1, + responseMIMEType: "text/plain" + ) + let systemInstruction = ModelContent( + role: "system", + parts: "You are a friendly and helpful assistant." + ) + let safetySettings = [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .probability), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove, method: .severity), + SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .dangerousContent, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .civicIntegrity, threshold: .blockLowAndAbove), + ] + + var vertex: VertexAI! + var model: GenerativeModel! + var storage: Storage! + var userID1 = "" + + override func setUp() async throws { + let authResult = try await Auth.auth().signIn( + withEmail: Credentials.emailAddress1, + password: Credentials.emailPassword1 + ) + userID1 = authResult.user.uid + + vertex = VertexAI.vertexAI() + model = vertex.generativeModel( + modelName: "gemini-1.5-flash", + generationConfig: generationConfig, + safetySettings: safetySettings, + tools: [], + toolConfig: .init(functionCallingConfig: .none()), + systemInstruction: systemInstruction + ) + + storage = Storage.storage() + } + + // MARK: - Generate Content + + func testGenerateContent() async throws { + let prompt = "Where is Google headquarters located? Answer with the city name only." + + let response = try await model.generateContent(prompt) + + let text = try XCTUnwrap(response.text).trimmingCharacters(in: .whitespacesAndNewlines) + XCTAssertEqual(text, "Mountain View") + } + + // MARK: - Count Tokens + + func testCountTokens_text() async throws { + let prompt = "Why is the sky blue?" + model = vertex.generativeModel( + modelName: "gemini-1.5-pro", + generationConfig: generationConfig, + safetySettings: [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .severity), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockMediumAndAbove), + SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockOnlyHigh), + SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), + SafetySetting(harmCategory: .civicIntegrity, threshold: .off, method: .probability), + ], + toolConfig: .init(functionCallingConfig: .auto()), + systemInstruction: systemInstruction + ) + + let response = try await model.countTokens(prompt) + + XCTAssertEqual(response.totalTokens, 14) + XCTAssertEqual(response.totalBillableCharacters, 51) + } + + #if canImport(UIKit) + func testCountTokens_image_inlineData() async throws { + guard let image = UIImage(systemName: "cloud") else { + XCTFail("Image not found.") + return + } + + let response = try await model.countTokens(image) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + #endif // canImport(UIKit) + + func testCountTokens_image_fileData_public() async throws { + let storageRef = storage.reference(withPath: "vertexai/public/green.png") + let fileData = FileDataPart(uri: storageRef.gsURI, mimeType: "image/png") + + let response = try await model.countTokens(fileData) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + + func testCountTokens_image_fileData_requiresAuth_signedIn() async throws { + let storageRef = storage.reference(withPath: "vertexai/authenticated/all_users/yellow.jpg") + let fileData = FileDataPart(uri: storageRef.gsURI, mimeType: "image/jpeg") + + let response = try await model.countTokens(fileData) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + + func testCountTokens_image_fileData_requiresUserAuth_userSignedIn() async throws { + let storageRef = storage.reference(withPath: "vertexai/authenticated/user/\(userID1)/red.webp") + + let fileData = FileDataPart(uri: storageRef.gsURI, mimeType: "image/webp") + + let response = try await model.countTokens(fileData) + + XCTAssertEqual(response.totalTokens, 266) + XCTAssertEqual(response.totalBillableCharacters, 35) + } + + func testCountTokens_image_fileData_requiresUserAuth_wrongUser_permissionDenied() async throws { + let userID = "3MjEzU6JIobWvHdCYHicnDMcPpQ2" + let storageRef = storage.reference(withPath: "vertexai/authenticated/user/\(userID)/pink.webp") + + let fileData = FileDataPart(uri: storageRef.gsURI, mimeType: "image/webp") + + do { + _ = try await model.countTokens(fileData) + XCTFail("Expected to throw an error.") + } catch { + let errorDescription = String(describing: error) + XCTAssertTrue(errorDescription.contains("403")) + XCTAssertTrue(errorDescription.contains("The caller does not have permission")) + } + } + + func testCountTokens_functionCalling() async throws { + let sumDeclaration = FunctionDeclaration( + name: "sum", + description: "Adds two integers.", + parameters: ["x": .integer(), "y": .integer()] + ) + model = vertex.generativeModel( + modelName: "gemini-1.5-flash", + tools: [.functionDeclarations([sumDeclaration])], + toolConfig: .init(functionCallingConfig: .any(allowedFunctionNames: ["sum"])) + ) + let prompt = "What is 10 + 32?" + let sumCall = FunctionCallPart(name: "sum", args: ["x": .number(10), "y": .number(32)]) + let sumResponse = FunctionResponsePart(name: "sum", response: ["result": .number(42)]) + + let response = try await model.countTokens([ + ModelContent(role: "user", parts: prompt), + ModelContent(role: "model", parts: sumCall), + ModelContent(role: "function", parts: sumResponse), + ]) + + XCTAssertEqual(response.totalTokens, 24) + XCTAssertEqual(response.totalBillableCharacters, 71) + } + + func testCountTokens_jsonSchema() async throws { + model = vertex.generativeModel( + modelName: "gemini-1.5-flash", + generationConfig: GenerationConfig( + responseMIMEType: "application/json", + responseSchema: Schema.object(properties: [ + "startDate": .string(format: .custom("date")), + "yearsSince": .integer(format: .custom("int16")), + "hoursSince": .integer(format: .int32), + "minutesSince": .integer(format: .int64), + ]) + ) + ) + let prompt = "It is 2050-01-01, how many years, hours and minutes since 2000-01-01?" + + let response = try await model.countTokens(prompt) + + XCTAssertEqual(response.totalTokens, 34) + XCTAssertEqual(response.totalBillableCharacters, 59) + } +} + +extension StorageReference { + var gsURI: String { + return "gs://\(bucket)/\(fullPath)" + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..145a13b2f90 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj @@ -0,0 +1,556 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661385B2CC943DD00F4B78E /* TestApp.swift */; }; + 8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661385D2CC943DD00F4B78E /* ContentView.swift */; }; + 8661386E2CC943DE00F4B78E /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */; }; + 868A7C482CCA931B00E449DD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 868A7C462CCA931B00E449DD /* GoogleService-Info.plist */; }; + 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868A7C4D2CCC1F4700E449DD /* Credentials.swift */; }; + 868A7C522CCC263300E449DD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 868A7C502CCC263300E449DD /* Preview Assets.xcassets */; }; + 868A7C542CCC26B500E449DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 868A7C532CCC26B500E449DD /* Assets.xcassets */; }; + 8692F2982CC9477800539E8F /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F2972CC9477800539E8F /* FirebaseAppCheck */; }; + 8692F29A2CC9477800539E8F /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F2992CC9477800539E8F /* FirebaseAuth */; }; + 8692F29C2CC9477800539E8F /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29B2CC9477800539E8F /* FirebaseStorage */; }; + 8692F29E2CC9477800539E8F /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29D2CC9477800539E8F /* FirebaseVertexAI */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8661386A2CC943DE00F4B78E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 866138502CC943DD00F4B78E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 866138572CC943DD00F4B78E; + remoteInfo = VertexAITestApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 866138582CC943DD00F4B78E /* VertexAITestApp-SPM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "VertexAITestApp-SPM.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8661385B2CC943DD00F4B78E /* TestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestApp.swift; sourceTree = ""; }; + 8661385D2CC943DD00F4B78E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 866138692CC943DE00F4B78E /* IntegrationTests-SPM.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "IntegrationTests-SPM.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = ""; }; + 868A7C462CCA931B00E449DD /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 868A7C4D2CCC1F4700E449DD /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + 868A7C502CCC263300E449DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 868A7C532CCC26B500E449DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 868A7C552CCC271300E449DD /* TestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestApp.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 866138552CC943DD00F4B78E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8692F2982CC9477800539E8F /* FirebaseAppCheck in Frameworks */, + 8692F29E2CC9477800539E8F /* FirebaseVertexAI in Frameworks */, + 8692F29A2CC9477800539E8F /* FirebaseAuth in Frameworks */, + 8692F29C2CC9477800539E8F /* FirebaseStorage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 866138662CC943DE00F4B78E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8661384F2CC943DD00F4B78E = { + isa = PBXGroup; + children = ( + 868A7C562CCC277100E449DD /* Sources */, + 868A7C582CCC27AF00E449DD /* Tests */, + 868A7C472CCA931B00E449DD /* Resources */, + 866138592CC943DD00F4B78E /* Products */, + ); + sourceTree = ""; + }; + 866138592CC943DD00F4B78E /* Products */ = { + isa = PBXGroup; + children = ( + 866138582CC943DD00F4B78E /* VertexAITestApp-SPM.app */, + 866138692CC943DE00F4B78E /* IntegrationTests-SPM.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 868A7C472CCA931B00E449DD /* Resources */ = { + isa = PBXGroup; + children = ( + 868A7C512CCC263300E449DD /* Preview Content */, + 868A7C532CCC26B500E449DD /* Assets.xcassets */, + 868A7C552CCC271300E449DD /* TestApp.entitlements */, + 868A7C462CCA931B00E449DD /* GoogleService-Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 868A7C512CCC263300E449DD /* Preview Content */ = { + isa = PBXGroup; + children = ( + 868A7C502CCC263300E449DD /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 868A7C562CCC277100E449DD /* Sources */ = { + isa = PBXGroup; + children = ( + 8661385B2CC943DD00F4B78E /* TestApp.swift */, + 8661385D2CC943DD00F4B78E /* ContentView.swift */, + ); + path = Sources; + sourceTree = ""; + }; + 868A7C572CCC27AF00E449DD /* Integration */ = { + isa = PBXGroup; + children = ( + 868A7C4D2CCC1F4700E449DD /* Credentials.swift */, + 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */, + ); + path = Integration; + sourceTree = ""; + }; + 868A7C582CCC27AF00E449DD /* Tests */ = { + isa = PBXGroup; + children = ( + 868A7C572CCC27AF00E449DD /* Integration */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 866138572CC943DD00F4B78E /* VertexAITestApp-SPM */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8661387D2CC943DE00F4B78E /* Build configuration list for PBXNativeTarget "VertexAITestApp-SPM" */; + buildPhases = ( + 866138542CC943DD00F4B78E /* Sources */, + 866138552CC943DD00F4B78E /* Frameworks */, + 866138562CC943DD00F4B78E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "VertexAITestApp-SPM"; + packageProductDependencies = ( + 8692F2972CC9477800539E8F /* FirebaseAppCheck */, + 8692F2992CC9477800539E8F /* FirebaseAuth */, + 8692F29B2CC9477800539E8F /* FirebaseStorage */, + 8692F29D2CC9477800539E8F /* FirebaseVertexAI */, + ); + productName = VertexAITestApp; + productReference = 866138582CC943DD00F4B78E /* VertexAITestApp-SPM.app */; + productType = "com.apple.product-type.application"; + }; + 866138682CC943DE00F4B78E /* IntegrationTests-SPM */ = { + isa = PBXNativeTarget; + buildConfigurationList = 866138802CC943DE00F4B78E /* Build configuration list for PBXNativeTarget "IntegrationTests-SPM" */; + buildPhases = ( + 866138652CC943DE00F4B78E /* Sources */, + 866138662CC943DE00F4B78E /* Frameworks */, + 866138672CC943DE00F4B78E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8661386B2CC943DE00F4B78E /* PBXTargetDependency */, + ); + name = "IntegrationTests-SPM"; + productName = VertexAITestAppTests; + productReference = 866138692CC943DE00F4B78E /* IntegrationTests-SPM.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 866138502CC943DD00F4B78E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1520; + LastUpgradeCheck = 1520; + TargetAttributes = { + 866138572CC943DD00F4B78E = { + CreatedOnToolsVersion = 15.2; + }; + 866138682CC943DE00F4B78E = { + CreatedOnToolsVersion = 15.2; + TestTargetID = 866138572CC943DD00F4B78E; + }; + }; + }; + buildConfigurationList = 866138532CC943DD00F4B78E /* Build configuration list for PBXProject "VertexAITestApp" */; + compatibilityVersion = "Xcode 15.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8661384F2CC943DD00F4B78E; + packageReferences = ( + 8692F2962CC9477800539E8F /* XCLocalSwiftPackageReference "../../.." */, + ); + productRefGroup = 866138592CC943DD00F4B78E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 866138572CC943DD00F4B78E /* VertexAITestApp-SPM */, + 866138682CC943DE00F4B78E /* IntegrationTests-SPM */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 866138562CC943DD00F4B78E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 868A7C522CCC263300E449DD /* Preview Assets.xcassets in Resources */, + 868A7C542CCC26B500E449DD /* Assets.xcassets in Resources */, + 868A7C482CCA931B00E449DD /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 866138672CC943DE00F4B78E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 866138542CC943DD00F4B78E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */, + 8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 866138652CC943DE00F4B78E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */, + 8661386E2CC943DE00F4B78E /* IntegrationTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8661386B2CC943DE00F4B78E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 866138572CC943DD00F4B78E /* VertexAITestApp-SPM */; + targetProxy = 8661386A2CC943DE00F4B78E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 8661387B2CC943DE00F4B78E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8661387C2CC943DE00F4B78E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 8661387E2CC943DE00F4B78E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8661387F2CC943DE00F4B78E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 866138812CC943DE00F4B78E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VertexAITestApp-SPM.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VertexAITestApp-SPM"; + }; + name = Debug; + }; + 866138822CC943DE00F4B78E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VertexAITestApp-SPM.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VertexAITestApp-SPM"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 866138532CC943DD00F4B78E /* Build configuration list for PBXProject "VertexAITestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8661387B2CC943DE00F4B78E /* Debug */, + 8661387C2CC943DE00F4B78E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8661387D2CC943DE00F4B78E /* Build configuration list for PBXNativeTarget "VertexAITestApp-SPM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8661387E2CC943DE00F4B78E /* Debug */, + 8661387F2CC943DE00F4B78E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 866138802CC943DE00F4B78E /* Build configuration list for PBXNativeTarget "IntegrationTests-SPM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 866138812CC943DE00F4B78E /* Debug */, + 866138822CC943DE00F4B78E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 8692F2962CC9477800539E8F /* XCLocalSwiftPackageReference "../../.." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../..; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 8692F2972CC9477800539E8F /* FirebaseAppCheck */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAppCheck; + }; + 8692F2992CC9477800539E8F /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseAuth; + }; + 8692F29B2CC9477800539E8F /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseStorage; + }; + 8692F29D2CC9477800539E8F /* FirebaseVertexAI */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseVertexAI; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 866138502CC943DD00F4B78E /* Project object */; +} diff --git a/scripts/build.sh b/scripts/build.sh index f3f5525a29b..0a8cdc857a6 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -503,6 +503,15 @@ case "$product-$platform-$method" in build ;; + VertexIntegration-*-*) + RunXcodebuild \ + -project 'FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj' \ + -scheme "VertexAITestApp-SPM" \ + "${xcb_flags[@]}" \ + build \ + test + ;; + VertexSample-*-*) RunXcodebuild \ -project 'FirebaseVertexAI/Sample/VertexAISample.xcodeproj' \ diff --git a/scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg b/scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg new file mode 100644 index 0000000000000000000000000000000000000000..f7beaecccb00ad479e8fb33659fe1cd8ec54158b GIT binary patch literal 602 zcmV-g0;TI=Df-gs0mBR|TFzsLJkS@s%|&wf(LU16uVk<&I{?}> zeG#J0)Txsul0PYEk{(|)(cEhc*qN4%3h{}rlE`mFl_JMEaCtKwBRDQGh5APcH$l7X{(be-A{ddV< zU~})*Cm<>>8f|wBg^^kXYt1!bf`$?vW|Bm#Vb*vAKMM>cuo+4BI?K?6B;?o_$xT9+({Cv6z2EnQkk*9^+=mZ>`WQYM{G&%e`*7{ls56K znY4x>dUg8Y+chXpeQ|<(Ywn~rXaWM?7HU9PuA{hlpp{EZMyr?i;Nwu|(am~~Qzy27 zdY<3jz(=?y@dgN#9~=+t%U>{SCQ(mWU1sb0<^{F95DKlwJs05Mx8V@L7X={mRyGwm zZRkEQ+ownGG)z*MCvAj1ND&ILD@*`3B!OpYS3WY=wY@*Y4t!ff&6)qYaL)Z=LYrGI o^H&oom>A@35MQ^#O!4ErKmgZ0cA3#5bfl=yNRrOXnS3ha>3)E!1Y| z)}B-o=h)uDn#$6!;=NWPngr+y9{y4k(1HaWT7#0$;y|kN4bNEWnL8xKU!T-JyYpl`PeFrVrKQrUb(b}Y_1t$|W^20oVz+aVB zBTyU9wX^JK&CVa+;;yo@O}`H7 zz@Pw%3+MJ^IL1F|@zH)qH$qJu8`w)vXR!@d>>5E>)C8%qFYJ|dIBpvqz?Q6$ He0efuWP~n4 literal 0 HcmV?d00001 From 469b4c93a10dd488e13c155c41acc1a499d20ed9 Mon Sep 17 00:00:00 2001 From: Srushti Vaidya Date: Mon, 28 Oct 2024 14:33:32 +0530 Subject: [PATCH 235/258] Adding Integration Test for Phone Number Authentication in Firebase (#13959) --- .../SwiftApiTests/PhoneAuthTests.swift | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 FirebaseAuth/Tests/SampleSwift/SwiftApiTests/PhoneAuthTests.swift diff --git a/FirebaseAuth/Tests/SampleSwift/SwiftApiTests/PhoneAuthTests.swift b/FirebaseAuth/Tests/SampleSwift/SwiftApiTests/PhoneAuthTests.swift new file mode 100644 index 00000000000..f8cacbc91bf --- /dev/null +++ b/FirebaseAuth/Tests/SampleSwift/SwiftApiTests/PhoneAuthTests.swift @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import FirebaseAuth +import Foundation +import XCTest + +class PhoneAuthTests: TestsBase { + let phoneNumber = "+12345678910" + // This test verification code is specified for the given test phone number in the developer + // console. + let verificationCode = "123456" + + func testSignInWithPhoneNumber() throws { + Auth.auth().settings?.isAppVerificationDisabledForTesting = true + let auth = Auth.auth() + let expectation = self.expectation(description: "Sign in with phone number") + + // PhoneAuthProvider used to initiate the Verification process and obtain a verificationID. + PhoneAuthProvider.provider() + .verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in + if let error { + XCTAssertNil(error, "Verification error should be nil") + XCTAssertNotNil(verificationID, "Verification ID should not be nil") + } + + // Create a credential using the test verification code. + let credential = PhoneAuthProvider.provider().credential( + withVerificationID: verificationID ?? "", + verificationCode: self.verificationCode + ) + // Signs in using the credential and verifies that the user is signed in correctly by + // checking auth.currentUser. + auth.signIn(with: credential) { authResult, error in + if let error { + XCTAssertNil(error, "Sign in error should be nil") + XCTAssertNotNil(authResult, "AuthResult should not be nil") + XCTAssertEqual( + auth.currentUser?.phoneNumber, + self.phoneNumber, + "Phone number does not match" + ) + } + expectation.fulfill() + } + } + + waitForExpectations(timeout: TestsBase.kExpectationsTimeout) + } + + func testSignInWithPhoneNumberAsync_Success() async throws { + Auth.auth().settings?.isAppVerificationDisabledForTesting = true + let auth = Auth.auth() + + // Start phone number verification + let verificationID = try await PhoneAuthProvider.provider().verifyPhoneNumber( + phoneNumber, + uiDelegate: nil + ) + XCTAssertNotNil(verificationID, "Expected a verification ID") + + // Create the phone auth credential + let credential = PhoneAuthProvider.provider().credential( + withVerificationID: verificationID, + verificationCode: verificationCode + ) + + // Sign in with the credential + let authResult = try await auth.signIn(with: credential) + XCTAssertNotNil(authResult, "Expected a non-nil AuthResult") + XCTAssertEqual(auth.currentUser?.phoneNumber, phoneNumber, "Phone number does not match") + } +} From 476c9eb80a8b7fc9c75492cf645ed36fd1eca200 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 28 Oct 2024 10:04:36 -0400 Subject: [PATCH 236/258] [Vertex AI] Fix deployment targets for TestApp (#13973) --- .../VertexAITestApp.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj index 145a13b2f90..3638b5b13c8 100644 --- a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj +++ b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj @@ -399,10 +399,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 14.2; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -435,10 +435,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 14.2; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -458,8 +458,8 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; - MACOSX_DEPLOYMENT_TARGET = 14.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,8 +480,8 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; - MACOSX_DEPLOYMENT_TARGET = 14.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestAppTests; PRODUCT_NAME = "$(TARGET_NAME)"; From 6ee2bdd98ac441597d9769dfb78f448fcb46a52f Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 28 Oct 2024 08:49:51 -0700 Subject: [PATCH 237/258] Update CocoaPods integration test Gemfile (#13980) --- .../Cocoapods_multiprojects_frameworks/Gemfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile index e54b6b9c765..5a05e463e3e 100644 --- a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile +++ b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile @@ -2,4 +2,5 @@ source "https://rubygems.org" -gem 'cocoapods', '1.14.3' +gem 'cocoapods', '1.15.2' +gem 'xcodeproj', '1.25.1' From dd0a4c6cb84aace05e8e49469f0fb2c1f70fd155 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:45:01 -0700 Subject: [PATCH 238/258] Bump rexml from 3.3.6 to 3.3.9 in /.github/actions/notices_generation (#13981) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/notices_generation/Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/actions/notices_generation/Gemfile.lock b/.github/actions/notices_generation/Gemfile.lock index ff45716919e..8ea9bd7b820 100644 --- a/.github/actions/notices_generation/Gemfile.lock +++ b/.github/actions/notices_generation/Gemfile.lock @@ -79,14 +79,12 @@ GEM sawyer (~> 0.8.0, >= 0.5.3) plist (3.6.0) public_suffix (4.0.6) - rexml (3.3.6) - strscan + rexml (3.3.9) ruby-macho (2.5.1) ruby2_keywords (0.0.2) sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) - strscan (3.1.0) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) From a0ad239f4327bc60734d4cc84fffd6e274da0d15 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 28 Oct 2024 18:03:26 -0700 Subject: [PATCH 239/258] Fix Auth Combine build for watchOS (#13984) --- .../Sources/Auth/GameCenterAuthProvider+Combine.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FirebaseCombineSwift/Sources/Auth/GameCenterAuthProvider+Combine.swift b/FirebaseCombineSwift/Sources/Auth/GameCenterAuthProvider+Combine.swift index 95491fbc5df..7bb8eab83ef 100644 --- a/FirebaseCombineSwift/Sources/Auth/GameCenterAuthProvider+Combine.swift +++ b/FirebaseCombineSwift/Sources/Auth/GameCenterAuthProvider+Combine.swift @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if canImport(Combine) && swift(>=5.0) +#if canImport(Combine) && !os(watchOS) import Combine import FirebaseAuth @available(swift 5.0) @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, *) - @available(watchOS, unavailable) public extension GameCenterAuthProvider { /// Creates an `AuthCredential` for a Game Center sign in. /// From f061a83c857b0c0e3805d1babdf014f822ca48b2 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:20:23 -0400 Subject: [PATCH 240/258] [Auth] Use constants for 'supportsSecureCoding' implementation (#13988) --- FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift | 2 +- FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift | 4 +--- .../Sources/Swift/AuthProvider/EmailAuthProvider.swift | 2 +- .../Sources/Swift/AuthProvider/FacebookAuthProvider.swift | 2 +- .../Sources/Swift/AuthProvider/GameCenterAuthProvider.swift | 2 +- .../Sources/Swift/AuthProvider/GitHubAuthProvider.swift | 2 +- .../Sources/Swift/AuthProvider/GoogleAuthProvider.swift | 2 +- FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift | 2 +- .../Sources/Swift/AuthProvider/PhoneAuthCredential.swift | 2 +- .../Sources/Swift/AuthProvider/TwitterAuthProvider.swift | 2 +- FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift | 4 +--- .../Sources/Swift/SystemService/AuthAppCredential.swift | 4 +--- .../Sources/Swift/SystemService/SecureTokenService.swift | 4 +--- FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift | 4 +--- FirebaseAuth/Sources/Swift/User/User.swift | 4 +--- FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift | 4 +--- FirebaseAuth/Sources/Swift/User/UserMetadata.swift | 4 +--- 17 files changed, 17 insertions(+), 33 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift index bd09c1ddac2..9d6dacd2687 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift @@ -54,7 +54,7 @@ extension AuthDataResult: NSSecureCoding {} private let kUserCodingKey = "user" private let kCredentialCodingKey = "credential" - public static var supportsSecureCoding = true + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { coder.encode(user, forKey: kUserCodingKey) diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift index 19019b9d8a2..90be2649ef6 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift @@ -129,9 +129,7 @@ extension AuthTokenResult: NSSecureCoding {} private static let kTokenKey = "token" - public static var supportsSecureCoding: Bool { - return true - } + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { coder.encode(token, forKey: AuthTokenResult.kTokenKey) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift index 0675f02f737..050dce30974 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift @@ -62,7 +62,7 @@ import Foundation // MARK: Secure Coding - static var supportsSecureCoding = true + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(email, forKey: "email") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift index 9ffbec55dba..cc89e7b3394 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift @@ -48,7 +48,7 @@ import Foundation // MARK: Secure Coding - static var supportsSecureCoding = true + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(accessToken, forKey: "accessToken") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift index aecab7ca77d..4560bad9aaf 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift @@ -163,7 +163,7 @@ // MARK: Secure Coding - static var supportsSecureCoding = true + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(playerID, forKey: "playerID") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift index 4704e4fe014..801e63fa8ee 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift @@ -48,7 +48,7 @@ import Foundation // MARK: Secure Coding - public static var supportsSecureCoding = true + public static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(token, forKey: "token") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift index b4f3b806837..41f223b25dc 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift @@ -53,7 +53,7 @@ import Foundation // MARK: Secure Coding - static var supportsSecureCoding = true + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(idToken, forKey: "idToken") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift index a7fc244e16e..920fd138af4 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift @@ -96,7 +96,7 @@ import Foundation // MARK: Secure Coding - public static var supportsSecureCoding: Bool = true + public static let supportsSecureCoding: Bool = true public func encode(with coder: NSCoder) { coder.encode(idToken, forKey: "IDToken") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift index 2b42df11e7d..49f643a34d1 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift @@ -39,7 +39,7 @@ import Foundation // MARK: Secure Coding - public static var supportsSecureCoding = true + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { switch credentialKind { diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift index 177b08ed894..6364241188f 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift @@ -52,7 +52,7 @@ import Foundation // MARK: Secure Coding - static var supportsSecureCoding = true + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(token, forKey: "token") diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index 744dbb2750f..8920da00ad1 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -282,9 +282,7 @@ import Foundation private let kEnrolledFactorsCodingKey = "enrolledFactors" - public static var supportsSecureCoding: Bool { - true - } + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { coder.encode(enrolledFactors, forKey: kEnrolledFactorsCodingKey) diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift index 39abc59e12f..beb0d15a7b1 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift @@ -38,9 +38,7 @@ class AuthAppCredential: NSObject, NSSecureCoding { private static let kReceiptKey = "receipt" private static let kSecretKey = "secret" - static var supportsSecureCoding: Bool { - true - } + static let supportsSecureCoding = true required convenience init?(coder: NSCoder) { guard let receipt = coder.decodeObject(of: NSString.self, diff --git a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift index e06ef8eaf0f..dd9e6dbde13 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift @@ -164,9 +164,7 @@ class SecureTokenService: NSObject, NSSecureCoding { private static let kAccessTokenKey = "accessToken" private static let kAccessTokenExpirationDateKey = "accessTokenExpirationDate" - static var supportsSecureCoding: Bool { - true - } + static let supportsSecureCoding = true required convenience init?(coder: NSCoder) { guard let refreshToken = coder.decodeObject(of: [NSString.self], diff --git a/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift b/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift index ed6d3fd3a53..a72e0baef02 100644 --- a/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift +++ b/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift @@ -49,9 +49,7 @@ extension AdditionalUserInfo: NSSecureCoding {} private static let usernameCodingKey = "username" private static let newUserKey = "newUser" - public static var supportsSecureCoding: Bool { - return true - } + public static let supportsSecureCoding = true public required init?(coder aDecoder: NSCoder) { guard let providerID = aDecoder.decodeObject( diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 35f64e65320..02cdeb8f979 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -1680,9 +1680,7 @@ extension User: NSSecureCoding {} private let kMultiFactorCodingKey = "multiFactor" private let kTenantIDCodingKey = "tenantID" - public static var supportsSecureCoding: Bool { - return true - } + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { coder.encode(uid, forKey: kUserIDCodingKey) diff --git a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift index 63cc46d4696..6f19622ddcb 100644 --- a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift +++ b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift @@ -72,9 +72,7 @@ extension UserInfoImpl: NSSecureCoding {} private static let kEmailCodingKey = "email" private static let kPhoneNumberCodingKey = "phoneNumber" - static var supportsSecureCoding: Bool { - return true - } + static let supportsSecureCoding = true func encode(with coder: NSCoder) { coder.encode(providerID, forKey: UserInfoImpl.kProviderIDCodingKey) diff --git a/FirebaseAuth/Sources/Swift/User/UserMetadata.swift b/FirebaseAuth/Sources/Swift/User/UserMetadata.swift index b126350c449..fa014ac6161 100644 --- a/FirebaseAuth/Sources/Swift/User/UserMetadata.swift +++ b/FirebaseAuth/Sources/Swift/User/UserMetadata.swift @@ -36,9 +36,7 @@ extension UserMetadata: NSSecureCoding {} private static let kCreationDateCodingKey = "creationDate" private static let kLastSignInDateCodingKey = "lastSignInDate" - public static var supportsSecureCoding: Bool { - return true - } + public static let supportsSecureCoding = true public func encode(with coder: NSCoder) { coder.encode(creationDate, forKey: UserMetadata.kCreationDateCodingKey) From 865535fda724ff4deaf3cd4556e765b14b7fd4fa Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Tue, 29 Oct 2024 17:47:34 +0100 Subject: [PATCH 241/258] Async `FunctionsContextProvider.context(options:)` (#13900) --- .../Sources/Internal/FunctionsContext.swift | 53 ++++++++- .../Tests/Unit/ContextProviderTests.swift | 103 ++++++++++++++++++ 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift index 7daaa6032d1..5a2f24ec41b 100644 --- a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift +++ b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift @@ -36,12 +36,53 @@ struct FunctionsContextProvider { self.appCheck = appCheck } - // TODO: Implement async await version -// @available(macOS 10.15.0, *) -// internal func getContext() async throws -> FunctionsContext { -// return FunctionsContext(authToken: nil, fcmToken: nil, appCheckToken: nil) -// -// } + @available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *) + func context(options: HTTPSCallableOptions?) async throws -> FunctionsContext { + async let authToken = auth?.getToken(forcingRefresh: false) + async let appCheckToken = getAppCheckToken(options: options) + async let limitedUseAppCheckToken = getLimitedUseAppCheckToken(options: options) + + // Only `authToken` is throwing, but the formatter script removes the `try` + // from `try authToken` and puts it in front of the initializer call. + return try await FunctionsContext( + authToken: authToken, + fcmToken: messaging?.fcmToken, + appCheckToken: appCheckToken, + limitedUseAppCheckToken: limitedUseAppCheckToken + ) + } + + @available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *) + private func getAppCheckToken(options: HTTPSCallableOptions?) async -> String? { + guard + options?.requireLimitedUseAppCheckTokens != true, + let tokenResult = await appCheck?.getToken(forcingRefresh: false), + tokenResult.error == nil + else { return nil } + return tokenResult.token + } + + @available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *) + private func getLimitedUseAppCheckToken(options: HTTPSCallableOptions?) async -> String? { + // At the moment, `await` doesn’t get along with Objective-C’s optional protocol methods. + await withCheckedContinuation { (continuation: CheckedContinuation) in + guard + options?.requireLimitedUseAppCheckTokens == true, + let appCheck, + // `getLimitedUseToken(completion:)` is an optional protocol method. Optional binding + // is performed to make sure `continuation` is called even if the method’s not implemented. + let limitedUseTokenClosure = appCheck.getLimitedUseToken + else { + return continuation.resume(returning: nil) + } + + limitedUseTokenClosure { tokenResult in + // Make sure there’s no error and the token is valid: + guard tokenResult.error == nil else { return continuation.resume(returning: nil) } + continuation.resume(returning: tokenResult.token) + } + } + } func getContext(options: HTTPSCallableOptions? = nil, _ completion: @escaping ((FunctionsContext, Error?) -> Void)) { diff --git a/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift b/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift index db565bcdb01..089c2d9f9ef 100644 --- a/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift +++ b/FirebaseFunctions/Tests/Unit/ContextProviderTests.swift @@ -35,6 +35,17 @@ class ContextProviderTests: XCTestCase { let appCheckTokenSuccess = FIRAppCheckTokenResultFake(token: "valid_token", error: nil) let messagingFake = FIRMessagingInteropFake() + func testAsyncContextWithAuth() async throws { + let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) + let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) + + let context = try await provider.context(options: nil) + + XCTAssertNotNil(context) + XCTAssertEqual(context.authToken, "token") + XCTAssertEqual(context.fcmToken, messagingFake.fcmToken) + } + func testContextWithAuth() { let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) @@ -49,6 +60,19 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncContextWithAuthError() async { + let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) + let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) + let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) + + do { + _ = try await provider.context(options: nil) + XCTFail("Expected an error") + } catch { + XCTAssertEqual(error as NSError, authError) + } + } + func testContextWithAuthError() { let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) @@ -63,6 +87,15 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncContextWithoutAuth() async throws { + let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: nil) + + let context = try await provider.context(options: nil) + + XCTAssertNil(context.authToken) + XCTAssertNil(context.fcmToken) + } + func testContextWithoutAuth() { let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: nil) let expectation = expectation(description: "Completion handler should succeed without Auth.") @@ -76,6 +109,17 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncContextWithAppCheckOnlySuccess() async throws { + appCheckFake.tokenResult = appCheckTokenSuccess + let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) + + let context = try await provider.context(options: nil) + + XCTAssertNil(context.authToken) + XCTAssertNil(context.fcmToken) + XCTAssertEqual(context.appCheckToken, appCheckTokenSuccess.token) + } + func testContextWithAppCheckOnlySuccess() { appCheckFake.tokenResult = appCheckTokenSuccess let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) @@ -91,6 +135,18 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncContextWithAppCheckOnlyError() async throws { + appCheckFake.tokenResult = appCheckTokenError + let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) + + let context = try await provider.context(options: nil) + + XCTAssertNil(context.authToken) + XCTAssertNil(context.fcmToken) + // Don't expect any token in the case of App Check error. + XCTAssertNil(context.appCheckToken) + } + func testContextWithAppCheckOnlyError() { appCheckFake.tokenResult = appCheckTokenError let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) @@ -107,6 +163,19 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncContextWithAppCheckWithoutOptionalMethods() async throws { + let appCheck = AppCheckFakeWithoutOptionalMethods(tokenResult: appCheckTokenSuccess) + let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheck) + + let context = try await provider.context(options: .init(requireLimitedUseAppCheckTokens: true)) + + XCTAssertNil(context.authToken) + XCTAssertNil(context.fcmToken) + XCTAssertNil(context.appCheckToken) + // If the method for limited-use tokens is not implemented, the value should be `nil`: + XCTAssertNil(context.limitedUseAppCheckToken) + } + func testContextWithAppCheckWithoutOptionalMethods() { let appCheck = AppCheckFakeWithoutOptionalMethods(tokenResult: appCheckTokenSuccess) let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheck) @@ -126,6 +195,22 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncAllContextsAvailableSuccess() async throws { + appCheckFake.tokenResult = appCheckTokenSuccess + let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) + let provider = FunctionsContextProvider( + auth: auth, + messaging: messagingFake, + appCheck: appCheckFake + ) + + let context = try await provider.context(options: nil) + + XCTAssertEqual(context.authToken, "token") + XCTAssertEqual(context.fcmToken, messagingFake.fcmToken) + XCTAssertEqual(context.appCheckToken, appCheckTokenSuccess.token) + } + func testAllContextsAvailableSuccess() { appCheckFake.tokenResult = appCheckTokenSuccess let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) @@ -146,6 +231,24 @@ class ContextProviderTests: XCTestCase { waitForExpectations(timeout: 0.1) } + func testAsyncAllContextsAuthAndAppCheckError() async { + appCheckFake.tokenResult = appCheckTokenError + let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) + let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) + let provider = FunctionsContextProvider( + auth: auth, + messaging: messagingFake, + appCheck: appCheckFake + ) + + do { + _ = try await provider.context(options: nil) + XCTFail("Expected an error") + } catch { + XCTAssertEqual(error as NSError, authError) + } + } + func testAllContextsAuthAndAppCheckError() { appCheckFake.tokenResult = appCheckTokenError let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) From 7244f542330f4b765e5e3dda2a5b757c9dab4354 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 29 Oct 2024 14:49:59 -0400 Subject: [PATCH 242/258] [Vertex AI] Disable xcodebuild parallel testing (#13985) --- scripts/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/build.sh b/scripts/build.sh index 0a8cdc857a6..830e4ace0d1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -508,6 +508,7 @@ case "$product-$platform-$method" in -project 'FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj' \ -scheme "VertexAITestApp-SPM" \ "${xcb_flags[@]}" \ + -parallel-testing-enabled NO \ build \ test ;; From b1c28c67bd0bb179580d2b553ee80fcb4e44fc4b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 29 Oct 2024 14:50:31 -0400 Subject: [PATCH 243/258] [Vertex AI] Remove `spm-integration` job (#13982) --- .github/workflows/vertexai.yml | 35 ---- .../Tests/Integration/IntegrationTests.swift | 177 ------------------ .../Integration/Resources/placeholder.txt | 0 Package.swift | 8 - .../FirebaseVertexAIIntegration.xcscheme | 77 -------- 5 files changed, 297 deletions(-) delete mode 100644 FirebaseVertexAI/Tests/Integration/IntegrationTests.swift delete mode 100644 FirebaseVertexAI/Tests/Integration/Resources/placeholder.txt delete mode 100644 scripts/spm_test_schemes/FirebaseVertexAIIntegration.xcscheme diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 9cc70054bbb..ca9c214be93 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -90,41 +90,6 @@ jobs: retry_wait_seconds: 120 command: scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm - spm-integration: - strategy: - matrix: - target: [iOS] - os: [macos-14] - include: - - os: macos-14 - xcode: Xcode_15.2 - runs-on: ${{ matrix.os }} - needs: spm-package-resolved - env: - TEST_RUNNER_VertexAIRunIntegrationTests: 1 - FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 - plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - steps: - - uses: actions/checkout@v4 - - uses: actions/cache/restore@v4 - with: - path: .build - key: ${{needs.spm-package-resolved.outputs.cache_key}} - - name: Install Secret GoogleService-Info.plist - run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/vertexai-integration.plist.gpg \ - FirebaseVertexAI/Tests/Integration/Resources/GoogleService-Info.plist "$plist_secret" - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Initialize xcodebuild - run: scripts/setup_spm_tests.sh - - uses: nick-fields/retry@v3 - with: - timeout_minutes: 120 - max_attempts: 3 - retry_on: error - retry_wait_seconds: 120 - command: scripts/build.sh FirebaseVertexAIIntegration ${{ matrix.target }} spm - testapp-integration: strategy: matrix: diff --git a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift deleted file mode 100644 index b884a41e9a5..00000000000 --- a/FirebaseVertexAI/Tests/Integration/IntegrationTests.swift +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import FirebaseCore -import FirebaseVertexAI -import XCTest - -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -final class IntegrationTests: XCTestCase { - // Set temperature, topP and topK to lowest allowed values to make responses more deterministic. - let generationConfig = GenerationConfig( - temperature: 0.0, - topP: 0.0, - topK: 1, - responseMIMEType: "text/plain" - ) - let systemInstruction = ModelContent( - role: "system", - parts: "You are a friendly and helpful assistant." - ) - let safetySettings = [ - SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .probability), - SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove, method: .severity), - SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockLowAndAbove), - SafetySetting(harmCategory: .dangerousContent, threshold: .blockLowAndAbove), - SafetySetting(harmCategory: .civicIntegrity, threshold: .blockLowAndAbove), - ] - - var vertex: VertexAI! - var model: GenerativeModel! - - override func setUp() async throws { - try XCTSkipIf(ProcessInfo.processInfo.environment["VertexAIRunIntegrationTests"] == nil, """ - Vertex AI integration tests skipped; to enable them, set the VertexAIRunIntegrationTests \ - environment variable in Xcode or CI jobs. - """) - - let plistPath = try XCTUnwrap(Bundle.module.path( - forResource: "GoogleService-Info", - ofType: "plist" - )) - let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath)) - FirebaseApp.configure(options: options) - - vertex = VertexAI.vertexAI() - model = vertex.generativeModel( - modelName: "gemini-1.5-flash", - generationConfig: generationConfig, - safetySettings: safetySettings, - tools: [], - toolConfig: .init(functionCallingConfig: .none()), - systemInstruction: systemInstruction - ) - } - - override func tearDown() async throws { - if let app = FirebaseApp.app() { - await app.delete() - } - } - - // MARK: - Generate Content - - func testGenerateContent() async throws { - let prompt = "Where is Google headquarters located? Answer with the city name only." - - let response = try await model.generateContent(prompt) - - let text = try XCTUnwrap(response.text).trimmingCharacters(in: .whitespacesAndNewlines) - XCTAssertEqual(text, "Mountain View") - } - - // MARK: - Count Tokens - - func testCountTokens_text() async throws { - let prompt = "Why is the sky blue?" - model = vertex.generativeModel( - modelName: "gemini-1.5-pro", - generationConfig: generationConfig, - safetySettings: [ - SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove, method: .severity), - SafetySetting(harmCategory: .hateSpeech, threshold: .blockMediumAndAbove), - SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockOnlyHigh), - SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone), - SafetySetting(harmCategory: .civicIntegrity, threshold: .off, method: .probability), - ], - toolConfig: .init(functionCallingConfig: .auto()), - systemInstruction: systemInstruction - ) - - let response = try await model.countTokens(prompt) - - XCTAssertEqual(response.totalTokens, 14) - XCTAssertEqual(response.totalBillableCharacters, 51) - } - - func testCountTokens_image_inlineData() async throws { - guard let image = UIImage(systemName: "cloud") else { - XCTFail("Image not found.") - return - } - - let response = try await model.countTokens(image) - - XCTAssertEqual(response.totalTokens, 266) - XCTAssertEqual(response.totalBillableCharacters, 35) - } - - func testCountTokens_image_fileData() async throws { - let fileData = FileDataPart( - uri: "gs://ios-opensource-samples.appspot.com/ios/public/blank.jpg", - mimeType: "image/jpeg" - ) - - let response = try await model.countTokens(fileData) - - XCTAssertEqual(response.totalTokens, 266) - XCTAssertEqual(response.totalBillableCharacters, 35) - } - - func testCountTokens_functionCalling() async throws { - let sumDeclaration = FunctionDeclaration( - name: "sum", - description: "Adds two integers.", - parameters: ["x": .integer(), "y": .integer()] - ) - model = vertex.generativeModel( - modelName: "gemini-1.5-flash", - tools: [.functionDeclarations([sumDeclaration])], - toolConfig: .init(functionCallingConfig: .any(allowedFunctionNames: ["sum"])) - ) - let prompt = "What is 10 + 32?" - let sumCall = FunctionCallPart(name: "sum", args: ["x": .number(10), "y": .number(32)]) - let sumResponse = FunctionResponsePart(name: "sum", response: ["result": .number(42)]) - - let response = try await model.countTokens([ - ModelContent(role: "user", parts: prompt), - ModelContent(role: "model", parts: sumCall), - ModelContent(role: "function", parts: sumResponse), - ]) - - XCTAssertEqual(response.totalTokens, 24) - XCTAssertEqual(response.totalBillableCharacters, 71) - } - - func testCountTokens_jsonSchema() async throws { - model = vertex.generativeModel( - modelName: "gemini-1.5-flash", - generationConfig: GenerationConfig( - responseMIMEType: "application/json", - responseSchema: Schema.object(properties: [ - "startDate": .string(format: .custom("date")), - "yearsSince": .integer(format: .custom("int16")), - "hoursSince": .integer(format: .int32), - "minutesSince": .integer(format: .int64), - ]) - ) - ) - let prompt = "It is 2050-01-01, how many years, hours and minutes since 2000-01-01?" - - let response = try await model.countTokens(prompt) - - XCTAssertEqual(response.totalTokens, 34) - XCTAssertEqual(response.totalBillableCharacters, 59) - } -} diff --git a/FirebaseVertexAI/Tests/Integration/Resources/placeholder.txt b/FirebaseVertexAI/Tests/Integration/Resources/placeholder.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/Package.swift b/Package.swift index 55dfbba46e0..cbec9e17fde 100644 --- a/Package.swift +++ b/Package.swift @@ -1319,14 +1319,6 @@ let package = Package( .headerSearchPath("../../../"), ] ), - .testTarget( - name: "FirebaseVertexAIIntegration", - dependencies: ["FirebaseVertexAI"], - path: "FirebaseVertexAI/Tests/Integration", - resources: [ - .process("Resources"), - ] - ), ] + firestoreTargets(), cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 diff --git a/scripts/spm_test_schemes/FirebaseVertexAIIntegration.xcscheme b/scripts/spm_test_schemes/FirebaseVertexAIIntegration.xcscheme deleted file mode 100644 index b2658440824..00000000000 --- a/scripts/spm_test_schemes/FirebaseVertexAIIntegration.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From d9a2a35e6f7fd081bd18f849b431aba9fb62ca0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:29:54 -0700 Subject: [PATCH 244/258] Bump rexml from 3.3.6 to 3.3.9 (#13992) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e8071afb43f..68d2d453cb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,14 +145,12 @@ GEM open4 (1.3.4) public_suffix (4.0.7) rchardet (1.8.0) - rexml (3.3.6) - strscan + rexml (3.3.9) ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.1) From 0ac8bbbe2b8fff7a256241477e8d071c8521d532 Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Tue, 29 Oct 2024 23:44:26 +0100 Subject: [PATCH 245/258] Async Function Calling (#13901) --- FirebaseFunctions/Sources/Functions.swift | 104 +++++++++++++----- FirebaseFunctions/Sources/HTTPSCallable.swift | 12 +- .../Tests/Unit/FunctionsTests.swift | 32 ++++++ 3 files changed, 110 insertions(+), 38 deletions(-) diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index 75091b26815..d4cd4e4f54f 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -385,6 +385,30 @@ enum FunctionsConstants { return URL(string: "https://\(region)-\(projectID).cloudfunctions.net/\(name)") } + @available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *) + func callFunction(at url: URL, + withObject data: Any?, + options: HTTPSCallableOptions?, + timeout: TimeInterval) async throws -> HTTPSCallableResult { + let context = try await contextProvider.context(options: options) + let fetcher = try makeFetcher( + url: url, + data: data, + options: options, + timeout: timeout, + context: context + ) + + do { + let rawData = try await fetcher.beginFetch() + return try callableResultFromResponse(data: rawData, error: nil) + } catch { + // This method always throws when `error` is not `nil`, but ideally, + // it should be refactored so it looks less confusing. + return try callableResultFromResponse(data: nil, error: error) + } + } + func callFunction(at url: URL, withObject data: Any?, options: HTTPSCallableOptions?, @@ -413,17 +437,15 @@ enum FunctionsConstants { timeout: TimeInterval, context: FunctionsContext, completion: @escaping ((Result) -> Void)) { - let request = URLRequest(url: url, - cachePolicy: .useProtocolCachePolicy, - timeoutInterval: timeout) - let fetcher = fetcherService.fetcher(with: request) - + let fetcher: GTMSessionFetcher do { - let data = data ?? NSNull() - let encoded = try serializer.encode(data) - let body = ["data": encoded] - let payload = try JSONSerialization.data(withJSONObject: body) - fetcher.bodyData = payload + fetcher = try makeFetcher( + url: url, + data: data, + options: options, + timeout: timeout, + context: context + ) } catch { DispatchQueue.main.async { completion(.failure(error)) @@ -431,6 +453,38 @@ enum FunctionsConstants { return } + fetcher.beginFetch { [self] data, error in + let result: Result + do { + result = try .success(callableResultFromResponse(data: data, error: error)) + } catch { + result = .failure(error) + } + + DispatchQueue.main.async { + completion(result) + } + } + } + + private func makeFetcher(url: URL, + data: Any?, + options: HTTPSCallableOptions?, + timeout: TimeInterval, + context: FunctionsContext) throws -> GTMSessionFetcher { + let request = URLRequest( + url: url, + cachePolicy: .useProtocolCachePolicy, + timeoutInterval: timeout + ) + let fetcher = fetcherService.fetcher(with: request) + + let data = data ?? NSNull() + let encoded = try serializer.encode(data) + let body = ["data": encoded] + let payload = try JSONSerialization.data(withJSONObject: body) + fetcher.bodyData = payload + // Set the headers. fetcher.setRequestValue("application/json", forHTTPHeaderField: "Content-Type") if let authToken = context.authToken { @@ -462,33 +516,27 @@ enum FunctionsConstants { fetcher.allowedInsecureSchemes = ["http"] } - fetcher.beginFetch { [self] data, error in - let result: Result - do { - let data = try responseData(data: data, error: error) - let json = try responseDataJSON(from: data) - // TODO: Refactor `decode(_:)` so it either returns a non-optional object or throws - let payload = try serializer.decode(json) - // TODO: Remove `as Any` once `decode(_:)` is refactored - result = .success(HTTPSCallableResult(data: payload as Any)) - } catch { - result = .failure(error) - } + return fetcher + } - DispatchQueue.main.async { - completion(result) - } - } + private func callableResultFromResponse(data: Data?, + error: (any Error)?) throws -> HTTPSCallableResult { + let processedData = try processedResponseData(from: data, error: error) + let json = try responseDataJSON(from: processedData) + // TODO: Refactor `decode(_:)` so it either returns a non-optional object or throws + let payload = try serializer.decode(json) + // TODO: Remove `as Any` once `decode(_:)` is refactored + return HTTPSCallableResult(data: payload as Any) } - private func responseData(data: Data?, error: (any Error)?) throws -> Data { + private func processedResponseData(from data: Data?, error: (any Error)?) throws -> Data { // Case 1: `error` is not `nil` -> always throws if let error = error as NSError? { let localError: (any Error)? if error.domain == kGTMSessionFetcherStatusDomain { localError = FunctionsError( httpStatusCode: error.code, - body: data, + body: data ?? error.userInfo["data"] as? Data, serializer: serializer ) } else if error.domain == NSURLErrorDomain, error.code == NSURLErrorTimedOut { diff --git a/FirebaseFunctions/Sources/HTTPSCallable.swift b/FirebaseFunctions/Sources/HTTPSCallable.swift index 4a196134e3e..2c772bc8c78 100644 --- a/FirebaseFunctions/Sources/HTTPSCallable.swift +++ b/FirebaseFunctions/Sources/HTTPSCallable.swift @@ -130,15 +130,7 @@ open class HTTPSCallable: NSObject { /// - Returns: The result of the call. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func call(_ data: Any? = nil) async throws -> HTTPSCallableResult { - return try await withCheckedThrowingContinuation { continuation in - // TODO(bonus): Use task to handle and cancellation. - self.call(data) { callableResult, error in - if let callableResult { - continuation.resume(returning: callableResult) - } else { - continuation.resume(throwing: error!) - } - } - } + try await functions + .callFunction(at: url, withObject: data, options: options, timeout: timeoutInterval) } } diff --git a/FirebaseFunctions/Tests/Unit/FunctionsTests.swift b/FirebaseFunctions/Tests/Unit/FunctionsTests.swift index 89cf70fa6a0..42e684cdf1a 100644 --- a/FirebaseFunctions/Tests/Unit/FunctionsTests.swift +++ b/FirebaseFunctions/Tests/Unit/FunctionsTests.swift @@ -293,6 +293,38 @@ class FunctionsTests: XCTestCase { waitForExpectations(timeout: 1.5) } + func testAsyncCallFunctionWhenAppCheckIsNotInstalled() async { + let networkError = NSError( + domain: "testCallFunctionWhenAppCheckIsInstalled", + code: -1, + userInfo: nil + ) + + let httpRequestExpectation = expectation(description: "HTTPRequestExpectation") + fetcherService.testBlock = { fetcherToTest, testResponse in + let appCheckTokenHeader = fetcherToTest.request? + .value(forHTTPHeaderField: "X-Firebase-AppCheck") + XCTAssertNil(appCheckTokenHeader) + testResponse(nil, nil, networkError) + httpRequestExpectation.fulfill() + } + + do { + _ = try await functionsCustomDomain? + .callFunction( + at: URL(string: "https://example.com/fake_func")!, + withObject: nil, + options: nil, + timeout: 10 + ) + XCTFail("Expected an error") + } catch { + XCTAssertEqual(error as NSError, networkError) + } + + await fulfillment(of: [httpRequestExpectation], timeout: 1.5) + } + func testCallFunctionWhenAppCheckIsNotInstalled() { let networkError = NSError( domain: "testCallFunctionWhenAppCheckIsInstalled", From 00674c85bd73f88d9e10e10fb541a29a5ffa7cf4 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:46:52 -0400 Subject: [PATCH 246/258] [Infra] Only compile core extension headers once (#13993) --- FirebaseCore/Extension/FIRAppInternal.h | 5 +++++ FirebaseCore/Extension/FIRComponent.h | 5 +++++ FirebaseCore/Extension/FIRComponentContainer.h | 6 ++++++ FirebaseCore/Extension/FIRComponentType.h | 5 +++++ FirebaseCore/Extension/FIRHeartbeatLogger.h | 5 +++++ FirebaseCore/Extension/FIRLibrary.h | 5 +++++ FirebaseCore/Extension/FIRLogger.h | 5 +++++ FirebaseCore/Extension/FirebaseCoreInternal.h | 5 +++++ 8 files changed, 41 insertions(+) diff --git a/FirebaseCore/Extension/FIRAppInternal.h b/FirebaseCore/Extension/FIRAppInternal.h index a5669608449..96d42ef48be 100644 --- a/FirebaseCore/Extension/FIRAppInternal.h +++ b/FirebaseCore/Extension/FIRAppInternal.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef FIREBASECORE_FIRAPPINTERNAL_H +#define FIREBASECORE_FIRAPPINTERNAL_H + #import @class FIRComponentContainer; @@ -175,3 +178,5 @@ extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey; @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRAPPINTERNAL_H diff --git a/FirebaseCore/Extension/FIRComponent.h b/FirebaseCore/Extension/FIRComponent.h index c58a8517d9b..98c7a89cadb 100644 --- a/FirebaseCore/Extension/FIRComponent.h +++ b/FirebaseCore/Extension/FIRComponent.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef FIREBASECORE_FIRCOMPONENT_H +#define FIREBASECORE_FIRCOMPONENT_H + #import @class FIRApp; @@ -82,3 +85,5 @@ NS_SWIFT_NAME(init(_:instantiationTiming:creationBlock:)); @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRCOMPONENT_H diff --git a/FirebaseCore/Extension/FIRComponentContainer.h b/FirebaseCore/Extension/FIRComponentContainer.h index 6ec61470aa3..68640877b16 100644 --- a/FirebaseCore/Extension/FIRComponentContainer.h +++ b/FirebaseCore/Extension/FIRComponentContainer.h @@ -13,6 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#ifndef FIREBASECORE_FIRCOMPONENTCONTAINER_H +#define FIREBASECORE_FIRCOMPONENTCONTAINER_H + #import NS_ASSUME_NONNULL_BEGIN @@ -43,3 +47,5 @@ NS_SWIFT_NAME(FirebaseComponentContainer) @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRCOMPONENTCONTAINER_H diff --git a/FirebaseCore/Extension/FIRComponentType.h b/FirebaseCore/Extension/FIRComponentType.h index c69085d1983..92a5aab6bf6 100644 --- a/FirebaseCore/Extension/FIRComponentType.h +++ b/FirebaseCore/Extension/FIRComponentType.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef FIREBASECORE_FIRCOMPONENTTYPE_H +#define FIREBASECORE_FIRCOMPONENTTYPE_H + #import @class FIRComponentContainer; @@ -33,3 +36,5 @@ NS_SWIFT_NAME(ComponentType) @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRCOMPONENTTYPE_H diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 962974e1bca..807aea22062 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef FIREBASECORE_FIRHEARTBEATLOGGER_H +#define FIREBASECORE_FIRHEARTBEATLOGGER_H + #import NS_ASSUME_NONNULL_BEGIN @@ -102,3 +105,5 @@ NSString *_Nullable FIRHeaderValueFromHeartbeatsPayload(FIRHeartbeatsPayload *he @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRHEARTBEATLOGGER_H diff --git a/FirebaseCore/Extension/FIRLibrary.h b/FirebaseCore/Extension/FIRLibrary.h index 17664ac619f..fe256ad824f 100644 --- a/FirebaseCore/Extension/FIRLibrary.h +++ b/FirebaseCore/Extension/FIRLibrary.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef FIREBASECORE_FIRLIBRARY_H +#define FIREBASECORE_FIRLIBRARY_H + #ifndef FIRLibrary_h #define FIRLibrary_h @@ -37,3 +40,5 @@ NS_SWIFT_NAME(Library) NS_ASSUME_NONNULL_END #endif /* FIRLibrary_h */ + +#endif // FIREBASECORE_FIRLIBRARY_H diff --git a/FirebaseCore/Extension/FIRLogger.h b/FirebaseCore/Extension/FIRLogger.h index 629b5c4bbef..8117189ef04 100644 --- a/FirebaseCore/Extension/FIRLogger.h +++ b/FirebaseCore/Extension/FIRLogger.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef FIREBASECORE_FIRLOGGER_H +#define FIREBASECORE_FIRLOGGER_H + #import typedef NS_ENUM(NSInteger, FIRLoggerLevel); @@ -191,3 +194,5 @@ NS_SWIFT_NAME(FirebaseLogger) @end NS_ASSUME_NONNULL_END + +#endif // FIREBASECORE_FIRLOGGER_H diff --git a/FirebaseCore/Extension/FirebaseCoreInternal.h b/FirebaseCore/Extension/FirebaseCoreInternal.h index 89a20493e08..25610089c08 100644 --- a/FirebaseCore/Extension/FirebaseCoreInternal.h +++ b/FirebaseCore/Extension/FirebaseCoreInternal.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef FIREBASECORE_FIREBASECOREINTERNAL_H +#define FIREBASECORE_FIREBASECOREINTERNAL_H + @import FirebaseCore; #import "FIRAppInternal.h" @@ -21,3 +24,5 @@ #import "FIRHeartbeatLogger.h" #import "FIRLibrary.h" #import "FIRLogger.h" + +#endif // FIREBASECORE_FIREBASECOREINTERNAL_H From 45a5a4da09cee94e4a1c3d5a0da2344ba50cbad9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:55:10 -0400 Subject: [PATCH 247/258] [Core] Make storage class conform to Sendable and subsequent changes (#13939) --- .../HeartbeatController.swift | 24 +++---- .../HeartbeatLogging/HeartbeatStorage.swift | 20 +++--- .../Sources/HeartbeatLogging/Storage.swift | 29 +++++---- .../HeartbeatLogging/StorageFactory.swift | 6 +- .../_ObjC_HeartbeatController.swift | 2 +- .../Tests/Common/AdjustableDate.swift | 23 +++++++ .../HeartbeatLoggingIntegrationTests.swift | 6 +- .../Tests/Unit/HeartbeatControllerTests.swift | 37 ++++++----- .../Tests/Unit/HeartbeatStorageTests.swift | 63 +++++++++++-------- .../Internal/Tests/Unit/StorageTests.swift | 31 +++------ 10 files changed, 135 insertions(+), 106 deletions(-) create mode 100644 FirebaseCore/Internal/Tests/Common/AdjustableDate.swift diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift index e7f4ece2781..cbc01844834 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift @@ -15,7 +15,7 @@ import Foundation /// An object that provides API to log and flush heartbeats from a synchronized storage container. -public final class HeartbeatController { +public final class HeartbeatController: Sendable { /// Used for standardizing dates for calendar-day comparison. private enum DateStandardizer { private static let calendar: Calendar = { @@ -31,18 +31,18 @@ public final class HeartbeatController { } /// The thread-safe storage object to log and flush heartbeats from. - private let storage: HeartbeatStorageProtocol + private let storage: any HeartbeatStorageProtocol /// The max capacity of heartbeats to store in storage. - private let heartbeatsStorageCapacity: Int = 30 + private static let heartbeatsStorageCapacity: Int = 30 /// Current date provider. It is used for testability. - private let dateProvider: () -> Date + private let dateProvider: @Sendable () -> Date /// Used for standardizing dates for calendar-day comparison. private static let dateStandardizer = DateStandardizer.self /// Public initializer. /// - Parameter id: The `id` to associate this controller's heartbeat storage with. public convenience init(id: String) { - self.init(id: id, dateProvider: Date.init) + self.init(id: id, dateProvider: { Date() }) } /// Convenience initializer. Mirrors the semantics of the public initializer with the added @@ -51,7 +51,7 @@ public final class HeartbeatController { /// - Parameters: /// - id: The id to associate this controller's heartbeat storage with. /// - dateProvider: A date provider. - convenience init(id: String, dateProvider: @escaping () -> Date) { + convenience init(id: String, dateProvider: @escaping @Sendable () -> Date) { let storage = HeartbeatStorage.getInstance(id: id) self.init(storage: storage, dateProvider: dateProvider) } @@ -61,7 +61,7 @@ public final class HeartbeatController { /// - storage: A heartbeat storage container. /// - dateProvider: A date provider. Defaults to providing the current date. init(storage: HeartbeatStorageProtocol, - dateProvider: @escaping () -> Date = Date.init) { + dateProvider: @escaping @Sendable () -> Date = { Date() }) { self.storage = storage self.dateProvider = { Self.dateStandardizer.standardize(dateProvider()) } } @@ -76,7 +76,7 @@ public final class HeartbeatController { storage.readAndWriteAsync { heartbeatsBundle in var heartbeatsBundle = heartbeatsBundle ?? - HeartbeatsBundle(capacity: self.heartbeatsStorageCapacity) + HeartbeatsBundle(capacity: Self.heartbeatsStorageCapacity) // Filter for the time periods where the last heartbeat to be logged for // that time period was logged more than one time period (i.e. day) ago. @@ -109,7 +109,7 @@ public final class HeartbeatController { // The new value that's stored will use the old's cache to prevent the // logging of duplicates after flushing. return HeartbeatsBundle( - capacity: self.heartbeatsStorageCapacity, + capacity: Self.heartbeatsStorageCapacity, cache: oldHeartbeatsBundle.lastAddedHeartbeatDates ) } @@ -126,15 +126,15 @@ public final class HeartbeatController { } } - public func flushAsync(completionHandler: @escaping (HeartbeatsPayload) -> Void) { - let resetTransform = { (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in + public func flushAsync(completionHandler: @escaping @Sendable (HeartbeatsPayload) -> Void) { + let resetTransform = { @Sendable (heartbeatsBundle: HeartbeatsBundle?) -> HeartbeatsBundle? in guard let oldHeartbeatsBundle = heartbeatsBundle else { return nil // Storage was empty. } // The new value that's stored will use the old's cache to prevent the // logging of duplicates after flushing. return HeartbeatsBundle( - capacity: self.heartbeatsStorageCapacity, + capacity: Self.heartbeatsStorageCapacity, cache: oldHeartbeatsBundle.lastAddedHeartbeatDates ) } diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift index 7fd808b4457..07088a5cf68 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift @@ -15,21 +15,22 @@ import Foundation /// A type that can perform atomic operations using block-based transformations. -protocol HeartbeatStorageProtocol { +protocol HeartbeatStorageProtocol: Sendable { func readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) - func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?) + func readAndWriteAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) + -> HeartbeatsBundle?) func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws -> HeartbeatsBundle? - func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?, - completion: @escaping (Result) -> Void) + func getAndSetAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) -> HeartbeatsBundle?, + completion: @escaping @Sendable (Result) -> Void) } /// Thread-safe storage object designed for transforming heartbeat data that is persisted to disk. -final class HeartbeatStorage: HeartbeatStorageProtocol { +final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol { /// The identifier used to differentiate instances. private let id: String /// The underlying storage container to read from and write to. - private let storage: Storage + private let storage: any Storage /// The encoder used for encoding heartbeat data. private let encoder: JSONEncoder = .init() /// The decoder used for decoding heartbeat data. @@ -130,7 +131,8 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { /// Asynchronously reads from and writes to storage using the given transform block. /// - Parameter transform: A block to transform the currently stored heartbeats bundle to a new /// heartbeats bundle value. - func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?) { + func readAndWriteAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) + -> HeartbeatsBundle?) { queue.async { [self] in let oldHeartbeatsBundle = try? load(from: storage) let newHeartbeatsBundle = transform(oldHeartbeatsBundle) @@ -166,8 +168,8 @@ final class HeartbeatStorage: HeartbeatStorageProtocol { /// - completion: An escaping block used to process the heartbeat data that /// was stored (before the `transform` was applied); otherwise, the error /// that occurred. - func getAndSetAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?, - completion: @escaping (Result) -> Void) { + func getAndSetAsync(using transform: @escaping @Sendable (HeartbeatsBundle?) -> HeartbeatsBundle?, + completion: @escaping @Sendable (Result) -> Void) { queue.async { do { let oldHeartbeatsBundle = try? self.load(from: self.storage) diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift index a4cac33e27a..69bf613b690 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/Storage.swift @@ -15,7 +15,7 @@ import Foundation /// A type that reads from and writes to an underlying storage container. -protocol Storage { +protocol Storage: Sendable { /// Reads and returns the data stored by this storage type. /// - Returns: The data read from storage. /// - Throws: An error if the read failed. @@ -38,16 +38,12 @@ enum StorageError: Error { final class FileStorage: Storage { /// A file system URL to the underlying file resource. private let url: URL - /// The file manager used to perform file system operations. - private let fileManager: FileManager /// Designated initializer. /// - Parameters: /// - url: A file system URL for the underlying file resource. - /// - fileManager: A file manager. Defaults to `default` manager. - init(url: URL, fileManager: FileManager = .default) { + init(url: URL) { self.url = url - self.fileManager = fileManager } /// Reads and returns the data from this object's associated file resource. @@ -90,7 +86,7 @@ final class FileStorage: Storage { /// - Parameter url: The URL to create directories in. private func createDirectories(in url: URL) throws { do { - try fileManager.createDirectory( + try FileManager.default.createDirectory( at: url, withIntermediateDirectories: true ) @@ -104,17 +100,26 @@ final class FileStorage: Storage { /// A object that provides API for reading and writing to a user defaults resource. final class UserDefaultsStorage: Storage { - /// The underlying defaults container. - private let defaults: UserDefaults + /// The suite name for the underlying defaults container. + private let suiteName: String + /// The key mapping to the object's associated resource in `defaults`. private let key: String + /// The underlying defaults container. + private var defaults: UserDefaults { + // It's safe to force unwrap the below defaults instance because the + // initializer only returns `nil` when the bundle id or `globalDomain` + // is passed in as the `suiteName`. + UserDefaults(suiteName: suiteName)! + } + /// Designated initializer. /// - Parameters: - /// - defaults: The defaults container. + /// - suiteName: The suite name for the defaults container. /// - key: The key mapping to the value stored in the defaults container. - init(defaults: UserDefaults, key: String) { - self.defaults = defaults + init(suiteName: String, key: String) { + self.suiteName = suiteName self.key = key } diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift index 6552a318158..d6d97cf78ba 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/StorageFactory.swift @@ -56,11 +56,7 @@ extension FileManager { extension UserDefaultsStorage: StorageFactory { static func makeStorage(id: String) -> Storage { let suiteName = Constants.heartbeatUserDefaultsSuiteName - // It's safe to force unwrap the below defaults instance because the - // initializer only returns `nil` when the bundle id or `globalDomain` - // is passed in as the `suiteName`. - let defaults = UserDefaults(suiteName: suiteName)! let key = "heartbeats-\(id)" - return UserDefaultsStorage(defaults: defaults, key: key) + return UserDefaultsStorage(suiteName: suiteName, key: key) } } diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift index c60a1e11cc5..520e4f96085 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/_ObjC_HeartbeatController.swift @@ -49,7 +49,7 @@ public class _ObjC_HeartbeatController: NSObject { /// /// - Note: This API is thread-safe. /// - Returns: A heartbeats payload for the flushed heartbeat(s). - public func flushAsync(completionHandler: @escaping (_ObjC_HeartbeatsPayload) -> Void) { + public func flushAsync(completionHandler: @escaping @Sendable (_ObjC_HeartbeatsPayload) -> Void) { // TODO: When minimum version moves to iOS 13.0, restore the async version // removed in #13952. heartbeatController.flushAsync { heartbeatsPayload in diff --git a/FirebaseCore/Internal/Tests/Common/AdjustableDate.swift b/FirebaseCore/Internal/Tests/Common/AdjustableDate.swift new file mode 100644 index 00000000000..e852d2f63f2 --- /dev/null +++ b/FirebaseCore/Internal/Tests/Common/AdjustableDate.swift @@ -0,0 +1,23 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// Used to manipulate a date across multiple concurrent contexts for simulation purposes. +final class AdjustableDate: @unchecked Sendable { + var date: Date + init(date: Date) { + self.date = date + } +} diff --git a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift index 3a9823aa94a..3c5cdba3950 100644 --- a/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift +++ b/FirebaseCore/Internal/Tests/Integration/HeartbeatLoggingIntegrationTests.swift @@ -232,8 +232,8 @@ class HeartbeatLoggingIntegrationTests: XCTestCase { func testLogRepeatedly_WithoutFlushing_LimitsOnWrite() throws { // Given - var testdate = date - let heartbeatController = HeartbeatController(id: #function, dateProvider: { testdate }) + let testDate = AdjustableDate(date: date) + let heartbeatController = HeartbeatController(id: #function, dateProvider: { testDate.date }) // When // Iterate over 35 days and log a heartbeat each day. @@ -252,7 +252,7 @@ class HeartbeatLoggingIntegrationTests: XCTestCase { heartbeatController.log("dummy_agent_3") } - testdate.addTimeInterval(60 * 60 * 24) // Advance the test date by 1 day. + testDate.date.addTimeInterval(60 * 60 * 24) // Advance the test date by 1 day. } // Then diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift index 82691ed3f24..bac98cf4647 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatControllerTests.swift @@ -99,11 +99,11 @@ class HeartbeatControllerTests: XCTestCase { func testLogAtEndOfTimePeriodAndAcceptAtStartOfNextOne() throws { // Given - var testDate = date + let testDate = AdjustableDate(date: date) let controller = HeartbeatController( storage: HeartbeatStorageFake(), - dateProvider: { testDate } + dateProvider: { testDate.date } ) assertHeartbeatControllerFlushesEmptyPayload(controller) @@ -113,12 +113,12 @@ class HeartbeatControllerTests: XCTestCase { controller.log("dummy_agent") // - Advance to 2021-11-01 @ 23:59:59 (EST) - testDate.addTimeInterval(60 * 60 * 24 - 1) + testDate.date.addTimeInterval(60 * 60 * 24 - 1) controller.log("dummy_agent") // - Advance to 2021-11-02 @ 00:00:00 (EST) - testDate.addTimeInterval(1) + testDate.date.addTimeInterval(1) controller.log("dummy_agent") @@ -271,18 +271,18 @@ class HeartbeatControllerTests: XCTestCase { ).date // 2021-11-02 @ 11 PM (Tokyo time zone) ) - var testDate = newYorkDate + let testDate = AdjustableDate(date: newYorkDate) let heartbeatController = HeartbeatController( storage: HeartbeatStorageFake(), - dateProvider: { testDate } + dateProvider: { testDate.date } ) // When heartbeatController.log("dummy_agent") // Device travels from NYC to Tokyo. - testDate = tokyoDate + testDate.date = tokyoDate heartbeatController.log("dummy_agent") @@ -308,10 +308,10 @@ class HeartbeatControllerTests: XCTestCase { func testLoggingDependsOnDateNotUserAgent() throws { // Given - var testDate = date + let testDate = AdjustableDate(date: date) let heartbeatController = HeartbeatController( storage: HeartbeatStorageFake(), - dateProvider: { testDate } + dateProvider: { testDate.date } ) // When @@ -319,11 +319,11 @@ class HeartbeatControllerTests: XCTestCase { heartbeatController.log("dummy_agent") // - Day 2 - testDate.addTimeInterval(60 * 60 * 24) + testDate.date.addTimeInterval(60 * 60 * 24) heartbeatController.log("some_other_agent") // - Day 3 - testDate.addTimeInterval(60 * 60 * 24) + testDate.date.addTimeInterval(60 * 60 * 24) heartbeatController.log("dummy_agent") // Then @@ -359,20 +359,20 @@ class HeartbeatControllerTests: XCTestCase { let todaysDate = date let tomorrowsDate = date.addingTimeInterval(60 * 60 * 24) - var testDate = yesterdaysDate + let testDate = AdjustableDate(date: yesterdaysDate) let heartbeatController = HeartbeatController( storage: HeartbeatStorageFake(), - dateProvider: { testDate } + dateProvider: { testDate.date } ) // When heartbeatController.log("yesterdays_dummy_agent") - testDate = todaysDate + testDate.date = todaysDate heartbeatController.log("todays_dummy_agent") - testDate = tomorrowsDate + testDate.date = tomorrowsDate heartbeatController.log("tomorrows_dummy_agent") - testDate = todaysDate + testDate.date = todaysDate // Then let payload = heartbeatController.flushHeartbeatFromToday() @@ -426,7 +426,10 @@ class HeartbeatControllerTests: XCTestCase { // MARK: - Fakes -private class HeartbeatStorageFake: HeartbeatStorageProtocol { +private final class HeartbeatStorageFake: HeartbeatStorageProtocol, @unchecked Sendable { + // The unchecked Sendable conformance is used to prevent warnings for the below var, which + // violates the class's Sendable conformance. Ignoring this violation should be okay for + // testing purposes. private var heartbeatsBundle: HeartbeatsBundle? func readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) { diff --git a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift index 79f4cc5abf6..5e704a7fb89 100644 --- a/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift @@ -15,6 +15,14 @@ @testable import FirebaseCoreInternal import XCTest +extension HeartbeatsBundle { + static let testHeartbeatBundle: Self = { + var heartbeatBundle = HeartbeatsBundle(capacity: 1) + heartbeatBundle.append(Heartbeat(agent: "dummy_agent", date: Date())) + return heartbeatBundle + }() +} + class HeartbeatStorageTests: XCTestCase { // MARK: - Instance Management @@ -95,15 +103,12 @@ class HeartbeatStorageTests: XCTestCase { let expectation = expectation(description: #function) let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake()) - var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1) - dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date())) - // When heartbeatStorage.readAndWriteAsync { heartbeatsBundle in // Assert that heartbeat storage is empty. XCTAssertNil(heartbeatsBundle) // Write new value. - return dummyHeartbeatsBundle + return HeartbeatsBundle.testHeartbeatBundle } heartbeatStorage.readAndWriteAsync { heartbeatsBundle in @@ -111,7 +116,7 @@ class HeartbeatStorageTests: XCTestCase { // Assert old value is read. XCTAssertEqual( heartbeatsBundle?.makeHeartbeatsPayload(), - dummyHeartbeatsBundle.makeHeartbeatsPayload() + HeartbeatsBundle.testHeartbeatBundle.makeHeartbeatsPayload() ) // Write some new value. return heartbeatsBundle @@ -145,9 +150,6 @@ class HeartbeatStorageTests: XCTestCase { let storageFake = StorageFake() let heartbeatStorage = HeartbeatStorage(id: #function, storage: storageFake) - var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1) - dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date())) - // When storageFake.onWrite = { _ in expectation.fulfill() // Fulfilled 2 times. @@ -156,7 +158,7 @@ class HeartbeatStorageTests: XCTestCase { heartbeatStorage.readAndWriteAsync { heartbeatsBundle in expectation.fulfill() - return dummyHeartbeatsBundle + return HeartbeatsBundle.testHeartbeatBundle } // Then @@ -164,10 +166,10 @@ class HeartbeatStorageTests: XCTestCase { expectation.fulfill() XCTAssertNotEqual( heartbeatsBundle?.makeHeartbeatsPayload(), - dummyHeartbeatsBundle.makeHeartbeatsPayload(), + HeartbeatsBundle.testHeartbeatBundle.makeHeartbeatsPayload(), "They should not be equal because the previous save failed." ) - return dummyHeartbeatsBundle + return HeartbeatsBundle.testHeartbeatBundle } wait(for: [expectation], timeout: 0.5) @@ -212,16 +214,13 @@ class HeartbeatStorageTests: XCTestCase { // Given let heartbeatStorage = HeartbeatStorage(id: #function, storage: StorageFake()) - var dummyHeartbeatsBundle = HeartbeatsBundle(capacity: 1) - dummyHeartbeatsBundle.append(Heartbeat(agent: "dummy_agent", date: Date())) - // When let expectation1 = expectation(description: #function + "_1") heartbeatStorage.getAndSetAsync { heartbeatsBundle in // Assert that heartbeat storage is empty. XCTAssertNil(heartbeatsBundle) // Write new value. - return dummyHeartbeatsBundle + return HeartbeatsBundle.testHeartbeatBundle } completion: { result in switch result { case .success: break @@ -237,7 +236,7 @@ class HeartbeatStorageTests: XCTestCase { // Assert old value is read. XCTAssertEqual( heartbeatsBundle?.makeHeartbeatsPayload(), - dummyHeartbeatsBundle.makeHeartbeatsPayload() + HeartbeatsBundle.testHeartbeatBundle.makeHeartbeatsPayload() ) // Write some new value. expectation2.fulfill() @@ -368,7 +367,7 @@ class HeartbeatStorageTests: XCTestCase { let expectations: [XCTestExpectation] = try (0 ... 1000).map { i in let expectation = expectation(description: "\(#function)_\(i)") - let transform: (HeartbeatsBundle?) -> HeartbeatsBundle? = { heartbeatsBundle in + let transform: @Sendable (HeartbeatsBundle?) -> HeartbeatsBundle? = { heartbeatsBundle in expectation.fulfill() return heartbeatsBundle } @@ -401,11 +400,24 @@ class HeartbeatStorageTests: XCTestCase { } func testForMemoryLeakInInstanceManager() { + // This unchecked Sendable class is used to avoid passing a non-sendable + // type '[WeakContainer]' to a `@Sendable` closure + // (`DispatchQueue.global().async { ... }`). + final class WeakRefs: @unchecked Sendable { + private(set) var weakRefs: [WeakContainer] = [] + // Lock is used to synchronize `weakRefs` during concurrent access. + private let weakRefsLock = NSLock() + + func append(_ weakRef: WeakContainer) { + weakRefsLock.withLock { + weakRefs.append(weakRef) + } + } + } + // Given let id = "testID" - var weakRefs: [WeakContainer] = [] - // Lock is used to synchronize `weakRefs` during concurrent access. - let weakRefsLock = NSLock() + let weakRefs = WeakRefs() // When // Simulate concurrent access. This will help expose race conditions that could cause a crash. @@ -414,9 +426,7 @@ class HeartbeatStorageTests: XCTestCase { group.enter() DispatchQueue.global().async { let instance = HeartbeatStorage.getInstance(id: id) - weakRefsLock.withLock { - weakRefs.append(WeakContainer(object: instance)) - } + weakRefs.append(WeakContainer(object: instance)) group.leave() } } @@ -425,13 +435,16 @@ class HeartbeatStorageTests: XCTestCase { // Then // The `weakRefs` array's references should all be nil; otherwise, something is being // unexpectedly strongly retained. - for weakRef in weakRefs { + for weakRef in weakRefs.weakRefs { XCTAssertNil(weakRef.object, "Potential memory leak detected.") } } } -private class StorageFake: Storage { +private final class StorageFake: Storage, @unchecked Sendable { + // The unchecked Sendable conformance is used to prevent warnings for the below var, which + // violates the class's Sendable conformance. Ignoring this violation should be okay for + // testing purposes. var fakeFile: Data? var onRead: (() throws -> Data)? var onWrite: ((Data?) throws -> Void)? diff --git a/FirebaseCore/Internal/Tests/Unit/StorageTests.swift b/FirebaseCore/Internal/Tests/Unit/StorageTests.swift index 21b4fded8c1..adc47ba81d6 100644 --- a/FirebaseCore/Internal/Tests/Unit/StorageTests.swift +++ b/FirebaseCore/Internal/Tests/Unit/StorageTests.swift @@ -97,22 +97,23 @@ class FileStorageTests: XCTestCase { class UserDefaultsStorageTests: XCTestCase { var defaults: UserDefaults! - let suiteName = #file + let suiteName = "com.firebase.userdefaults.storageTests" override func setUpWithError() throws { - defaults = try XCTUnwrap(UserDefaultsFake(suiteName: suiteName)) + // Clear the user default suite before testing. + UserDefaults(suiteName: suiteName)?.removePersistentDomain(forName: suiteName) } func testRead_WhenDefaultDoesNotExist_ThrowsError() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) // Then XCTAssertThrowsError(try defaultsStorage.read()) } func testRead_WhenDefaultExists_ReturnsDefault() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) XCTAssertNoThrow(try defaultsStorage.write(Constants.testData)) // When let storedData = try defaultsStorage.read() @@ -122,7 +123,7 @@ class UserDefaultsStorageTests: XCTestCase { func testWriteData_WhenDefaultDoesNotExist_CreatesDefault() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) XCTAssertThrowsError(try defaultsStorage.read()) // When XCTAssertNoThrow(try defaultsStorage.write(Constants.testData)) @@ -133,7 +134,7 @@ class UserDefaultsStorageTests: XCTestCase { func testWriteData_WhenDefaultExists_ModifiesDefault() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) XCTAssertNoThrow(try defaultsStorage.write(Constants.testData)) // When let modifiedData = #function.data(using: .utf8) @@ -146,7 +147,7 @@ class UserDefaultsStorageTests: XCTestCase { func testWriteNil_WhenDefaultDoesNotExist_RemovesDefault() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) XCTAssertThrowsError(try defaultsStorage.read()) // When XCTAssertNoThrow(try defaultsStorage.write(nil)) @@ -156,7 +157,7 @@ class UserDefaultsStorageTests: XCTestCase { func testWriteNil_WhenDefaultExists_RemovesDefault() throws { // Given - let defaultsStorage = UserDefaultsStorage(defaults: defaults, key: #function) + let defaultsStorage = UserDefaultsStorage(suiteName: suiteName, key: #function) XCTAssertNoThrow(try defaultsStorage.write(Constants.testData)) // When XCTAssertNoThrow(try defaultsStorage.write(nil)) @@ -164,17 +165,3 @@ class UserDefaultsStorageTests: XCTestCase { XCTAssertThrowsError(try defaultsStorage.read()) } } - -// MARK: - Fakes - -private class UserDefaultsFake: UserDefaults { - private var defaults = [String: Any]() - - override func object(forKey defaultName: String) -> Any? { - defaults[defaultName] - } - - override func set(_ value: Any?, forKey defaultName: String) { - defaults[defaultName] = value - } -} From 283a141812ee7c7505ef2f5cbd05c2920ead4b91 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 30 Oct 2024 10:06:15 -0400 Subject: [PATCH 248/258] [Infra] Replace `xcpretty` with `xcbeautify` in build.sh (#13983) --- scripts/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 830e4ace0d1..67e576ac990 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -105,15 +105,15 @@ esac # Source function to check if CI secrets are available. source scripts/check_secrets.sh -# Runs xcodebuild with the given flags, piping output to xcpretty +# Runs xcodebuild with the given flags, piping output to xcbeautify # If xcodebuild fails with known error codes, retries once. function RunXcodebuild() { echo xcodebuild "$@" - xcpretty_cmd=(xcpretty) + xcbeautify_cmd=(xcbeautify --renderer github-actions) result=0 - xcodebuild "$@" | tee xcodebuild.log | "${xcpretty_cmd[@]}" || result=$? + xcodebuild "$@" | tee xcodebuild.log | "${xcbeautify_cmd[@]}" || result=$? if [[ $result == 65 ]]; then ExportLogs "$@" @@ -122,7 +122,7 @@ function RunXcodebuild() { sleep 5 result=0 - xcodebuild "$@" | tee xcodebuild.log | "${xcpretty_cmd[@]}" || result=$? + xcodebuild "$@" | tee xcodebuild.log | "${xcbeautify_cmd[@]}" || result=$? fi if [[ $result != 0 ]]; then From 56b0475b427c8b50372acdad70f9e5375978a2db Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:33:40 -0400 Subject: [PATCH 249/258] [Auth] Prefer immutable properties and private access level in `*Request` types (#14001) --- .../Backend/RPC/CreateAuthURIRequest.swift | 12 ++-- .../RPC/GetOOBConfirmationCodeRequest.swift | 22 +++---- .../Enroll/FinalizeMFAEnrollmentRequest.swift | 46 +++++++++----- .../Enroll/StartMFAEnrollmentRequest.swift | 43 ++++++++----- .../SignIn/FinalizeMFASignInRequest.swift | 4 +- .../SignIn/StartMFASignInRequest.swift | 6 +- .../Unenroll/WithdrawMFARequest.swift | 4 +- .../Backend/RPC/RevokeTokenRequest.swift | 8 +-- .../Backend/RPC/SecureTokenRequest.swift | 12 ++-- .../Backend/RPC/SetAccountInfoRequest.swift | 17 +++--- .../RPC/VerifyPhoneNumberRequest.swift | 60 +++++++++++++------ .../Swift/Backend/VerifyClientRequest.swift | 4 +- .../Swift/User/UserProfileUpdate.swift | 11 ++-- .../Tests/Unit/SetAccountInfoTests.swift | 2 +- 14 files changed, 153 insertions(+), 98 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift index 1a7503ced9a..419419f4862 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift @@ -51,25 +51,25 @@ class CreateAuthURIRequest: IdentityToolkitRequest, AuthRPCRequest { let identifier: String /// The URI to which the IDP redirects the user after the federated login flow. - let continueURI: String + private let continueURI: String /// Optional realm for OpenID protocol. The sub string "scheme://domain:port" of the param /// "continueUri" is used if this is not set. - var openIDRealm: String? + private let openIDRealm: String? = nil /// The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, /// live.net and yahoo.com. For other OpenID IdPs it's the OP identifier. - var providerID: String? + private let providerID: String? = nil /// The relying party OAuth client ID. - var clientID: String? + private let clientID: String? = nil /// The opaque value used by the client to maintain context info between the authentication /// request and the IDP callback. - var context: String? + private let context: String? = nil /// The iOS client application's bundle identifier. - var appID: String? + private let appID: String? = nil init(identifier: String, continueURI: String, requestConfiguration: AuthRequestConfiguration) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift index 7c1fc262c0b..c1de49fd071 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift @@ -107,38 +107,38 @@ class GetOOBConfirmationCodeRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = GetOOBConfirmationCodeResponse /// The types of OOB Confirmation Code to request. - let requestType: GetOOBConfirmationCodeRequestType + private let requestType: GetOOBConfirmationCodeRequestType /// The email of the user for password reset. - private(set) var email: String? + let email: String? /// The new email to be updated for verifyBeforeUpdateEmail. - private(set) var updatedEmail: String? + private let updatedEmail: String? /// The STS Access Token of the authenticated user for email change. - private(set) var accessToken: String? + private let accessToken: String? /// This URL represents the state/Continue URL in the form of a universal link. - private(set) var continueURL: String? + let continueURL: String? /// The iOS bundle Identifier, if available. - private(set) var iOSBundleID: String? + private let iOSBundleID: String? /// The Android package name, if available. - private(set) var androidPackageName: String? + private let androidPackageName: String? /// The minimum Android version supported, if available. - private(set) var androidMinimumVersion: String? + private let androidMinimumVersion: String? /// Indicates whether or not the Android app should be installed if not already available. - private(set) var androidInstallApp: Bool + private let androidInstallApp: Bool /// Indicates whether the action code link will open the app directly or after being /// redirected from a Firebase owned web widget. - private(set) var handleCodeInApp: Bool + let handleCodeInApp: Bool /// The Firebase Dynamic Link domain used for out of band code flow. - private(set) var dynamicLinkDomain: String? + private let dynamicLinkDomain: String? /// Response to the captcha. var captchaResponse: String? diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift index e7c478e23bd..89374c9de4f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift @@ -27,33 +27,47 @@ class FinalizeMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { let displayName: String? - var phoneVerificationInfo: AuthProtoFinalizeMFAPhoneRequestInfo? + let phoneVerificationInfo: AuthProtoFinalizeMFAPhoneRequestInfo? - var totpVerificationInfo: AuthProtoFinalizeMFATOTPEnrollmentRequestInfo? + let totpVerificationInfo: AuthProtoFinalizeMFATOTPEnrollmentRequestInfo? - init(idToken: String?, displayName: String?, - phoneVerificationInfo: AuthProtoFinalizeMFAPhoneRequestInfo?, - requestConfiguration: AuthRequestConfiguration) { - self.idToken = idToken - self.displayName = displayName - self.phoneVerificationInfo = phoneVerificationInfo - super.init( - endpoint: kFinalizeMFAEnrollmentEndPoint, - requestConfiguration: requestConfiguration, - useIdentityPlatform: true + convenience init(idToken: String?, displayName: String?, + phoneVerificationInfo: AuthProtoFinalizeMFAPhoneRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { + self.init( + idToken: idToken, + displayName: displayName, + phoneVerificationInfo: phoneVerificationInfo, + totpVerificationInfo: nil, + requestConfiguration: requestConfiguration ) } - init(idToken: String?, displayName: String?, - totpVerificationInfo: AuthProtoFinalizeMFATOTPEnrollmentRequestInfo?, - requestConfiguration: AuthRequestConfiguration) { + convenience init(idToken: String?, displayName: String?, + totpVerificationInfo: AuthProtoFinalizeMFATOTPEnrollmentRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { + self.init( + idToken: idToken, + displayName: displayName, + phoneVerificationInfo: nil, + totpVerificationInfo: totpVerificationInfo, + requestConfiguration: requestConfiguration + ) + } + + private init(idToken: String?, displayName: String?, + phoneVerificationInfo: AuthProtoFinalizeMFAPhoneRequestInfo?, + totpVerificationInfo: AuthProtoFinalizeMFATOTPEnrollmentRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { self.idToken = idToken self.displayName = displayName + self.phoneVerificationInfo = phoneVerificationInfo self.totpVerificationInfo = totpVerificationInfo super.init( endpoint: kFinalizeMFAEnrollmentEndPoint, requestConfiguration: requestConfiguration, - useIdentityPlatform: true + useIdentityPlatform: true, + useStaging: false ) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift index 2ae904eac46..3896ec14b3d 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift @@ -24,30 +24,43 @@ class StartMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = StartMFAEnrollmentResponse let idToken: String? - private(set) var phoneEnrollmentInfo: AuthProtoStartMFAPhoneRequestInfo? - private(set) var totpEnrollmentInfo: AuthProtoStartMFATOTPEnrollmentRequestInfo? + let phoneEnrollmentInfo: AuthProtoStartMFAPhoneRequestInfo? + let totpEnrollmentInfo: AuthProtoStartMFATOTPEnrollmentRequestInfo? - init(idToken: String?, - enrollmentInfo: AuthProtoStartMFAPhoneRequestInfo?, - requestConfiguration: AuthRequestConfiguration) { - self.idToken = idToken - phoneEnrollmentInfo = enrollmentInfo - super.init( - endpoint: kStartMFAEnrollmentEndPoint, - requestConfiguration: requestConfiguration, - useIdentityPlatform: true + convenience init(idToken: String?, + enrollmentInfo: AuthProtoStartMFAPhoneRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { + self.init( + idToken: idToken, + enrollmentInfo: enrollmentInfo, + totpEnrollmentInfo: nil, + requestConfiguration: requestConfiguration ) } - init(idToken: String?, - totpEnrollmentInfo: AuthProtoStartMFATOTPEnrollmentRequestInfo?, - requestConfiguration: AuthRequestConfiguration) { + convenience init(idToken: String?, + totpEnrollmentInfo: AuthProtoStartMFATOTPEnrollmentRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { + self.init( + idToken: idToken, + enrollmentInfo: nil, + totpEnrollmentInfo: totpEnrollmentInfo, + requestConfiguration: requestConfiguration + ) + } + + private init(idToken: String?, + enrollmentInfo: AuthProtoStartMFAPhoneRequestInfo?, + totpEnrollmentInfo: AuthProtoStartMFATOTPEnrollmentRequestInfo?, + requestConfiguration: AuthRequestConfiguration) { self.idToken = idToken + phoneEnrollmentInfo = enrollmentInfo self.totpEnrollmentInfo = totpEnrollmentInfo super.init( endpoint: kStartMFAEnrollmentEndPoint, requestConfiguration: requestConfiguration, - useIdentityPlatform: true + useIdentityPlatform: true, + useStaging: false ) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift index 53f8a783b67..399e426d456 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift @@ -23,8 +23,8 @@ private let kTenantIDKey = "tenantId" class FinalizeMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = FinalizeMFAEnrollmentResponse - var mfaPendingCredential: String? - var verificationInfo: AuthProto? + let mfaPendingCredential: String? + let verificationInfo: AuthProto? init(mfaPendingCredential: String?, verificationInfo: AuthProto?, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift index 413245776a7..1098c2ef4ea 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift @@ -24,9 +24,9 @@ private let kTenantIDKey = "tenantId" class StartMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = StartMFASignInResponse - var MFAPendingCredential: String? - var MFAEnrollmentID: String? - var signInInfo: AuthProtoStartMFAPhoneRequestInfo? + let MFAPendingCredential: String? + let MFAEnrollmentID: String? + let signInInfo: AuthProtoStartMFAPhoneRequestInfo? init(MFAPendingCredential: String?, MFAEnrollmentID: String?, signInInfo: AuthProtoStartMFAPhoneRequestInfo?, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift index 5f8156a5191..19d9a04faed 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift @@ -23,8 +23,8 @@ private let kTenantIDKey = "tenantId" class WithdrawMFARequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = WithdrawMFAResponse - var idToken: String? - var mfaEnrollmentID: String? + let idToken: String? + let mfaEnrollmentID: String? init(idToken: String?, mfaEnrollmentID: String?, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift index 632060237dd..c3afae3b573 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift @@ -37,16 +37,16 @@ class RevokeTokenRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = RevokeTokenResponse /// The provider that issued the token to revoke. - private(set) var providerID: String + let providerID: String /// The type of the token to revoke. - private(set) var tokenType: TokenType + let tokenType: TokenType /// The token to be revoked. - private(set) var token: String + let token: String /// The ID Token associated with this credential. - private(set) var idToken: String + private let idToken: String enum TokenType: Int { case unspecified = 0, refreshToken = 1, accessToken = 2, authorizationCode = 3 diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index 920afc34158..e7fa5ee57e5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -64,16 +64,16 @@ class SecureTokenRequest: AuthRPCRequest { /// The type of grant requested. /// See FIRSecureTokenRequestGrantType - var grantType: SecureTokenRequestGrantType + let grantType: SecureTokenRequestGrantType /// The scopes requested (a comma-delimited list of scope strings). - var scope: String? + let scope: String? /// The client's refresh token. - var refreshToken: String? + let refreshToken: String? /// The client's authorization code (legacy Gitkit "ID Token"). - var code: String? + let code: String? /// The client's API Key. let apiKey: String @@ -107,8 +107,8 @@ class SecureTokenRequest: AuthRPCRequest { ) } - init(grantType: SecureTokenRequestGrantType, scope: String?, refreshToken: String?, - code: String?, requestConfiguration: AuthRequestConfiguration) { + private init(grantType: SecureTokenRequestGrantType, scope: String?, refreshToken: String?, + code: String?, requestConfiguration: AuthRequestConfiguration) { self.grantType = grantType self.scope = scope self.refreshToken = refreshToken diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift index be61874df7f..82842ace520 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift @@ -89,19 +89,19 @@ class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { var displayName: String? /// The local ID of the user. - var localID: String? + var localID: String? = nil /// The email of the user. - var email: String? + var email: String? = nil /// The photoURL of the user. var photoURL: URL? /// The new password of the user. - var password: String? + var password: String? = nil /// The associated identity providers of the user. - var providers: [String]? + var providers: [String]? = nil /// The out-of-band code of the change email request. var oobCode: String? @@ -113,16 +113,16 @@ class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { var upgradeToFederatedLogin: Bool = false /// The captcha challenge. - var captchaChallenge: String? + var captchaChallenge: String? = nil /// Response to the captcha. - var captchaResponse: String? + var captchaResponse: String? = nil /// The list of user attributes to delete. /// /// Every element of the list must be one of the predefined constant starts with /// `SetAccountInfoUserAttribute`. - var deleteAttributes: [String]? + var deleteAttributes: [String]? = nil /// The list of identity providers to delete. var deleteProviders: [String]? @@ -131,7 +131,8 @@ class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { /// The default value is `true` . var returnSecureToken: Bool = true - init(requestConfiguration: AuthRequestConfiguration) { + init(accessToken: String? = nil, requestConfiguration: AuthRequestConfiguration) { + self.accessToken = accessToken super.init(endpoint: kSetAccountInfoEndpoint, requestConfiguration: requestConfiguration) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift index 2fb0b183709..791c6fc9e01 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift @@ -61,52 +61,76 @@ class VerifyPhoneNumberRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = VerifyPhoneNumberResponse /// The verification ID obtained from the response of `sendVerificationCode`. - var verificationID: String? + let verificationID: String? /// The verification code provided by the user. - var verificationCode: String? + let verificationCode: String? /// The STS Access Token for the authenticated user. var accessToken: String? /// The temporary proof code, previously returned from the backend. - var temporaryProof: String? + let temporaryProof: String? /// The phone number to be verified in the request. - var phoneNumber: String? + let phoneNumber: String? /// The type of operation triggering this verify phone number request. - var operation: AuthOperationType + let operation: AuthOperationType - /// Designated initializer. + /// Convenience initializer. /// - Parameter temporaryProof: The temporary proof sent by the backed. /// - Parameter phoneNumber: The phone number associated with the credential to be signed in . /// - Parameter operation: Indicates what operation triggered the verify phone number request. /// - Parameter requestConfiguration: An object containing configurations to be added to the /// request. - init(temporaryProof: String, phoneNumber: String, operation: AuthOperationType, - requestConfiguration: AuthRequestConfiguration) { - self.temporaryProof = temporaryProof - self.phoneNumber = phoneNumber - self.operation = operation - super.init(endpoint: kVerifyPhoneNumberEndPoint, requestConfiguration: requestConfiguration) + convenience init(temporaryProof: String, phoneNumber: String, operation: AuthOperationType, + requestConfiguration: AuthRequestConfiguration) { + self.init( + temporaryProof: temporaryProof, + phoneNumber: phoneNumber, + verificationID: nil, + verificationCode: nil, + operation: operation, + requestConfiguration: requestConfiguration + ) } - /// Designated initializer. + /// Convenience initializer. /// - Parameter verificationID: The verification ID obtained from the response of /// `sendVerificationCode`. /// - Parameter verificationCode: The verification code provided by the user. /// - Parameter operation: Indicates what operation triggered the verify phone number request. /// - Parameter requestConfiguration: An object containing configurations to be added to the /// request. - init(verificationID: String, - verificationCode: String, - operation: AuthOperationType, - requestConfiguration: AuthRequestConfiguration) { + convenience init(verificationID: String, + verificationCode: String, + operation: AuthOperationType, + requestConfiguration: AuthRequestConfiguration) { + self.init( + temporaryProof: nil, + phoneNumber: nil, + verificationID: verificationID, + verificationCode: verificationCode, + operation: operation, + requestConfiguration: requestConfiguration + ) + } + + private init(temporaryProof: String?, phoneNumber: String?, verificationID: String?, + verificationCode: String?, operation: AuthOperationType, + requestConfiguration: AuthRequestConfiguration) { + self.temporaryProof = temporaryProof + self.phoneNumber = phoneNumber self.verificationID = verificationID self.verificationCode = verificationCode self.operation = operation - super.init(endpoint: kVerifyPhoneNumberEndPoint, requestConfiguration: requestConfiguration) + super.init( + endpoint: kVerifyPhoneNumberEndPoint, + requestConfiguration: requestConfiguration, + useIdentityPlatform: false, + useStaging: false + ) } func unencodedHTTPRequestBody() throws -> [String: AnyHashable] { diff --git a/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift b/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift index 892e0cbd4c1..f62f9def230 100644 --- a/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/VerifyClientRequest.swift @@ -39,10 +39,10 @@ class VerifyClientRequest: IdentityToolkitRequest, AuthRPCRequest { } /// The APNS device token. - private(set) var appToken: String? + let appToken: String? /// The flag that denotes if the appToken pertains to Sandbox or Production. - private(set) var isSandbox: Bool + let isSandbox: Bool init(withAppToken appToken: String?, isSandbox: Bool, diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift index 75ff6a9168c..2d561246557 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift @@ -49,8 +49,9 @@ actor UserProfileUpdate { func unlink(user: User, fromProvider provider: String) async throws -> User { let accessToken = try await user.internalGetTokenAsync() - let request = SetAccountInfoRequest(requestConfiguration: user.requestConfiguration) - request.accessToken = accessToken + let request = SetAccountInfoRequest( + accessToken: accessToken, requestConfiguration: user.requestConfiguration + ) if user.providerDataRaw[provider] == nil { throw AuthErrorUtils.noSuchProviderError() @@ -108,8 +109,10 @@ actor UserProfileUpdate { // Mutate setAccountInfoRequest in block let setAccountInfoRequest = - SetAccountInfoRequest(requestConfiguration: user.requestConfiguration) - setAccountInfoRequest.accessToken = accessToken + SetAccountInfoRequest( + accessToken: accessToken, + requestConfiguration: user.requestConfiguration + ) changeBlock(userAccountInfo, setAccountInfoRequest) do { let accountInfoResponse = try await AuthBackend.call(with: setAccountInfoRequest) diff --git a/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift b/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift index 061b23e3707..4efe951991a 100644 --- a/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift +++ b/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift @@ -221,6 +221,6 @@ class SetAccountInfoTests: RPCBaseTests { } private func setAccountInfoRequest() -> SetAccountInfoRequest { - return SetAccountInfoRequest(requestConfiguration: makeRequestConfiguration()) + return SetAccountInfoRequest(accessToken: nil, requestConfiguration: makeRequestConfiguration()) } } From 948c460cdbc55fb11c0c6fb6a8e26ea974d56c29 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 30 Oct 2024 16:06:01 -0700 Subject: [PATCH 250/258] Update to a more recent rtdb emulator version (#14002) --- .../Tests/Helpers/FTestContants.h | 2 +- .../Tests/Integration/FEventTests.m | 55 ++++++++----------- scripts/run_database_emulator.sh | 2 +- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/FirebaseDatabase/Tests/Helpers/FTestContants.h b/FirebaseDatabase/Tests/Helpers/FTestContants.h index bc8dd8de75e..d310d0abec9 100644 --- a/FirebaseDatabase/Tests/Helpers/FTestContants.h +++ b/FirebaseDatabase/Tests/Helpers/FTestContants.h @@ -18,6 +18,6 @@ #define Firebase_FTestContants_h #define kFirebaseTestTimeout 7 -#define kFirebaseTestWaitUntilTimeout 5 +#define kFirebaseTestWaitUntilTimeout 10 #endif diff --git a/FirebaseDatabase/Tests/Integration/FEventTests.m b/FirebaseDatabase/Tests/Integration/FEventTests.m index 69da5c9479c..8d2e87477c2 100644 --- a/FirebaseDatabase/Tests/Integration/FEventTests.m +++ b/FirebaseDatabase/Tests/Integration/FEventTests.m @@ -362,42 +362,35 @@ - (void)testOnceValueFiresExactlyOnce { } - (void)testOnceChildAddedFiresExactlyOnce { - __block int badCount = 0; + for (int i = 0; i < 100; i++) { + FIRDatabaseReference* path = [FTestHelpers getRandomNode]; + __block BOOL firstCall = YES; - // for(int i = 0; i < 100; i++) { + __block BOOL done = NO; - FIRDatabaseReference* path = [FTestHelpers getRandomNode]; - __block BOOL firstCall = YES; - - __block BOOL done = NO; - - [path observeSingleEventOfType:FIRDataEventTypeChildAdded - withBlock:^(FIRDataSnapshot* snapshot) { - XCTAssertTrue(firstCall, @"Properly saw first call"); - firstCall = NO; - XCTAssertEqualObjects(@42, [snapshot value], @"Properly saw node value"); - XCTAssertEqualObjects(@"foo", [snapshot key], - @"Properly saw the first node"); - if (![[snapshot value] isEqual:@42]) { - exit(-1); - badCount = badCount + 1; - } - - done = YES; - }]; - - [[path child:@"foo"] setValue:@42]; - [[path child:@"bar"] setValue:@84]; // XXX FIXME sometimes this event fires first - [[path child:@"foo"] setValue:@168]; + [path observeSingleEventOfType:FIRDataEventTypeChildAdded + withBlock:^(FIRDataSnapshot* snapshot) { + XCTAssertTrue(firstCall, @"Properly saw first call"); + firstCall = NO; + XCTAssertEqualObjects(@42, [snapshot value], @"Properly saw node value"); + XCTAssertEqualObjects(@"foo", [snapshot key], + @"Properly saw the first node"); + if (![[snapshot value] isEqual:@42]) { + exit(-1); + } - // [path setValue:nil withCompletionBlock:^(BOOL status) { done = YES; }]; - [self waitUntil:^BOOL { - return done; - }]; + done = YES; + }]; - // } + [[path child:@"foo"] setValue:@42]; + [[path child:@"bar"] setValue:@84]; // XXX FIXME sometimes this event fires first + [[path child:@"foo"] setValue:@168]; - NSLog(@"BADCOUNT: %d", badCount); + // [path setValue:nil withCompletionBlock:^(BOOL status) { done = YES; }]; + [self waitUntil:^BOOL { + return done; + }]; + } } - (void)testOnceValueFiresExactlyOnceEvenIfThereIsASetInsideCallback { diff --git a/scripts/run_database_emulator.sh b/scripts/run_database_emulator.sh index f8d846f10a9..806bde338fd 100755 --- a/scripts/run_database_emulator.sh +++ b/scripts/run_database_emulator.sh @@ -20,7 +20,7 @@ set -euo pipefail -VERSION='4.7.2' +VERSION='4.12.0' FILENAME="firebase-database-emulator-v${VERSION}.jar" URL="https://storage.googleapis.com/firebase-preview-drop/emulator/${FILENAME}" From 32c80831658e9d63541764187d47d78fa5424056 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 31 Oct 2024 14:06:08 -0700 Subject: [PATCH 251/258] CocoaPods 1.16.2 (#13989) --- Gemfile | 5 +---- Gemfile.lock | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index fdb8d3bc67d..cc267b2e746 100644 --- a/Gemfile +++ b/Gemfile @@ -12,9 +12,6 @@ source 'https://rubygems.org' # gem 'cocoapods-core', git: "https://github.com/CocoaPods/Core.git", ref: "f7cf05720eab935d7d50e35224d263952176fb53" # gem 'xcodeproj', git: "https://github.com/CocoaPods/Xcodeproj.git", ref: "eeccae7275645753cbaf45d96fc4b23e4b8b3b9f" -gem 'cocoapods', '1.15.2' +gem 'cocoapods', '1.16.2' gem 'cocoapods-generate', '2.2.5' gem 'danger', '8.4.5' - -# TODO: delete explicit xcodeproj after cocoapods goes beyond 1.15.2. -gem 'xcodeproj', '1.25.0' diff --git a/Gemfile.lock b/Gemfile.lock index 68d2d453cb6..aa840195966 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,15 +5,18 @@ GEM base64 nkf rexml - activesupport (7.1.3.4) + activesupport (7.1.5) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) @@ -22,16 +25,17 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) + benchmark (0.3.0) bigdecimal (3.1.8) claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.15.2) + cocoapods (1.16.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) + cocoapods-core (= 1.16.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -45,8 +49,8 @@ GEM molinillo (~> 0.8.0) nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -69,7 +73,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.3.3) + concurrent-ruby (1.3.4) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) @@ -123,18 +127,19 @@ GEM addressable (~> 2.8) rchardet (~> 1.8) httpclient (2.8.3) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) - json (2.7.2) + json (2.7.5) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.24.1) + logger (1.6.1) + minitest (5.25.1) molinillo (0.8.0) multipart-post (2.4.1) mutex_m (0.2.0) - nanaimo (0.3.0) + nanaimo (0.4.0) nap (1.1.0) netrc (0.11.0) nkf (0.2.0) @@ -151,6 +156,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + securerandom (0.3.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.1) @@ -158,22 +164,21 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) - xcodeproj (1.25.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.15.2) + cocoapods (= 1.16.2) cocoapods-generate (= 2.2.5) danger (= 8.4.5) - xcodeproj (= 1.25.0) BUNDLED WITH 2.1.4 From 33a44a979127a4c2b5b19e7228d65939aa55f967 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 31 Oct 2024 17:59:04 -0400 Subject: [PATCH 252/258] [Vertex AI] Fix caching of Vertex AI instances (#14007) --- FirebaseVertexAI/CHANGELOG.md | 5 ++ FirebaseVertexAI/Sources/VertexAI.swift | 9 +-- .../Tests/Unit/VertexComponentTests.swift | 67 +++++++++++++------ 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index cd290c5c1be..870f0c53788 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,3 +1,8 @@ +# 11.5.0 +- [fixed] Fixed an issue where `VertexAI.vertexAI(app: app1)` and + `VertexAI.vertexAI(app: app2)` would return the same instance if their + `location` was the same, including the default `us-central1`. (#14007) + # 11.4.0 - [feature] Vertex AI in Firebase is now Generally Available (GA) and can be used in production apps. (#13725) diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index ad378c067d1..c0cd2cb66a3 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -55,11 +55,12 @@ public class VertexAI { // Unlock before the function returns. defer { os_unfair_lock_unlock(&instancesLock) } - if let instance = instances[location] { + let instanceKey = "\(app.name):\(location)" + if let instance = instances[instanceKey] { return instance } let newInstance = VertexAI(app: app, location: location) - instances[location] = newInstance + instances[instanceKey] = newInstance return newInstance } @@ -116,8 +117,8 @@ public class VertexAI { private let auth: AuthInterop? - /// A map of active `VertexAI` instances for `app`, keyed by model resource names - /// (e.g., "projects/my-project-id/locations/us-central1/publishers/google/models/gemini-pro"). + /// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in + /// the format `appName:location`. private static var instances: [String: VertexAI] = [:] /// Lock to manage access to the `instances` array to avoid race conditions. diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift index bea85bf96e7..5e685dd98bd 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift @@ -24,22 +24,21 @@ import XCTest class VertexComponentTests: XCTestCase { static let projectID = "test-project-id" static let apiKey = "test-api-key" + static let options = { + let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", + gcmSenderID: "00000000000000000-00000000000-000000000") + options.projectID = VertexComponentTests.projectID + options.apiKey = VertexComponentTests.apiKey - static var app: FirebaseApp? + return options + }() - let location = "test-location" + static let app = { + FirebaseApp.configure(options: options) + return FirebaseApp(instanceWithName: "test", options: options) + }() - override class func setUp() { - super.setUp() - if app == nil { - let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", - gcmSenderID: "00000000000000000-00000000000-000000000") - options.projectID = VertexComponentTests.projectID - options.apiKey = VertexComponentTests.apiKey - FirebaseApp.configure(options: options) - app = FirebaseApp(instanceWithName: "test", options: options) - } - } + let location = "test-location" /// Test that the objc class is available for the component system to update the user agent. func testComponentsBeingRegistered() throws { @@ -48,9 +47,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly. func testVertexInstanceCreation() throws { - let app = try XCTUnwrap(VertexComponentTests.app) - - let vertex = VertexAI.vertexAI(app: app, location: location) + let vertex = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.projectID, VertexComponentTests.projectID) @@ -58,17 +55,47 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(vertex.location, location) } - /// Tests that a vertex instances are reused properly. - func testMultipleComponentInstancesCreated() throws { + /// Tests that Vertex instances are reused properly. + func testSameAppAndLocation_instanceReused() throws { let app = try XCTUnwrap(VertexComponentTests.app) + let vertex1 = VertexAI.vertexAI(app: app, location: location) let vertex2 = VertexAI.vertexAI(app: app, location: location) // Ensure they're the same instance. XCTAssert(vertex1 === vertex2) + } + + func testSameAppAndDifferentLocation_newInstanceCreated() throws { + let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = VertexAI.vertexAI(app: VertexComponentTests.app, location: "differentLocation") + + // Ensure they are different instances. + XCTAssert(vertex1 !== vertex2) + } + + func testDifferentAppAndSameLocation_newInstanceCreated() throws { + FirebaseApp.configure(name: "test-2", options: VertexComponentTests.options) + let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) + addTeardownBlock { await app2.delete() } + + let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = VertexAI.vertexAI(app: app2, location: location) + + XCTAssert(VertexComponentTests.app != app2) + XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. + } + + func testDifferentAppAndDifferentLocation_newInstanceCreated() throws { + FirebaseApp.configure(name: "test-2", options: VertexComponentTests.options) + let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) + addTeardownBlock { await app2.delete() } + + let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = VertexAI.vertexAI(app: app2, location: "differentLocation") - let vertex3 = VertexAI.vertexAI(app: app, location: "differentLocation") - XCTAssert(vertex1 !== vertex3) + XCTAssert(VertexComponentTests.app != app2) + XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. } /// Test that vertex instances get deallocated. From 53667174e72cbb99bd4be1aaf5d44909ea37ebe1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 31 Oct 2024 21:24:49 -0400 Subject: [PATCH 253/258] [Vertex AI] Add integration tests for App Check failure (#14008) --- .../Tests/TestApp/Sources/TestApp.swift | 2 +- .../Sources/TestAppCheckProviderFactory.swift | 36 +++++++++++++++++ .../Tests/Integration/IntegrationTests.swift | 32 ++++++++++++++- .../Utilities/FirebaseAppTestUtils.swift | 40 +++++++++++++++++++ .../VertexAITestApp.xcodeproj/project.pbxproj | 28 +++++++++++-- 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift create mode 100644 FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift index 2226e352203..737072bccb6 100644 --- a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift +++ b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift @@ -19,7 +19,7 @@ import SwiftUI @main struct TestApp: App { init() { - AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory()) + AppCheck.setAppCheckProviderFactory(TestAppCheckProviderFactory()) FirebaseApp.configure() } diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift b/FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift new file mode 100644 index 00000000000..ec7a08ceafc --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift @@ -0,0 +1,36 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAppCheck +import FirebaseCore +import Foundation + +/// An `AppCheckProviderFactory` for the Test App. +/// +/// Defaults to the `AppCheckDebugProvider` unless the `FirebaseApp` `name` contains +/// ``notConfiguredName``, in which case App Check is not configured; this facilitates integration +/// testing of App Check failure cases. +public class TestAppCheckProviderFactory: NSObject, AppCheckProviderFactory { + /// The name, or a substring of the name, of Firebase apps where App Check is not configured. + public static let notConfiguredName = "app-check-not-configured" + + /// Returns the `AppCheckDebugProvider` unless `app.name` contains ``notConfiguredName``. + public func createProvider(with app: FirebaseApp) -> (any AppCheckProvider)? { + if app.name.contains(TestAppCheckProviderFactory.notConfiguredName) { + return nil + } + + return AppCheckDebugProvider(app: app) + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index b90a13515a6..15e0435e275 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -16,9 +16,9 @@ import FirebaseAuth import FirebaseCore import FirebaseStorage import FirebaseVertexAI +import VertexAITestApp import XCTest -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class IntegrationTests: XCTestCase { // Set temperature, topP and topK to lowest allowed values to make responses more deterministic. let generationConfig = GenerationConfig( @@ -75,6 +75,21 @@ final class IntegrationTests: XCTestCase { XCTAssertEqual(text, "Mountain View") } + func testGenerateContent_appCheckNotConfigured_shouldFail() async throws { + let app = try FirebaseApp.defaultNamedCopy(name: TestAppCheckProviderFactory.notConfiguredName) + addTeardownBlock { await app.delete() } + let vertex = VertexAI.vertexAI(app: app) + let model = vertex.generativeModel(modelName: "gemini-1.5-flash") + let prompt = "Where is Google headquarters located? Answer with the city name only." + + do { + _ = try await model.generateContent(prompt) + XCTFail("Expected a Firebase App Check error; none thrown.") + } catch let GenerateContentError.internalError(error) { + XCTAssertTrue(String(describing: error).contains("Firebase App Check token is invalid")) + } + } + // MARK: - Count Tokens func testCountTokens_text() async throws { @@ -205,6 +220,21 @@ final class IntegrationTests: XCTestCase { XCTAssertEqual(response.totalTokens, 34) XCTAssertEqual(response.totalBillableCharacters, 59) } + + func testCountTokens_appCheckNotConfigured_shouldFail() async throws { + let app = try FirebaseApp.defaultNamedCopy(name: TestAppCheckProviderFactory.notConfiguredName) + addTeardownBlock { await app.delete() } + let vertex = VertexAI.vertexAI(app: app) + let model = vertex.generativeModel(modelName: "gemini-1.5-flash") + let prompt = "Why is the sky blue?" + + do { + _ = try await model.countTokens(prompt) + XCTFail("Expected a Firebase App Check error; none thrown.") + } catch { + XCTAssertTrue(String(describing: error).contains("Firebase App Check token is invalid")) + } + } } extension StorageReference { diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift new file mode 100644 index 00000000000..5bd0fbec1e7 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift @@ -0,0 +1,40 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore + +extension FirebaseApp { + /// Configures another `FirebaseApp` with the specified `name` and the same `FirebaseOptions`. + func namedCopy(name: String) throws -> FirebaseApp { + FirebaseApp.configure(name: name, options: options) + guard let app = FirebaseApp.app(name: name) else { + throw AppNotFound(name: name) + } + return app + } + + /// Configures an app with the specified `name` and the same `FirebaseOptions` as the default app. + static func defaultNamedCopy(name: String) throws -> FirebaseApp { + guard FirebaseApp.isDefaultAppConfigured(), let defaultApp = FirebaseApp.app() else { + throw DefaultAppNotConfigured() + } + return try defaultApp.namedCopy(name: name) + } + + struct AppNotFound: Error { + let name: String + } + + struct DefaultAppNotConfigured: Error {} +} diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj index 3638b5b13c8..0039528d9c2 100644 --- a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj +++ b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 8692F29A2CC9477800539E8F /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F2992CC9477800539E8F /* FirebaseAuth */; }; 8692F29C2CC9477800539E8F /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29B2CC9477800539E8F /* FirebaseStorage */; }; 8692F29E2CC9477800539E8F /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29D2CC9477800539E8F /* FirebaseVertexAI */; }; + 8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */; }; + 8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -41,6 +43,8 @@ 868A7C502CCC263300E449DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 868A7C532CCC26B500E449DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 868A7C552CCC271300E449DD /* TestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestApp.entitlements; sourceTree = ""; }; + 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAppTestUtils.swift; sourceTree = ""; }; + 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAppCheckProviderFactory.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -107,6 +111,7 @@ isa = PBXGroup; children = ( 8661385B2CC943DD00F4B78E /* TestApp.swift */, + 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */, 8661385D2CC943DD00F4B78E /* ContentView.swift */, ); path = Sources; @@ -125,10 +130,19 @@ isa = PBXGroup; children = ( 868A7C572CCC27AF00E449DD /* Integration */, + 8698D7442CD3CEF700ABA833 /* Utilities */, ); path = Tests; sourceTree = ""; }; + 8698D7442CD3CEF700ABA833 /* Utilities */ = { + isa = PBXGroup; + children = ( + 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */, + ); + path = Utilities; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -181,7 +195,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1520; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1600; TargetAttributes = { 866138572CC943DD00F4B78E = { CreatedOnToolsVersion = 15.2; @@ -241,6 +255,7 @@ files = ( 8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */, 8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */, + 8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -248,6 +263,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */, 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */, 8661386E2CC943DE00F4B78E /* IntegrationTests.swift in Sources */, ); @@ -298,6 +314,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -359,6 +376,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -386,6 +404,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -405,6 +424,7 @@ MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; + PRODUCT_MODULE_NAME = VertexAITestApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; @@ -422,6 +442,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -441,6 +462,7 @@ MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp; + PRODUCT_MODULE_NAME = VertexAITestApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; @@ -453,10 +475,10 @@ 866138812CC943DE00F4B78E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.0; @@ -475,10 +497,10 @@ 866138822CC943DE00F4B78E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.0; From ae2554220afb03a5d6fc389c28a86061a12c7caa Mon Sep 17 00:00:00 2001 From: themiswang Date: Fri, 1 Nov 2024 15:52:10 -0400 Subject: [PATCH 254/258] remove all CFRelease methods to use modern classes (#14010) --- Crashlytics/CHANGELOG.md | 3 +++ Crashlytics/upload-symbols | Bin 810016 -> 810048 bytes 2 files changed, 3 insertions(+) diff --git a/Crashlytics/CHANGELOG.md b/Crashlytics/CHANGELOG.md index b13fb66749b..a257d45b885 100644 --- a/Crashlytics/CHANGELOG.md +++ b/Crashlytics/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [changed] Updated `upload-symbols` to version 3.19, removed all methods require CFRelease and switch to modern classes (#13420). + # 11.4.0 - [fixed] Updated `upload-symbols` to version 3.18 with support for uploading multiple DWARF contents in a dSYM bundle (#13543). diff --git a/Crashlytics/upload-symbols b/Crashlytics/upload-symbols index f77bbe4304369166cfc1ab30e6bcf4e9a1b2f4ec..d49594ba35e139f047ef6a8974b1d4bb24609f69 100755 GIT binary patch literal 810048 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
    #J$3t7Vi>XcY=O? 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 b5FjByLV$z-2>}uUBm_tZkP!I)g1~;r; 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è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

    Idd$o`wiRsl*)?6Vc*p34NeFz%HRWa$<% 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

    _6k;boO+ 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 E4fcriHvj+t From 15eb852fef3c8f1056d28091ab023fabe95927c7 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:22:01 -0500 Subject: [PATCH 255/258] [Infra] Disable Auth deprecations warnings in SPM CI (#14013) --- .github/workflows/spm.yml | 3 +++ Package.swift | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 8986f7b8d82..324d688ead4 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -21,6 +21,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true +env: + FIREBASE_CI: true + jobs: spm-package-resolved: env: diff --git a/Package.swift b/Package.swift index cbec9e17fde..dc4f3cc99ca 100644 --- a/Package.swift +++ b/Package.swift @@ -419,6 +419,7 @@ let package = Package( "ObjC", "Public", ], resources: [.process("Resources/PrivacyInfo.xcprivacy")], + swiftSettings: Context.environment["FIREBASE_CI"] != nil ? [.define("FIREBASE_CI")] : [], linkerSettings: [ .linkedFramework("Security"), .linkedFramework("SafariServices", .when(platforms: [.iOS])), From cdaa05ff88b505f1d8aa2af046ac6c89a9972788 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:20:57 -0500 Subject: [PATCH 256/258] [Auth] Convert *Response classes to structs (#14012) --- .../Sources/Swift/Backend/AuthBackend.swift | 177 +++++--------- .../Swift/Backend/AuthBackendRPCIssuer.swift | 71 ++++++ .../Swift/Backend/AuthRPCResponse.swift | 4 +- .../Backend/RPC/CreateAuthURIResponse.swift | 8 +- .../Backend/RPC/DeleteAccountResponse.swift | 6 +- .../Backend/RPC/EmailLinkSignInResponse.swift | 6 +- .../Backend/RPC/GetAccountInfoResponse.swift | 215 +++++++++--------- .../RPC/GetOOBConfirmationCodeResponse.swift | 6 +- .../RPC/GetProjectConfigResponse.swift | 6 +- .../RPC/GetRecaptchaConfigResponse.swift | 6 +- .../FinalizeMFAEnrollmentResponse.swift | 6 +- .../Enroll/StartMFAEnrollmentResponse.swift | 6 +- .../SignIn/FinalizeMFASignInResponse.swift | 6 +- .../SignIn/StartMFASignInResponse.swift | 6 +- .../Unenroll/WithdrawMFAResponse.swift | 6 +- .../Swift/Backend/RPC/Proto/AuthProto.swift | 3 +- .../RPC/Proto/AuthProtoMFAEnrollment.swift | 12 +- ...uthProtoFinalizeMFAPhoneResponseInfo.swift | 4 +- .../AuthProtoStartMFAPhoneResponseInfo.swift | 4 +- ...inalizeMFATOTPEnrollmentResponseInfo.swift | 4 +- ...toStartMFATOTPEnrollmentResponseInfo.swift | 7 +- .../Backend/RPC/ResetPasswordResponse.swift | 6 +- .../Backend/RPC/RevokeTokenResponse.swift | 6 +- .../Backend/RPC/SecureTokenRequest.swift | 2 +- .../Backend/RPC/SecureTokenResponse.swift | 6 +- .../RPC/SendVerificationTokenResponse.swift | 6 +- .../Backend/RPC/SetAccountInfoResponse.swift | 44 ++-- .../RPC/SignInWithGameCenterResponse.swift | 6 +- .../Backend/RPC/SignUpNewUserResponse.swift | 6 +- .../Backend/RPC/VerifyAssertionResponse.swift | 9 +- .../RPC/VerifyCustomTokenResponse.swift | 6 +- .../Backend/RPC/VerifyPasswordResponse.swift | 6 +- .../RPC/VerifyPhoneNumberResponse.swift | 6 +- .../Swift/Backend/VerifyClientResponse.swift | 6 +- FirebaseAuth/Sources/Swift/User/User.swift | 4 +- .../Sources/Swift/User/UserInfoImpl.swift | 9 +- .../Swift/User/UserProfileUpdate.swift | 4 +- ...tionTests.swift => AuthBackendTests.swift} | 56 +++-- .../Unit/Fakes/FakeBackendRPCIssuer.swift | 4 +- FirebaseAuth/Tests/Unit/RPCBaseTests.swift | 2 - FirebaseCore/Extension/FIRHeartbeatLogger.h | 1 + 41 files changed, 363 insertions(+), 401 deletions(-) create mode 100644 FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift rename FirebaseAuth/Tests/Unit/{AuthBackendRPCImplementationTests.swift => AuthBackendTests.swift} (95%) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 24726262840..58fb6c4be80 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -22,82 +22,61 @@ import Foundation #endif @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthBackendRPCIssuer { - /// Asynchronously send a HTTP request. - /// - Parameter request: The request to be made. - /// - Parameter body: Request body. - /// - Parameter contentType: Content type of the body. - /// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously - /// on the auth global work queue in the future. - func asyncCallToURL(with request: T, - body: Data?, - contentType: String) async -> (Data?, Error?) -} - -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackendRPCIssuerImplementation: AuthBackendRPCIssuer { - let fetcherService: GTMSessionFetcherService - - init() { - fetcherService = GTMSessionFetcherService() - fetcherService.userAgent = AuthBackend.authUserAgent() - fetcherService.callbackQueue = kAuthGlobalWorkQueue - - // Avoid reusing the session to prevent - // https://github.com/firebase/firebase-ios-sdk/issues/1261 - fetcherService.reuseSession = false - } - - func asyncCallToURL(with request: T, - body: Data?, - contentType: String) async -> (Data?, Error?) { - let requestConfiguration = request.requestConfiguration() - let request = await AuthBackend.request(withURL: request.requestURL(), - contentType: contentType, - requestConfiguration: requestConfiguration) - let fetcher = fetcherService.fetcher(with: request) - if let _ = requestConfiguration.emulatorHostAndPort { - fetcher.allowLocalhostRequest = true - fetcher.allowedInsecureSchemes = ["http"] - } - fetcher.bodyData = body - - return await withUnsafeContinuation { continuation in - fetcher.beginFetch { data, error in - continuation.resume(returning: (data, error)) - } - } - } +protocol AuthBackendProtocol { + func call(with request: T) async throws -> T.Response } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackend { +class AuthBackend: AuthBackendProtocol { static func authUserAgent() -> String { return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))" } - private static var realRPCBackend = AuthBackendRPCImplementation() - private static var gBackendImplementation = realRPCBackend + static func call(with request: T) async throws -> T.Response { + return try await shared.call(with: request) + } - class func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { - gBackendImplementation.rpcIssuer = issuer + static func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { + shared.rpcIssuer = issuer } - class func resetRPCIssuer() { - gBackendImplementation.rpcIssuer = realRPCBackend.rpcIssuer + static func resetRPCIssuer() { + shared.rpcIssuer = AuthBackendRPCIssuer() } - class func implementation() -> AuthBackendImplementation { - return gBackendImplementation + private static let shared: AuthBackend = .init(rpcIssuer: AuthBackendRPCIssuer()) + + private var rpcIssuer: any AuthBackendRPCIssuerProtocol + + init(rpcIssuer: any AuthBackendRPCIssuerProtocol) { + self.rpcIssuer = rpcIssuer } - class func call(with request: T) async throws -> T.Response { - return try await implementation().call(with: request) + /// Calls the RPC using HTTP request. + /// Possible error responses: + /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError + /// * See FIRAuthInternalErrorCodeJSONSerializationError + /// * See FIRAuthInternalErrorCodeNetworkError + /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse + /// * See FIRAuthInternalErrorCodeUnexpectedResponse + /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError + /// - Parameter request: The request. + /// - Returns: The response. + func call(with request: T) async throws -> T.Response { + let response = try await callInternal(with: request) + if let auth = request.requestConfiguration().auth, + let mfaError = Self.generateMFAError(response: response, auth: auth) { + throw mfaError + } else if let error = Self.phoneCredentialInUse(response: response) { + throw error + } else { + return response + } } - class func request(withURL url: URL, - contentType: String, - requestConfiguration: AuthRequestConfiguration) async -> URLRequest { + static func request(withURL url: URL, + contentType: String, + requestConfiguration: AuthRequestConfiguration) async -> URLRequest { // Kick off tasks for the async header values. async let heartbeatsHeaderValue = requestConfiguration.heartbeatLogger?.asyncHeaderValue() async let appCheckTokenHeaderValue = requestConfiguration.appCheck? @@ -132,41 +111,11 @@ class AuthBackend { } return request } -} -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthBackendImplementation { - func call(with request: T) async throws -> T.Response -} - -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -private class AuthBackendRPCImplementation: AuthBackendImplementation { - var rpcIssuer: AuthBackendRPCIssuer = AuthBackendRPCIssuerImplementation() - - /// Calls the RPC using HTTP request. - /// Possible error responses: - /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError - /// * See FIRAuthInternalErrorCodeJSONSerializationError - /// * See FIRAuthInternalErrorCodeNetworkError - /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse - /// * See FIRAuthInternalErrorCodeUnexpectedResponse - /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError - /// - Parameter request: The request. - /// - Returns: The response. - fileprivate func call(with request: T) async throws -> T.Response { - let response = try await callInternal(with: request) - if let auth = request.requestConfiguration().auth, - let mfaError = Self.generateMFAError(response: response, auth: auth) { - throw mfaError - } else if let error = Self.phoneCredentialInUse(response: response) { - throw error - } else { - return response - } - } - - #if os(iOS) - private class func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { + private static func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { + #if !os(iOS) + return nil + #else if let mfaResponse = response as? AuthMFAResponse, mfaResponse.idToken == nil, let enrollments = mfaResponse.mfaInfo { @@ -189,17 +138,15 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } else { return nil } - } - #else - private class func generateMFAError(response: AuthRPCResponse, auth: Auth?) -> Error? { - return nil - } - #endif + #endif // !os(iOS) + } - #if os(iOS) - // Check whether or not the successful response is actually the special case phone - // auth flow that returns a temporary proof and phone number. - private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { + // Check whether or not the successful response is actually the special case phone + // auth flow that returns a temporary proof and phone number. + private static func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { + #if !os(iOS) + return nil + #else if let phoneAuthResponse = response as? VerifyPhoneNumberResponse, let phoneNumber = phoneAuthResponse.phoneNumber, phoneNumber.count > 0, @@ -214,12 +161,8 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } else { return nil } - } - #else - private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { - return nil - } - #endif + #endif // !os(iOS) + } /// Calls the RPC using HTTP request. /// @@ -308,7 +251,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } dictionary = decodedDictionary - let response = T.Response() + var response = T.Response() // At this point we either have an error with successfully decoded // details in the body, or we have a response which must pass further @@ -318,7 +261,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { if error != nil { if let errorDictionary = dictionary["error"] as? [String: AnyHashable] { if let errorMessage = errorDictionary["message"] as? String { - if let clientError = AuthBackendRPCImplementation.clientError( + if let clientError = Self.clientError( withServerErrorMessage: errorMessage, errorDictionary: errorDictionary, response: response, @@ -351,7 +294,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { if let verifyAssertionRequest = request as? VerifyAssertionRequest { if verifyAssertionRequest.returnIDPCredential { if let errorMessage = dictionary["errorMessage"] as? String { - if let clientError = AuthBackendRPCImplementation.clientError( + if let clientError = Self.clientError( withServerErrorMessage: errorMessage, errorDictionary: dictionary, response: response, @@ -365,10 +308,10 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { return response } - private class func clientError(withServerErrorMessage serverErrorMessage: String, - errorDictionary: [String: Any], - response: AuthRPCResponse, - error: Error?) -> Error? { + private static func clientError(withServerErrorMessage serverErrorMessage: String, + errorDictionary: [String: Any], + response: AuthRPCResponse, + error: Error?) -> Error? { let split = serverErrorMessage.split(separator: ":") let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines) let serverDetailErrorMessage = String(split.count > 1 ? split[1] : "") diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift new file mode 100644 index 00000000000..01001c5d712 --- /dev/null +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift @@ -0,0 +1,71 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseCoreExtension +import Foundation +#if COCOAPODS + import GTMSessionFetcher +#else + import GTMSessionFetcherCore +#endif + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +protocol AuthBackendRPCIssuerProtocol { + /// Asynchronously send a HTTP request. + /// - Parameter request: The request to be made. + /// - Parameter body: Request body. + /// - Parameter contentType: Content type of the body. + /// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously + /// on the auth global work queue in the future. + func asyncCallToURL(with request: T, + body: Data?, + contentType: String) async -> (Data?, Error?) +} + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class AuthBackendRPCIssuer: AuthBackendRPCIssuerProtocol { + let fetcherService: GTMSessionFetcherService + + init() { + fetcherService = GTMSessionFetcherService() + fetcherService.userAgent = AuthBackend.authUserAgent() + fetcherService.callbackQueue = kAuthGlobalWorkQueue + + // Avoid reusing the session to prevent + // https://github.com/firebase/firebase-ios-sdk/issues/1261 + fetcherService.reuseSession = false + } + + func asyncCallToURL(with request: T, + body: Data?, + contentType: String) async -> (Data?, Error?) { + let requestConfiguration = request.requestConfiguration() + let request = await AuthBackend.request(withURL: request.requestURL(), + contentType: contentType, + requestConfiguration: requestConfiguration) + let fetcher = fetcherService.fetcher(with: request) + if let _ = requestConfiguration.emulatorHostAndPort { + fetcher.allowLocalhostRequest = true + fetcher.allowedInsecureSchemes = ["http"] + } + fetcher.bodyData = body + + return await withUnsafeContinuation { continuation in + fetcher.beginFetch { data, error in + continuation.resume(returning: (data, error)) + } + } + } +} diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift index b1284762fb2..0b7ea8a3a52 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift @@ -14,7 +14,7 @@ import Foundation -protocol AuthRPCResponse { +protocol AuthRPCResponse: Sendable { /// Bare initializer for a response. init() @@ -22,7 +22,7 @@ protocol AuthRPCResponse { /// - Parameter dictionary: The dictionary decoded from HTTP JSON response. /// - Parameter error: An out field for an error which occurred constructing the request. /// - Returns: Whether the operation was successful or not. - func setFields(dictionary: [String: AnyHashable]) throws + mutating func setFields(dictionary: [String: AnyHashable]) throws /// This optional method allows response classes to create client errors given a short error /// message and a detail error message from the server. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift index de26c51ed3a..019a92f4b90 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift @@ -16,8 +16,7 @@ import Foundation /// Represents the parameters for the createAuthUri endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri - -class CreateAuthURIResponse: AuthRPCResponse { +struct CreateAuthURIResponse: AuthRPCResponse { /// The URI used by the IDP to authenticate the user. var authURI: String? @@ -36,10 +35,7 @@ class CreateAuthURIResponse: AuthRPCResponse { /// A list of sign-in methods available for the passed identifier. var signinMethods: [String] = [] - /// Bare initializer. - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { providerID = dictionary["providerId"] as? String authURI = dictionary["authUri"] as? String registered = dictionary["registered"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift index 868d36d25d2..4b6802dafb3 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift @@ -17,8 +17,6 @@ import Foundation /// Represents the response from the deleteAccount endpoint. /// /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount -class DeleteAccountResponse: AuthRPCResponse { - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws {} +struct DeleteAccountResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index 107c0859ac7..608c38237cc 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -15,9 +15,7 @@ import Foundation /// Represents the response from the emailLinkSignin endpoint. -class EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - +struct EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { /// The ID token in the email link sign-in response. private(set) var idToken: String? @@ -42,7 +40,7 @@ class EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { /// Info on which multi-factor authentication providers are enabled. private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String idToken = dictionary["idToken"] as? String isNewUser = dictionary["isNewUser"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift index 8b4ae17d627..e0f9c463959 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift @@ -14,143 +14,138 @@ import Foundation -/// The key for the "error" value in JSON responses from the server. -private let kErrorKey = "error" - -/// Represents the provider user info part of the response from the getAccountInfo endpoint. +/// Represents the response from the setAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseProviderUserInfo { - /// The ID of the identity provider. - let providerID: String? - - /// The user's display name at the identity provider. - let displayName: String? - - /// The user's photo URL at the identity provider. - let photoURL: URL? - - /// The user's identifier at the identity provider. - let federatedID: String? - - /// The user's email at the identity provider. - let email: String? - - /// A phone number associated with the user. - let phoneNumber: String? - - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - providerID = dictionary["providerId"] as? String - displayName = dictionary["displayName"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) - } else { - photoURL = nil +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +struct GetAccountInfoResponse: AuthRPCResponse { + /// Represents the provider user info part of the response from the getAccountInfo endpoint. + /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + struct ProviderUserInfo { + /// The ID of the identity provider. + let providerID: String? + + /// The user's display name at the identity provider. + let displayName: String? + + /// The user's photo URL at the identity provider. + let photoURL: URL? + + /// The user's identifier at the identity provider. + let federatedID: String? + + /// The user's email at the identity provider. + let email: String? + + /// A phone number associated with the user. + let phoneNumber: String? + + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + providerID = dictionary["providerId"] as? String + displayName = dictionary["displayName"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } else { + photoURL = nil + } + federatedID = + dictionary["federatedId"] as? String + email = dictionary["email"] as? String + phoneNumber = dictionary["phoneNumber"] as? String } - federatedID = - dictionary["federatedId"] as? String - email = dictionary["email"] as? String - phoneNumber = dictionary["phoneNumber"] as? String } -} -/// Represents the firebase user info part of the response from the getAccountInfo endpoint. -/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseUser { - /// The ID of the user. - let localID: String? + /// Represents the firebase user info part of the response from the getAccountInfo endpoint. + /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + struct User { + /// The ID of the user. + let localID: String? - /// The email or the user. - let email: String? + /// The email or the user. + let email: String? - /// Whether the email has been verified. - let emailVerified: Bool + /// Whether the email has been verified. + let emailVerified: Bool - /// The display name of the user. - let displayName: String? + /// The display name of the user. + let displayName: String? - /// The user's photo URL. - let photoURL: URL? + /// The user's photo URL. + let photoURL: URL? - /// The user's creation date. - let creationDate: Date? + /// The user's creation date. + let creationDate: Date? - /// The user's last login date. - let lastLoginDate: Date? + /// The user's last login date. + let lastLoginDate: Date? - /// The user's profiles at the associated identity providers. - let providerUserInfo: [GetAccountInfoResponseProviderUserInfo]? + /// The user's profiles at the associated identity providers. + let providerUserInfo: [GetAccountInfoResponse.ProviderUserInfo]? - /// Information about user's password. - /// This is not necessarily the hash of user's actual password. - let passwordHash: String? + /// Information about user's password. + /// This is not necessarily the hash of user's actual password. + let passwordHash: String? - /// A phone number associated with the user. - let phoneNumber: String? + /// A phone number associated with the user. + let phoneNumber: String? - let mfaEnrollments: [AuthProtoMFAEnrollment]? + let mfaEnrollments: [AuthProtoMFAEnrollment]? - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - if let providerUserInfoData = dictionary["providerUserInfo"] as? [[String: Any]] { - providerUserInfo = providerUserInfoData.map { - GetAccountInfoResponseProviderUserInfo(dictionary: $0) + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + if let providerUserInfoData = dictionary["providerUserInfo"] as? [[String: Any]] { + providerUserInfo = providerUserInfoData + .map(GetAccountInfoResponse.ProviderUserInfo.init(dictionary:)) + } else { + providerUserInfo = nil + } + localID = dictionary["localId"] as? String + displayName = dictionary["displayName"] as? String + email = dictionary["email"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } else { + photoURL = nil + } + if let createdAt = dictionary["createdAt"] as? String, + let timeInterval = Double(createdAt) { + // Divide by 1000 in order to convert milliseconds to seconds. + creationDate = Date(timeIntervalSince1970: timeInterval / 1000) + } else { + creationDate = nil + } + if let lastLoginAt = dictionary["lastLoginAt"] as? String, + let timeInterval = Double(lastLoginAt) { + // Divide by 1000 in order to convert milliseconds to seconds. + lastLoginDate = Date(timeIntervalSince1970: timeInterval / 1000) + } else { + lastLoginDate = nil } - } else { - providerUserInfo = nil - } - localID = dictionary["localId"] as? String - displayName = dictionary["displayName"] as? String - email = dictionary["email"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) - } else { - photoURL = nil - } - if let createdAt = dictionary["createdAt"] as? String, - let timeInterval = Double(createdAt) { - // Divide by 1000 in order to convert milliseconds to seconds. - creationDate = Date(timeIntervalSince1970: timeInterval / 1000) - } else { - creationDate = nil - } - if let lastLoginAt = dictionary["lastLoginAt"] as? String, - let timeInterval = Double(lastLoginAt) { - // Divide by 1000 in order to convert milliseconds to seconds. - lastLoginDate = Date(timeIntervalSince1970: timeInterval / 1000) - } else { - lastLoginDate = nil - } - emailVerified = dictionary["emailVerified"] as? Bool ?? false - passwordHash = dictionary["passwordHash"] as? String - phoneNumber = dictionary["phoneNumber"] as? String - if let mfaEnrollmentData = dictionary["mfaInfo"] as? [[String: AnyHashable]] { - mfaEnrollments = mfaEnrollmentData.map { AuthProtoMFAEnrollment(dictionary: $0) + emailVerified = dictionary["emailVerified"] as? Bool ?? false + passwordHash = dictionary["passwordHash"] as? String + phoneNumber = dictionary["phoneNumber"] as? String + if let mfaEnrollmentData = dictionary["mfaInfo"] as? [[String: AnyHashable]] { + mfaEnrollments = mfaEnrollmentData.map { AuthProtoMFAEnrollment(dictionary: $0) + } + } else { + mfaEnrollments = nil } - } else { - mfaEnrollments = nil } } -} - -/// Represents the response from the setAccountInfo endpoint. -/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetAccountInfoResponse: AuthRPCResponse { - required init() {} /// The requested users' profiles. - var users: [GetAccountInfoResponseUser]? - func setFields(dictionary: [String: AnyHashable]) throws { + var users: [Self.User]? + + mutating func setFields(dictionary: [String: AnyHashable]) throws { guard let usersData = dictionary["users"] as? [[String: AnyHashable]] else { throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary) } guard usersData.count == 1 else { throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary) } - users = [GetAccountInfoResponseUser(dictionary: usersData[0])] + users = [Self.User(dictionary: usersData[0])] } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift index 9a19848151a..91721fd659e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift @@ -16,12 +16,10 @@ import Foundation private let kOOBCodeKey = "oobCode" -class GetOOBConfirmationCodeResponse: AuthRPCResponse { - required init() {} - +struct GetOOBConfirmationCodeResponse: AuthRPCResponse { var OOBCode: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { OOBCode = dictionary[kOOBCodeKey] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift index fb5f282c18a..b9fb18771b7 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift @@ -14,14 +14,12 @@ import Foundation -class GetProjectConfigResponse: AuthRPCResponse { - required init() {} - +struct GetProjectConfigResponse: AuthRPCResponse { var projectID: String? var authorizedDomains: [String]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { projectID = dictionary["projectId"] as? String if let authorizedDomains = dictionary["authorizedDomains"] as? String, let data = authorizedDomains.data(using: .utf8) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift index 9331670db86..b62bd84a695 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift @@ -14,13 +14,11 @@ import Foundation -class GetRecaptchaConfigResponse: AuthRPCResponse { - required init() {} - +struct GetRecaptchaConfigResponse: AuthRPCResponse { private(set) var recaptchaKey: String? private(set) var enforcementState: [[String: String]]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { recaptchaKey = dictionary["recaptchaKey"] as? String enforcementState = dictionary["recaptchaEnforcementState"] as? [[String: String]] } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift index af4bd6c7658..bdd3ce89d31 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift @@ -15,15 +15,13 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFAEnrollmentResponse: AuthRPCResponse { - required init() {} - +struct FinalizeMFAEnrollmentResponse: AuthRPCResponse { private(set) var idToken: String? private(set) var refreshToken: String? private(set) var phoneSessionInfo: AuthProtoFinalizeMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoFinalizeMFATOTPEnrollmentResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift index 158be37c33d..dadbb9a15be 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift @@ -15,13 +15,11 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFAEnrollmentResponse: AuthRPCResponse { - required init() {} - +struct StartMFAEnrollmentResponse: AuthRPCResponse { private(set) var phoneSessionInfo: AuthProtoStartMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoStartMFATOTPEnrollmentResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneSessionInfo"] as? [String: AnyHashable] { phoneSessionInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else if let data = dictionary["totpSessionInfo"] as? [String: AnyHashable] { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift index c93f0817b27..032dab3ff86 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift @@ -15,13 +15,11 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFASignInResponse: AuthRPCResponse { - required init() {} - +struct FinalizeMFASignInResponse: AuthRPCResponse { var IDToken: String? var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { IDToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift index a2b3399d03e..cc597172010 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift @@ -15,12 +15,10 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFASignInResponse: AuthRPCResponse { - required init() {} - +struct StartMFASignInResponse: AuthRPCResponse { var responseInfo: AuthProtoStartMFAPhoneResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneResponseInfo"] as? [String: AnyHashable] { responseInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift index 38d0e9b9aad..4744a6965ee 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift @@ -14,13 +14,11 @@ import Foundation -class WithdrawMFAResponse: AuthRPCResponse { - required init() {} - +struct WithdrawMFAResponse: AuthRPCResponse { var idToken: String? var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift index e8356c98375..8983e54dfac 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift @@ -14,7 +14,6 @@ import Foundation -@objc protocol AuthProto: NSObjectProtocol { +protocol AuthProto { init(dictionary: [String: AnyHashable]) - @objc optional var dictionary: [String: AnyHashable] { get } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift index 494252ac56e..d13e34112f9 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift @@ -14,18 +14,22 @@ import Foundation -class AuthProtoMFAEnrollment: NSObject, AuthProto { +struct AuthProtoMFAEnrollment: AuthProto { let phoneInfo: String? // In practice, this will be an empty dictionary. The presence of which // indicates TOTP MFA enrollment rather than phone MFA enrollment. - let totpInfo: NSObject? + let totpInfo: String? let mfaEnrollmentID: String? let displayName: String? let enrolledAt: Date? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { phoneInfo = dictionary["phoneInfo"] as? String - totpInfo = dictionary["totpInfo"] as? NSObject + if let _ = dictionary["totpInfo"] as? NSObject { + totpInfo = "" + } else { + totpInfo = nil + } mfaEnrollmentID = dictionary["mfaEnrollmentId"] as? String displayName = dictionary["displayName"] as? String if let enrolledAt = dictionary["enrolledAt"] as? String { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift index 2274e2826ea..690e998b27e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift @@ -14,10 +14,10 @@ import Foundation -class AuthProtoFinalizeMFAPhoneResponseInfo: NSObject, AuthProto { +struct AuthProtoFinalizeMFAPhoneResponseInfo: AuthProto { var phoneNumber: String? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { phoneNumber = dictionary["phoneNumber"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift index c888b2e2fff..bd1c9a06d08 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift @@ -14,10 +14,10 @@ import Foundation -class AuthProtoStartMFAPhoneResponseInfo: NSObject, AuthProto { +struct AuthProtoStartMFAPhoneResponseInfo: AuthProto { var sessionInfo: String? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { sessionInfo = dictionary["sessionInfo"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift index 825d15aef78..1dfae6f0cb1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift @@ -14,7 +14,7 @@ import Foundation -class AuthProtoStartMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { +struct AuthProtoStartMFATOTPEnrollmentResponseInfo: AuthProto { let sharedSecretKey: String let verificationCodeLength: Int let hashingAlgorithm: String? @@ -22,7 +22,7 @@ class AuthProtoStartMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { let sessionInfo: String? let finalizeEnrollmentTime: Date? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { guard let key = dictionary["sharedSecretKey"] as? String else { fatalError("Missing sharedSecretKey for AuthProtoStartMFATOTPEnrollmentResponseInfo") } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift index 88096a9f1a5..4c9d03d018c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift @@ -14,10 +14,9 @@ import Foundation -class AuthProtoFinalizeMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { +// TODO(ncooke3): This implementation looks useless? +struct AuthProtoFinalizeMFATOTPEnrollmentResponseInfo: AuthProto { var sessionInfo: String? - required init(dictionary: [String: AnyHashable]) { - super.init() - } + init(dictionary: [String: AnyHashable]) {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift index fcfb619732e..22cb703e5ad 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift @@ -24,9 +24,7 @@ import Foundation /// * FIRAuthErrorCodeInvalidActionCode /// /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/resetPassword -class ResetPasswordResponse: AuthRPCResponse { - required init() {} - +struct ResetPasswordResponse: AuthRPCResponse { /// The email address corresponding to the reset password request. var email: String? @@ -36,7 +34,7 @@ class ResetPasswordResponse: AuthRPCResponse { /// The type of request as returned by the backend. var requestType: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String requestType = dictionary["requestType"] as? String verifiedEmail = dictionary["newEmail"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift index 081d1917550..7e6bf0e7378 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift @@ -14,10 +14,8 @@ import Foundation -class RevokeTokenResponse: AuthRPCResponse { - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws { +struct RevokeTokenResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws { // Nothing to set or throw. } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index e7fa5ee57e5..c07d60c7a60 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -59,7 +59,7 @@ private var gAPIHost = "securetoken.googleapis.com" /// Represents the parameters for the token endpoint. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SecureTokenRequest: AuthRPCRequest { +struct SecureTokenRequest: AuthRPCRequest { typealias Response = SecureTokenResponse /// The type of grant requested. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift index f16bd573da0..86291a60015 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift @@ -29,9 +29,7 @@ private let kAccessTokenKey = "access_token" private let kIDTokenKey = "id_token" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SecureTokenResponse: AuthRPCResponse { - required init() {} - +struct SecureTokenResponse: AuthRPCResponse { var approximateExpirationDate: Date? var refreshToken: String? var accessToken: String? @@ -39,7 +37,7 @@ class SecureTokenResponse: AuthRPCResponse { var expectedKind: String? { nil } - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { refreshToken = dictionary[kRefreshTokenKey] as? String self.accessToken = dictionary[kAccessTokenKey] as? String idToken = dictionary[kIDTokenKey] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift index a532806c005..f9e6872cfcb 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift @@ -14,12 +14,10 @@ import Foundation -class SendVerificationCodeResponse: AuthRPCResponse { - required init() {} - +struct SendVerificationCodeResponse: AuthRPCResponse { var verificationID: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { verificationID = dictionary["sessionInfo"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift index 4990e0dc0a0..c1b932b83c2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift @@ -14,33 +14,31 @@ import Foundation -/// Represents the provider user info part of the response from the setAccountInfo endpoint. +/// Represents the response from the setAccountInfo endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo -class SetAccountInfoResponseProviderUserInfo { - /// The ID of the identity provider. - var providerID: String? +struct SetAccountInfoResponse: AuthRPCResponse { + /// Represents the provider user info part of the response from the setAccountInfo endpoint. + /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo + struct ProviderUserInfo { + /// The ID of the identity provider. + var providerID: String? - /// The user's display name at the identity provider. - var displayName: String? + /// The user's display name at the identity provider. + var displayName: String? - /// The user's photo URL at the identity provider. - var photoURL: URL? + /// The user's photo URL at the identity provider. + var photoURL: URL? - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - providerID = dictionary["providerId"] as? String - displayName = dictionary["displayName"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + providerID = dictionary["providerId"] as? String + displayName = dictionary["displayName"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } } } -} - -/// Represents the response from the setAccountInfo endpoint. -/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo -class SetAccountInfoResponse: AuthRPCResponse { - required init() {} /// The email or the user. var email: String? @@ -49,7 +47,7 @@ class SetAccountInfoResponse: AuthRPCResponse { var displayName: String? /// The user's profiles at the associated identity providers. - var providerUserInfo: [SetAccountInfoResponseProviderUserInfo]? + var providerUserInfo: [Self.ProviderUserInfo]? /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set @@ -62,7 +60,7 @@ class SetAccountInfoResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String idToken = dictionary["idToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift index 75d9d5ce1dd..d291384ff49 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift @@ -14,9 +14,7 @@ import Foundation -class SignInWithGameCenterResponse: AuthRPCResponse { - required init() {} - +struct SignInWithGameCenterResponse: AuthRPCResponse { var idToken: String? var refreshToken: String? var localID: String? @@ -27,7 +25,7 @@ class SignInWithGameCenterResponse: AuthRPCResponse { var isNewUser: Bool = false var displayName: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift index fe6c8d9556e..7b25c6a6f8c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift @@ -14,9 +14,7 @@ import Foundation -class SignUpNewUserResponse: AuthRPCResponse { - required init() {} - +struct SignUpNewUserResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -28,7 +26,7 @@ class SignUpNewUserResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let approximateExpirationDate = dictionary["expiresIn"] as? String { self diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index 0f3aafe66b7..9cf822cae0e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -16,10 +16,7 @@ import Foundation /// Represents the response from the verifyAssertion endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion -class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - - /// The unique ID identifies the IdP account. +struct VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { var federatedID: String? /// The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, @@ -109,7 +106,7 @@ class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { var isNewUser: Bool = false /// Dictionary containing the additional IdP specific information. - var profile: [String: Any]? + var profile: [String: Sendable]? /// The name of the user. var username: String? @@ -135,7 +132,7 @@ class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { federatedID = dictionary["federatedId"] as? String providerID = dictionary["providerId"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift index 74053b5f3b5..5c66bf6ba74 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift @@ -15,9 +15,7 @@ import Foundation /// Represents the response from the verifyCustomToken endpoint. -class VerifyCustomTokenResponse: AuthRPCResponse { - required init() {} - +struct VerifyCustomTokenResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -32,7 +30,7 @@ class VerifyCustomTokenResponse: AuthRPCResponse { /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let dateString = dictionary["expiresIn"] as? NSString { approximateExpirationDate = Date(timeIntervalSinceNow: dateString.doubleValue) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift index 7735450ed76..ba93ac1e0a5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift @@ -21,9 +21,7 @@ import Foundation /// * FIRAuthInternalErrorCodeEmailNotFound /// /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword -class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - +struct VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { /// The RP local ID if it's already been mapped to the IdP account identified by the federated ID. var localID: String? @@ -53,7 +51,7 @@ class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { localID = dictionary["localId"] as? String email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift index 82325a25fbe..b5efb964c73 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift @@ -14,9 +14,7 @@ import Foundation -class VerifyPhoneNumberResponse: AuthRPCResponse { - required init() {} - +struct VerifyPhoneNumberResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -45,7 +43,7 @@ class VerifyPhoneNumberResponse: AuthRPCResponse { nil } - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String isNewUser = (dictionary["isNewUser"] as? Bool) ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift index 36508691d8d..683ed33a01f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift @@ -14,8 +14,8 @@ import Foundation -class VerifyClientResponse: AuthRPCResponse { - required init() {} +struct VerifyClientResponse: AuthRPCResponse { + init() {} /// Receipt that the APNS token was successfully validated with APNS. private(set) var receipt: String? @@ -23,7 +23,7 @@ class VerifyClientResponse: AuthRPCResponse { /// The date after which delivery of the silent push notification is considered to have failed. private(set) var suggestedTimeOutDate: Date? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { receipt = dictionary["receipt"] as? String let suggestedTimeout = dictionary["suggestedTimeout"] if let string = suggestedTimeout as? String, diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 02cdeb8f979..0100fedfdc6 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -1229,7 +1229,7 @@ extension User: NSSecureCoding {} /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on /// the auth global work queue in the future. - func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponseUser, + func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponse.User, SetAccountInfoRequest) -> Void, callback: @escaping (Error?) -> Void) { Task { @@ -1250,7 +1250,7 @@ extension User: NSSecureCoding {} /// Gets the users' account data from the server, updating our local values. /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an /// error has been detected. Invoked asynchronously on the auth global work queue in the future. - func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponseUser?, + func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponse.User?, Error?) -> Void) { Task { do { diff --git a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift index 6f19622ddcb..a1fe938ae03 100644 --- a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift +++ b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift @@ -14,18 +14,21 @@ import Foundation +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension UserInfoImpl: NSSecureCoding {} -@objc(FIRUserInfoImpl) class UserInfoImpl: NSObject, UserInfo { +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class UserInfoImpl: NSObject, UserInfo { /// A convenience factory method for constructing a `UserInfo` instance from data /// returned by the getAccountInfo endpoint. /// - Parameter providerUserInfo: Data returned by the getAccountInfo endpoint. /// - Returns: A new instance of `UserInfo` using data from the getAccountInfo endpoint. - class func userInfo(withGetAccountInfoResponseProviderUserInfo providerUserInfo: GetAccountInfoResponseProviderUserInfo) + class func userInfo(withGetAccountInfoResponseProviderUserInfo providerUserInfo: GetAccountInfoResponse + .ProviderUserInfo) -> UserInfoImpl { guard let providerID = providerUserInfo.providerID else { // This was a crash in ObjC implementation. Should providerID be not nullable? - fatalError("Missing providerID from GetAccountInfoResponseProviderUserInfo") + fatalError("Missing providerID from GetAccountInfoResponse.ProviderUserInfo") } return UserInfoImpl(withProviderID: providerID, userID: providerUserInfo.federatedID ?? "", diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift index 2d561246557..74aa5cf5c73 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift @@ -101,7 +101,7 @@ actor UserProfileUpdate { /// atomically in regards to other calls to this method. /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` func executeUserUpdateWithChanges(user: User, - changeBlock: @escaping (GetAccountInfoResponseUser, + changeBlock: @escaping (GetAccountInfoResponse.User, SetAccountInfoRequest) -> Void) async throws { let userAccountInfo = try await getAccountInfoRefreshingCache(user) @@ -179,7 +179,7 @@ actor UserProfileUpdate { /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an /// error has been detected. Invoked asynchronously on the auth global work queue in the future. func getAccountInfoRefreshingCache(_ user: User) async throws - -> GetAccountInfoResponseUser { + -> GetAccountInfoResponse.User { let token = try await user.internalGetTokenAsync() let request = GetAccountInfoRequest(accessToken: token, requestConfiguration: user.requestConfiguration) diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift similarity index 95% rename from FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift rename to FirebaseAuth/Tests/Unit/AuthBackendTests.swift index 7180313df34..a7a484a7d5d 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift @@ -27,7 +27,7 @@ private let kFakeAPIKey = "kTestAPIKey" private let kFakeAppID = "kTestFirebaseAppID" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackendRPCImplementationTests: RPCBaseTests { +class AuthBackendTests: RPCBaseTests { let kFakeErrorDomain = "fakeDomain" let kFakeErrorCode = -1 @@ -41,7 +41,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let request = FakeRequest(withEncodingError: encodingError) do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -71,7 +71,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { func testBodyDataSerializationError() async throws { let request = FakeRequest(withRequestBody: ["unencodable": self]) do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -99,7 +99,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: nil, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -131,7 +131,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -168,7 +168,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -210,7 +210,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -252,7 +252,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -287,7 +287,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -329,7 +329,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { ) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -356,7 +356,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -398,7 +398,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -436,7 +436,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let _ = try self.rpcIssuer.respond(withJSON: [:], error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -473,7 +473,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(serverErrorMessage: customErrorMessage, error: responseError) } do { - let _ = try await rpcImplementation.call(with: FakeRequest(withRequestBody: [:])) + let _ = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -497,7 +497,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } do { let request = FakeDecodingErrorRequest(withRequestBody: [:]) - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -528,8 +528,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // value it was given. try self.rpcIssuer.respond(withJSON: [kTestKey: kTestValue]) } - let rpcResponse = try await rpcImplementation.call(with: FakeRequest(withRequestBody: [:])) - XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedDictionary[kTestKey] as? String), kTestValue) + let rpcResponse = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) + XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedValue), kTestValue) } #if COCOAPODS || SWIFT_PACKAGE @@ -593,7 +593,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) // Then let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue() @@ -620,7 +620,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Just force return from async call. try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) let completeRequest = await rpcIssuer.completeRequest.value let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck") @@ -650,7 +650,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) // Then let completeRequest = await rpcIssuer.completeRequest.value @@ -711,12 +711,10 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } } - private class FakeResponse: AuthRPCResponse { - required init() {} - - var receivedDictionary: [String: AnyHashable] = [:] - func setFields(dictionary: [String: AnyHashable]) throws { - receivedDictionary = dictionary + private struct FakeResponse: AuthRPCResponse { + var receivedValue: String? + mutating func setFields(dictionary: [String: AnyHashable]) throws { + receivedValue = dictionary["TestKey"] as? String } } @@ -740,10 +738,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } } - private class FakeDecodingErrorResponse: FakeResponse { - required init() {} - - override func setFields(dictionary: [String: AnyHashable]) throws { + private struct FakeDecodingErrorResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws { throw NSError(domain: "dummy", code: -1) } } diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift index f1b4cee7a5d..e09e8d2106c 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift @@ -77,8 +77,8 @@ class FakeBackendRPCIssuer: AuthBackendRPCIssuer { var secureTokenErrorString: String? var recaptchaSiteKey = "unset recaptcha siteKey" - func asyncCallToURL(with request: T, body: Data?, - contentType: String) async -> (Data?, Error?) + override func asyncCallToURL(with request: T, body: Data?, + contentType: String) async -> (Data?, Error?) where T: FirebaseAuth.AuthRPCRequest { return await withCheckedContinuation { continuation in self.asyncCallToURL(with: request, body: body, contentType: contentType) { data, error in diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index 567b3aa4bbd..009289a1b44 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -68,12 +68,10 @@ class RPCBaseTests: XCTestCase { let kTestIdentifier = "Identifier" var rpcIssuer: FakeBackendRPCIssuer! - var rpcImplementation: AuthBackendImplementation! override func setUp() { rpcIssuer = FakeBackendRPCIssuer() AuthBackend.setTestRPCIssuer(issuer: rpcIssuer) - rpcImplementation = AuthBackend.implementation() } override func tearDown() { diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 807aea22062..95497d2a689 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -33,6 +33,7 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) { FIRDailyHeartbeatCodeSome = 2, }; +NS_SWIFT_SENDABLE @protocol FIRHeartbeatLoggerProtocol /// Asynchronously logs a heartbeat. From c73f83a6a3e34c9fbe8517e53bc578efe5f5796c Mon Sep 17 00:00:00 2001 From: Osman Saral Date: Mon, 4 Nov 2024 23:43:17 +0300 Subject: [PATCH 257/258] Fixing upload-symbols run script argument order (#13966) --- Crashlytics/run | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Crashlytics/run b/Crashlytics/run index 93a6f11e7ae..bb1d43d8f0f 100755 --- a/Crashlytics/run +++ b/Crashlytics/run @@ -46,8 +46,8 @@ for i in "$@"; do ARGUMENTS="$ARGUMENTS \"$i\"" done -VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate" -UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase" +VALIDATE_ARGUMENTS="--build-phase --validate $ARGUMENTS" +UPLOAD_ARGUMENTS="--build-phase $ARGUMENTS " # Quote the path to handle folders with special characters COMMAND_PATH="\"$DIR/upload-symbols\" " From 0a492326659c61bb2f905b674959d22f4cc6837d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:44:22 -0500 Subject: [PATCH 258/258] [Auth] Fix casting error in Swift 6 (#14025) --- .../Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index 9cf822cae0e..c031aa22c43 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -158,10 +158,10 @@ struct VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { if let rawUserInfo = dictionary["rawUserInfo"] as? String, let data = rawUserInfo.data(using: .utf8) { if let info = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves), - let profile = info as? [String: Any] { + let profile = info as? [String: any Sendable] { self.profile = profile } - } else if let profile = dictionary["rawUserInfo"] as? [String: Any] { + } else if let profile = dictionary["rawUserInfo"] as? [String: any Sendable] { self.profile = profile } username = dictionary["username"] as? String

    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